Socket 编程

目录

Socket 是什么?它是如何诞生的?

在现代网络编程中,Socket(套接字) 是一个核心概念。要理解 Socket,我们需要从操作系统内核说起。

操作系统的内核协议栈(如 Linux 内核中的 TCP/IP 模块) 是 UDP/TCP 规则的真正落地执行者 ------ 它通过成千上万行 C 语言代码将滑动窗口、拥塞控制等算法实体化。

为了方便用户态程序安全地触发这些内核功能,操作系统封装出了 Socket(套接字)编程接口(API)。简单来说,Socket 就是应用程序与网络协议栈之间的「桥梁」,让程序员能够以文件描述符的形式操作网络连接。

Socket API 详解

在 Linux 系统中,使用 Socket 编程需要包含以下头文件:

c 复制代码
#include <sys/socket.h>

下面将逐一介绍 Socket API 中最核心的函数。

socket() --- 创建套接字

socket() 函数用于创建一个新的套接字,无论是服务端还是客户端都需要调用此函数。

c 复制代码
int socket(int domain, int type, int protocol);
domain(协议族 / 地址族)
常量 含义
AF_INET IPv4 互联网协议族 (最常用),地址为 32 位,如 192.168.1.1
AF_INET6 IPv6 互联网协议族,地址为 128 位。

这两个是实际开发中最常用的选项,记住即可。

type(套接字类型)
常量 含义 典型协议
SOCK_STREAM 流式套接字,面向连接、可靠、有序的字节流传输。 TCP
SOCK_DGRAM 数据报套接字,无连接、不可靠、固定最大长度的报文传输。 UDP

这两个是实际开发中最常用的选项,记住即可。

💡 Linux 特有标志(可选):

从 Linux 2.6.27 开始,type 参数可以与以下标志按位或(|)组合使用:

  • SOCK_NONBLOCK :创建非阻塞套接字,省去后续 fcntl() 调用。
  • SOCK_CLOEXEC :设置 FD_CLOEXEC 标志,在执行 exec() 时自动关闭该套接字。
protocol(协议类型)

通常情况下,给定 domaintype 后只有一种协议支持,此时0 即可,系统会自动选择默认协议。

返回值
结果 返回值 说明
成功 非负整数 套接字文件描述符(sockfd)。
失败 -1 设置全局变量 errno 以指示具体错误原因。

bind() --- 绑定地址与端口

bind() 函数将套接字文件描述符绑定到指定的 IP 地址和端口上,为服务端的后续操作做准备。

c 复制代码
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数详解
  • sockfdsocket() 函数返回的文件描述符。
  • addr :指向协议地址结构的指针。这是一个通用指针类型 struct sockaddr * ,实际上需要传入具体协议族对应的地址结构体,并在使用时强制转换为 (struct sockaddr *)
协议族 实际使用的结构体 关键成员赋值
IPv4 struct sockaddr_in sin_family = AF_INET; sin_port = htons(端口); sin_addr.s_addr = INADDR_ANY;inet_addr("IP")
IPv6 struct sockaddr_in6 sin6_family = AF_INET6; sin6_port = htons(端口); sin6_addr = in6addr_any;

INADDR_ANY 表示监听所有网络接口,无论客户端连接哪个 IP(内网或公网),服务器都能接收到,一般推荐使用。

  • addrlen :地址结构体的大小,通常使用 sizeof 获取即可。
返回值
结果 返回值 说明
成功 0 绑定成功。
失败 -1 设置全局变量 errno 以指示错误原因。

listen() --- 开始监听

listen() 函数将套接字设置为被动监听状态,准备接受客户端的连接请求。

c 复制代码
int listen(int sockfd, int backlog);
参数详解
  • sockfdsocket() 函数返回并已 bind() 的文件描述符。
  • backlog (等待队列的最大长度):当服务器忙于处理现有连接时,新客户端发起的 connect() 请求不会被丢弃,而是会在这个队列里排队等待 accept() 处理。

不要设得太大(浪费内存),也不要太小(高并发下客户端会连接失败)。一般情况下设置为 1024 即可。

