Linux Socket 是网络编程的核心接口,它提供了进程间通信(IPC)的能力,尤其用于不同主机(通过网络)或同一主机上的进程通信。它本质上是应用层与 TCP/IP 协议栈之间的编程接口(API),遵循“一切皆文件”的哲学,操作 Socket 就像操作文件一样(使用文件描述符)。
在 Linux 环境下使用 Socket。
#include <sys/socket.h>
socket()
创建 Socket:int socket(int domain, int type, int protocol); 。
domain
domain:参数用于指定协议族。PF_*(Protocol Family) 和AF_*(Address Family) 宏定义在历史上有细微区别,但在现代 Linux 系统中实际上是完全等价且可互换的。
| 用途 | 推荐写法 | 等价写法 | 值 | 
|---|---|---|---|
| IPv4 通信 | AF_INET | PF_INET | 2 | 
| IPv6 通信 | AF_INET6 | PF_INET6 | 10 | 
| 本地进程间通信 | AF_UNIX | PF_UNIX | 1 | 
| 原始网络包访问 | AF_PACKET | PF_PACKET | 17 | 
| Netlink 通信 | AF_NETLINK | PF_NETLINK | 16 | 
type
type:指定套接字的通信类型和特性,它决定了数据传输的方式和套接字的行为。
| 类型 | 说明 | 
|---|---|
SOCK_STREAM | 流式套接字,TCP协议 | 
SOCK_DGRAM | 数据报套接字,UDP协议 | 
SOCK_RAW | 原始套接字,允许直接访问底层协议(如 IP、ICMP);可以自定义协议头部;需要 root 权限 (CAP_NET_RAW) | 
SOCK_SEQPACKET | 有序分组套接字,结合了流式和数据报的特点;提供可靠、有序的消息传输;保留消息边界 | 
Linux 特有类型标志(与基本类型组合使用)。SOCK_NONBLOCK :非阻塞模式,创建时直接设置为非阻塞模式;替代传统的 fcntl(fd, F_SETFL, O_NONBLOCK)。
使用方法:
int nonblock_sock = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0);
protocol
protocol:用于指定要使用的具体网络协议。这个参数与 domain和 type一起决定了套接字的完整行为。
在大多数情况下,protocol 可以设置为 0,表示使用给定协议族和套接字类型的默认协议:
domain = AF_INET+type = SOCK_STREAM→ TCP (默认)domain = AF_INET+type = SOCK_DGRAM→ UDP (默认)domain = AF_INET6+type = SOCK_STREAM→ TCPv6 (默认)domain = AF_INET6+type = SOCK_DGRAM→ UDPv6 (默认)
特殊场景:当使用 SOCK_RAW 类型时,protocol 参数变得特别重要,它决定了:
- 套接字接收哪些类型的数据包。
 - 套接字发送数据包时自动添加的协议头。
 
