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 portreuse或addrreuse 导致 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