【网络】(三)流协议粘包问题
来源:互联网 发布:ubuntu golang 安装 编辑:程序博客网 时间:2024/06/03 16:29
在第二篇中,谈到了流协议的粘包问题以及一种解决方法的实现,在数据的开始显示的指明数据的长度;这是一种不错的解决方法,另外,也有说到可以根据数据中的特殊字符来判断是否接收完了一整条消息,本篇将在上一篇的基础上实现这一方法!
原理:依据数据尾部是否有’\n’,来表示消息是否接收完成
1、核心函数实现
//使用recv函数从套接字接收缓冲区中接收数据,但并不从缓冲去中清除数据ssize_t recv_peek(int sockfd, void *buf, size_t len){ while(true) { int iret = recv(sockfd, buf, len, MSG_PEEK); if(iret == -1 && errno == EINTR) //如果失败是因为信号中端,那么就重新再试 continue; return iret; }}
注意:
这里使用了recv函数,它和read函数的一个不同在于,它最后一个参数,可以指定一个选项,表示从接收缓冲区中读取数据后,该数据是否被移出消息队列,MSG_PEEK标志表示不移出数据而偷窥数据!
//如果读到的数据包含\n,则返回,表示一条完整的消息读取完毕ssize_t recvline(int sockfd, void *buf, size_t maxlen){ int iret = 0; int nread = 0; //已读数据字节数 char *pbuf = (char*)buf; int nleft = maxlen; //剩余数据字节数 while(true) { iret = recv_peek(sockfd, pbuf, nleft); if(iret < 0) return iret; //读取失败 else if(iret == 0) return iret; //对方关闭套接口 nread = iret; if(nread > nleft) //已经读取到的数据只可能小于或者等于剩余的数据 exit(EXIT_FAILURE); for(int i = 0; i < nread; i++) { if(pbuf[i] == '\n') { iret = readn(sockfd, pbuf, i+1); //从缓冲区中读走包括\n在内的数据 if(iret != i+1) exit(EXIT_FAILURE); //没有读取都i+1个数据,说明失败 return iret; //读取都\n返回 } } //在当前读到的数据中没有发现\n,那么先将这部分数据从缓冲区中读走,然后接着偷窥后面的数据 nleft -= nread; iret = readn(sockfd, pbuf, nread); if(iret != nread) exit(EXIT_FAILURE); pbuf += nread; } return -1;}
readn和writen函数参照第二篇文章!
2、服务器代码
编译命令:gcc -Wall -g -std=gnu99 server.c -o server
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <stdbool.h>#include <unistd.h>#include <netinet/in.h>#include <arpa/inet.h>#include <errno.h>#include <string.h>#include <signal.h>#include <sys/wait.h>#define handle_error(msg) \ do{perror(msg);exit(EXIT_FAILURE);}while(0)//忽略子进程的SIG_CHLD信号void handle_SIGIGN(void){ struct sigaction act_chld; act_chld.sa_handler = SIG_IGN; act_chld.sa_flags = 0; sigemptyset(&act_chld.sa_mask); if(-1 == sigaction(SIGCHLD, &act_chld, NULL))//捕获终端中断信号 handle_error("sigaction SIGCHLD");}void do_work(int sock);int main(void){ handle_SIGIGN(); int sk_fd = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP); if(sk_fd < 0) handle_error("socket"); //使用REUSEADDR,不必等待TIME_WAIT 状态消失,就可以重新使用端口 int on = 1; if(setsockopt(sk_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) < 0) { close(sk_fd); handle_error("setsockopt"); } struct sockaddr_in sr_addr; memset(&sr_addr,0,sizeof(sr_addr)); sr_addr.sin_family = AF_INET; sr_addr.sin_port = htons(5188); sr_addr.sin_addr.s_addr = htonl(INADDR_ANY); //sr_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&sr_addr.sin_addr); if(bind(sk_fd, (struct sockaddr*)&sr_addr, sizeof(sr_addr)) < 0) { close(sk_fd); handle_error("bind"); } //被动套接字 if(listen(sk_fd, SOMAXCONN) < 0) //内核为此套接字排队的最大连接数由SOMAXCONN宏指定 { close(sk_fd); handle_error("listen"); } struct sockaddr_in cl_addr; socklen_t cl_length = sizeof(cl_addr); pid_t pid; while (true) { memset(&cl_addr,0,sizeof(cl_addr)); int ac_sk = accept(sk_fd, (struct sockaddr *)&cl_addr, &cl_length); if(ac_sk < 0) { if(errno == EINTR) continue; close(sk_fd); handle_error("accept"); } printf("Connect ip = %s\tport = %d\n",inet_ntoa(cl_addr.sin_addr),ntohs(cl_addr.sin_port)); //每个客户端对应一个子进程 pid = fork(); if(pid == -1) handle_error("fork"); if(pid == 0) { close(sk_fd); do_work(ac_sk); close(ac_sk); exit(EXIT_SUCCESS); } else close(ac_sk); } close(sk_fd); return 0;}void do_work(int sock){ char recvbuf[1024]; while(true) { memset(recvbuf,0,sizeof(recvbuf)); int iret = recvline(sock,recvbuf,sizeof(recvbuf)); //获取包数据长度 if(iret == -1) handle_error("read"); else if(iret == 0) { printf("Client was closed!\n"); break; } fputs(recvbuf,stdout); writen(sock, recvbuf, strlen(recvbuf)); //回传数据 }}
3、客户端代码
#include <stdio.h>#include <sys/types.h>#include <sys/socket.h>#include <stdlib.h>#include <stdbool.h>#include <unistd.h>#include <netinet/in.h>#include <arpa/inet.h>#include <errno.h>#include <string.h>#define handle_error(msg) \ do{perror(msg);exit(EXIT_FAILURE);}while(0)int main(void){ int sk_fd = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP); if(sk_fd < 0) handle_error("socket"); struct sockaddr_in sr_addr; memset(&sr_addr,0,sizeof(sr_addr)); sr_addr.sin_family = AF_INET; sr_addr.sin_port = htons(5188); //sr_addr.sin_addr.s_addr = htonl(INADDR_ANY); sr_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); //inet_aton("127.0.0.1",&sr_addr.sin_addr); if(connect(sk_fd, (struct sockaddr*)&sr_addr, sizeof(sr_addr)) < 0) { close(sk_fd); handle_error("connect"); } char sendbuf[1024] = {0}; char recvbuf[1024] = {0}; while (fgets(sendbuf, sizeof(sendbuf), stdin) != NULL) { writen(sk_fd, sendbuf, strlen(sendbuf)); //发送数据 int iret = recvline(sk_fd, recvbuf, sizeof(recvbuf)); //接收包数据长度 if(iret == -1) handle_error("read"); else if(iret == 0) { printf("Server was closed!\n"); break; } fputs(recvbuf, stdout); memset(&sendbuf, 0, sizeof(sendbuf)); memset(&recvbuf, 0, sizeof(recvbuf)); } close(sk_fd); return 0;}
0 0
- 【网络】(三)流协议粘包问题
- 【网络】(二)流协议粘包问题
- Unity C# 自定义TCP传输协议以及封包拆包、解决粘包问题(网络应用层协议)
- Netty(三):网络传输中的拆包、粘包问题
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- linux网络编程之socket(五):tcp流协议产生的粘包问题和解决方案
- UNIX网络编程——tcp流协议产生的粘包问题和解决方案
- Wireshark网络抓包(三)——网络协议
- Wireshark网络抓包(三)——网络协议
- Wireshark网络抓包(三)——网络协议
- Wireshark网络抓包(三)——网络协议
- Wireshark网络抓包(三)——网络协议
- 在Eclipse或者ADT中使用ButterKnifeZelezny,Android组件初始化从此变得简单易懂!!!!
- SG定理
- chrome设置UA
- 关于listview下拉刷新中滑动时会点进item的问题
- 树的中心
- 【网络】(三)流协议粘包问题
- 项目阶段总结
- hdoj1072,Nightmare
- Spring注解大全以及案例
- C#调用Excel VBA宏
- 实模式、保护模式以及虚拟80806方式简介
- iGriamceV8.0 IG8.0.0 iOS8 V8基本设置和使用教程图
- Java 驼峰与下划线的属性名互相转换
- C++中智能指针的设计和使用