socket基础概念
在网络编程中,Socket(套接字) 是进程间通信的抽象接口,用于实现不同主机或同一主机内不同进程之间的网络数据传输。它是 TCP/IP 协议栈的编程接口,现代 C++ 网络开发中依然是核心基础(尽管常被封装为更高层库如 Boost.Asio)。
一、Socket 核心概念
-
本质:Socket 是一个 “文件描述符”(Linux/Unix)或 “句柄”(Windows),代表一个网络通信端点,通过它可进行数据的发送 / 接收。
-
分类:
- 流式套接字(SOCK_STREAM):基于 TCP 协议,提供可靠、有序、双向的字节流传输(无数据丢失 / 重复,适合文件传输、HTTP 等)。
- 数据报套接字(SOCK_DGRAM):基于 UDP 协议,提供无连接、不可靠的数据包传输(速度快,适合视频通话、DNS 等)。
- 原始套接字(SOCK_RAW):直接操作底层协议(如 IP),用于网络诊断或自定义协议(需管理员权限)。
-
通信模型:
- TCP 采用 “客户端 - 服务器” 模型,需先建立连接(三次握手),再传输数据。
- UDP 无连接,直接发送数据包,无需提前建立连接。
二、核心函数解析(基于 TCP,现代 C++ 视角)
以下函数是 TCP 通信的核心,主要用于服务器端和客户端的连接建立过程。
1. socket():创建套接字
作用:初始化一个套接字,返回其描述符(后续操作的句柄)。
函数原型:
1 |
|
参数解析:
-
domain:地址族(协议族),指定网络层协议。常见值:AF_INET:IPv4 协议(最常用)。AF_INET6:IPv6 协议。AF_UNIX:本地进程间通信(Unix 域套接字)。
-
type:套接字类型,指定传输层协议特性:SOCK_STREAM:TCP 流式套接字。SOCK_DGRAM:UDP 数据报套接字。
-
protocol:具体协议(通常为 0,由系统根据前两个参数自动选择)。
返回值:成功返回非负套接字描述符(sockfd),失败返回 - 1(需检查errno)。
现代 C++ 实践:用智能指针(如std::unique_ptr)配合自定义删除器管理sockfd,避免资源泄漏:
1 | // 自定义删除器:关闭套接字 |
2. bind():绑定地址与端口
作用:将套接字与特定的 IP 地址和端口号绑定(主要用于服务器端,客户端通常不绑定,由系统自动分配端口)。
函数原型:
1 | int bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen); |
参数解析:
-
sockfd:socket()返回的套接字描述符。 -
addr:指向地址结构的指针(需根据domain选择对应类型,如 IPv4 用sockaddr_in)。 -
addrlen:地址结构的大小(字节数)。
地址结构处理(以 IPv4 为例):sockaddr是通用地址结构,实际使用时需转换为具体协议的结构(如sockaddr_in):
1 | struct sockaddr_in { |
字节序转换:网络协议使用大端字节序(网络字节序),而主机可能是小端 / 大端,需用函数转换:
-
htons():主机字节序→网络字节序(16 位,用于端口)。 -
htonl():主机字节序→网络字节序(32 位,用于 IPv4 地址)。 -
inet_pton():字符串 IP(如 “127.0.0.1”)→网络字节序的整数(现代 C++ 推荐,替代旧的inet_addr())。
示例:
1 | // 绑定到IPv4地址127.0.0.1:8080 |
3. listen():监听连接(服务器端)
作用:将套接字转为 “监听状态”,允许接收客户端的连接请求(仅用于 TCP 服务器端的sockfd)。
函数原型:
1 | int listen(int sockfd, int backlog); |
参数解析:
-
sockfd:已绑定的套接字描述符(bind()之后)。 -
backlog:未完成连接队列(三次握手未完成)的最大长度(超过则客户端连接失败)。实际值受系统限制(如/proc/sys/net/core/somaxconn),现代系统通常建议设为 128 或更高。
返回值:成功返回 0,失败返回 - 1。
注意:listen()不阻塞,仅设置套接字状态。后续需通过accept()获取实际连接。
4. accept():接受连接(服务器端)
作用:从listen()创建的连接队列中取出一个已完成的连接,返回一个新的套接字描述符(用于与该客户端通信)。
函数原型:
1 | int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen); |
参数解析:
-
sockfd:处于监听状态的套接字(监听套接字)。 -
addr:输出参数,用于存储客户端的地址信息(可设为nullptr忽略)。 -
addrlen:输入输出参数,传入addr的大小,输出实际存储的地址长度(可设为nullptr)。
返回值:成功返回新的套接字描述符(client_fd,用于与客户端通信),失败返回 - 1。
特性:accept()默认是阻塞函数,直到有客户端连接到达才返回。现代 C++ 中可结合非阻塞 I/O 或多线程处理(如用std::thread为每个新连接创建线程)。
示例:
1 | struct sockaddr_in client_addr; |
5. connect():发起连接(客户端)
作用:客户端通过该函数向服务器发起 TCP 连接(三次握手的触发点)。
函数原型:
1 | int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen); |
参数解析:
-
sockfd:客户端的套接字描述符(socket()创建,通常不bind())。 -
addr:服务器的地址结构(包含服务器 IP 和端口)。 -
addrlen:服务器地址结构的大小。
返回值:成功返回 0(连接建立),失败返回 - 1(如服务器未监听、网络不可达等)。
特性:默认是阻塞函数,直到连接建立或失败才返回。非阻塞模式下,可能返回EINPROGRESS(连接正在建立),需通过select()/poll()等检查状态。
示例:
1 | // 客户端连接服务器(127.0.0.1:8080) |
三、现代 C++ 最佳实践
-
资源管理:用 RAII(如智能指针、自定义封装类)管理
sockfd,避免手动close()导致的泄漏。 -
错误处理:用异常(
std::runtime_error)替代传统的if-else检查,提高代码可读性。 -
类型安全:用
reinterpret_cast显式转换地址结构,避免隐式转换错误。 -
异步操作:结合 Boost.Asio 或 C++20 的
std::net(提案阶段)实现非阻塞 I/O,避免线程阻塞。 -
跨平台:封装系统差异(如 Windows 的
WSASend与 Linux 的send),用条件编译或跨平台库。
四、总结
socket()/bind()/listen()/accept()/connect()是 TCP 通信的 “骨架”:服务器通过socket()创建套接字→bind()绑定地址→listen()监听→accept()接受连接;客户端通过socket()创建套接字→connect()连接服务器。理解这些函数的参数和交互流程,是掌握网络编程的基础。现代 C++ 通过封装和抽象(如智能指针、异常),可让这些底层操作更安全、易维护。
附: socket 选项
| 选项名 | 数据类型 | 功能描述 | 可操作(get/set) | 备注 |
|---|---|---|---|---|
| SOL_SOCKET 级别(通用选项) | ||||
| SO_DEBUG | int (0 / 非 0) | 启用调试模式,内核记录套接字调试信息 | get/set | 仅超级用户可设置 |
| SO_REUSEADDR | int (0 / 非 0) | 允许重用本地地址和端口(如 TIME_WAIT 状态端口复用) | get/set | 需在 bind 前设置,解决 “地址已被使用” 错误 |
| SO_REUSEPORT | int (0 / 非 0) | 允许多个套接字绑定到同一端口(需同 UID,内核分发数据) | get/set | 部分系统支持(如 Linux 3.9+),常用于多进程监听同一端口 |
| SO_KEEPALIVE | int (0 / 非 0) | 启用 TCP 保活机制,定期检测对端是否存活 | get/set | 超时时间由系统配置(通常 2 小时无活动后触发) |
| SO_DONTROUTE | int (0 / 非 0) | 禁止路由,数据仅在本地子网传输 | get/set | 用于本地通信或诊断 |
| SO_BROADCAST | int (0 / 非 0) | 允许发送广播报文 | get/set | 仅适用于 UDP,需在 sendto 前设置 |
| SO_SNDBUF | int | 设置 / 获取发送缓冲区大小(字节) | get/set | 内核可能调整实际大小(不超过系统上限) |
| SO_RCVBUF | int | 设置 / 获取接收缓冲区大小(字节) | get/set | 影响接收性能和拥塞控制,建议在 connect/bind 前设置 |
| SO_SNDTIMEO | struct timeval | 设置发送操作(send/sendto)的超时时间 | get/set | 超时后返回 EAGAIN/EWOULDBLOCK |
| SO_RCVTIMEO | struct timeval | 设置接收操作(recv/recvfrom)的超时时间 | get/set | 同 SO_SNDTIMEO |
| SO_TYPE | int | 获取套接字类型(如 SOCK_STREAM、SOCK_DGRAM) | get(不可 set) | 用于判断套接字是 TCP 还是 UDP |
| SO_ERROR | int | 获取套接字上的待处理错误(如连接失败原因) | get(不可 set) | 常用于非阻塞 connect 后的错误检查 |
| SO_LINGER | struct linger | 控制 close 时的延迟关闭行为(等待未发送数据) | get/set | struct linger {int l_onoff; int l_linger;},l_onoff=1 启用,l_linger 为等待秒数 |
| SO_PASSCRED | int (0 / 非 0) | 允许接收辅助数据中的凭证信息(如 UID/GID) | get/set | 用于 Unix 域套接字的权限验证 |
| SO_PEERCRED | struct ucred | 获取对端 Unix 域套接字的用户凭证(UID/GID/PID) | get(不可 set) | 仅适用于已连接的 Unix 域套接字 |
| IPPROTO_IP 级别(IPv4 选项) | ||||
| IP_HDRINCL | int (0 / 非 0) | 应用程序自行构造 IP 头部,不依赖内核 | get/set | 仅超级用户可设置,用于自定义 IP 报文 |
| IP_OPTIONS | void * | 设置 / 获取 IP 选项(如源路由、记录路由) | get/set | 选项数据需符合 IP 协议规范,长度有限制 |
| IP_TOS | int | 设置 / 获取 IP 服务类型(TOS),控制路由优先级 | get/set | 值为 0~255,具体含义见 RFC 规范 |
| IP_TTL | int | 设置 / 获取 IP 生存时间(TTL),限制报文转发跳数 | get/set | 通常设为 64、128 等,超过 TTL 则报文被丢弃 |
| IP_MULTICAST_TTL | u_char | 设置多播报文的 TTL 值 | get/set | 控制多播范围(0 = 本地,1 = 同一子网,…,255 = 全局) |
| IP_MULTICAST_LOOP | u_char (0/1) | 启用多播报文回环(本地发送的多播自己能否接收) | get/set | 默认 1(启用),多播应用需根据需求关闭 |
| IP_ADD_MEMBERSHIP | struct ip_mreq | 加入多播组 | set(不可 get) | struct ip_mreq 包含多播组 IP 和本地接口 IP |
| IP_DROP_MEMBERSHIP | struct ip_mreq | 退出多播组 | set(不可 get) | 同 IP_ADD_MEMBERSHIP |
| IPPROTO_TCP 级别(TCP 选项) | ||||
| TCP_NODELAY | int (0 / 非 0) | 禁用 Nagle 算法(立即发送小数据包,不延迟合并) | get/set | 适用于低延迟场景(如实时通信),可能增加网络拥塞 |
| TCP_MAXSEG | int | 设置 / 获取 TCP 最大段大小(MSS) | get/set | 通常由内核协商,手动设置需谨慎 |
| TCP_CORK | int (0 / 非 0) | 启用 “塞子” 模式,累积数据后一次性发送(类似 Nagle 但更激进) | get/set | 适用于批量数据发送,需在完成后关闭(避免延迟) |
| TCP_KEEPIDLE | int | 设置 TCP 保活探测的空闲时间(秒) | get/set | 需 SO_KEEPALIVE 启用,默认 7200 秒(2 小时) |
| TCP_KEEPINTVL | int | 设置 TCP 保活探测的间隔时间(秒) | get/set | 探测未响应时,间隔多久再次发送,默认 75 秒 |
| TCP_KEEPCNT | int | 设置 TCP 保活探测的最大重试次数 | get/set | 超过次数则判定对端不可达,默认 9 次 |
说明
-
选项级别说明:
SOL_SOCKET为通用选项,IPPROTO_IP为 IPv4 协议选项,IPPROTO_TCP为 TCP 协议选项,还有IPPROTO_UDP(UDP 选项,较少用)等。 -
部分选项为系统特定(如
SO_REUSEPORT在不同 OS 上行为可能不同),使用前需参考目标系统的man手册。 -
多播相关选项(如
IP_ADD_MEMBERSHIP)需结合多播地址(224.0.0.0~239.255.255.255)使用。