使用协议常量需要 #include <netinet/in.h>。
| 协议常量 | 用途说明 | 
|---|---|
IPPROTO_ICMP | 发送/接收 ICMP 包 (ping, traceroute) | 
IPPROTO_IGMP | 组播管理协议 | 
IPPROTO_IPIP | IP-in-IP 隧道协议 | 
IPPROTO_TCP | 直接处理 TCP 段 (需自行处理 TCP 头) | 
IPPROTO_UDP | 直接处理 UDP 数据报 (需自行处理 UDP 头) | 
IPPROTO_RAW | 发送时需要自行构造完整 IP 头 | 
ETH_P_ALL | 接收所有以太网帧 (需使用 AF_PACKET domain) | 
组合结果:
+-------------+---------------+-------------------------+---------------------+
| domain      | type          | protocol                | result protocol     |
+-------------+---------------+-------------------------+---------------------+
| AF_INET     | SOCK_STREAM   | 0 or IPPROTO_TCP        | TCP                 |
| AF_INET     | SOCK_DGRAM    | 0 or IPPROTO_UDP        | UDP                 |
| AF_INET     | SOCK_RAW      | IPPROTO_ICMP            | ICMP                |
| AF_INET     | SOCK_RAW      | IPPROTO_RAW             | raw IP packet       |
| AF_INET6    | SOCK_STREAM   | 0 or IPPROTO_TCP        | TCP over IPv6       |
| AF_INET6    | SOCK_DGRAM    | 0 or IPPROTO_UDP        | UDP over IPv6       |
| AF_INET6    | SOCK_RAW      | IPPROTO_ICMPV6          | ICMPv6              |
| AF_PACKET   | SOCK_RAW      | ETH_P_ALL               | ethernet frame      |
| AF_NETLINK  | SOCK_RAW      | NETLINK_ROUTE           | route info          |
+-------------+---------------+-------------------------+---------------------+
return
返回值: 成功时返回新的 Socket 描述符(非负整数),失败返回 -1 并设置 errno。errno 需要 #include <errno.h> 。
| 错误码 | 常量定义 | 含义与典型原因 | 
|---|---|---|
| 权限错误 | EACCES | 创建指定类型套接字权限不足(特别是原始套接字需要 root 权限) | 
| 地址族不支持 | EAFNOSUPPORT | 不支持的协议族(如在不支持 IPv6 的系统上使用 AF_INET6) | 
| 无效参数 | EINVAL | 无效参数组合(如无效的类型/协议组合) | 
| 文件描述符超限 | EMFILE | 进程打开文件描述符数量已达上限(ulimit -n 查看限制) | 
| 系统文件表满 | ENFILE | 系统打开文件总数达到内核限制(/proc/sys/fs/file-max) | 
| 无缓冲区空间 | ENOBUFS 或 ENOMEM | 内核内存不足,无法分配套接字资源 | 
| 协议不支持 | EPROTONOSUPPORT | 系统不支持请求的协议或协议不可用 | 
| 套接字类型不支持 | ESOCKTNOSUPPORT | 协议族不支持请求的套接字类型 | 
| 协议不可用 | EPROTOTYPE | 套接字类型不支持请求的协议(如对 UDP 使用 SOCK_STREAM) | 
| 网络子系统未运行 | ENETDOWN | 网络子系统未运行(罕见,通常表示严重系统问题) | 
使用方法:
int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (fd < 0) {
    // errno 为全局变量(线程安全),需要保存以防被修改
    int errsv = errno;
    fprintf(stderr, "Error: %s\n", strerror(errsv)); 
    // ...
}
// ...
bind()
绑定 socket:int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 。
- 将创建的 Socket (
sockfd) 与一个特定的本地地址 (addr) 和端口 (addrlen是地址结构长度) 关联起来。 - 服务器必须调用 
bind来指定监听的端口。 - 客户端通常不需要显式调用 
bind,内核会在connect或sendto时自动分配一个临时端口(称为临时端口 Ephemeral Port)。 
sockfd
sockfd:套接字文件描述符,由 socket() 系统调用创建,需要有效且未绑定。
addr
addr:指向包含绑定地址信息的结构体指针。
通用结构:
struct sockaddr {
    sa_family_t sa_family;   // 地址族(AF_INET, AF_INET6 等)
    char        sa_data[14]; // 地址数据
};
ipv4:
struct sockaddr_in {
    sa_family_t    sin_family; // 地址族:AF_INET
    in_port_t      sin_port;   // 16位端口号(网络字节序)
    struct in_addr sin_addr;   // 32位 IPv4 地址
    unsigned char  sin_zero[8];// 填充字段(设为0)
};
struct in_addr {
    uint32_t s_addr; // IPv4 地址(网络字节序)
};
ipv6:
struct sockaddr_in6 {
    sa_family_t     sin6_family;   // 地址族:AF_INET6
    in_port_t       sin6_port;     // 16位端口号(网络字节序)
    uint32_t        sin6_flowinfo; // IPv6 流信息
    struct in6_addr sin6_addr;     // 128位 IPv6 地址
    uint32_t        sin6_scope_id; // 作用域ID
};
struct in6_addr {
    unsigned char s6_addr[16]; // IPv6 地址
};
unix:
struct sockaddr_un {
    sa_family_t sun_family;        // 地址族:AF_UNIX
    char        sun_path[108];     // 路径名
};
addrlen
addrlen:指定 addr 结构体的实际长度。
// IPv4
sizeof(struct sockaddr_in)
// IPv6
sizeof(struct sockaddr_in6)
// UNIX 域
sizeof(struct sockaddr_un)
return
返回值: 成功时返回 0,失败返回 -1 并设置 errno。errno 需要 #include <errno.h> 。
| 错误码 | 常量 | 原因与解决方案 | 
|---|---|---|
| 权限错误 | EACCES | 绑定到特权端口(<1024)需要 root 权限 解决方案:使用 sudo 或绑定到 ≥1024 的端口 | 
| 地址已使用 | EADDRINUSE | 请求的地址(IP+端口)已被占用 解决方案:使用 SO_REUSEADDR 选项或更换端口 | 
| 地址不可用 | EADDRNOTAVAIL | 尝试绑定到不存在的网络接口 解决方案:使用 INADDR_ANY 或有效的本地 IP | 
| 错误文件描述符 | EBADF | sockfd 不是有效的文件描述符解决方案:检查套接字创建是否成功  | 
| 无效参数 | EINVAL | 套接字已绑定/已连接,或地址族不匹配 解决方案:确保套接字未绑定,地址族正确  | 
| 非套接字 | ENOTSOCK | sockfd 不是套接字描述符解决方案:检查文件描述符类型  | 
| 内存不足 | ENOMEM | 内核内存不足 解决方案:释放系统资源或减少负载  | 
| 只读文件系统 | EROFS | UNIX 域套接字路径在只读文件系统 解决方案:更改绑定路径  | 
使用示例:
sockaddr_in addr = {
    .sin_family = AF_INET,
    .sin_addr = INADDR_ANY, // 绑定到所有网络接口
    .sin_port = htons(8080) // 绑定到 8080 端口
};
// c++ 中
struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(8080);
addr.sin_addr.s_addr = INADDR_ANY;
if (bind(fd, (struct sockaddr*)&addr, sizeof(addr)) < 0) {
    int errsv = errno;
    fprintf(stderr, "Error: %s\n", strerror(errsv)); 
    // ...
}
// ...
listen()
监听:int listen(int sockfd, int backlog); 。将套接字从"主动"状态转为"被动"状态,准备接受客户端连接。
sockfd
要求:
- 必须是已创建并成功绑定的套接字。
 - 类型必须是 
