linux TCP状态转换 半关闭 shutdown netstat 端口复用 setsockopt

TCP状态转换:发生在三次握手四次挥手的过程中

黑色为异常的信息; 

红色线为客户端;

绿色虚线为服务器端;

报文段寿命:

  2MSL(Maximum Segment Lifetime)

  主动断开连接的一方,最后进出入一个 TIME_WAIT状态,这个状态会持续: 2msl

  msl:官方建议:2分钟,实际是 30s

为什么四次挥手的同意客户端的断开(第二次挥手)和服务器的请求断开(第三次挥手)为什么分开发送?

  因为同意客户端断开,但服务器端不一定断开,第二次挥手和第三次挥手期间还可以发送数据,可能相隔一段时间;

为什么用2MSL?(双方都可以主动申请断开连接)

  因为第三次挥手的后,第四次挥手的信息服务端  可能  没有接收到第四次挥手的ACK信息,未收到ACK后,再次发送FIN,直到收到ACK为止,2次MSL为了确保收到第四次挥手的ACK。

    当TCP连接主动关闭方接收到被动关闭方发送的 FIN 和最终的 ACK后,连接的主动关闭方必须处于 TIME_WAIT 状态并持续 2MSL时间

    这样就能够让TCP连接的主动关闭方在它发送的ACK丢失的情况下重新发送最终的ACK

    主动关闭方重新发送的最终 ACK 并不是因为被动关闭方重传了 ACK(它们并不消耗序列号,被动关闭方也不会重传),而是因为被动关闭方重传了它的 FIN 。

    事实上,被动关闭方总是重传 FIN 直到它收到一个最终的 ACK。

 半关闭:

  当TCP连接中 A 向 B 发送 FIN请求关闭,另一端 B 回应 ACK 之后(A端进入 FIN_WAIT 状态),并没有立即发送 FIN 给 A,,A处于半连接状态(半开关),此时A可以接收B发送的数据,但是A已经不能再向B发送数据。

  从程序的角度看,可以使用 API 来控制实现半连接状态:

复制代码
#include <sys/socket.h>
int shutdown(int sockfd, int how);
/*  sockfd : 需要关闭的socket的描述符
    how :  允许为 shutdown 操作选择以下几种方式:
        SHUT_RD(0): 关闭 sockfd上的读功能, 此选项不允许sockfd进行读操作
                    该套接字不再接收数据, 任何当前在套接字接收缓冲区的数据将被无声的丢弃掉
        SHUT_WR(1): 关闭sockfd的写功能, 此选项不允许sockfd进行写操作, 进程不能在对此套接字发出写操作
        SHUT_RDWR(2): 关闭sockfd的读写功能, 相当于调用 shutdown 两次, 首先是 SHUT_RD, 然后是 SHUT_WR
*/
复制代码

  使用 close 中止一个连接,但它只是减少描述符的引用计数,并不直接关闭连接,只有当描述符的引用计数为 0 时才关闭连接。 shutdown 不考虑描述符的引用计数,直接关闭描述符。也可选择中止一个方向的连接,只中止读或只中止写。

注意:

  1. 如果有多个进程共享一个套接字, close 每被调用一次,计数减1, 直到计数为0时,也就是所有进程都调用了 close,套接字将被释放。

  2. 在多进程中如果一个进程调用了 shutdown(sfd,SHUT_RDWR)后,其他的进程将无法进行通信。但如果一个进程 close(sfd) 将不会影响到其他进程。

客户端:

复制代码
 1 #include <stdio.h>
 2 #include <arpa/inet.h>
 3 #include <stdlib.h>
 4 #include <unistd.h>
 5 #include <string.h>
 6 
 7 int main() {
 8 
 9     // 创建socket
10     int fd = socket(PF_INET, SOCK_STREAM, 0);
11     if(fd == -1) {
12         perror("socket");
13         return -1;
14     }
15 
16     struct sockaddr_in seraddr;
17     inet_pton(AF_INET, "127.0.0.1", &seraddr.sin_addr.s_addr);
18     seraddr.sin_family = AF_INET;
19     seraddr.sin_port = htons(9999);
20 
21     // 连接服务器
22     int ret = connect(fd, (struct sockaddr *)&seraddr, sizeof(seraddr));
23 
24     if(ret == -1){
25         perror("connect");
26         return -1;
27     }
28 
29     while(1) {
30         char sendBuf[1024] = {0};
31         fgets(sendBuf, sizeof(sendBuf), stdin);
32 
33         write(fd, sendBuf, strlen(sendBuf) + 1);
34 
35         // 接收
36         int len = read(fd, sendBuf, sizeof(sendBuf));
37         if(len == -1) {
38             perror("read");
39             return -1;
40         }else if(len > 0) {
41             printf("read buf = %s\n", sendBuf);
42         } else {
43             printf("服务器已经断开连接...\n");
44             break;
45         }
46     }
47 
48     close(fd);
49 
50     return 0;
51 }
复制代码