返回值
结果 返回值 说明
成功 0 进入监听状态。
失败 -1 设置全局变量 errno

accept() --- 接受连接

accept() 函数从已完成连接的队列中取出一个客户端连接,返回一个专门用于与该客户端通信的新套接字。

c 复制代码
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数详解
  • sockfd :已调用 listen() 的监听套接字。
  • addr输出参数 ):传入一个 struct sockaddr_in(IPv4)或 struct sockaddr_in6(IPv6)结构体的指针,用于存放发起连接请求的客户端 IP 地址和端口号。如果不关心客户端地址信息,可以直接填 NULL
  • addrlen :这是一个值-结果 参数,⚠️ 极其重要,容易出错
    • 传入时 :必须初始化为 addr 结构体的大小(如 sizeof(struct sockaddr_in))。
    • 返回时:系统会修改此值为客户端地址的实际大小。
返回值
结果 返回值 说明
成功 新的非负整数文件描述符 连接套接字,专门用于和该客户端通信。
失败 -1 设置全局变量 errno

connect() --- 发起连接

connect() 函数用于客户端向服务器发起连接请求(完成 TCP 三次握手)。

c 复制代码
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
参数详解
  • sockfdsocket() 函数返回的客户端套接字描述符(主动套接字)。
  • addr :指向目标服务器的协议地址结构体(如 struct sockaddr_in),包含服务器的 IP 地址端口号 。使用时需要强制转换为 (struct sockaddr *)
  • addrlen :地址结构体的大小,即 sizeof(结构体)
返回值
结果 返回值 说明
成功 0 三次握手已成功完成。
失败 -1 设置全局变量 errno

send() --- 发送数据

send() 函数用于通过已连接的套接字发送数据。

c 复制代码
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
参数详解
  • sockfd :通常指 connect()(客户端)或 accept()(服务端)返回的已连接套接字
  • buf:指向存放待发送数据的内存区域指针(通常是字符数组)。
  • len:要发送的数据长度(字节数)。
  • flags :控制发送行为,大多数情况下填 0 即可(默认阻塞模式)。在高并发场景下可填 MSG_DONTWAIT 实现非阻塞发送,也可以使用 fcntl() 修改套接字为非阻塞模式。
返回值(必须严格判断)
返回值 含义 后续操作
> 0 成功发送了 n 个字节。注意:n 可能小于 len(部分发送)。 需要循环调用,将剩余数据继续发送。
== 0 (极少见)没有发送任何数据,通常表示连接已关闭。 关闭套接字。
-1 发生错误。查看 errno 根据错误码重试或报错。

recv() --- 接收数据

recv() 函数用于从已连接的套接字接收数据。

c 复制代码
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
参数详解
  • sockfd :通常指 accept() 返回的连接套接字conn_fd),用于与特定客户端通信。
  • buf:指向存放接收数据的内存区域指针(通常是字符数组)。
  • len:缓冲区最多能容纳的字节数。
  • flags :控制接收行为,大多数情况下填 0 即可(默认阻塞模式)。在高并发场景下可填 MSG_DONTWAIT 实现非阻塞接收,也可以使用 fcntl() 修改套接字为非阻塞模式。
返回值(极其重要,必须判断)

返回值类型是 ssize_t(有符号整型),三种情况

返回值 含义 后续操作
> 0 成功读取到 n 个字节。注意:n 可能小于 len(剩余数据还在路上)。 处理这 n 个字节,继续循环读取直到收完整条消息。
== 0 连接已关闭(对端调用了 close()),这是「优雅关闭」信号。 立即关闭本端 conn_fd,不要再读。
-1 发生错误。查看 errno 获取原因。 根据错误码处理(如重试或退出)。

close() --- 关闭连接

通信完成后要关闭套接字,避免资源泄漏。

c 复制代码
#include <unistd.h>

int close(int fd);
  • fd:要关闭的套接字文件描述符。
  • 返回值 :成功返回 0,失败返回 -1 并设置 errno

💡 提示:本文介绍了 Socket 编程中最核心的九个函数。掌握它们的用法后,你就可以开始编写自己的网络程序了。建议在实际编码中多练习,加深对这些 API 的理解。