下面我们用最简单的一对一的客户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. css3-10 如何使用滚动条

    css3-10 如何使用滚动条 一.总结 一句话总结:给设置了宽高的块标签使用,直接将overflow属性写到style里面即可. 1.滚动条的使用对象时谁? 一般是div,当div比较小(设置了宽高 ...

  2. 数学分析告诉偶们什么(vamei)

    1]人生的痛苦在于追求错误的东西.所谓追求错误的东西,就是你在无限趋近于它的时候,才猛然发现,你和它是不连续的. 2]人和人就像数轴上的有理数点,彼此能够靠得非常近非常近,但你们之间始终存在隔阂. 3 ...

  3. linux 内核驱动编程 简单例子 与_IO, _IOR, _IOW, _IOWR 宏解析

    一._IO, _IOR, _IOW, _IOWR 宏的用法与解析 在驱动程序里, ioctl() 函数上传送的变量 cmd 是应用程序用于区别设备驱动程序请求处理内容的值.cmd除了可区别数字外,还包 ...

  4. 【24.91】【Tsinsen 1302】&【BZOJ 2626】JZPFAR

    时间限制:5.0s   内存限制:256.0MB   总提交次数:547   AC次数:137   平均分:40.31 将本题分享到:        查看未格式化的试题   提交   试题讨论 试题来 ...

  5. [Angular] Create custom validators for formControl and formGroup

    Creating custom validators is easy, just create a class inject AbstractControl. Here is the form we ...

  6. Git使用经验小结

    2012春,开始正式使用SVN,2014年9月加入一起好后,开始使用Git.  有了一些使用经验,也看了下网上关于"Git全胜SVN"的言论. 结合自己的实际情况,简要写几句: 1 ...

  7. php定时任务(自己)

    php定时任务(自己) 一.总结 一句话总结:可用php.exe连接php文件和bat文件,bat文件在计划任务中可以设置定时执行, 二. 1.php 2.bat E: "D:\softwa ...

  8. TOP 10 ONLINE COMPILER

    Top 10 Online Compilers +1338 Tweet Share106 Share Pin 444 Shares Online compilers are one type of t ...

  9. Redis+Mysql模式和内存+硬盘模式的异同

    http://www.open-open.com/lib/view/open1346029825942.html 学习任何新知识,都是一个循序渐进的过程,从刚开始的懵懂无知,到简单熟悉,然后突然的彻悟 ...

  10. Qt写入unicode编码格式的文本(用QChar写入BOM标记,并且列出所有Qt支持的字符集)

    1.文本流设置unicode小端模式 2.写入文本前两个字节FF FE 3.字符串转成unicode编码 QList<QByteArray> list = QTextCodec::avai ...