作者: andylin02
学习章节: 第四章 基本TCP套接字编程
关键词: TCP套接字, socket, connect, bind, listen, accept, fork, 并发服务器, 迭代服务器, 字节流, 套接字描述符


一、章节概述

1.1 本章焦点

第四章是UNP全书的真正编程起点。前一章学习了套接字地址结构和字节序转换这些基础构件,本章将这些概念落到实处——开始编写真正的TCP客户/服务器程序

本章的使命是完整介绍编写一个TCP客户/服务器程序所需要的全部基本套接字函数,包括socket、connect、bind、listen、accept、close和fork等。学完本章,你将能够独立编写一个简单的TCP回射(echo)客户/服务器程序。

1.2 本章内容结构

节号 标题 核心内容
4.2 socket函数 创建套接字描述符,协议族与套接字类型的有效组合
4.3 connect函数 TCP客户端连接服务器,三次握手触发点,错误处理
4.4 bind函数 将本地协议地址绑定到套接字,服务器端绑定知名端口
4.5 listen函数 将主动套接字转换为被动套接字,内核连接队列机制
4.6 accept函数 从已完成连接队列返回下一个已完成连接
4.7 fork和exec函数 进程创建,并发服务器的基石
4.8 并发服务器 使用fork实现每客户一个子进程的并发模型
4.9 close函数 关闭套接字,TCP连接终止的起点
4.10 getsockname和getpeername 获取本地和对端协议地址

💡 本章核心价值:读完第四章,你应该能够——

  • 掌握TCP客户/服务器程序的完整函数调用流程
  • 理解socket、connect、bind、listen、accept每个函数的作用和调用时机
  • 写出一个能工作的TCP回射服务器和对应的客户端
  • 理解fork并发模型的基本原理
  • 知道如何正确关闭套接字避免资源泄漏

二、TCP套接字编程核心函数

2.1 函数调用全景图

// 服务器端调用流程
socket()   → 创建监听套接字
   ↓
bind()     → 绑定知名端口
   ↓
listen()   → 转换为被动监听套接字
   ↓
accept()   → 阻塞等待客户连接
   ↓
read()/write() → 与特定客户端通信
   ↓
close()    → 关闭连接

// 客户端调用流程
socket()   → 创建套接字
   ↓
connect()  → 主动发起连接(触发三次握手)
   ↓
write()/read() → 与服务器通信
   ↓
close()    → 关闭连接

2.2 socket函数——一切从这里开始

进程进行网络I/O的第一步就是调用socket函数,指定期望的通信协议类型。

#include <sys/socket.h>

int socket(int family, int type, int protocol);
// 返回:成功则为非负描述符(套接字描述符),若出错则为-1

参数详解:

参数 常用值 含义
family AF_INET IPv4协议
AF_INET6 IPv6协议
AF_LOCAL/AF_UNIX Unix域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
type SOCK_STREAM 字节流套接字(TCP使用)
SOCK_DGRAM 数据报套接字(UDP使用)
SOCK_SEQPACKET 有序分组套接字(SCTP使用)
SOCK_RAW 原始套接字
protocol IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
0 系统默认协议

💡 关键理解:并不是所有family和type的组合都有效。例如,AF_INETSOCK_STREAM的组合默认使用TCP,AF_INETSOCK_DGRAM的组合默认使用UDP。

AF_XXX与PF_XXX的关系:

AF_前缀表示地址族(Address Family),PF_前缀表示协议族(Protocol Family)。历史上曾设想单个协议族可支持多个地址族,PF_值用于创建套接字,AF_值用于套接字地址结构。但实践中从未实现过,且PF_值与AF_值总是相等的。为与现存代码保持一致,本书仅使用AF_常值。

socket创建后的状态:

socket函数调用成功后,得到的套接字是一个主动套接字,处于CLOSED状态。它还不能用于接收连接请求,需要后续函数将其转换为所需状态。

