下面我们用最简单的一对一的客户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. php用两个栈来实现队列

    php用两个栈来实现队列 一.总结 我主要的问题是不知道的是题目描述,题目和贵的代码之间的关系,以及返回值 思路:A栈做入队操作,B栈做出队操作,入队的时候元素直接入A,出队的时候判断B栈是否为空,如 ...

  2. 命令行运行python模块时提示包找不到的问题

    庄稼人不是专职python开发的道友.尽管与python相识已多年,可惜相识不相知,仅仅是偶尔借助pydev写一些简单的小工具. 多年来.一直困惑于这样一个问题:相同的project.相同的代码,使用 ...

  3. 驱动程序调试方法之printk——自制proc文件(一)

    首先我们需要弄清楚proc机制,来看看fs/proc/proc_misc.c这个文件,从入口函数开始看: proc_misc_init(void)         #ifdef CONFIG_PRIN ...

  4. 【codeforces 742C】Arpa's loud Owf and Mehrdad's evil plan

    time limit per test1 second memory limit per test256 megabytes inputstandard input outputstandard ou ...

  5. [Angular Directive] 1. Write an Angular Directive

    Angular 2 Directives allow you manipulate elements by adding custom behaviors through attributes. Th ...

  6. Android bitmap绘制文字自动换行

    public Bitmap getNewBitMap(String text) { Bitmap newBitmap = Bitmap.createBitmap(,, Config.ARGB_4444 ...

  7. pycharm的集成vim

    pycharm的集成vim 1. 基本使用 激活码 BIG3CLIK6F-eyJsaWNlbnNlSWQiOiJCSUczQ0xJSzZGIiwibGljZW5zZWVOYW1lIjoibGFuIHl ...

  8. Android解决Fragment多层嵌套时onActivityResult无法正确回调的问题

    前言: Fragment也可以使用startActivityForResult方法去打开一个Activity,然后在其onActivityResult方法中处理结果,可是当Fragment嵌套的时候, ...

  9. Thrift是一款由Fackbook开发的可伸缩、跨语言的服务开发框架

    这段时间,一直在整理公司的内部 rpc 服务接口,面临的一个问题就是:由于公司内部的系统由几个不同的语言编写的.C# ,java,node.js 等,如何实现这些内部系统之间的接口统一调用,确实是比较 ...

  10. GTID的限制

    1.不支持非事务引擎(从库报错,stop slave;start slave;忽略). 2.不支持create table ... select 语句复制(主库直接报错). 3.不允许一个SQL同时更 ...