服务器端:

复制代码
 1 #include <stdio.h>
 2 #include <ctype.h>
 3 #include <arpa/inet.h>
 4 #include <unistd.h>
 5 #include <stdlib.h>
 6 #include <string.h>
 7 
 8 int main(int argc, char *argv[]) {
 9 
10     // 创建socket
11     int lfd = socket(PF_INET, SOCK_STREAM, 0);
12 
13     if(lfd == -1) {
14         perror("socket");
15         return -1;
16     }
17 
18     struct sockaddr_in saddr;
19     saddr.sin_family = AF_INET;
20     saddr.sin_addr.s_addr = INADDR_ANY;
21     saddr.sin_port = htons(9999);
22     
23     //int optval = 1;
24     //setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
25    //端口复用,断开服务器,可以重新启动 server 服务端,正常显示******
26     int optval = 1;
27     setsockopt(lfd, SOL_SOCKET, SO_REUSEPORT, &optval, sizeof(optval));
28 
29     // 绑定
30     int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));
31     if(ret == -1) {
32         perror("bind");
33         return -1;
34     }
35 
36     // 监听
37     ret = listen(lfd, 8);
38     if(ret == -1) {
39         perror("listen");
40         return -1;
41     }
42 
43     // 接收客户端连接
44     struct sockaddr_in cliaddr;
45     socklen_t len = sizeof(cliaddr);
46     int cfd = accept(lfd, (struct sockaddr *)&cliaddr, &len);
47     if(cfd == -1) {
48         perror("accpet");
49         return -1;
50     }
51 
52     // 获取客户端信息
53     char cliIp[16];
54     inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
55     unsigned short cliPort = ntohs(cliaddr.sin_port);
56 
57     // 输出客户端的信息
58     printf("client's ip is %s, and port is %d\n", cliIp, cliPort );
59 
60     // 接收客户端发来的数据
61     char recvBuf[1024] = {0};
62     while(1) {
63         int len = recv(cfd, recvBuf, sizeof(recvBuf), 0);
64         if(len == -1) {
65             perror("recv");
66             return -1;
67         } else if(len == 0) {
68             printf("客户端已经断开连接...\n");
69             break;
70         } else if(len > 0) {
71             printf("read buf = %s\n", recvBuf);
72         }
73 
74         // 小写转大写
75         for(int i = 0; i < len; ++i) {
76             recvBuf[i] = toupper(recvBuf[i]);
77         }
78 
79         printf("after buf = %s\n", recvBuf);
80 
81         // 大写字符串发给客户端
82         ret = send(cfd, recvBuf, strlen(recvBuf) + 1, 0);
83         if(ret == -1) {
84             perror("send");
85             return -1;
86         }
87     }
88     
89     close(cfd);
90     close(lfd);
91 
92     return 0;
93 }
复制代码

查看网络相关信息的命令:

  netstat:

  参数:

    -a  所有的 socket

    -p  显示正在使用 socket 的程序的名称

    -n 直接使用 IP地址,而不通过域名服务器

 监听的socket  和  通信的 socket(established)  

此时先关闭服务器端:

关闭客户端:

关闭服务器端和客户端后,重新启动服务器端,显示端口号被使用

需要等待  2MSL(60秒),后端口号被释放,方可重新使用。这期间此端口号不能使用,因此需要端口复用。

端口复用:

端口复用最常用的用途是:

  ⚪ 防止服务器重启时之前绑定的端口还未释放

  ⚪ 程序突然退出而系统没有释放端口

复制代码
#include <sys/types.h>
#include <sys/socket.h>
//设置套接字的属性(不仅仅能设置端口复用)
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
    参数:
        - sockfd : 要操作的文件描述符
        - level : 级别 - SQL_SOCKET(端口复用的级别)
        - optname : 选项的名称
            - SO_REUSEADDR
            - SO_REUSEPORT
        - optval : 端口复用的值(整型)
            - 1 : 可以复用
            - 0 : 不可以复用
        - optlen : optval参数的大小
端口复用,设置的时机是在服务器绑定端口之前
setsockopt();
bind();
复制代码

 

posted on   廿陆  阅读(60)  评论(0编辑  收藏  举报

相关博文:
阅读排行:
· 全程不用写代码,我用AI程序员写了一个飞机大战
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· MongoDB 8.0这个新功能碉堡了,比商业数据库还牛
· .NET10 - 预览版1新功能体验(一)
点击右上角即可分享
微信分享提示