SOCK_STREAM或SOCK_SEQPACKET。 - 必须处于未连接状态。
 
backlog
指定等待连接队列的最大长度。队列满时连接将会被丢弃。
- 内核队列机制:
- 未完成连接队列 (SYN队列):
- 存储已收到客户端 SYN 但未完成三次握手的连接
 - 状态:
SYN_RCVD 
 - 已完成连接队列 (ACCEPT队列):
- 存储已完成三次握手等待 
accept()的连接 - 状态:
ESTABLISHED 
 - 存储已完成三次握手等待 
 
 - 未完成连接队列 (SYN队列):
 - 现代系统行为:
- Linux 4.3+ 开始使用单个队列模型
 backlog参数仅控制已完成连接队列的大小- SYN 队列大小由 
/proc/sys/net/ipv4/tcp_max_syn_backlog控制 
 
return
返回值: 成功时返回 0,失败返回 -1 并设置 errno。errno 需要 #include <errno.h> 。
| 错误码 | 常量 | 原因与解决方案 | 
|---|---|---|
| 错误描述符 | EBADF | sockfd 不是有效的文件描述符 | 
| 非套接字 | ENOTSOCK | sockfd 不是套接字描述符 | 
| 已连接 | EISCONN | 套接字已处于连接状态 | 
| 操作不支持 | EOPNOTSUPP | 套接字类型不支持监听(如 UDP) | 
| 地址已使用 | EADDRINUSE | 另一个套接字已在监听相同地址 | 
| 无效参数 | EINVAL | 套接字未绑定或 backlog 为负 | 
accept()
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);。
用于从已完成连接队列中提取第一个连接请求,创建一个新的套接字,并返回其文件描述符。
- 默认阻塞:若无连接请求,进程将阻塞等待。
 - 非阻塞模式(需用 
fcntl()设置):- 若无连接,立即返回 
-1,并设errno为EAGAIN或EWOULDBLOCK。 
 - 若无连接,立即返回 
 
