一、Socket 是什么
Socket 是一套网络编程接口,把网络通信抽象成文件描述符,可以像读写文件一样收发网络数据。
AF_INET:IPv4 协议SOCK_STREAM:TCP 流式套接字SOCK_DGRAM:UDP 数据报套接字
二、通用基础 API
1. socket 创建套接字
c
int socket(int domain, int type, int protocol);
- domain:协议族,
AF_INET代表 IPv4 - type:套接字类型,
SOCK_STREAM(TCP)/SOCK_DGRAM(UDP) - protocol:协议编号,填 0 自动匹配
- 返回值:成功返回套接字文件描述符;失败返回 -1
TCP:
socket(AF_INET, SOCK_STREAM, 0)UDP:
socket(AF_INET, SOCK_DGRAM, 0)
2. bind 绑定 IP 与端口
c
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- sockfd:socket 返回的文件描述符
- addr:地址结构体,实际传
struct sockaddr_in,需要强制转换(struct sockaddr *)&var - addrlen:地址结构体大小
sizeof(struct sockaddr_in) - 返回:成功 0,失败 -1
✅ 服务端必须 bind;客户端 TCP 一般不需要 bind,内核自动分配临时端口。
INADDR_ANY:监听本机所有网卡,必须套htonl(INADDR_ANY)。
3. connect 发起连接(TCP 客户端)
c
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 功能:向服务端发起 TCP 连接请求,触发三次握手。
- sockfd:客户端 socket fd
- addr:目标服务端地址(IP、端口)
- addrlen:地址结构体大小
- 返回:成功返回 0;失败返回 -1
UDP 也可以调用 connect,但不会产生网络报文,仅仅在内核记录对端地址,后续可以直接 send/recv,不用 sendto/recvfrom。
4. send 发送数据(TCP)
c
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
- sockfd:通信文件描述符(accept 返回 cfd)
- buf:待发送数据缓冲区首地址
- len:想要发送的数据字节长度
- flags:标志位,日常填 0
- 返回值:成功返回实际发送字节数;失败 -1
send 不保证全部发完;返回值小于 len 属于正常现象,业务代码建议循环发送。
5. recv 接收数据(TCP)
c
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
- buf:接收缓冲区
- len:缓冲区最大可接收字节
- flags:默认 0
- 返回:
*0:收到的字节数量
- 0:对端关闭连接(TCP 断开)
- -1:出错
⚠️ 考试高频考点:recv 返回 0 代表对方关闭 socket,不是没有数据。
6. listen 设置监听(TCP 服务端)
c
int listen(int sockfd, int backlog);
- sockfd:已经 bind 过的 socket
- backlog:未处理连接请求队列的最大长度,并发连接排队上限。
- 返回:成功 0,失败 -1
socket→bind→listen 顺序不能颠倒。listen 把套接字从主动套接字变成被动监听套接字,等待别人来连接。
7. accept 接受客户端连接(TCP 服务端)
c
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
- sockfd:listen 之后的监听套接字
- addr:输出参数,保存客户端的 IP 端口信息,不需要可以填 NULL
- addrlen:输入输出参数,传入结构体大小;调用结束返回真实客户端地址长度
- 返回值:成功返回新的通信文件描述符 cfd;失败 -1
重点区分两个 fd:
- sockfd:监听套接字,一直用来等待新连接
- cfd:accept 返回,专门和这一个客户端收发数据
三、UDP 专用接口 sendto / recvfrom
UDP 是无连接,没有 connect 建立连接,每次收发都要携带对方地址。
c
// 发送
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
// 接收
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
- sendto:发送的时候带上目标 IP 端口
- recvfrom:接收的时候,同时拿到发送方的地址信息
- UDP 不存在返回 0 代表断开;UDP 无连接,对方退出你收不到任何通知。
如果你 UDP 调用 connect,之后可以直接使用 send/recv,不再需要 sendto/recvfrom。
四、地址结构体 sockaddr_in
c
struct sockaddr_in {
sa_family_t sin_family; // 协议族 AF_INET
in_port_t sin_port; // 端口,**网络字节序 htons()**
struct in_addr sin_addr; // IPv4 地址结构体
char sin_zero[8]; // 占位填充,置 0
};
struct in_addr {
in_addr_t s_addr; // 32 位 IP,网络大端
};
赋值示例
c
struct sockaddr_in ser;
ser.sin_family = AF_INET;
ser.sin_port = htons(8888);
ser.sin_addr.s_addr = inet_addr("192.168.1.100");
易错:
inet_addr()返回已经是网络大端,不要再套 htonl!
五、TCP 与 UDP 对比
| 项目 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接,三次握手建立连接 | 无连接,直接发数据包 |
| 可靠性 | 可靠,丢包重传、有序 | 不可靠,不保证到达、不保证顺序 |
| 数据边界 | 字节流,没有消息边界 | 数据报,保留消息边界 |
| 开销 | 大 | 小 |
| 典型 API | socket-bind-listen-accept-connect-send-recv | socket-bind-sendto-recvfrom |
UDP 头部简要
UDP 头部 4 个字段:源端口、目的端口、数据包长度、校验和,之后直接携带业务数据。特点:机制简单,资源开销小,不可靠。
TCP 三次握手简要
- 客户端发送 SYN 报文,请求建立连接
- 服务端返回 SYN+ACK 报文
- 客户端回复 ACK,连接正式建立,之后传输业务数据
六、TCP 完整调用流程
服务端
text
socket() → bind() → listen() → accept()阻塞等待连接 → send()/recv() → close()
客户端
text
socket() → connect() → send()/recv() → close()
七、高频坑点总结
- TCP recv 返回
0= 对方关闭连接;UDP recvfrom 永远不会返回 0 代表断开。 sin_port端口必须htons()转为网络字节序。- accept 第二个参数是输出参数,用来拿到客户端 IP 端口,不需要可以填 NULL。
- listen 只是开启监听,不会接收连接;真正获取连接靠 accept。
- send 返回值不等于你传入 len,不一定全部发送完成,需要循环处理。
- UDP 无连接,对方进程退出,本程序不会收到任何通知。