标签:
下面我们用最简单的一对一的客户server编程模型重现遇到的一些问题:
初学者socket当写作socket名其妙的问题。比方说bind函数返回的常见错误是EADDRINUSE
使用以下的程序重现这个状态:
client:
int main(int argc, const char * argv[]) { struct sockaddr_in serverAdd; bzero(&serverAdd, sizeof(serverAdd)); serverAdd.sin_family = AF_INET; serverAdd.sin_addr.s_addr = inet_addr(SERV_ADDR); serverAdd.sin_port = htons(SERV_PORT); int connfd = socket(AF_INET, SOCK_STREAM, 0); int connResult = connect(connfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd)); if (connResult < 0) { printf("连接失败\n"); close(connfd); return -1; } ssize_t writeLen; ssize_t readLen; char recvMsg[65535] = {0}; char sendMsg[20] = "I am client"; writeLen = write(connfd, sendMsg, sizeof(sendMsg)); if (writeLen < 0) { printf("发送失败\n"); close(connfd); return -1; } else { printf("发送成功\n"); } while (1) { // sleep(1); readLen = read(connfd, recvMsg, sizeof(recvMsg)); if (readLen < 0) { printf("读取失败\n"); close(connfd); return -1; } if (readLen == 0) { printf("服务器关闭\n"); close(connfd); return -1; } printf("server said:%s\n",recvMsg); } close(connfd); return 0; }
int main(int argc, const char * argv[]) { struct sockaddr_in serverAdd; struct sockaddr_in clientAdd; bzero(&serverAdd, sizeof(serverAdd)); serverAdd.sin_family = AF_INET; serverAdd.sin_addr.s_addr = htonl(INADDR_ANY); serverAdd.sin_port = htons(SERV_PORT); socklen_t clientAddrLen; int listenfd = socket(AF_INET, SOCK_STREAM, 0); if (listenfd < 0) { printf("创建socket失败\n"); close(listenfd); return -1; } int bindResult = bind(listenfd, (struct sockaddr *)&serverAdd, sizeof(serverAdd)); if (bindResult < 0) { close(listenfd); printf("绑定port失败,errno = %d\n",errno); return -1; } else { printf("绑定port成功\n"); } listen(listenfd, 20); int connfd; unsigned char recvMsg[65535]; char replyMsg[20] = "I am server"; clientAddrLen = sizeof(clientAdd); connfd = accept(listenfd,(struct sockaddr *)&clientAdd,&clientAddrLen); if (connfd < 0) { close(listenfd); printf("连接失败\n"); return -1; } else { printf("连接成功\n"); } ssize_t readLen = read(connfd, recvMsg, sizeof(recvMsg)); printf("readLen:%ld\n",readLen); if (readLen < 0) { printf("读取失败\n"); return -1; } else if (readLen == 0) { printf("读取完毕\n"); close(listenfd); return 0; } printf("client said:%s\n",recvMsg); while (1) { write(connfd, replyMsg, sizeof(replyMsg)); } close(connfd); return 0; }
绑定port失败,errno= 48
这里要说明一个问题:
当一个Unix进程不管自愿的(调用exit或者从main函数返回)还是非自愿的(收到一个终止本进程的信号)终止时,全部打开的描写叙述符都被关闭,这也将导致仍然打开的不论什么TCP连接上发出一个FIN。
非常明显server已经关闭了。为什么会绑定port失败呢。以下是TCP连接终止的四个分节:
某些情况下第一个分节的FIN随数据一起发送。另外,第二个和第三个分节有可能被合并成一个分节。
这里我们的server是主动关闭的一端。当主动发送FIN分节以后等待确认,状态变为FIN_WAIT_1,收到确认以后状态变为FIN_WAIT_2,收到client的FIN分节以后状态变为TIME_WAIT状态。这里在TIME_WAIT状态会停留2MSL后才会进入CLOSED状态。所以我们再立刻启动server的时候,之前的连接还没有处于CLOSED状态,还存在者,所以就会绑定失败了;后面会讲到为什么会存在TIME_WAIT状态;
这里client是被动关闭的一端,收到服务端的FIN之后状态进入CLOSE_WAIT,这个时候read方法会返回0,然后发送对第一个分节的确认。此时client调用close方法发送FIN分节给服务端进入LAST_ACK状态,等待确认到达。收到确认以后连接状态变为CLOSED;
TIME_WAIT状态:停留在该状态的持续时间是最长分节生命期(maximum segment lifetime,MSL)的两倍。有时候称为2MSL。MSL是不论什么IP数据报可以在因特网中存活的最长时间,最大值为255。这是一个跳数限制而不是真正的时间限制。
TIME_WAIT状态存在理由:
可靠地实现TCP全双工连接的终止:可能不得不重传终于那个ack,TIME_WAIT后是CLOSED,假设没有TIME_WAIT的2MSL,直接CLOSED,那么假设最后一个ACK丢失了,是不会又一次再发送ACK的,那服务端收不到ACK就会又一次发送终于那个FIN,这个时候client已经是CLOSED了,就会响应一个RST,这个RST就会被server解释成一个错误。
同意来的反复分节在网络中消逝:保证每成功建立一个TCP连接时。来自该连接先前化身的老的反复分组都已经在网络中消逝了。从而不会被误解成新连接的分组。
这里另一种情况:打开clientwhile里面的sleep,(或者屏蔽掉client以下的代码)然后再先执行server程序。再执行client,然后关闭服务端后立刻再打开服务端,仍然会绑定失败,此时的状态和之前的有点不一样
if (readLen == 0) { printf("server关闭\n"); close(connfd); return -1; }
wanglijuntekiMac-mini:~ wanglijun$ netstat -an |grep 8000
tcp4 0 0 192.168.1.103.8000 192.168.1.103.49632 FIN_WAIT_2
tcp4 290960 0 192.168.1.103.49632 192.168.1.103.8000 CLOSE_WAIT
这里有一个SO_REUSEADDR套接字选项,打开之后就能解决如上的问题,我们在band之前加入例如以下设置代码:
<span style="font-size:12px;">int yes = 1; setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (void *)&yes, sizeof(yes));</span>
对于TCP,我们绝不可能启动捆绑同样IP地址和同样port号的多个server。
參考:
《UNIX Network ProgrammingVolume 1, Third Edition: TheSockets Networking API》
版权声明:本文博客原创文章,博客,未经同意,不得转载。
网络编程Socket它TCP它TIME_WAIT国家具体解释
标签:
原文地址:http://www.cnblogs.com/gcczhongduan/p/4688595.html