sockfd
监听套接字描述符(由 socket() 创建并通过 listen() 进入监听状态)。类型必须是 SOCK_STREAM 或 SOCK_SEQPACKET。
addr
指向存放客户端地址信息的缓冲区(如 struct sockaddr_in)。若不需要客户端地址,可设为 NULL。
addrlen
- 输入时:指向的值需设为 
addr缓冲区的长度(如sizeof(struct sockaddr_in))。 - 输出时:内核将其修改为实际地址结构的大小。
若addr为NULL,此参数也应为NULL。 
return
返回值: 成功返回一个新的 sockfd(非负数),用于与客户端通信,失败返回 -1 并设置 errno。errno 需要 #include <errno.h> 。
| 错误码 | 原因说明 | 
|---|---|
EAGAIN/EWOULDBLOCK | 非阻塞模式下无连接可用 | 
EBADF | sockfd 无效 | 
ECONNABORTED | 连接已中止(客户端在完成前关闭) | 
EINTR | 被信号中断(可安全重启调用) | 
EINVAL | 套接字未处于监听状态 | 
EMFILE | 进程文件描述符耗尽 | 
ENFILE | 系统文件描述符耗尽 | 
EFAULT | addr 或 addrlen 指向无效地址 | 
EPROTO | 协议错误(如客户端在连接建立前重置) | 
connect()
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 。
用于客户端主动与服务器建立连接(TCP)或指定默认地址(UDP)。
- 对于 TCP 套接字:触发三次握手过程,建立可靠连接。
 - 对于 UDP 套接字:不建立实际连接,但后续的 send()/write() 调用可省略目标地址(数据报默认发送至此地址)。
 - 默认阻塞:调用后进程阻塞,直到连接成功或失败(通常最长75秒)。
 - 非阻塞模式(需用 
fcntl()设置):- 立即返回 
-1,并设errno为EINPROGRESS(表示连接正在进行)。 - 后续需用 
select()/poll()检查套接字是否可写(表示连接完成)。 
 - 立即返回 
 
sockfd
客户端套接字描述符(通过 socket() 创建)。
addr
指向服务器地址结构(如 struct sockaddr_in 或 struct sockaddr_in6)。必须包含服务器的 IP 地址和端口号。
addrlen
服务器地址结构的长度(如 sizeof(struct sockaddr_in))。
return
返回值: 成功返回 0, 失败返回 -1 并设置 errno。errno 需要 #include <errno.h> 。
| 错误码 | 原因说明 | 
|---|---|
EINPROGRESS | 非阻塞模式下连接正在建立(需后续检查) | 
EALREADY | 非阻塞套接字上已有连接尝试正在进行 | 
ECONNREFUSED | 服务器拒绝连接(端口无监听) | 
ETIMEDOUT | 连接超时(服务器未响应) | 
ENETUNREACH | 网络不可达 | 
EADDRINUSE | 本地地址已被占用 | 
EINTR | 被信号中断(可安全重启调用) | 
EISCONN | 套接字已处于连接状态 | 
EAFNOSUPPORT | 地址族不支持(如 IPv6 地址用于 IPv4 套接字) | 
EFAULT | addr 指向无效地址空间 | 
recv()
ssize_t recv(int sockfd, void *buf, size_t len, int flags); 。ssize_t read(int sockfd, void *buf, size_t count); = ssize_t recv(int sockfd, void *buf, size_t len, 0)
从已连接的套接字接收数据(主要用于 TCP,也可用于已调用 connect() 的 UDP 套接字)。将接收到的数据存储到缓冲区 buf 中,最多接收 len 个字节。是 read() 的增强版,增加了额外的控制标志。
部分读(Partial Read):
- 常见场景:
- 接收缓冲区数据量小于请求的 
count。 - 数据包分片到达。
 - 被信号中断。
 
 - 接收缓冲区数据量小于请求的 
 - 必须循环读取直到获取完整数据:
 
// 循环接收完整消息的范式
size_t total = 0;
while (total < expected_length) {
    ssize_t n = recv(sockfd, buf + total, len - total, 0);
    if (n <= 0) /* 处理错误或关闭 */;
    total += n;
}
数据边界:
- TCP:面向字节流,无消息边界。可能返回任意长度(1~len),需循环读取完整消息。
 - UDP:面向数据报,每次调用接收一个完整数据报(若 
buf过小,多余数据被丢弃)。 
阻塞行为:
- 默认阻塞:无数据时进程挂起等待。
 - 非阻塞模式(
MSG_DONTWAIT或fcntl(O_NONBLOCK)):- 无数据时立即返回 
-1,errno=EAGAIN/EWOULDBLOCK。 
 - 无数据时立即返回 
 
带外数据:
- 使用 
MSG_OOB接收紧急数据(通常只占用 1 字节)。 - 紧急指针指向最后一个带外字节。
 
