三、细节问题一个也不能少
Socket编程说简单也简单,程序很容易就能跑起来,说麻烦还真是麻烦,程序动不动就出问题。记得刚开始写网络代码的时候,那真是令人抓狂的经历,问题一个套一个,一会服务器起不来了,一会数据接收异常了,到最后自己都对那些系统调用都不放心了,怎么会要考虑那么多东西?起初,我是一万个怀疑,是不是自己人品出问题了,怎么别人没遇到,全给自己赶上了。后来,拿着《UNIX网络编程》随便看看,那书怎么会这么了解我的?细节!细节!细节!那些问题都被别人明明写出来了,自己又SX了。没办法,细节不注意,有苦说不出啊。
不过也不能怪自己不爱学习啊,说实话那书实在太厚了,下面只记录一些自己遇到的、知道原因和解决方法的细节问题,还有很多后面慢慢学习吧!问题中使用的示例程序多少有点编造的意思,旨在说明问题。现实当中肯定是会发生的,概率也不能说低,我也没那么多闲时去统计具体数据。
-
端口复用
此处描述的内容可能和端口复用的真实概念不符合,但我习惯用这种描述方法,理解下面内容即可。
在调试网络程序的时候,TCP服务器经常起不来,总是在bind时出错。经验告诉我,此时换一个绑定端口往往就能起效,或者等个几分钟,服务器也能正常启动了。因为当时跑的都是些小的测试代码,换个端口还是很方便的。可这种问题要是放到一个系统或者一个服务器上,怎能接受?
当时,自己摸索了几天都没什么结果,最后还是师傅威武,告诉我还有个端口复用的东西。现在整理,已经没了当时的环境,自己构造了这一问题。
客户端程序为:
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 10 #define PORT 1234 11 #define MAXDATASIZE 1000 12 13 int main(int argc, char *argv[]) 14 { 15 int sockfd, num; 16 char buf[MAXDATASIZE + 1] = {0}; 17 struct sockaddr_in server; 18 19 if (argc != 2) 20 { 21 printf("Usage:%s <IP Address>\n", argv[0]); 22 exit(1); 23 } 24 25 if ((sockfd=socket(AF_INET, SOCK_STREAM, 0)) == -1) 26 { 27 printf("socket()error\n"); 28 exit(1); 29 } 30 bzero(&server, sizeof(server)); 31 server.sin_family = AF_INET; 32 server.sin_port = htons(PORT); 33 server.sin_addr.s_addr = inet_addr(argv[1]); 34 if (connect(sockfd, (struct sockaddr *)&server, \ 35 sizeof(server)) == -1) 36 { 37 printf("connect()error\n"); 38 exit(1); 39 } 40 41 while (1) 42 { 43 memset(buf, 0, sizeof(buf)); 44 if ((num = recv(sockfd, buf, MAXDATASIZE,0)) == -1) 45 { 46 printf("recv() error\n"); 47 exit(1); 48 } 49 buf[num - 1]='\0'; 50 printf("Server Message: %s\n",buf); 51 } 52 53 close(sockfd); 54 55 return 0; 56 }
服务器程序为:
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 11 #define PORT 1234 12 #define BACKLOG 5 13 #define MAXDATASIZE 1000 14 15 int main() 16 { 17 int listenfd, connectfd; 18 struct sockaddr_in server; 19 struct sockaddr_in client; 20 socklen_t addrlen; 21 char szbuf[MAXDATASIZE] = {0}; 22 int iCount = 0; 23 24 if ((listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) 25 { 26 perror("Creating socket failed."); 27 exit(1); 28 } 29 30 int opt = SO_REUSEADDR; 31 // setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)); 32 33 bzero(&server, sizeof(server)); 34 server.sin_family = AF_INET; 35 server.sin_port = htons(PORT); 36 server.sin_addr.s_addr = htonl(INADDR_ANY); 37 if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1) 38 { 39 perror("Bind()error."); 40 exit(1); 41 } 42 if (listen(listenfd, BACKLOG) == -1) 43 { 44 perror("listen()error\n"); 45 exit(1); 46 } 47 48 addrlen = sizeof(client); 49 if ((connectfd = accept(listenfd, (struct sockaddr*)&client, &addrlen)) == -1) 50 { 51 perror("accept()error\n"); 52 exit(1); 53 } 54 printf("You got a connection from cient's ip is %s, prot is %d\n", inet_ntoa(client.sin_addr), htons(client.sin_port)); 55 56 memset(szbuf, 'a', sizeof(szbuf)); 57 while (iCount < 100) 58 { 59 send(connectfd, szbuf, sizeof(szbuf), 0); 60 iCount++; 61 } 62 63 printf("send over!\n"); 64 sleep(10); 65 66 close(connectfd); 67 close(listenfd); 68 69 return 0; 70 }
客户端和服务器在两台电脑上运行,电脑通过交换机相连,当服务器将数据发送完成之后,sleep一段时间再关闭链接。根据程序,sleep的时候,手动断电关闭交换机,sleep完成,服务器关闭链路,进程退出。然后再重启服务器,服务器就起不来了,打印:
使用netstat –an查看端口使用情况:
1234端口处于FIN_WAIT2状态,端口处于占用状态,所以bind返回失败了。解决办法:
代码中增加下面语句即可
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
上述语句在TCP服务器代码中是必不可少的。