网络编程——Socket 编程详解(TCP / UDP)
Socket 编程详解(TCP / UDP)
Socket 是应用层与传输层之间的通信接口,是网络编程的基石。无论是 TCP(面向连接)还是 UDP(无连接),通信双方都要通过 Socket 进行数据的发送与接收。
一、基于 TCP 的 Socket 编程
TCP 是面向连接的协议,通信前必须完成“三次握手”,因此编程流程更为复杂但稳定可靠。
1. TCP 服务器端编程流程图
如下图所示,服务器端典型的 TCP 通信流程为:
2. 服务器端主要函数详解
(1)socket()
创建一个套接字,返回用于网络通讯的文件描述符。
int socket(int domain, int type, int protocol);
常见用法:
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
AF_INET
:IPv4SOCK_STREAM
:TCP协议0
:默认协议
(2)bind()
将 socket 绑定到一个本地地址(IP + 端口)。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
(3)listen()
监听端口,设置连接请求的队列大小。
int listen(int sockfd, int backlog);
(4)accept()
从连接队列里边儿取出已经成完成三次握手的连接,返回新的 socket 文件描述符。
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
(5)recv()
和 send()
将要发送的数据从用户空间拷贝到TCP关联的内核发送缓冲区
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
也可以使用 read()
和 write()
。
(6)close()
关闭 socket 连接。
3. 客户端主要函数详解
客户端流程更简单,不需要监听:
socket()
:创建 socketconnect()
:连接到服务端send()
/recv()
:发送和接收数据close()
:关闭 socket
二、基于 UDP 的 Socket 编程
UDP 是无连接的协议,通信前不需要建立连接,效率更高,但不保证可靠性。
1. UDP 客户端与服务器流程图
如下图所示:
- 服务器端流程
(1)、建立套接字文件描述符,使用函数socket(),生成套接字文件描述符。
(2)、设置服务器地址和侦听端口,初始化要绑定的网络地址结构。
(3)、绑定侦听端口,使用bind()函数,将套接字文件描述符和一个地址类型变量进行绑定。
(4)、接收客户端的数据,使用recvfrom()函数接收客户端的网络数据。
(5)、向客户端发送数据,使用sendto()函数向服务器主机发送数据。
(6)、关闭套接字,使用close()函数释放资源。UDP协议的客户端流程 - 客户端流程
(1)、建立套接字文件描述符,socket()。
(2)、设置服务器地址和端口,struct sockaddr。
(3)、向服务器发送数据,sendto()。
(4)、接收服务器的数据,recvfrom()。
(5)、关闭套接字,close()
2. UDP 服务器端函数详解
(1)socket()
与 TCP 相同,但 type
使用 SOCK_DGRAM
:
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
(2)bind()
绑定 IP 地址和端口。
(3)recvfrom()
与 sendto()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
recvfrom()
:接收数据并获取发送方地址sendto()
:发送数据到指定地址
(4)close()
关闭 socket。
3. UDP 客户端流程
socket()
sendto()
:发送数据recvfrom()
:接收服务端响应close()
三、函数工作机制详解
✅ socket()
创建一个用于网络通信的“文件描述符”,是 TCP 和 UDP 的起点。
✅ bind()
绑定 socket 和本地地址,用于服务端监听特定端口。
✅ listen()
仅适用于 TCP,设置连接请求队列的大小。
✅ accept()
TCP 特有,用于接收客户端连接请求,返回一个新的 socket(用于收发数据)。
✅ connect()
客户端使用,将 socket 与服务端地址连接。
✅ send()
和 recv()
send()
:将数据拷贝到内核缓冲区,由内核发送。recv()
:从内核缓冲区拷贝数据到用户程序缓冲区。
四、TCP 与 UDP 的对比
特性 | TCP | UDP |
---|---|---|
是否连接 | 有连接(三次握手) | 无连接 |
是否可靠 | 可靠(顺序、无丢包) | 不可靠(可能丢失或乱序) |
速度 | 较慢 | 较快 |
适用场景 | 文件传输、登录、网页 | 视频、语音、广播 |
五、典型示例代码(TCP 服务端)
// 创建一个套接字文件描述符,用于网络通信
int server_fd = socket(AF_INET, SOCK_STREAM, 0);
// 参数解释:
// AF_INET 表示使用 IPv4 地址族
// SOCK_STREAM 表示使用 TCP 协议(面向连接的可靠传输)
// 0 表示默认使用 TCP 协议的默认选项
// 返回值 server_fd 是一个文件描述符,用于后续的网络操作// 定义一个结构体变量,用于存储服务器的地址信息
struct sockaddr_in server_addr;
// 初始化服务器地址结构体
server_addr.sin_family = AF_INET; // 地址族为 IPv4
server_addr.sin_port = htons(8888); // 设置服务器监听的端口号为 8888
// htons 函数将主机字节序转换为网络字节序(大端序)
server_addr.sin_addr.s_addr = INADDR_ANY; // 表示服务器可以监听本机的任意 IP 地址
// INADDR_ANY 是一个特殊的 IP 地址,用于绑定到本机的任意网络接口// 将服务器地址绑定到套接字上
bind(server_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));
// 参数解释:
// server_fd 是要绑定的套接字文件描述符
// (struct sockaddr*)&server_addr 是服务器地址结构体的指针,强制转换为通用的 sockaddr 类型
// sizeof(server_addr) 是地址结构体的大小,确保绑定操作正确处理整个结构体// 将套接字设置为监听状态,等待客户端的连接请求
listen(server_fd, 5);
// 参数解释:
// server_fd 是要监听的套接字文件描述符
// 5 是监听队列的长度,表示最多可以有 5 个未处理的连接请求排队等待// 接受客户端的连接请求,返回一个新的套接字文件描述符,用于与客户端通信
int client_fd = accept(server_fd, NULL, NULL);
// 参数解释:
// server_fd 是监听套接字文件描述符
// NULL 表示不获取客户端的地址信息(实际应用中通常会获取客户端地址)
// NULL 表示不获取客户端地址信息的长度
// 返回值 client_fd 是一个新的套接字文件描述符,用于与连接的客户端进行通信// 定义一个字符数组缓冲区,用于接收客户端发送的数据
char buffer[1024];
// 接收客户端发送的数据
recv(client_fd, buffer, sizeof(buffer), 0);
// 参数解释:
// client_fd 是与客户端通信的套接字文件描述符
// buffer 是接收数据的缓冲区
// sizeof(buffer) 是缓冲区的大小,表示最多可以接收 1024 字节的数据
// 0 是标志位,表示正常接收数据(无特殊选项)// 向客户端发送数据
send(client_fd, "Hello", strlen("Hello"), 0);
// 参数解释:
// client_fd 是与客户端通信的套接字文件描述符
// "Hello" 是要发送的数据
// strlen("Hello") 是发送数据的长度(5 字节)
// 0 是标志位,表示正常发送数据(无特殊选项)// 关闭与客户端通信的套接字文件描述符
close(client_fd);
// 关闭监听套接字文件描述符
close(server_fd);