Linux高性能服务器编程中的TCP带外数据梳理总结 1.TCP 带外数据总结 至此,我们讨论完了 TCP 带外数据相关的所有知识。总结梳理一下:
1 2 3 4 5 6 7 8 // 异常事件,采用带MSG_OOB标志的recv函数读取带外数据 else if (FD_ISSET(connfd, &exception_fds)) { ret = recv(connfd, buf, sizeof(buf) - 1, MSG_OOB); if (ret <= 0) { break; } printf("get %d bytes of oob data: %s\n", ret, buf); }
在应用程序检测到带外数据到达以后,还需要进一步判断带外数据在数据流中的具体位置,才能够读取带外数据。**《Linux 高性能服务器编程》第 5 章 5.9 小节 (P87)**带外标记介绍的 sockatmark
调用可以判断 sockfd
是否处于带外标记,即下一个被读取到的数据是否是带外数据。如果是,sockatmark
返回1,此时我们就可以利用带MSG_OOB
标志的recv
调用来接收带外数据。如果不是,则sockatmark
返回0。
2.第五章带外数据 send.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main (int argc,char * argv[]) { if (argc<2 ) { printf ("usage:%s if_address port_number\n" ,argv[0 ]); return 1 ; } const char *ip=argv[1 ]; int port=atoi(argv[2 ]); struct sockaddr_in server_address ; bzero(&server_address,sizeof (server_address)); server_address.sin_family=AF_INET; inet_pton(AF_INET,ip,&server_address.sin_addr); server_address.sin_port=htons(port); int sockfd=socket(PF_INET,SOCK_STREAM,0 ); assert(sockfd>=0 ); if (connect(sockfd,(struct sockaddr*)&server_address,sizeof (server_address))<0 ) { printf ("connection failed\n" ); } else { const char * oob_data="abc" ; const char * normal_data="123" ; send(sockfd,normal_data,strlen (normal_data),0 ); send(sockfd,oob_data,strlen (oob_data),MSG_OOB); send(sockfd,normal_data,strlen (normal_data),0 ); } close(sockfd); return 0 ; }
recv.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #define BUFSIZE 1024 int main (int argc,char * argv[]) { if (argc<2 ) { printf ("usage:%s if_address port_number\n" ,argv[0 ]); return 1 ; } const char *ip=argv[1 ]; int port=atoi(argv[2 ]); struct sockaddr_in address ; bzero(&address,sizeof (address)); address.sin_family=AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port=htons(port); int sockfd=socket(PF_INET,SOCK_STREAM,0 ); assert(sockfd>=0 ); int ret=bind(sockfd,(struct sockaddr*)&address,sizeof (address)); assert(ret!=-1 ); ret=listen(sockfd,5 ); assert(ret!=-1 ); struct sockaddr_in client ; socklen_t client_len=sizeof (client); int connfd=accept(sockfd,(struct sockaddr*)&client,&client_len); if (connfd<0 ) { printf ("connection failed\n" ); } else { char buffer[BUFSIZE]; memset (buffer,'\0' ,BUFSIZE); ret=recv(connfd,buffer,BUFSIZE-1 ,0 ); printf ("got %d bytes of normal data '%s'\n" ,ret,buffer); memset (buffer,'\0' ,BUFSIZE); ret=recv(connfd,buffer,BUFSIZE-1 ,MSG_OOB); printf ("got %d bytes of normal data '%s'\n" ,ret,buffer); memset (buffer,'\0' ,BUFSIZE); ret=recv(connfd,buffer,BUFSIZE-1 ,0 ); printf ("got %d bytes of normal data '%s'\n" ,ret,buffer); close(connfd); } close(sockfd); return 0 ; }
运行结果:
由此可见,客户端发送给服务器的3字节的带外数据”abc”中,仅有最后一个字符’c’被服务器当成真正的带外数据接收。并且服务器对正常数据的接收将被带外数据截断,即123ab123不能一次性读出,第二个123得在调用一次recv。
flags参数只对send和recv的当前调用生效。
3.第九章带外数据 send.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main (int argc,char * argv[]) { if (argc<2 ) { printf ("usage:%s if_address port_number\n" ,argv[0 ]); return 1 ; } const char *ip=argv[1 ]; int port=atoi(argv[2 ]); struct sockaddr_in server_address ; bzero(&server_address,sizeof (server_address)); server_address.sin_family=AF_INET; inet_pton(AF_INET,ip,&server_address.sin_addr); server_address.sin_port=htons(port); int sockfd=socket(PF_INET,SOCK_STREAM,0 ); assert(sockfd>=0 ); if (connect(sockfd,(struct sockaddr*)&server_address,sizeof (server_address))<0 ) { printf ("connection failed\n" ); } else { const char * oob_data="abc" ; const char * normal_data="123" ; send(sockfd,normal_data,strlen (normal_data),0 ); sleep(1 ); send(sockfd,oob_data,strlen (oob_data),MSG_OOB); sleep(1 ); send(sockfd,normal_data,strlen (normal_data),0 ); } close(sockfd); return 0 ; }
select.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> #include <errno.h> #include <fcntl.h> #include <sys/select.h> int main (int argc,char * argv[]) { if (argc<=2 ) { printf ("usage:%s if_address port_number\n" ,argv[0 ]); return 1 ; } const char *ip=argv[1 ]; int port=atoi(argv[2 ]); int ret=0 ; struct sockaddr_in address ; bzero(&address,sizeof (address)); address.sin_family=AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port=htons(port); int listenfd=socket(PF_INET,SOCK_STREAM,0 ); assert(listenfd>=0 ); ret=bind(listenfd,(struct sockaddr*)&address,sizeof (address)); assert(ret!=-1 ); ret=listen(listenfd,5 ); assert(ret!=-1 ); struct sockaddr_in client ; socklen_t client_len=sizeof (client); int connfd=accept(listenfd,(struct sockaddr*)&client,&client_len); if (connfd<0 ) { printf ("connection failed\n" ); close(listenfd); } char buf[1024 ]; fd_set read_fds; fd_set exception_fds; FD_ZERO(&read_fds); FD_ZERO(&exception_fds); while (1 ) { memset (buf,'\0' ,sizeof (buf)); FD_SET(connfd,&read_fds); FD_SET(connfd,&exception_fds); ret=select(connfd+1 ,&read_fds,NULL ,&exception_fds,NULL ); if (ret<0 ) { printf ("select failure\n" ); break ; } if (FD_ISSET(connfd,&read_fds)) { ret=recv(connfd,buf,sizeof (buf)-1 ,0 ); if (ret<=0 ) { break ; } printf ("get %d bytes of normal data:%s\n" ,ret,buf); } else if (FD_ISSET(connfd,&exception_fds)) { ret=recv(connfd,buf,sizeof (buf)-1 ,MSG_OOB); if (ret<=0 ) { break ; } printf ("get %d bytes of oob data:%s\n" ,ret,buf); } } close(listenfd); close(connfd); return 0 ; }
运行结果图:
可以看到一开始只有123ab和123没有带外数据c
是什么原因导致的呢?我们在第五节测试的时候,明明 TCP 服务器终端是打印出了带外数据的(使用带 MSG_OOB
标志的 recv
函数),说明问题肯定没有出在数据的发送上(因为这两次数据发送使用的是同一个客户端程序)。
那么数据接收出了什么问题呢?猜测可能是由于数据发送间隔太短,导致服务器没有时间处理带外数据。
于是我们在 sned.c 函数中加入延时:
编译后重新运行就有带外数据abc了,可以看到ab会被当做普通数据处理,带外数据只有字符c。
4.第十章带外数据 send.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <string.h> #include <stdlib.h> int main (int argc,char * argv[]) { if (argc<2 ) { printf ("usage:%s if_address port_number\n" ,argv[0 ]); return 1 ; } const char *ip=argv[1 ]; int port=atoi(argv[2 ]); struct sockaddr_in server_address ; bzero(&server_address,sizeof (server_address)); server_address.sin_family=AF_INET; inet_pton(AF_INET,ip,&server_address.sin_addr); server_address.sin_port=htons(port); int sockfd=socket(PF_INET,SOCK_STREAM,0 ); assert(sockfd>=0 ); if (connect(sockfd,(struct sockaddr*)&server_address,sizeof (server_address))<0 ) { printf ("connection failed\n" ); } else { const char * oob_data="abc" ; const char * normal_data="123" ; send(sockfd,normal_data,strlen (normal_data),0 ); sleep(1 ); send(sockfd,oob_data,strlen (oob_data),MSG_OOB); sleep(1 ); send(sockfd,normal_data,strlen (normal_data),0 ); } close(sockfd); return 0 ; }
sig_msg.c 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <stdlib.h> #include <errno.h> #include <string.h> #include <signal.h> #include <fcntl.h> #include <libgen.h> #define BUF_SIZE 1024 static int connfd; void sig_urg (int sig) { int save_errno = errno; char buffer[BUF_SIZE]; memset (buffer, '\0' , BUF_SIZE); int ret; while ((ret = recv(connfd, buffer, BUF_SIZE - 1 , MSG_OOB)) < 0 ) { if (errno == EWOULDBLOCK) { continue ; } else { break ; } } if (ret > 0 ) { printf ("got %d bytes of oob data '%s'\n" , ret, buffer); } errno = save_errno; } void addsig (int sig, void (*sig_handler)(int )) { struct sigaction sa ; memset (&sa, '\0' , sizeof (sa)); sa.sa_handler = sig_handler; sa.sa_flags |= SA_RESTART; sigfillset(&sa.sa_mask); assert(sigaction(sig, &sa, NULL ) != -1 ); } int main (int argc, char *argv[]) { if (argc != 3 ) { printf ("usage: %s ip_address port_number\n" , basename(argv[0 ])); return 1 ; } const char *ip = argv[1 ]; int port = atoi(argv[2 ]); struct sockaddr_in address ; bzero(&address, sizeof (address)); address.sin_family = AF_INET; inet_pton(AF_INET, ip, &address.sin_addr); address.sin_port = htons(port); int sock = socket(PF_INET, SOCK_STREAM, 0 ); assert(sock >= 0 ); int ret = bind(sock, (struct sockaddr *)&address, sizeof (address)); assert(ret != -1 ); ret = listen(sock, 5 ); assert(ret != -1 ); struct sockaddr_in client ; socklen_t client_addrlength = sizeof (client); connfd = accept(sock, (struct sockaddr *)&client, &client_addrlength); if (connfd < 0 ) { printf ("errno is: %d\n" , errno); } else { addsig(SIGURG, sig_urg); fcntl(connfd, F_SETOWN, getpid()); char buffer[BUF_SIZE]; while (1 ) { memset (buffer, '\0' , BUF_SIZE); ret = recv(connfd, buffer, BUF_SIZE - 1 , 0 ); if (ret < 0 ) { if (errno == EINTR) { continue ; } break ; } else if (ret == 0 ) { printf ("Client disconnected.\n" ); break ; } printf ("get %d bytes of normal data '%s'\n" , ret, buffer); } close(connfd); } close(sock); return 0 ; }
运行结果图:
第一次是加了sleep的,可以打出来c这个带外数据
第二没加,就打不出来了,原因还是同上