2.3 connect函数——客户端发起连接

TCP客户端使用connect函数建立与TCP服务器的连接。

#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen);
// 返回:若成功则为0,若出错则为-1

参数说明:

  • sockfd:由socket函数返回的套接字描述符
  • servaddr:指向服务器套接字地址结构的指针(必须包含服务器的IP地址和端口号)
  • addrlen:套接字地址结构的大小

connect前的bind问题:

客户端在调用connect之前不必调用bind函数。如果需要,内核会为该套接字自动选择一个临时端口作为源端口,并选择一个本地IP地址。这正是典型的客户端行为——不关心使用哪个端口和哪个IP地址。

connect触发的三次握手:

调用connect函数将触发TCP的三次握手过程,且仅在连接建立成功或出错时才返回。

三种常见出错情况:

错误类型 原因 处理方式
ETIMEDOUT 客户端没有收到SYN分节的响应(超时) 检查服务器是否可达,网络是否正常
ECONNREFUSED 对客户端的SYN响应是RST(复位) 服务器指定端口没有进程在监听
EHOSTUNREACH/ENETUNREACH 中间路由器引发ICMP错误 检查路由和网络连通性

⚠️ 重要:若connect失败,该套接字描述符不再可用,必须关闭它,然后重新调用socket创建新套接字。

connect触发的状态转换:

调用connect使套接字从CLOSED状态转换到SYN_SENT状态;若成功,再转换到ESTABLISHED状态。

2.4 bind函数——服务器绑定地址

bind函数把一个本地协议地址赋予一个套接字。对于TCP服务器,这通常用于绑定一个知名端口。

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *myaddr, socklen_t addrlen);
// 返回:若成功则为0,若出错则为-1

bind的两大作用:

角色 bind的作用
TCP服务器 绑定知名端口,限定套接字只接收目的地为该IP地址的客户连接
TCP客户端 可选。如果调用bind,则指定发送IP数据报的源IP地址(通常不需要)

常见用法:

struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);  // 通配地址,监听所有网络接口
servaddr.sin_port = htons(SERV_PORT);          // 知名端口
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

关于INADDR_ANY:

INADDR_ANY的值定义为0,表示“任意地址”或“所有地址”。当服务器绑定INADDR_ANY时,内核在处理连接时,不会进行目的地址匹配——即套接字可以接收发往本机任何网络接口IP地址的客户连接。这是绝大多数服务器程序的标准做法,使服务器能处理来自任何网络接口的连接请求。

⚠️ 常见错误:bind返回EADDRINUSE表示地址已在使用(通常是端口被占用或处于TIME_WAIT状态)。

2.5 listen函数——转换为被动套接字

listen函数仅由TCP服务器调用,完成两个关键任务:

#include <sys/socket.h>

int listen(int sockfd, int backlog);
// 返回:若成功则为0,若出错则为-1

listen的两大功能:

  1. 状态转换:将socket创建的主动套接字转换为被动套接字(监听套接字),指示内核应接受指向该套接字的连接请求。

  2. 队列初始化:规定内核为该套接字排队的最大连接个数。

listen的时机:通常在socket和bind之后,accept之前调用。

内核的两个连接队列:

┌─────────────────────────────────────────────────────────────┐
│                    监听套接字的两个队列                      │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│   客户端SYN到达                                              │
│        │                                                    │
│        ▼                                                    │
│   ┌─────────────────────────┐                              │
│   │  未完成连接队列          │                              │
│   │  (incomplete connection)│ ← 处于SYN_RCVD状态           │
│   │  - 正在进行三次握手的连接 │                               │
│   └────────────┬────────────┘                              │
│                │ 三次握手完成                               │
│                ▼                                           │
│   ┌─────────────────────────┐                              │
│   │  已完成连接队列          │                              │
│   │  (completed connection) │ ← 处于ESTABLISHED状态        │
│   │  - 等待accept取走的连接   │                              │
│   └────────────┬────────────┘                              │
│                │                                           │
│                ▼                                           │
│           accept()取走                                      │
└─────────────────────────────────────────────────────────────┘
  • 未完成连接队列:每个这样的SYN分节对应其中一项,套接字处于SYN_RCVD状态,正在等待TCP三次握手完成。
  • 已完成连接队列:每个已完成TCP三次握手的客户对应其中一项,套接字处于ESTABLISHED状态,等待accept取走。

