
前言
网络编程是 Linux 应用层开发的核心技能之一,无论是服务端程序、客户端工具,还是物联网、音视频、分布式系统等场景,都离不开网络通信。本章作为网络编程的入门开篇,将聚焦通信标识、通信角色、传输协议三大基础核心,为后续编写TCP/UDP代码打下扎实理论基础。

1 网络编程核心基础概念
1.1 IP 和端口:网络通信的「地址标识」
所有网络数据传输,都离不开三个核心要素:源、目的、长度。其中,如何精准定位数据的发送方(源)和接收方(目的),是网络通信的首要问题。
在网络传输中,我们通过IP+端口的组合,唯一标识一个网络通信端点:
- IP地址 :定位网络中的主机(电脑、服务器、嵌入式设备等),相当于设备的「家庭住址」,解决「数据发给哪台设备」的问题;
- 端口号 :定位主机上的具体应用进程,相当于设备上的「房间号」,解决「数据发给设备上的哪个程序」的问题。
二者缺一不可:仅有IP无法区分同一设备上的多个网络程序,仅有端口无法定位目标设备。IP:端口的组合,是网络通信中源和目的的标准表示方式。
1.2 网络传输的两大核心角色
网络通信是双向交互的过程,一般抽象为两个标准角色:服务端(Server) 和客户端(Client),这是所有网络程序的基础模型。
以日常访问网站为例:
- 网站后台程序是Server:被动等待,持续监听网络请求,不会主动发起通信;
- 浏览器是Client:主动发起连接请求,向服务端索要数据,接收服务端响应。
两大角色的核心区别:
- 服务端(Server):启动后持续监听指定IP和端口,等待客户端连接,被动响应请求;
- 客户端(Client):主动发起连接请求,主动发送数据,等待服务端处理后返回结果。
++Linux网络编程的核心,就是分别实现服务端和客户端的通信逻辑++ 。
1.3 两种核心传输方式:TCP与UDP
网络协议分为5层模型,对于Linux应用层开发者而言,无需关注底层网络层、链路层、物理层 ,我们的应用程序运行在应用层 ,编写网络程序时,直接使用传输层 提供的两种协议:TCP 和UDP。
- 应用层 :为用户进程提供服务(HTTP、FTP、DNS 等协议),我们的业务代码运行于此;
- 传输层:实现主机进程间的通信,提供 TCP/UDP 两种传输服务,是网络编程的核心依赖;
- 网络层:负责数据包在主机间的路由转发;
- 链路层:配合路由器完成数据报的传输;
- 物理层:以比特流形式传输原始数据。
1.3.1 TCP与UDP的区别
①TCP(传输控制协议)
- 核心特点:面向连接、可靠交付;
- 传输单位:报文段;
- 核心能力:✅建立专属连接(三次握手),通信结束后断开连接(四次挥手);✅保证数据不丢失、不重复、按顺序到达;✅自带流量控制、拥塞控制,匹配发送方与接收方的传输速率;✅自动将大报文拆分、重组,适配网络传输。
②UDP(用户数据包协议)
- 核心特点:无连接、尽最大努力交付;
- 传输单位:用户数据报;
- 核心能力:❌不建立连接,直接发送数据;❌不保证可靠交付,可能丢包、乱序;❌无流量控制、无拥塞控制。
1.3.2 为什么需要UDP?
很多初学者会疑惑:TCP能保证可靠传输,为什么还要用UDP?答案是:**场景决定选择,**UDP拥有TCP无法替代的优势:
- 传输延迟极低:UDP收到应用数据后立即发送,无重传等待,实时性极强;
- 无连接开销:无需三次握手建立连接,没有连接延迟;
- 资源占用更低:无连接状态管理,单服务端可支持海量客户端;
- 首部开销小:数据传输的额外消耗更低。
1.3.3 典型应用场景
- UDP:视频通话、直播、在线游戏、DNS查询(容忍少量丢包,追求低延迟);
- TCP:文件传输、网页访问、邮件发送(不容忍数据丢失,追求可靠性)。
1.3.4 TCP与UDP通信模式对比
- TCP通信 :面向连接的流模式,类似打电话,先拨号建立连接,通话过程中数据连续传输,挂断后结束通信;
- UDP通信 :无连接的数据包模式,类似寄快递,无需提前建立连接,直接打包数据发送,不保证对方一定收到。
2 网络编程核心函数
2.1 socket()函数
在Linux 网络编程中,socket()函数是创建套接字的核心入口,用于初始化一个网络通信端点,成功后返回套接字文件描述符,是TCP/UDP通信的第一步。
cs
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/*
创建一个用于网络通信的套接字(socket),
返回对应的文件描述符,
后续的绑定、监听、连接、数据收发等操作都基于该描述符展开。
*/
参数说明:
[1] domain:协议族/地址族
指定套接字使用的通信协议簇,决定了网络通信的范围和地址格式,常用取值:
AF_UNIX/AF_LOCAL:本地进程间通信协议族,仅适用于同一台Unix/Linux主机内的进程通信,无法跨网络。AF_INET:IPv4网络协议族,适用于Internet网络通信,支持远程主机间的数据传输(最常用)。AF_INET6:IPv6网络协议族,用于IPv6网络通信。
补充:
PF_xxx与AF_xxx等价,Linux中可通用,推荐使用AF_xxx。
[2] type:套接字类型
指定套接字的数据传输方式,对应TCP/UDP等通信协议,常用取值:
SOCK_STREAM:流式套接字,基于TCP协议,特点:✅面向连接、可靠传输✅数据按顺序到达、无丢失无重复✅双向字节流传输SOCK_DGRAM:数据报套接字,基于 UDP 协议,特点:❌无连接、不可靠传输❌数据可能丢失、乱序✅传输速度快、开销小SOCK_RAW:原始套接字,用于底层协议开发(如 ping、抓包)。
[3] protocol:协议编号
指定具体的传输协议,通常填0即可:
- 当
domain和type确定后,系统会自动匹配默认协议(如AF_INET+SOCK_STREAM默认对应 TCP)。 - 仅在同一类型套接字支持多种协议时,需要指定具体编号(极少使用)。
返回值
- 成功 :返回一个非负整数,即套接字文件描述符(后续操作的唯一标识)。
- 失败 :返回
-1,通过errno变量可获取具体错误原因。
2.2 bind()函数
cs
#include <sys/socket.h>
int bind(int sockfd, struct sockaddr *my_addr, int addrlen);
/*
将本机IP地址 + 端口号绑定到创建好的套接字上。
*/
参数说明:
[1] sockfd:socket () 函数返回的套接字文件描述符
[2] my_addr:指向本机地址结构体的指针
[3] addrlen:地址结构体的长度(sizeof)
重点:地址结构体
通用结构体(实际使用时强制转换)
csstruct sockaddr{ unsigned short sa_family; // 协议族 char sa_data[14]; // 地址数据 };实际使用:IPv4 专用结构体(sockaddr_in)
csstruct sockaddr_in{ unsigned short sin_family; // 协议族,Internet填 AF_INET unsigned short sin_port; // 绑定的端口号 struct in_addr sin_addr; // IP地址 unsigned char sin_zero[8]; // 填充,保持长度一致 };
sin_addr.s_addr = INADDR_ANY:绑定本机所有网卡IPsin_port:需要绑定的监听端口
返回值
- 成功:返回0
- 失败:返回**-1**,通过errno获取错误信息
2.3 listen()函数
cs
#include <sys/socket.h>
int listen(int sockfd, int backlog);
/*
将套接字设为监听模式,等待客户端连接,仅TCP服务端使用。
*/
参数说明:
[1] sockfd :经过bind()绑定后的套接字文件描述符
[2] backlog:等待连接队列的最大长度(客户端连接排队上限)
返回值
- 成功:返回0
- 失败:返回**-1**,通过errno获取错误信息
2.4 accept()函数
cs
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, int *addrlen);
/*
TCP服务端接受客户端连接,建立通信链路(阻塞等待直到有客户端连接)。
*/
参数说明:
[1] sockfd :经过listen()监听后的套接字文件描述符
[2] addr :输出参数,用于存储客户端的IP和端口
[3] addrlen:输出参数,客户端地址结构体长度
返回值
- 成功:返回新的套接字描述符(专门用于和该客户端通信)
- 失败:返回
-1
2.5 connect()函数
cs
#include <sys/socket.h>
int connect(int sockfd, struct sockaddr *serv_addr, int addrlen);
/*
客户端专用函数,用于主动与服务端建立 TCP 连接。
*/
参数说明:
[1] sockfd:socket ()创建的套接字文件描述符
[2] serv_addr:服务端的IP地址 + 端口号结构体
[3] addrlen:地址结构体长度(sizeof)
返回值
- 成功:返回0
- 失败:返回**-1**
2.6 send()函数
cs
#include <sys/socket.h>
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
/*
用于TCP连接中,向对端发送数据。
*/
参数说明:
[1] sockfd:已连接的套接字文件描述符
[2] buf:发送数据的缓冲区
[3] len:要发送的数据长度(字节)
[4] flags :标志位,一般填0
返回值
- 成功:返回实际发送的字节数
- 失败:返回
-1
2.7 recv()函数
cs
#include <sys/socket.h>
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
/*
TCP连接中,接收对方发送的数据(阻塞等待直到收到数据)。
*/
参数说明:
[1] sockfd:已连接的套接字文件描述符
[2] buf:接收数据的缓冲区
[3] len:缓冲区最大长度(字节)
[4] flags :标志位,一般填0
返回值
- 成功:返回实际接收到的字节数
- 连接关闭:返回0
- 失败:返回
-1
2.8 recvfrom()函数
cs
#include <sys/socket.h>
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
/*
UDP专用,接收数据,并获取发送端的IP和端口。
*/
参数说明:
[1] sockfd:套接字文件描述符
[2] buf:接收数据的缓冲区
[3] len:缓冲区最大长度
[4] flags :标志位,一般填0
[5] src_addr :输出参数,存储发送端IP+端口
[6] addrlen:地址结构体长度
返回值
- 成功:返回实际接收的字节数
- 失败:返回
-1
2.9 sendto()函数
cs
#include <sys/socket.h>
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
/*
UDP专用,向指定目标地址发送数据(无连接传输)。
*/
参数说明:
[1] sockfd:套接字文件描述符
[2] buf:接收数据的缓冲区
[3] len:缓冲区最大长度
[4] flags :标志位,一般填0
[5] dest_addr:目标主机的IP+端口结构体
[6] addrlen:地址结构体长度
返回值
- 成功:返回实际发送的字节数
- 失败:返回
-1
3 TCP编程
3.1 完整代码
3.1.1 TCP 服务端(多进程并发)
cs
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <stdlib.h>
#define SERVER_PORT 8888
#define BACKLOG 10
int main()
{
int server_fd; // 监听 socket
int client_fd; // 连接 socket
struct sockaddr_in server_addr; // 服务器地址
struct sockaddr_in client_addr; // 客户端地址
socklen_t addr_len;
char buf[1000];
int ret;
// 1. 忽略子进程退出信号,避免僵尸进程
signal(SIGCHLD, SIG_IGN);
// 2. 创建 socket
server_fd = socket(AF_INET, SOCK_STREAM, 0);
if (server_fd == -1)
{
perror("socket");
return -1;
}
// 3. 配置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY;
memset(server_addr.sin_zero, 0, 8);
// 4. 绑定端口
ret = bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret == -1)
{
perror("bind");
close(server_fd);
return -1;
}
// 5. 开始监听
listen(server_fd, BACKLOG);
printf("TCP Server 已启动,端口:%d\n", SERVER_PORT);
while (1)
{
// 6. 等待客户端连接
addr_len = sizeof(client_addr);
client_fd = accept(server_fd, (struct sockaddr *)&client_addr, &addr_len);
if (client_fd == -1)
{
perror("accept");
continue;
}
printf("客户端连接:%s\n", inet_ntoa(client_addr.sin_addr));
// 7. 创建子进程处理客户端
if (fork() == 0)
{
// 子进程:关闭监听socket
close(server_fd);
while (1)
{
// 接收数据
ret = recv(client_fd, buf, sizeof(buf) - 1, 0);
if (ret <= 0)
{
printf("客户端断开连接\n");
break;
}
buf[ret] = '\0';
printf("收到:%s", buf);
}
close(client_fd);
exit(0);
}
// 父进程:关闭连接socket,继续监听
close(client_fd);
}
close(server_fd);
return 0;
}
3.1.2 TCP 客户端
cs
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#define SERVER_PORT 8888
int main(int argc, char *argv[])
{
int client_fd;
struct sockaddr_in server_addr;
char buf[1000];
int ret;
// 检查参数
if (argc != 2)
{
printf("用法:%s <服务器IP>\n", argv[0]);
return -1;
}
// 1. 创建客户端 socket
client_fd = socket(AF_INET, SOCK_STREAM, 0);
if (client_fd == -1)
{
perror("socket");
return -1;
}
// 2. 配置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
inet_aton(argv[1], &server_addr.sin_addr);
// 3. 连接服务器
ret = connect(client_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if (ret == -1)
{
perror("connect");
close(client_fd);
return -1;
}
printf("连接服务器成功!\n");
// 4. 循环发送数据
while (fgets(buf, sizeof(buf), stdin) != NULL)
{
send(client_fd, buf, strlen(buf), 0);
}
close(client_fd);
return 0;
}
3.2 TCP 编程核心流程
服务端流程
socket() → bind() → listen() → accept() → recv()/send() → close()
客户端流程
socket() → connect() → send()/recv() → close()
3.3 函数逐行分析
socket()创建一个套接字(网络通信的文件描述符)
cs
socket(AF_INET, SOCK_STREAM, 0);
AF_INET:IPv4 协议SOCK_STREAM:TCP 协议(面向连接、可靠)- 返回值:成功返回文件描述符,失败返回 -1
bind()把 socket 绑定到 IP + 端口
cs
bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr));
- 服务端必须绑定,否则客户端不知道连哪里
INADDR_ANY:监听本机所有网卡
listen()让 socket 变成被动监听模式,等待客户端连接
cs
listen(server_fd, BACKLOG);
BACKLOG:等待连接队列的长度
accept()阻塞等待客户端连接 ,成功后返回一个新的socket
cs
client_fd = accept(server_fd, ...);
server_fd:监听 socketclient_fd:专门和这个客户端通信的socket(关键)
connect():客户端主动连接服务器
cs
connect(client_fd, &server_addr, ...);
send() / recv():发送 / 接收数据
cs
recv(client_fd, buf, size, 0);
send(client_fd, buf, size, 0);
fork()创建子进程,实现多客户端并发连接
- 父进程:继续监听新客户端
- 子进程:专门和当前客户端通信
3.4 核心总结
- TCP 是面向连接的,必须先建立连接才能通信
- 服务端必须:
socket → bind → listen → accept - 客户端必须:
socket → connect accept会返回新的socket用于通信- 多进程可以让服务端同时处理多个客户端
- 端口号需要用
htons()转成网络字节序
4 UDP编程
4.1 完整代码
4.1.1 UDP服务端
cs
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#define SERVER_PORT 8888 // 端口号
int main(void)
{
int server_fd; // UDP 套接字
ssize_t recv_len; // 接收数据长度
unsigned char recv_buf[1024]; // 接收缓冲区
socklen_t addr_len; // 地址长度(必须用这个类型)
struct sockaddr_in server_addr; // 服务器地址结构体
struct sockaddr_in client_addr; // 客户端地址结构体
// 1. 创建 UDP 套接字
server_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (server_fd == -1)
{
perror("socket failed");
return -1;
}
// 2. 配置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = INADDR_ANY; // 监听本机所有网卡
memset(server_addr.sin_zero, 0, sizeof(server_addr.sin_zero));
// 3. 绑定 IP 和端口
if (bind(server_fd, (struct sockaddr *)&server_addr, sizeof(server_addr)) == -1)
{
perror("bind failed");
close(server_fd);
return -1;
}
printf("UDP Server Running on Port %d...\n", SERVER_PORT);
// 4. 循环接收客户端数据
while (1)
{
addr_len = sizeof(client_addr);
// 接收 UDP 数据(自动获取客户端地址)
recv_len = recvfrom(server_fd, recv_buf, sizeof(recv_buf) - 1, 0,
(struct sockaddr *)&client_addr, &addr_len);
if (recv_len > 0)
{
recv_buf[recv_len] = '\0'; // 添加字符串结束符
printf("[%s:%d] 发送消息: %s",
inet_ntoa(client_addr.sin_addr),
ntohs(client_addr.sin_port),
recv_buf);
}
}
// 关闭套接字
close(server_fd);
return 0;
}
4.1.2 UDP 客户端
cs
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdio.h>
#define SERVER_PORT 8888
int main(int argc, char **argv)
{
int client_fd;
ssize_t send_len;
char send_buf[1024];
socklen_t addr_len;
struct sockaddr_in server_addr;
// 检查参数:需要输入服务器 IP
if (argc != 2)
{
printf("用法: %s <服务器IP>\n", argv[0]);
printf("示例: %s 127.0.0.1\n", argv[0]);
return -1;
}
// 1. 创建 UDP 套接字
client_fd = socket(AF_INET, SOCK_DGRAM, 0);
if (client_fd == -1)
{
perror("socket failed");
return -1;
}
// 2. 配置服务器地址
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
inet_aton(argv[1], &server_addr.sin_addr); // 转换服务器 IP
memset(server_addr.sin_zero, 0, 8);
printf("UDP 客户端已启动,输入消息发送(Ctrl+C 退出)\n");
// 3. 循环读取输入并发送
while (fgets(send_buf, sizeof(send_buf), stdin) != NULL)
{
addr_len = sizeof(server_addr);
// 发送 UDP 数据(必须指定服务器地址)
send_len = sendto(client_fd, send_buf, strlen(send_buf), 0,
(struct sockaddr *)&server_addr, addr_len);
if (send_len == -1)
{
perror("sendto failed");
break;
}
}
// 关闭套接字
close(client_fd);
return 0;
}
4.2 UDP 编程核心流程
服务端流程
socket() → bind() → recvfrom() → 处理数据 → close()
客户端流程
socket() → sendto() → close()
4.3 函数逐行分析
socket(AF_INET, SOCK_DGRAM, 0)创建一个 UDP 网络套接字
AF_INET:使用 IPv4 协议SOCK_DGRAM:UDP 数据报类型- 返回值:成功返回文件描述符,失败返回 -1
bind()将套接字与 IP + 端口 绑定
- 只有服务端需要绑定,客户端不需要
recvfrom():UDP 专用接收函数
- 会自动获取发送方的 IP 和端口
- 是阻塞函数,没有数据时会一直等待
sendto():UDP 专用发送函数
- 必须指定目标 IP + 端口
- 不需要建立连接,直接发送
4.4 UDP 特点
- 无连接不需要 connect、listen、accept,发数据直接指定目标地址。
- 不可靠不保证数据一定到达,不保证有序。
- 数据报模式数据分开发送、分开接收。
- 速度快没有连接建立与断开,开销小。
4.5 UDP 与 TCP 核心区别
| 特性 | TCP | UDP |
|---|---|---|
| 连接 | 面向连接 | 无连接 |
| 可靠性 | 可靠传输 | 不可靠 |
| 速度 | 较慢 | 极快 |
| 适用场景 | 文件、网页、指令传输 | 视频、直播、游戏、DNS |
| 核心函数 | connect / send / recv | sendto / recvfrom |
| 服务端 | 需要 listen + accept | 不需要 |
4.6 核心总结
- UDP 是无连接、不可靠、高速的传输协议
- 服务端:
socket → bind → recvfrom - 客户端:
socket → sendto - 不需要连接、不需要多进程、结构简单