sockfd
已经连接的套接字描述符。
buf
数据接收缓冲区的起始位置。
len
缓冲区的最大容量(字节数)。
flags
控制接收行为的标志位(常用值见下表,可通过 | 组合)。
| 标志 | 值 (Linux) | 说明 | 
|---|---|---|
MSG_PEEK | 0x02 | 查看数据但不从接收队列移除(下次调用仍可读到相同数据) | 
MSG_OOB | 0x01 | 接收带外数据(紧急数据) | 
MSG_WAITALL | 0x100 | 阻塞直到请求的字节数全部接收完(除非遇到错误或连接关闭) | 
MSG_DONTWAIT | 0x40 | 非阻塞操作(等价于 fcntl(O_NONBLOCK)) | 
MSG_TRUNC | 0x20 | 即使数据被截断也返回原始数据报长度(用于原始套接字) | 
MSG_ERRQUEUE | 0x2000 | 从错误队列接收错误信息(用于诊断) | 
return
返回值:
- 成功返回实际接收的字节数(>0)。
 - 连接关闭返回 0 。
 - 失败返回 -1,并设置 
errno。 
| 错误码 | 原因说明 | 
|---|---|
EAGAIN/EWOULDBLOCK | 非阻塞模式下无可用数据 | 
EBADF | 无效文件描述符 | 
ECONNREFUSED | 远程主机拒绝连接(UDP) | 
EFAULT | buf 指向无效地址空间 | 
EINTR | 被信号中断(可安全重启) | 
EINVAL | 无效参数(如负的 len 值) | 
ENOTCONN | 套接字未连接 | 
ENOTSOCK | 文件描述符不是套接字 | 
ETIMEDOUT | 连接超时 | 
ECONNRESET | 连接被对方重置(如对方进程崩溃) | 
send()
ssize_t send(int sockfd, const void *buf, size_t len, int flags);。ssize_t write(int sockfd, const void *buf, size_t count); = ssize_t send(int sockfd, const void *buf, size_t len, 0); 。
向已连接的套接字发送数据(TCP 或已连接 UDP),是 write() 的增强版,提供额外的控制标志,适用于需要特殊发送选项的场景(如带外数据、非阻塞操作等)。
阻塞行为:
- 默认阻塞:当套接字发送缓冲区满时,进程将阻塞直到有空间。
 - 非阻塞模式(通过 
fcntl(O_NONBLOCK)设置):- 立即返回 
-1,errno=EAGAIN/EWOULDBLOCK(当缓冲区满时)。 - 可能执行部分写(返回实际写入的字节数)。
 
 - 立即返回 
 
部分发送:
- 必须检查返回值并处理未发送数据:
 
size_t total_sent = 0;
while (total_sent < len) {
    ssize_t n = send(sockfd, buf + total_sent, len - total_sent, flags);
    if (n <= 0) /* 处理错误 */;
    total_sent += n;
}
sockfd
已连接的套接字描述符。
buf
指向待发送数据的缓冲区。
len
需要发送的字节数。
flags
控制发送行为的标志位(常用值如下,可通过 | 组合)。
| 标志 | 值 (Linux) | 说明 | 
|---|---|---|
MSG_OOB | 0x01 | 发送带外数据(紧急数据) | 
MSG_DONTROUTE | 0x04 | 绕过标准路由(仅限本地网络) | 
MSG_DONTWAIT | 0x40 | 非阻塞操作(等价于 fcntl(O_NONBLOCK)) | 
MSG_NOSIGNAL | 0x4000 | 连接断开时不产生 SIGPIPE 信号 | 
MSG_EOR | 0x80 | 标记记录结束(SOCK_SEQPACKET 类型) | 
MSG_MORE | 0x8000 | 提示内核后续还有数据(优化小包合并) | 
MSG_CONFIRM | 0x800 | 确认链路有效(用于 ARP 表维护) | 
return
返回值: 成功返回实际发送的字节数,失败返回 -1 并设置 errno 。
| 错误码 | 原因说明 | 
|---|---|
EAGAIN/EWOULDBLOCK | 非阻塞模式下发送缓冲区满 | 
EBADF | 无效文件描述符 | 
ECONNRESET | 连接被对方重置(对方进程崩溃) | 
EPIPE | 连接已关闭(未设置 MSG_NOSIGNAL 时会触发 SIGPIPE) | 
EFAULT | buf 指向无效地址空间 | 
EINTR | 被信号中断(可安全重启) | 
EINVAL | 无效参数(如负的 len 值) | 
ENOTCONN | 套接字未连接 | 
ENOTSOCK | 文件描述符不是套接字 | 
EMSGSIZE | 消息过大(UDP 数据报超过路径 MTU) | 
ENOBUFS | 系统缓冲区资源耗尽 | 
sendto()
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);。
UPD 数据发送。
- 必须指定目标地址 (
dest_addr) 和地址长度 (addrlen)。 - 如果 UDP Socket 已 
connect,也可以使用send或write(不需要指定目标地址)。 
recvfrom()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);。
UPD 数据接收。
- 接收数据报,并通过 
src_addr和addrlen返回发送方的地址。 - 如果 UDP Socket 已 
connect,也可以使用recv或read(不会返回发送方地址)。 
sendmsg()
ssize_t sendmsg(int sockfd, const struct msghdr *msg, int flags);。
通用数据发送:最灵活,支持分散/聚集 I/O 和辅助数据。
recvmsg()
ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);。
通用数据接收:最灵活,支持分散/聚集 I/O 和辅助数据。
close()
int close(int fd);。
头文件 #include <unistd.h> 。
- 关闭文件描述符(包括 Socket 描述符)。对于 TCP Socket,会触发四次挥手过程(如果必要)。
 - 如果有多个进程共享同一个 Socket 描述符(通过 
fork),close只是减少该描述符的引用计数。只有当引用计数为 0 时,才会真正关闭连接并释放资源。 
shutdown()
int shutdown(int sockfd, int how);。
- 提供更精细的控制,关闭连接的一个方向或两个方向。
 how:SHUT_RD(关闭读端,不再接收数据),SHUT_WR(关闭写端,发送 FIN 通知对方不再发送数据),SHUT_RDWR(同时关闭读写)。- 不影响文件描述符的引用计数,无论有多少个进程持有该描述符,
