Posts 聊聊端口
Post
Cancel

聊聊端口

1. 概览

1
socket 链接双方

c 伪码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// 服务端
    addr.sin_family = AF_INET;
    addr.sin_port = htons(8080);              // 端口 8080
    addr.sin_addr.s_addr = htonl(INADDR_ANY); // 监听所有网卡  0.0.0.0
    bind(server_fd, (struct sockaddr *)&addr, sizeof(addr));

    listen(server_fd, 5);

    while (1) {
        // 等待客户端连接,返回新的 socket fd
        int client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &client_len);

        // 用 client_fd 和客户端进行通信(读写数据)
        recv(client_fd, buffer, sizeof(buffer), 0);
    }

// 客户端
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    inet_pton(AF_INET, "127.0.0.1", &server_addr.sin_addr);
    
    // 指定源 ip & 端口 才需要用bind 
    // 否则connect 自选择port
    // bind(sockfd, (struct sockaddr *)&local, sizeof(local));  

    connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));

fd & port ?

  1. fd (文件描述符)

概念:操作系统内核给进程分配的一个小整数,用来标识一个打开的文件、socket、管道等。

  1. port (端口号)

概念:TCP/UDP 协议栈在内核中的一个标识,用来区分同一 IP 上的不同服务。

1
addr.sin_port = htons(8080);

这里的 8080 是一个 全局的网络端口,只要某个进程已经绑定(bind)了它,别的进程就不能再用。

内核用 (本地 IP, 本地 port, 远端 IP, 远端 port, 协议) 这个五元组来区分一个 TCP 连接;而用户态进程只是通过 fd 来访问这个连接。

并发问题

从两个角度来看:

  1. 服务端
1
2
root>ulimit -n
1000000

单进程最多维持百万并发。(socket 编程, 四元组确定唯一fd)

  1. 客户端
1
server_addr.sin_port = htons(8080);

单个客户端 IP + 目标服务器端口最多能发起约 65535 个 TCP 连接(端口号 16 位,0-65535)

2. socket & fd 问题 概览

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
应用层代码
 ┌───────────────────────────────┐
  int fd = socket(...);            得到文件描述符 fd
  bind(fd, ...)                    绑定本地 IP/Port (服务端)
  listen(fd, backlog)              监听端口
  client_fd = accept(fd, ...)      接收客户端连接
 └───────────────┬───────────────┘
                 
                 
用户态文件描述符表
 ┌───────────────────────────────┐
  fd=3  指向 server socket       
  fd=4  指向 client socket       
 └───────────────┬───────────────┘
                 
                 
内核 socket 对象
 ┌───────────────────────────────┐
  协议族:AF_INET / AF_INET6      
  类型:SOCK_STREAM / SOCK_DGRAM  
  本地 IP/Port                    
  对端 IP/Port                    
  发送缓冲区 / 接收缓冲区          
  状态:LISTEN / ESTABLISHED ...   
 └───────────────┬───────────────┘
                 
                 
TCP 四元组(唯一标识一条 TCP 连接)
 ┌───────────────────────────────┐
  (SrcIP, SrcPort, DstIP, DstPort) 
  例:192.168.1.20:52344  192.168.1.10:8080 
 └───────────────┬───────────────┘
                 
                 
内核缓冲区 / TCP/IP 
 ┌───────────────────────────────┐
  send(fd, buf)  放入发送缓冲区 
  内核拆分成 TCP segment          
  加入 IP 数据报  以太网帧  网卡发送 
  recv(fd, buf)  内核从接收缓冲区读取 
 └───────────────────────────────┘
                 
                 
网络传输 & 对端处理
 ┌───────────────────────────────┐
  对端内核接收 TCP 报文  放入接收缓冲区 
  对端应用通过 fd 读取数据           
 └───────────────────────────────┘

3. port 问题

1
一个端口可以对应多个 socket(内核对象),每个 socket 绑定在不同的四元组上。应用层看到的是多个 fd,每个 fd 都指向不同的 socket。
  1. 在 Linux 里,只要四元组不冲突,就可以 bind 同一个端口。

  2. 来自 bug 单:
    1
    2
    3
    
     wafd bind  set portreuseaddrreuse 导致 inet_bind_bucket 相关位被置位
     后续别的进程通过 connect port 发现此状态位被置位的 都不用导致每端口可占
     通过bind port 不会做此状态位的检测
    
  3. UNIX socket 不需要端口,因为通信只在本地内核里,内核通过 socket 对象和 fd 就能区分各个通信端点,不需要像网络通信那样依赖 IP+port。

4. time_wait

TIME_WAIT 是 TCP 协议的一个标准状态,又叫 2MSL 等待状态。

  • 出现在 主动关闭连接的一方

  • 作用:

  1. 确保最后的 ACK 包能被对方收到,如果对方没收到,可以重发 FIN

  2. 避免端口号被立即复用导致旧数据干扰新连接

This post is licensed under CC BY 4.0 by the author.

Contents

Trending Tags