下面我们用最简单的一对一的客户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;
}

server:

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;
}

首先执行server程序,再执行client。然后关闭服务端后立刻再打开服务端,就会打印例如以下信息:48相应EADDRINUSE错误码

绑定port失败,errno=
48

这里要说明一个问题:

当一个Unix进程不管自愿的(调用exit或者从main函数返回)还是非自愿的(收到一个终止本进程的信号)终止时,全部打开的描写叙述符都被关闭,这也将导致仍然打开的不论什么TCP连接上发出一个FIN。

非常明显server已经关闭了。为什么会绑定port失败呢。以下是TCP连接终止的四个分节:

watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQvanVuanVuMTUwMDEzNjUy/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/SouthEast" alt="">

某些情况下第一个分节的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;
}

我们从终端信息打印例如以下,此时server处于FIN_WAIT_2状态,就如上面说的由于client还没有关闭连接,没有发送第三个FIN分节,此时client由于已经收到来自服务端的FIN分节而处于CLOSE_WAIT状态;

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>

server重新启动监听时,试图捆绑现有连接上的port会失败,(另一种情况可能之前派生出来的子进程还处理着连接)假设设置了SO_REUSEADDR套接字选项。就会bind成功,全部的TCPserver都应该指定SO_REUSEADDR套接字选项,以同意server在这样的情形下被又一次启动;SO_REUSEADDR同意在同一port上启动同一server的多个实例,仅仅要每一个实例捆绑一个不同的本地IP地址就可以。

对于TCP,我们绝不可能启动捆绑同样IP地址和同样port号的多个server。

參考:

《UNIX Network ProgrammingVolume 1, Third Edition: TheSockets Networking API》

版权声明:本文博客原创文章,博客,未经同意,不得转载。

网络编程Socket它TCP它TIME_WAIT国家具体解释的更多相关文章

  1. 网络编程Socket之TCP之close/shutdown具体解释(续)

    接着上一篇网络编程Socket之TCP之close/shutdown具体解释 如今我们看看对于不同情况的close的返回情况和可能遇到的一些问题: 1.默认操作的close 说明:我们已经知道writ ...

  2. java网络编程socket\server\TCP笔记(转)

    java网络编程socket\server\TCP笔记(转) 2012-12-14 08:30:04|  分类: Socket |  标签:java  |举报|字号 订阅     1 TCP的开销 a ...

  3. python网络编程(Socket、TCP、UDP)

    Socket 是网络编程的一个抽象概念,通常我们用一个Socket表示 "打开了一个网络链接",而打开一个Socket 需要知道目标计算机的IP 地址和端口号,再指定协议类型即可. ...

  4. 网络编程Socket之TCP

            服务端: 1. 创建 ServerSocket 对象并监听一个端口 2. 调用accept()方法等待客户端的连接(阻塞式) 3. 输入流(记取客户端发送过来的数据) 4. 输出流(响 ...

  5. IPv6下网络编程socket, TCP和UDP例子,以及兼容IPV4和IPV6的类

    一.TCP socket ipv6与ipv4的区别 服务器端源代码如下: #include <stdio.h> #include <stdlib.h> #include < ...

  6. 二、网络编程-socket之TCP协议开发客户端和服务端通信

    知识点:之前讲的udp协议传输数据是不安全的,不可靠不稳定的,tcp协议传输数据安全可靠,因为它们的通讯机制是不一样的.udp是用户数据报传输,也就是直接丢一个数据包给另外一个程序,就好比寄信给别人, ...

  7. 网络编程Socket之TCP之connect具体解释

    对TCP套接字调用connect会激发三次握手,例如以下: client是主动打开连接的一端,会发送第一个SYN分节,然后等待确认,此时连接状态为SYN_SENT,当收到服务端的确认后连接建立,状态变 ...

  8. python_网络编程socket(TCP)

    服务端: import socket sk = socket.socket() #创建对象 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) ...

  9. Python网络编程02 /基于TCP、UDP协议的socket简单的通信、字符串转bytes类型

    Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes类型 目录 Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes ...

随机推荐

  1. Xposed也要热更新

    好久没写博客了.这次玩一点不一样的. 吐槽&起因 相信熟悉Xposed的小伙伴们都知道,每次写完Xposed都要重新启动啊.有木有!反射错了,写错了名字.改一个log,都要重新启动啊有木有!重 ...

  2. YUV与RGB格式转换

    YUV格式具有亮度信息和色彩信息分离的特点,但大多数图像处理操作都是基于RGB格式. 因此当要对图像进行后期处理显示时,需要把YUV格式转换成RGB格式. RGB与YUV的变换公式如下: YUV(25 ...

  3. [HTML] Creating visual skip links in HTML and CSS

    Skip links are an extremely helpful navigation pattern for keyboard and screen reader users, since t ...

  4. 使用DatagramSocket与DatagramPacket传输数据 分类: B1_JAVA 2013-10-12 13:00 1936人阅读 评论(0) 收藏

    参考传智播客毕向东JAVA视频. 注: DatagramSocket发送的每个包都需要指定地址,而Socket则是在首次创建时指定地址,以后所有数据的发送都通过此socket. A socket is ...

  5. 怎样把ul li 前面的点去掉

    在li 属性框里 放入 <li style="list-style-type:none;">...<li> 就可以了

  6. ebook https://salttiger.com/category/notification/

    https://salttiger.com/category/notification/ 因为个人精力有限,持续搜集.整理.分享电子书已经占用我大部分业余时间,今后不再给评论区留言的朋友们找书,还希望 ...

  7. An Overview of Cisco IOS Versions and Naming

    An Overview of Cisco IOS Versions and Naming http://www.ciscopress.com/articles/article.asp?p=210654 ...

  8. uboot中rtc顶层分析

    uboot一般不会要求开启rtc,只是还是支持rtc以备特殊需求的. 底层驱动移植前面两篇已经介绍.这里介绍顶层的调用过程.顶层在uboot/common/cmd_date.c /* * (C) Co ...

  9. [React Router v4] Use the React Router v4 Link Component for Navigation Between Routes

    If you’ve created several Routes within your application, you will also want to be able to navigate ...

  10. Angular.js回想+学习笔记(1)【ng-app和ng-model】

    Angular.js中index.html简单结构: <!doctype html> <html ng-app> <head> <script src=&qu ...