shutdown都会立即影响连接的可用性。 
利用 socket 实现简易C/S。server.cpp :
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <iostream>
#include <thread>
void handle(int fd) {
    while (true) {
        const size_t N = 1024;
        char buf[N + 1] = {};
        size_t n = recv(fd, buf, N, 0);
        if (n > 0) {
            buf[n] = '\0';
            std::cout << "thread: " << std::this_thread::get_id() << " recv: " << buf << std::endl;
        } else {
            if (n == 0) {
                std::cout << "thread: " << std::this_thread::get_id() << " connection closed" << std::endl;
            } else {
                std::cerr << "thread: " << std::this_thread::get_id() << " recv error: " << strerror(errno) << std::endl; 
            }
            break;
        }
    }
}
void start_server(const char* ip, uint16_t port) {
    int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (fd < 0) {
        std::cerr << "create socket error: " << strerror(errno) << std::endl; 
        return;
    }
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
        std::cerr << "invalid ip: " << ip << std::endl;
        return;
    }
    if (bind(fd, (sockaddr*) &addr, sizeof(addr)) < 0) {
       std::cerr << "bind ip: " << ip << " error: " << strerror(errno) << std::endl;
       return; 
    }
    int backlog = 128;
    if (listen(fd, backlog) < 0) {
        std::cerr << "listen ip: " << ip << " error: " << strerror(errno) << std::endl; 
    }
    while (true) {
        sockaddr_in addr;
        socklen_t len = sizeof(addr);
        int req = accept(fd, (sockaddr*) &addr, &len);
        if (req < 0) {
            std::cerr << "accept error: " << strerror(errno) << std::endl;
        } else {
            char client_ip[INET_ADDRSTRLEN];
            inet_ntop(AF_INET, &addr.sin_addr, client_ip, sizeof(client_ip));
            std::cout << "accept " << client_ip << ":" << ntohs(addr.sin_port) << std::endl;
            // create thread.
            std::thread t(handle, req);
            t.detach();
        }
    }
}
int main() {
    start_server("127.0.0.1", 8080);
    return 0;
}
client.cpp :
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <errno.h>
#include <iostream>
void start_client(const char*ip, uint16_t port) {
    int fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    if (fd < 0) {
        std::cerr << "create socket error: " << strerror(errno) << std::endl; 
        return;
    }
    sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(port);
    if (inet_pton(AF_INET, ip, &addr.sin_addr) != 1) {
        std::cerr << "invalid ip: " << ip << std::endl;
        return;
    }
    if (connect(fd, (sockaddr*) &addr, sizeof(addr)) < 0) {
        std::cerr << "connect ip: " << ip << " error: " << strerror(errno) << std::endl;
        return;
    }
    while (true) {
        const size_t N = 1024;
        char buf[N];
        std::cin >> buf;
        send(fd, buf, strlen(buf), 0);
    }
}
int main() {
    start_client("127.0.0.1", 8080);
    return 0;
}
			
                            
Comments NOTHING