网络编程Socket它TCP它TIME_WAIT国家具体解释
下面我们用最简单的一对一的客户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国家具体解释的更多相关文章
- 网络编程Socket之TCP之close/shutdown具体解释(续)
接着上一篇网络编程Socket之TCP之close/shutdown具体解释 如今我们看看对于不同情况的close的返回情况和可能遇到的一些问题: 1.默认操作的close 说明:我们已经知道writ ...
- java网络编程socket\server\TCP笔记(转)
java网络编程socket\server\TCP笔记(转) 2012-12-14 08:30:04| 分类: Socket | 标签:java |举报|字号 订阅 1 TCP的开销 a ...
- python网络编程(Socket、TCP、UDP)
Socket 是网络编程的一个抽象概念,通常我们用一个Socket表示 "打开了一个网络链接",而打开一个Socket 需要知道目标计算机的IP 地址和端口号,再指定协议类型即可. ...
- 网络编程Socket之TCP
服务端: 1. 创建 ServerSocket 对象并监听一个端口 2. 调用accept()方法等待客户端的连接(阻塞式) 3. 输入流(记取客户端发送过来的数据) 4. 输出流(响 ...
- IPv6下网络编程socket, TCP和UDP例子,以及兼容IPV4和IPV6的类
一.TCP socket ipv6与ipv4的区别 服务器端源代码如下: #include <stdio.h> #include <stdlib.h> #include < ...
- 二、网络编程-socket之TCP协议开发客户端和服务端通信
知识点:之前讲的udp协议传输数据是不安全的,不可靠不稳定的,tcp协议传输数据安全可靠,因为它们的通讯机制是不一样的.udp是用户数据报传输,也就是直接丢一个数据包给另外一个程序,就好比寄信给别人, ...
- 网络编程Socket之TCP之connect具体解释
对TCP套接字调用connect会激发三次握手,例如以下: client是主动打开连接的一端,会发送第一个SYN分节,然后等待确认,此时连接状态为SYN_SENT,当收到服务端的确认后连接建立,状态变 ...
- python_网络编程socket(TCP)
服务端: import socket sk = socket.socket() #创建对象 sk.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) ...
- Python网络编程02 /基于TCP、UDP协议的socket简单的通信、字符串转bytes类型
Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes类型 目录 Python网络编程02 /基于TCP.UDP协议的socket简单的通信.字符串转bytes ...
随机推荐
- mac系统创建.开头文件.htaccess
thinkphp5 隐藏index.php的时候需要用的.htaccess文件,但是mac默认不让创建这种文件 感谢 https://blog.csdn.net/gyz413977349/articl ...
- (八)RabbitMQ消息队列-通过Topic主题模式分发消息
原文:(八)RabbitMQ消息队列-通过Topic主题模式分发消息 前两章我们讲了RabbitMQ的direct模式和fanout模式,本章介绍topic主题模式的应用.如果对direct模式下通过 ...
- ios开发网络学习AFN框架的使用一:get和post请求
#import "ViewController.h" #import "AFNetworking.h" @interface ViewController () ...
- Docker CE for Windows安装使用
原文:Docker CE for Windows安装使用 官网下载并安装Docker CE for Windows IDEA连接Docker Docker一些常用命令 Docker for windo ...
- 【oracle11g ,19】索引管理
一.索引的分类: 1.逻辑上分为: 单列索引和复合索引 唯一索引和非唯一索引 函数索引 domain索引 2.物理上分: 分区索引和非分区索引 b-tree bitmap 注意:表和索引最好 ...
- Oracle 自己主动内存參数依赖性
图例:在该图中使用了下面參数名称缩写: MT = MEMORY_TARGET MMT = MEMORY_MAX_TARGET ST = SGA_TARGET PAT = PGA_AGGREGATE_T ...
- android 连接USB按power键锁屏2声锁屏音
alps\frameworks\base\packages\Keyguard\src\com\android\keyguard\KeyguardViewMediator.java #1384 行左右: ...
- 开源库Fab-Transformation简单使用解析
转载请注明出处王亟亟的大牛之路 相似于IPhone的悬浮按钮的操作,仅仅只是是固定的,当然经过自己的改动也能够动.这边仅仅是给伸手党一个福祉,外加加上一些自己的理解.让大家能够拿来就用.看了就懂,废话 ...
- 关系型数据库工作原理-快速缓存(翻译自Coding-Geek文章)
本文翻译自Coding-Geek文章:< How does a relational database work>. 原文链接:http://coding-geek.com/how-dat ...
- WPF 针对数据源某个属性进行排序
原文:WPF 针对数据源某个属性进行排序 版权声明:本文为博主原创文章,未经博主允许不得转载. https://blog.csdn.net/wanlong360599336/article/detai ...