backlog参数的含义

历史上,backlog指定的是两个队列之和的最大值。但在现代Linux实现中,backlog指定的是已完成连接队列的最大长度,未完成连接队列长度则由系统参数控制。

实际队列长度取min(backlog, core.sysctl_somaxconn)。在Linux中,/proc/sys/net/core/somaxconn定义了系统级的上限。

💡 注意:当连接请求到达而队列已满时,TCP会忽略该SYN分节(不发送RST),客户端会重传SYN,期待未来队列有空位。

2.6 accept函数——接受连接

accept函数由TCP服务器调用,用于从已完成连接队列队头返回下一个已完成连接。

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *cliaddr, socklen_t *addrlen);
// 返回:若成功则为非负描述符(已连接套接字描述符),若出错则为-1

参数说明:

  • sockfd:监听套接字描述符(由socket创建,bind和listen使用的那个)
  • cliaddr:用于返回已连接的对端进程(客户)的协议地址(可为NULL)
  • addrlen:值-结果参数。调用时传入cliaddr结构的大小,返回时内核告知实际存储的字节数

💡 关键区分:在讨论accept函数时,第一个参数称为监听套接字(listening socket),返回值称为已连接套接字描述符(connected socket descriptor)。

accept的工作流程:

  1. 检查已完成连接队列是否为空
  2. 若队列为空,则进程被投入睡眠(阻塞)直到有连接放入队列
  3. 若队列非空,取出队列中的第一个已完成连接
  4. 由内核自动生成一个全新的套接字描述符(已连接套接字)
  5. 该新套接字具有与监听套接字相同的属性,但不再处于监听状态
  6. 返回该新套接字描述符

💡 设计思想:为何不直接复用监听套接字?因为监听套接字需要持续监听新的连接请求。若将其用于与单个客户通信,将无法同时接受其他客户。通过创建新套接字,监听套接字得以保留,可以继续接受更多连接。这也是并发服务器的基础。

2.7 fork和exec函数——创建新进程

#include <unistd.h>

pid_t fork(void);
// 返回:子进程中返回0,父进程中返回子进程PID,出错返回-1

fork的特点:

  • fork是Unix中创建新进程的唯一方法
  • 子进程获得父进程数据空间、堆和栈的副本
  • 父进程和子进程共享文件描述符,但引用计数增加
  • fork后的两个进程独立执行
// fork的典型使用模式
if ((pid = fork()) == 0) {
    // 子进程代码
    do_child_work();
    exit(0);
} else if (pid > 0) {
    // 父进程代码
    do_parent_work();
}

2.8 close函数——关闭套接字

#include <unistd.h>

int close(int sockfd);
// 返回:若成功则为0,若出错则为-1

close的工作原理:

close将套接字描述符的引用计数减1。若引用计数变为0,则启动TCP连接终止过程(发送FIN)。若引用计数仍大于0,则仅减少计数,不真正关闭连接。

💡 引用计数的含义:每个进程独立持有文件描述符。父进程fork子进程后,父子进程共享同一个套接字,引用计数变为2。若父进程close了套接字,引用计数降为1,但套接字仍然打开,直到子进程也close它,才真正开始终止过程。

2.9 getsockname和getpeername——获取地址信息

#include <sys/socket.h>

int getsockname(int sockfd, struct sockaddr *localaddr, socklen_t *addrlen);
int getpeername(int sockfd, struct sockaddr *peeraddr, socklen_t *addrlen);
// 返回:若成功则为0,若出错则为-1

