Linux 网络编程基础:OSI/TCP‑IP 模型、字节序、Socket 全套 API 详解
为了让不同厂商、不同硬件的多种设备能够通过网络相互通信,解决异构设备网络连接的兼容性问题,国际标准化组织制定了开放式系统互联通信参考模型(Open System Interconnection Reference Model),也就是 OSI 网络模型。
1. 网络分层模型
1.1 OSI 七层模型
OSI 模型一共分为七层,从上至下依次为:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层。
- 应用层:负责给应用程序提供统一的接口
- 表示层:负责把数据转换为兼容另一个系统的格式
- 会话层:负责建立、管理和终止表示层实体之间的通信会话
- 传输层:负责端到端的数据传输
- 网络层:负责数据的路由、转发、分片
- 数据链路层:负责数据的封装和差错检测以及 MAC 寻址
- 物理层:负责在物理网络中传输数据帧
OSI 模型更多停留在理论层面,只提出分层概念,并没有成熟完整的工业实现方案。现实操作系统广泛使用的是 TCP/IP 网络模型。
1.2 TCP/IP 四层模型
TCP/IP 网络模型分为四层:应用层、传输层、网络层、网络接口层。
- 应用层:负责向用户提供一组应用程序,典型协议:HTTP、DNS、FTP
- 传输层:负责端到端的通信,典型协议:TCP、UDP
- 网络层:负责网络包的封装、分片、路由、转发,典型协议:IP、ICMP
- 网络接口层:负责网络包在物理网络中的传输,包含封帧、MAC 寻址、差错检测等能力
▲图片说明:TCP/IP 四层模型示意图,展示层级关系与对应协议。
2. 字节序与地址转换
网络通信会遇到主机字节序和网络字节序的问题,头文件:<arpa/inet.h>
2.1 字节序转换
- 小端格式:低位字节存储在低地址,高位字节存储在高地址,PC 主机大多为小端。
- 大端格式:低位字节存储在高地址,高位字节存储在低地址,网络传输统一使用大端,也叫网络字节序。
同一台主机内部进程通信一般不用处理字节序;异构计算机之间通信,必须完成主机字节序与网络字节序的转换。
// 将32位主机字节序数据转换为网络字节序数据
uint32_t htonl(uint32_t hostint32);
// 将16位主机字节序数据转换为网络字节序数据
uint16_t htons(uint16_t hostint16);
// 将32位网络字节序数据转换为主机字节序数据
uint32_t ntohl(uint32_t netint32);
// 将16位网络字节序数据转换为主机字节序数据
uint16_t ntohs(uint16_t netint16);
2.2 IP 地址转换函数
inet_ntop
将 32 位无符号整数转换为点分十进制 IP 字符串。
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len);
- family:协议族;addrptr:二进制 IP 地址;strptr:输出字符串缓冲区;len:缓冲区长度
- 成功返回字符串首地址,失败返回 NULL。
示例代码:
struct in_addr addr;
addr.s_addr = htonl(0xC0000207); // 192.0.2.7
char str[INET_ADDRSTRLEN];
if (inet_ntop(AF_INET, &addr, str, sizeof(str)))
printf("IP string: %s\n", str); // 输出:192.0.2.7
else
perror("inet_ntop");
inet_pton
将点分十进制 IP 字符串转换为二进制整数,支持 IPv4 与 IPv6。
int inet_pton(int family, const char *strptr, void *addrptr);
- family:协议族;strptr:点分十进制 IP;addrptr:二进制地址输出缓冲区
- 成功返回 1。
示例代码:
struct in_addr addr;
if (inet_pton(AF_INET, "192.0.2.7", &addr) == 1)
printf("binary: 0x%08x\n", ntohl(addr.s_addr));
else
printf("invalid IPv4\n");
3. 网络编程接口 Socket
Socket(套接字)是网络通信的端点,可以理解为主机之间、进程之间交换数据的通道。Socket 是操作系统提供的网络 API,程序可以像读写普通文件一样收发网络数据。
以下为 POSIX 标准(Linux/macOS)函数原型,Windows 部分接口名称、返回值存在差异。
3.1 API 函数介绍
创建与销毁套接字
#include <sys/socket.h>
#include <unistd.h>
// 申请通信端点,返回socket文件描述符
int socket(int domain, int type, int protocol);
// 关闭套接字,回收资源;Windows使用 closesocket(SOCKET s)
int close(int sockfd);
地址绑定
#include <sys/socket.h>
// 将IP+端口绑定到套接字,服务端必备
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
TCP 面向连接专用接口
// 将套接字切换为被动监听状态,维护连接排队
int listen(int sockfd, int backlog);
// 从全连接队列取出已经完成三次握手的连接,返回新fd
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 客户端发起连接,执行三次握手
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
数据收发接口
// TCP发送,仅代表数据拷贝至内核发送缓冲区
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
// TCP接收,读取内核缓冲区数据到用户空间
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
// UDP发送数据报,必须指定对端地址
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
// UDP接收数据报,同时获取发送端地址
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
Windows 返回值为 int,出错返回 SOCKET_ERROR。
Socket 选项、地址信息、半关闭
// 设置套接字属性:地址重用、缓冲区、KeepAlive、TCP_NODELAY等
int setsockopt(int sockfd, int level, int optname,
const void *optval, socklen_t optlen);
// 获取套接字当前配置
int getsockopt(int sockfd, int level, int optname,
void *optval, socklen_t *optlen);
// 获取本地绑定的IP与端口
int getsockname(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 获取对端IP与端口
int getpeername(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
// 半关闭,SHUT_RD / SHUT_WR / SHUT_RDWR
int shutdown(int sockfd, int how);
IO 多路复用接口
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
#include <sys/epoll.h> /* Linux独有 */
int epoll_create1(int flags);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,
int maxevents, int timeout);
3.2 常用结构体
struct sockaddr {
sa_family_t sa_family;
char sa_data[14];
};
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
struct sockaddr_in6 {
sa_family_t sin6_family;
in_port_t sin6_port;
struct in6_addr sin6_addr;
uint32_t sin6_flowinfo;
uint32_t sin6_scope_id;
};
struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
socklen_t ai_addrlen;
struct sockaddr *ai_addr;
char *ai_canonname;
struct addrinfo *ai_next;
};
4. TCP、UDP 通信流程
▲图片说明:TCP Socket 调用流程图,展示服务端客户端 API 调用时序
TCP 面向连接(服务端)
- socket () 创建套接字
- bind () 绑定 IP 端口
- listen () 开启监听
- accept () 阻塞等待客户端连接
- read/recv、write/send 收发数据
- close () 关闭套接字
TCP 面向连接(客户端)
- socket () 创建套接字
- bind () 可选
- connect () 发起三次握手建立连接
- read/recv、write/send 收发数据
- close () 关闭套接字
▲图片说明:UDP Socket 调用流程图,展示服务端客户端 API 调用时序
UDP 无连接(服务端)
- socket () 创建套接字
- bind () 绑定 IP 端口
- recvfrom () 接收报文,获取源地址
- sendto () 向客户端发送报文
- close () 关闭套接字
UDP 无连接(客户端)
- socket () 创建套接字
- bind () 可选
- sendto () 发送报文
- recvfrom () 接收服务端报文
- close () 关闭套接字
5. TCP 与 UDP 要点对比
- 是否连接:TCP 需要三次握手建立连接;UDP 无连接,可以直接发送报文。
- 可靠性:TCP 可靠传输,支持丢包重传、数据按序到达;UDP 不可靠,存在丢包、乱序可能。
- 流量控制:TCP 具备滑动窗口、拥塞控制机制;UDP 没有流量控制。
- 传输形式:TCP 是字节流,没有报文边界;UDP 是数据报,报文具备边界。
- 系统开销:TCP 内核维护连接状态,开销较高;UDP 无状态,系统开销很低。
- API 差异:TCP 使用 connect、accept、send、recv;UDP 一般不使用 accept,主要依靠 sendto、recvfrom。
- 典型场景:TCP 适用于文件传输、HTTP、SSH、数据库访问;UDP 多用于 DNS 查询、音视频通话、游戏实时通信。
总结
- OSI 七层为理论参考模型,TCP/IP 四层模型是操作系统实际实现的网络协议栈。
- 网络传输固定使用大端字节序,异构设备通信需要 hton 系列、ntoh 系列函数完成字节序转换;inet_pton/inet_ntop 完成 IP 字符串与二进制地址转换。
- Socket 是操作系统暴露给应用层的网络接口,TCP 与 UDP 拥有完全不同的调用流程。
- TCP 面向连接可靠字节流;UDP 无连接不可靠数据报,二者适用业务场景差别很大。
想要进一步深入,可以查阅《UNIX 网络编程》以及计算机网络相关教材。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)