前面提到,connect发生EINTR错误时,是不能重新启动的。那怎么办呢,是关闭套接字还是直接退出进程呢?如果EINTR前,三次握手已经发起,我们当然希望链路就此已经建立完成,不要再重新走流程了。这个时候我们就需要为connect量身定做一个使用方案。代码如下:
这部分代码是从原有工程里抠出来的,在学习完前面知识后,我觉得有些地方不是很完善,按照下面的代码做了修改。此处将老代码贴出只是为了防止自己的理解有误,做了画蛇添足的事情。新代码如下:
这是一个非阻塞式connect的模型,更为详细的介绍可以看《UNIX网络编程》(我真不是个做广告的)。但此处模型个人认为逻辑上还是完善的。
15-16行:设置套接口为非阻塞方式,这个步骤需要视使用环境而定,因此这里存在一个移植性问题。
18-21行:非阻塞模式下的connect,调用后立即返回,如果已经errno为EINPROGRESS,说明已经发起了三次握手。否则为异常。
23-45行:使用select去监测套接口状态。实现规定(那些乱七八糟的不同实现,我一直搞不清楚,哎,接触的不多!大家可以自己去查查):(1)当连接建立成功时,描述字变为可写,(2)当连接建立遇到错误时,描述字变为既可读又可写。我是个比较投机的人,归纳一下,描述字变为可写时说明连接有结果了。30行使用getsockopt获取描述字状态(使用SO_ERROR选项)。建立成功err为0,否则为-1。对于一个商业软件的“贡献者”,我们不能放过任何出错处理,但这里不对getsockopt的返回值进行出错处理是有原因的。在异常情况下,有些实现该调用返回0,有些则返回-1,因此我们不能根据它的返回值来判断连接是否异常。38-45行,前面已经提到,select这个阻塞的家伙很有可能发生EINTR错误,我们也必须兼容这种错误。注意reselect的位置,自己使用时位置放错了效果可是截然不同的。
48-51行:connect有时反应很快啊,一经调用就成功建立连接了,这种情况是存在的。所以我们也要兼容这种情况。
后面行:恢复调用前状态。
又到了热血澎湃的总结时刻:以前,看到有些地方使用非阻塞的connect,真的很费解,为什么简单的connect不直接使用,还要搞那么多花样?现在其实我们应该可以想明白了,作为一个完美程序的追求者,着眼于代码长期地维护(自己太高尚了),阻塞的connect确实会存在很多的问题。那是否存在一个稳健的阻塞的connect版本,说实话,我没仔细想过,粗略地想想那应该是个很复杂的东西,比如:connect返回时是否已经开始三次握手等等?因此,阻塞的或者非阻塞的connect目前并非再是二者选其一、根据喜好选择使用的问题了,而是必须使用非阻塞的connect。这个结论或许很武断,但本人通过目前了解的知识得出了这样的结论,还是希望有大神突然出现,指点一二。
这是《UNIX网络编程》上的原装问题,新发现的,按照书上的说法,貌似很严重,我要先测试一下。在我给出一个比较稳健的程序之前不允许存在已知的类似问题。又要牵涉到SO_LINGER选项了,真是有点无法自圆自说的感觉。就当初探SO_LINGER选项吧,现在它只是配角,后面有机会详细研究一下它。
服务器代码,这个代码同样可以用来测试select发生的EINTR的问题:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 #include <signal.h> 10 #include <errno.h> 11 12 #define PORT 1234 13 #define BACKLOG 5 14 #define MAXDATASIZE 1000 15 16 void sig_chld(int signo) 17 { 18 pid_t pid; 19 int stat = 0; 20 21 pid = wait(&stat); 22 printf("child %d terminated(stat:%d)\n", pid, stat); 23 24 return; 25 } 26 27 void signal_ex(int signo, void* func) 28 { 29 struct sigaction act, oact; 30 31 act.sa_handler = func; 32 sigemptyset(&act.sa_mask); //清空此信号集 33 act.sa_flags = 0; 34 35 if (sigaction(signo, &act, &oact) < 0) 36 { 37 printf("sig err!\n"); 38 } 39 40 //sigaction(SIGINT, &oact, NULL); //恢复成原始状态 41 return; 42 } 43 44 int main() 45 { 46 int listenfd, connectfd; 47 struct sockaddr_in server; 48 struct sockaddr_in client; 49 socklen_t addrlen; 50 char szbuf[MAXDATASIZE + 1] = {0}; 51 int num = 0; 52 pid_t pid_child; 53 int ret; 54 fd_set set; 55 struct timeval mytm; 56 57 if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 58 { 59 perror("Creating socket failed."); 60 exit(1); 61 } 62 63 int opt = SO_REUSEADDR; 64 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 65 66 bzero(&server, sizeof(server)); 67 server.sin_family = AF_INET; 68 server.sin_port = htons(PORT); 69 server.sin_addr.s_addr = htonl(INADDR_ANY); 70 if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) 71 { 72 perror("Bind()error."); 73 exit(1); 74 } 75 if (listen(listenfd, BACKLOG) == -1) 76 { 77 perror("listen()error\n"); 78 exit(1); 79 } 80 81 signal_ex(SIGCHLD, sig_chld); 82 while (1) 83 { 84 addrlen = sizeof(client); 85 sleep(10); 86 printf("start accept!\n"); 87 if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1) 88 { 89 #if 1 90 if (EINTR == errno) 91 { 92 printf("EINTR!\n"); 93 continue; 94 } 95 #endif 96 97 perror("accept()error\n"); 98 exit(1); 99 }100 printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));101 102 if (0 == (pid_child = fork()))103 {104 close(connectfd);105 close(listenfd);106 printf("child a ha!\n");107 sleep(5);108 exit(0);109 }110 111 mytm.tv_sec = 15;112 mytm.tv_usec = 0;113 reselect:114 FD_ZERO(&set);115 FD_SET(connectfd, &set);116 if ((ret = select(connectfd + 1, &set, NULL, NULL, &mytm)) > 0)117 {118 if(FD_ISSET(connectfd, &set))119 {120 printf("connectfd can be readn!\n");121 }122 }123 else if (0 == ret)124 {125 printf("timeout!\n");126 }127 else if (ret < 0)128 {129 //perror("error! ");130 if (EINTR == errno)131 {132 printf("error! errno = %s:%d\n", strerror(errno), errno);133 goto reselect;134 }135 }136 137 close(connectfd);138 connectfd = -1;139 }140 141 close(listenfd);142 143 return 0;144 }
客户端代码:
1 #include <sys/types.h> 2 #include <sys/socket.h> 3 #include <netinet/in.h> 4 #include <netdb.h> 5 #include <signal.h> 6 7 #define PORT 1234 8 #define MAXDATASIZE 1000 9 10 int main(int argc, char *argv[])11 {12 int sockfd = -1;13 struct sockaddr_in server;14 struct linger ling;15 16 if (argc != 2) 17 {18 printf("Usage:%s <IP Address>\n", argv[0]);19 exit(1);20 }21 22 signal(SIGPIPE, SIG_IGN);23 24 if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1)25 {26 printf("socket()error\n");27 exit(1);28 }29 bzero(&server, sizeof(server));30 server.sin_family = AF_INET;31 server.sin_port = htons(PORT);32 server.sin_addr.s_addr = inet_addr(argv[1]);33 if (connect(sockfd, (struct sockaddr *)&server, sizeof(server)) == -1)34 {35 printf("connect()error\n");36 exit(1);37 }38 39 ling.l_onoff = 1;40 ling.l_linger = 0;41 setsockopt(sockfd, SOL_SOCKET, SO_LINGER, &ling, sizeof(ling));42 43 close(sockfd);44 45 return 0;46 }
书上提到,有些实现内核会抛一个错误给应用进程,应用进程在accept时会返回错误。而有些实现内核完全能够兼容这种问题,不对应用进程产生任何影响。强大的linux显然是后者,客户端发送RST后,服务器accept没有返回任何错误,运行正常。
因此,该问题暂且略过,实际使用时还是应当注意这种极端的情况。
写到这,赶紧要为这个标题画句号了。一边写一边学,再这么下去就成无底洞了。而UDP的相关知识自己本身用得不多,在这不敢下笔,等哪一天心中有物再来详细整理一下。因此,这边要很不负责任地给出一个最终版本的TCP代码了。
个人认为,网络编程还有很多知识,但是该代码已经可以应付一个新手解决很多实际的问题了。之所以叫“快速上手”,干得也就是这种事,离精通还是很远的。之后,有什么问题再以专题的形式给出吧。
先是服务器代码:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <string.h> 4 #include <unistd.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <arpa/inet.h> 9 #include <signal.h> 10 #include <errno.h> 11 12 #define PORT 1234 13 #define BACKLOG 5 14 #define MAXDATASIZE 1000 15 #define TEST_STRING "HELLO, WORLD!" 16 #define TEST_STRING_LEN strlen(TEST_STRING) 17 18 int readn(int connfd, void *vptr, int n) 19 { 20 int nleft; 21 int nread; 22 char *ptr; 23 int ret = -1; 24 struct timeval select_timeout; 25 fd_set rset; 26 27 ptr = (char*)vptr; 28 nleft = n; 29 30 while (nleft > 0) 31 { 32 FD_ZERO(&rset); 33 FD_SET(connfd, &rset); 34 select_timeout.tv_sec = 5; 35 select_timeout.tv_usec = 0; 36 if ((ret = select(connfd+1, &rset, NULL, NULL, &select_timeout)) < 0) 37 { 38 if (errno == EINTR) 39 { 40 continue; 41 } 42 else 43 { 44 return -1; 45 } 46 } 47 else if (0 == ret) 48 { 49 return -1; 50 } 51 if ((nread = recv(connfd, ptr, nleft, 0)) < 0) 52 { 53 if(errno == EINTR) 54 { 55 nread = 0; 56 } 57 else 58 { 59 return -1; 60 } 61 } 62 else if (nread == 0) 63 { 64 break; 65 } 66 nleft -= nread; 67 ptr += nread; 68 } 69 70 return(n - nleft); 71 } 72 73 void sig_chld(int signo) 74 { 75 pid_t pid; 76 int stat = 0; 77 78 pid = wait(&stat); 79 80 return; 81 } 82 83 void signal_ex(int signo, void* func) 84 { 85 struct sigaction act, oact; 86 87 act.sa_handler = func; 88 sigemptyset(&act.sa_mask); //清空此信号集 89 act.sa_flags = 0; 90 91 if (sigaction(signo, &act, &oact) < 0) 92 { 93 printf("sig err!\n"); 94 } 95 96 //sigaction(SIGINT, &oact, NULL); //恢复成原始状态 97 return; 98 } 99 100 int main()101 {102 int listenfd, connectfd;103 struct sockaddr_in server;104 struct sockaddr_in client;105 socklen_t addrlen;106 char szbuf[MAXDATASIZE + 1] = {0};107 int num = 0;108 pid_t pid_child;109 110 signal(SIGPIPE, SIG_IGN);111 112 if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)113 {114 perror("Creating socket failed.");115 exit(1);116 }117 118 int opt = SO_REUSEADDR;119 setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));120 121 bzero(&server, sizeof(server));122 server.sin_family = AF_INET;123 server.sin_port = htons(PORT);124 server.sin_addr.s_addr = htonl(INADDR_ANY);125 if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) 126 {127 perror("Bind()error.");128 exit(1);129 } 130 if (listen(listenfd, BACKLOG) == -1)131 {132 perror("listen()error\n");133 exit(1);134 }135 136 signal_ex(SIGCHLD, sig_chld);137 while (1)138 {139 addrlen = sizeof(client);140 printf("start accept!\n");141 if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1) 142 {143 if (EINTR == errno)144 {145 printf("EINTR!\n");146 continue;147 }148 149 perror("accept()error\n");150 exit(1);151 }152 printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port));153 154 if (0 == (pid_child = fork()))155 {156 while (1)157 {158 num = readn(connectfd, szbuf, TEST_STRING_LEN);159 if (num < 0)160 {161 printf("read error!\n");162 break;163 }164 else if (0 == num)165 {166 printf("read over!\n");167 break;168 }169 else170 {171 printf("recv: %s\n", szbuf);172 }173 }174 close(connectfd);175 close(listenfd);176 sleep(5);177 exit(0);178 }179 180 close(connectfd);181 connectfd = -1;182 }183 184 close(listenfd);185 186 return 0;187 }
客户端:
1 #include <stdio.h> 2 #include <stdlib.h> 3 #include <unistd.h> 4 #include <string.h> 5 #include <sys/types.h> 6 #include <sys/socket.h> 7 #include <netinet/in.h> 8 #include <netdb.h> 9 #include <signal.h> 10 #include <errno.h> 11 #include <fcntl.h> 12 13 #define PORT 1234 14 #define MAXDATASIZE 1000 15 #define TEST_STRING "HELLO, WORLD!" 16 #define TEST_STRING_LEN strlen(TEST_STRING) 17 18 int writen(int connfd, void *vptr, size_t n) 19 { 20 int nleft, nwritten; 21 char *ptr; 22 23 ptr = (char*)vptr; 24 nleft = n; 25 26 while (nleft > 0) 27 { 28 if ((nwritten = send(connfd, ptr, nleft, MSG_NOSIGNAL)) == -1) 29 { 30 if (errno == EINTR) 31 { 32 nwritten = 0; 33 } 34 else 35 { 36 return -1; 37 } 38 } 39 nleft -= nwritten; 40 ptr += nwritten; 41 } 42 43 return(n); 44 } 45 46 int connectWithTimeout(int sock, struct sockaddr* addrs, int adrsLen,struct timeval* tm) 47 { 48 int err = 0; 49 int len = sizeof(int); 50 int flag; 51 int ret = -1; 52 int retselect = -1; 53 fd_set set; 54 struct timeval mytm; 55 56 if (tm != NULL){ 57 memcpy(&mytm, tm, sizeof(struct timeval)); 58 } 59 60 flag = fcntl(sock, F_GETFL, 0); 61 fcntl(sock, F_SETFL, flag | O_NONBLOCK); //linux用这个 62 // flag = 1; 63 // ioctl(sock,FIONBIO,&flag); 64 65 ret = connect(sock, addrs, adrsLen); 66 if (-1 == ret) 67 { 68 if (EINPROGRESS == errno) 69 { 70 reselect: 71 printf("start check!\n"); 72 FD_ZERO(&set); 73 FD_SET(sock,&set); 74 if ((retselect = select(sock+1, NULL, &set, NULL, tm)) > 0) 75 { 76 if (FD_ISSET(sock, &set)) 77 { 78 getsockopt(sock, SOL_SOCKET, SO_ERROR, &err, (socklen_t*)&len); 79 if (0 == err) 80 ret = 0; 81 else 82 ret = -1; 83 } 84 85 } 86 else if (retselect < 0) 87 { 88 if (EINTR == errno) 89 { 90 printf("error! errno = %s:%d\n", strerror(errno), errno); 91 goto reselect; 92 } 93 } 94 } 95 } 96 else if (0 == ret) 97 { 98 printf("OK at right!\n"); 99 ret = 0; //OK100 }101 102 fcntl(sock, F_SETFL, flag);103 // flag = 0;104 // ioctl(sock, FIONBIO, &flag);105 106 if (tm != NULL){107 memcpy(tm, &mytm, sizeof(struct timeval));108 }109 110 return ret;111 }112 113 int main(int argc, char *argv[])114 {115 int sockfd, num;116 char szbuf[MAXDATASIZE] = {0};117 struct sockaddr_in server;118 struct timeval timeOut;119 int ret = -1;120 int iSendTime = 0;121 122 if (argc != 2) 123 {124 printf("Usage:%s <IP Address>\n", argv[0]);125 exit(1);126 }127 128 signal(SIGPIPE, SIG_IGN);129 130 if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)131 {132 printf("socket()error\n");133 exit(1);134 }135 136 bzero(&server, sizeof(server));137 server.sin_family = AF_INET;138 server.sin_port = htons(PORT);139 server.sin_addr.s_addr = inet_addr(argv[1]);140 141 timeOut.tv_sec = 5;142 timeOut.tv_usec = 0;143 ret = connectWithTimeout(sockfd, (struct sockaddr *)&server, sizeof(server), &timeOut);144 if (-1 == ret)145 {146 printf("connect()error\n");147 exit(1);148 }149 150 memset(szbuf, 0, sizeof(szbuf));151 strcpy(szbuf, TEST_STRING);152 153 while (iSendTime < 5)154 {155 ret = writen(sockfd, szbuf, TEST_STRING_LEN);156 if (TEST_STRING_LEN != ret)157 {158 break;159 }160 else161 {162 printf("%dth send success!\n", iSendTime);163 iSendTime++;164 }165 }166 167 close(sockfd);168 169 return 0;170 }
总结的时候,有很多知识都得到了更新,最终代码对部分函数进行了完善。当然还有一些东西没有考虑进去,比如说对端掉电、路由器损坏等问题,以上程序都无法很好的适应这些问题。后续再慢慢改进吧。
最终的例子功能很简单,最主要还是在socket编程的各个细节的处理上。
在此要说OVER了!
联系客服