应用场景:

  • getsockname:获取与套接字关联的本地协议地址(如客户端想知道内核自动分配的临时端口)
  • getpeername:获取与套接字关联的对端协议地址(如exec后的子进程需要知道客户地址)

2.10 套接字函数时序图

TCP服务器                               TCP客户端
    │                                       │
    │  socket()                              │  socket()
    │  bind()                                │
    │  listen()                              │
    │  accept()  ←────阻塞等待─────           │
    │                                       │  connect()
    │  ←───TCP三次握手────→                  │
    │                                       │  (connect返回)
    │  (accept返回)                          │
    │                                       │
    │  read()                               │  write()
    │  ←───数据请求───────────────────       │
    │  处理请求                              │
    │  write()                              │  read()
    │  ───→数据响应──────────────────→       │
    │                                       │
    │  read()  ←─返回0(对端FIN)─           │  close()
    │  close()                              │
    │                                       │
    ▼                                       ▼

三、两种服务器模式详解

3.1 迭代服务器(Iterative Server)

迭代服务器是最简单的服务器模型:一次只处理一个客户端,在处理完当前客户的所有请求之前,不接受新的连接。

// 迭代服务器核心框架
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);

while (1) {
    connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
    // 处理当前客户——其他客户必须等待
    do_service(connfd);
    Close(connfd);
}

迭代服务器的局限性:

  • 若当前客户处理时间较长,所有后续客户都会长时间等待
  • 不适合实际生产环境(但适合学习和测试)

3.2 并发服务器(Concurrent Server)——核心知识点

并发服务器是Unix网络编程中的经典范式,通过在accept后fork子进程来处理每个客户,实现真正的并发处理。

// 并发服务器核心框架
listenfd = Socket(AF_INET, SOCK_STREAM, 0);
Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
Listen(listenfd, LISTENQ);

for ( ; ; ) {
    connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
    
    if ((childpid = Fork()) == 0) {
        /* 子进程 */
        Close(listenfd);      // 子进程关闭监听套接字(不需要)
        str_echo(connfd);     // 处理客户请求
        Close(connfd);
        exit(0);
    }
    /* 父进程 */
    Close(connfd);            // 父进程关闭已连接套接字(不需要)
}

💡 关键理解:父进程和子进程共享所有打开的描述符。子进程不需要监听套接字,父进程不需要已连接套接字。如果不主动关闭,引用计数不会减少,套接字资源无法释放,最终会导致可用文件描述符耗尽。

并发服务器的执行流程:

      父进程
        │
        │ accept()返回connfd
        ▼
     fork()执行
        │
        ├──────────────────────────┐
        │ (子进程)                  │ (父进程)
        │                          │
        │ Close(listenfd)          │ Close(connfd)
        │ (不需要监听)              │ (不需要连接)
        │                          │
        │ 处理客户请求              │ 继续accept()新连接
        │ ...                      │ ...
        │ Close(connfd)            │
        │ exit(0)                  │
        │                          │
        ▼                          ▼

为什么父子进程都要close不用的套接字?

因为文件和套接字有引用计数。当父进程fork后,监听套接字的引用计数为2,已连接套接字的引用计数也为2。若不主动关闭不需要的套接字,则:

  1. 子进程持有监听套接字→若父进程崩溃,监听套接字仍被占用→端口无法释放
  2. 父进程持有已连接套接字→已连接套接字引用计数不会降为0→TCP连接不会终止

四、完整源代码分析

4.1 时间获取服务器(迭代服务器版本)

这是书中第4章的完整示例,一个显示客户IP地址和端口号的时间获取服务器程序。

#include "unp.h"
#include <time.h>

int main(int argc, char **argv)
{
    int listenfd, connfd;
    socklen_t len;
    struct sockaddr_in servaddr, cliaddr;
    char buff[MAXLINE];
    time_t ticks;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(13);   /* daytime服务器端口 */

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));

    Listen(listenfd, LISTENQ);

    for ( ; ; ) {
        len = sizeof(cliaddr);
        connfd = Accept(listenfd, (SA *) &cliaddr, &len);
        /* 打印客户IP地址和端口号 */
        printf("connection from %s, port %d\n",
               Inet_ntop(AF_INET, &cliaddr.sin_addr, buff, sizeof(buff)),
               ntohs(cliaddr.sin_port));

        ticks = time(NULL);
        snprintf(buff, sizeof(buff), "%.24s\r\n", ctime(&ticks));
        Write(connfd, buff, strlen(buff));

        Close(connfd);
    }
}

4.2 TCP回射服务器(并发服务器版本)

这是第5章的完整代码,体现了并发服务器的核心设计模式:

#include "unp.h"

int main(int argc, char **argv)
{
    int listenfd, connfd;
    pid_t childpid;
    socklen_t clilen;
    struct sockaddr_in cliaddr, servaddr;

    listenfd = Socket(AF_INET, SOCK_STREAM, 0);

    bzero(&servaddr, sizeof(servaddr));
    servaddr.sin_family = AF_INET;
    servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    servaddr.sin_port = htons(SERV_PORT);

    Bind(listenfd, (SA *) &servaddr, sizeof(servaddr));
    Listen(listenfd, LISTENQ);

    for ( ; ; ) {
        clilen = sizeof(cliaddr);
        connfd = Accept(listenfd, (SA *) &cliaddr, &clilen);
        if ((childpid = Fork()) == 0) {
            /* 子进程 */
            Close(listenfd);          /* 子进程关闭监听套接字 */
            str_echo(connfd);         /* 处理客户请求的回射函数 */
            Close(connfd);
            exit(0);
        }
        /* 父进程 */
        Close(connfd);                /* 父进程关闭已连接套接字 */
    }
}

4.3 str_echo回射处理函数

#include "unp.h"

void str_echo(int sockfd)
{
    ssize_t n;
    char line[MAXLINE];

    while ((n = Readline(sockfd, line, MAXLINE)) > 0) {
        Writen(sockfd, line, n);     /* 将接收到的数据原样返回 */
    }
}

4.4 TCP回射客户端(str_cli)

#include "unp.h"

void str_cli(FILE *fp, int sockfd)
{
    char sendline[MAXLINE], recvline[MAXLINE];

    while (Fgets(sendline, MAXLINE, fp) != NULL) {
        Writen(sockfd, sendline, strlen(sendline));
        if (Readline(sockfd, recvline, MAXLINE) == 0)
            err_quit("str_cli: server terminated prematurely");
        Fputs(recvline, stdout);
    }
}

五、关键注意事项与常见错误

5.1 字节流协议的理解误区

关键点:TCP是字节流协议,没有记录边界。read和write调用传输的字节数可能比请求的数量少,这不是出错,而是因为内核中用于套接字的缓冲区可能已达到极限。

这意味着:

  • 不能假设一次read就能读完整条消息
  • 发送端调用一次write,接收端可能需要多次read才能读完
  • 发送端多次write,接收端可能一次read就读到所有数据

💡 解决方案:使用第三章介绍的readnwritenreadline等包裹函数,确保读写完整数量的字节或完整的一行数据。

5.2 并发服务器的fork陷阱

父子进程的套接字引用计数问题

  • fork后父子进程共享文件描述符,引用计数变为2
  • 父进程必须close已连接套接字(子进程也需要close监听套接字)
  • 若不close,引用计数不会降为0,连接无法终止,文件描述符会耗尽

5.3 accept的阻塞行为

  • 若已完成连接队列为空,accept会阻塞进程直到有连接可用
  • 阻塞模式下,accept会一直等待,不会主动返回

5.4 connect失败的套接字重用

重要规则:若connect失败,该套接字描述符不再可用,必须关闭它,然后重新调用socket创建新套接字。

5.5 服务器需要bind的原因

如果TCP服务器不bind一个地址,在listen的时候内核会随机分配端口。这种操作对服务器不合适,因为服务器需要运行在一个知名端口上,这样客户端才知道连接到哪里。

5.6 并发服务器的信号处理(提前预览)

当子进程终止时,内核会向父进程发送SIGCHLD信号。若不处理这个信号,子进程会成为僵尸进程(zombie)。第5章将详细讲解如何使用signal函数和waitpid来处理这个问题。

六、TCP状态转换回顾

/* 回顾第二章的TCP状态转换,在套接字函数调用中如何体现 */

socket()           → 套接字处于 CLOSED 状态
connect() 调用     → CLOSED → SYN_SENT
connect() 成功返回  → SYN_SENT → ESTABLISHED

bind()             → 不改变套接字状态
listen() 调用      → CLOSED → LISTEN
accept() 成功返回  → 监听套接字状态不变,返回新套接字处于 ESTABLISHED

七、本章习题解答

习题4.1

问题:在并发服务器中,父子进程为什么要关闭自己不使用的套接字描述符?

答案:因为文件和套接字是引用计数的。fork后父子进程共享套接字,引用计数变为2。父进程必须close已连接套接字,否则:

  1. 父进程不关闭,引用计数不会降为0,子进程close后仍为1,TCP连接不会真正终止(不会发送FIN)
  2. 长时间运行会耗尽文件描述符

子进程必须close监听套接字,否则父进程即使close了监听套接字,引用计数仍为1(被子进程持有),端口无法释放。

习题4.2

问题:在迭代服务器中,如果客户在处理过程中突然终止,会发生什么?

答案:当客户进程终止时,所有打开的描述符被自动关闭。这将导致向服务器发送FIN,服务器的read将返回0(EOF),服务器会关闭连接并继续接受下一个客户。这是正常的连接终止流程。

习题4.3

问题backlog参数如何影响服务器处理大量并发连接的能力?

答案backlog限制了已完成连接队列的最大长度。当大量客户同时连接而服务器来不及调用accept时,队列会填满。队列满后,新到达的SYN会被忽略(不发送RST),客户端会重传SYN。因此,在高并发场景下设置足够大的backlog值很重要。但实际值受系统参数net.core.somaxconn限制。

习题4.4

问题write返回成功是否意味着对方已经收到数据?

答案:不是。write返回成功只表示数据已从应用程序缓冲区复制到TCP发送缓冲区,并不表示数据已被对方接收。TCP的可靠传输是异步的,确认过程在后台进行。

八、本章小结

8.1 核心知识点回顾

函数 角色 关键点
socket 客户+服务器 创建主动套接字(CLOSED状态)
connect 客户 触发三次握手,阻塞等待完成
bind 服务器 绑定知名端口,INADDR_ANY监听所有接口
listen 服务器 转换为被动套接字,初始化连接队列
accept 服务器 从已完成队列取连接,返回新套接字
fork 服务器 创建子进程处理每个客户
close 客户+服务器 减少引用计数,0时触发FIN
getsockname/getpeername 工具函数 获取本地/对端地址

8.2 TCP套接字编程思维导图

第四章 基本TCP套接字编程
├── 核心函数调用顺序
│   ├── 服务器: socket → bind → listen → accept → read/write → close
│   └── 客户: socket → connect → write/read → close
├── socket函数
│   ├── family: AF_INET/AF_INET6/AF_LOCAL
│   ├── type: SOCK_STREAM/SOCK_DGRAM/SOCK_RAW
│   └── protocol: 0表示默认
├── connect函数
│   ├── 触发三次握手
│   ├── 阻塞到完成
│   └── 失败必须重新socket
├── bind函数
│   ├── 服务器绑定知名端口
│   ├── INADDR_ANY: 监听所有接口
│   └── 常见错误: EADDRINUSE
├── listen函数
│   ├── 主动→被动套接字
│   └── 维护两个连接队列
├── accept函数
│   ├── 监听套接字 vs 已连接套接字
│   └── 队列空时阻塞
├── 并发服务器
│   ├── fork创建子进程
│   └── 父子进程各close不用的套接字
└── close函数
    ├── 引用计数机制
    └── 触发FIN的条件

九、下一章预告

📌 下一篇:《UNIX网络编程》读书笔记(五):第五章 TCP客户/服务器程序示例

第五章将详细讲解:

  1. 完整的TCP回射服务器程序:包括main函数、str_echostr_cli的完整实现
  2. readline函数的优化:为什么标准readline效率低,如何改进
  3. SIGCHLD信号处理:如何处理僵尸进程,为什么wait不够,必须使用waitpid
  4. 连接中断的各种情况分析
    • 服务器进程终止时客户端的反应
    • 服务器主机崩溃时会发生什么
    • 服务器主机崩溃后重启的处理
    • 服务器主动关闭连接的影响
  5. 数据格式问题:网络传输中的二进制数据与文本数据的考虑
  6. TCP紧急数据(带外数据)MSG_OOB标志的使用

学习目标:学完第五章后,你将能够——

  • 写出一个健壮的、能处理各种异常情况的TCP回射服务器
  • 正确处理SIGCHLD信号,避免僵尸进程
  • 理解TCP连接在各种故障场景下的行为
  • 设计可靠的应用层协议

敬请期待!

参考资料

  1. W. Richard Stevens, Bill Fenner, Andrew M. Rudoff. 《UNIX网络编程 卷1:套接字联网API(第3版)》. 北京:人民邮电出版社
  2. UNIX网络编程卷一 学习笔记 第四章 基本TCP套接字编程,CSDN,https://blog.csdn.net/tus00000/article/details/107283780
  3. 《UNIX网络编程》读书笔记——第四章(基本TCP套接字编程),CSDN,https://blog.csdn.net/u012103747/article/details/40951395
  4. UNP学习笔记(第四章 基本TCP套接字编程),CSDN,https://blog.csdn.net/Runnyu/article/details/47450461
  5. unp第四章:基本套接字编程,博客园,https://www.cnblogs.com/ronnieos/p/15974327.html
  6. UNIX网络编程(UNP) 第四章学习笔记,www.e-com-net.com,https://www.e-com-net.com/article/1666979089786953728.htm
  7. UNP学习笔记二–简单的并发服务器(concurrent servers),CSDN,https://blog.csdn.net/aaaaa851766403/article/details/101404004
  8. unix网络编程第四章----基于TCP套接字编程,博客园,https://www.cnblogs.com/zengyiwen/p/5755213.html
  9. UNP——第四章,TCP套接字编程,博客园,https://www.cnblogs.com/yangxinrui/p/12219815.html
  10. UNP读书笔记第4章 基本套接字编程,CSDN,https://blog.csdn.net/weixin_41217899/article/details/97620326
  11. 绑定特殊 IP 之 0.0.0.0 的内部工作原理,腾讯云开发者社区,https://cloud.tencent.com.cn/developer/article/1969217
  12. listen linux,腾讯云开发者社区,https://cloud.tencent.com.cn/developer/information/listen%20linux
  13. unix网络编程之listen()详解,阿里云开发者社区,https://developer.aliyun.com/article/67762
  14. accept(2) — manpages-zh,https://manpages.debian.org/accept.2.zh.html
  15. accept()返回的套接字绑定哪个端口 新旧套接字的联系,腾讯云开发者社区,https://cloud.tencent.cn/developer/article/1361071

本文为个人学习笔记,仅用于知识分享。如有错误,欢迎指正。
👍🏻 点赞 + 收藏 + 分享,让更多开发者看到这篇深度解析!❤️ 如果觉得有用,请给个赞支持一下作者!

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