为了让不同厂商、不同硬件的多种设备能够通过网络相互通信,解决异构设备网络连接的兼容性问题,国际标准化组织制定了开放式系统互联通信参考模型(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 面向连接(服务端)

  1. socket () 创建套接字
  2. bind () 绑定 IP 端口
  3. listen () 开启监听
  4. accept () 阻塞等待客户端连接
  5. read/recv、write/send 收发数据
  6. close () 关闭套接字

TCP 面向连接(客户端)

  1. socket () 创建套接字
  2. bind () 可选
  3. connect () 发起三次握手建立连接
  4. read/recv、write/send 收发数据
  5. close () 关闭套接字

▲图片说明:UDP Socket 调用流程图,展示服务端客户端 API 调用时序

UDP 无连接(服务端)

  1. socket () 创建套接字
  2. bind () 绑定 IP 端口
  3. recvfrom () 接收报文,获取源地址
  4. sendto () 向客户端发送报文
  5. close () 关闭套接字

UDP 无连接(客户端)

  1. socket () 创建套接字
  2. bind () 可选
  3. sendto () 发送报文
  4. recvfrom () 接收服务端报文
  5. 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 查询、音视频通话、游戏实时通信。

总结

  1. OSI 七层为理论参考模型,TCP/IP 四层模型是操作系统实际实现的网络协议栈。
  2. 网络传输固定使用大端字节序,异构设备通信需要 hton 系列、ntoh 系列函数完成字节序转换;inet_pton/inet_ntop 完成 IP 字符串与二进制地址转换。
  3. Socket 是操作系统暴露给应用层的网络接口,TCP 与 UDP 拥有完全不同的调用流程。
  4. TCP 面向连接可靠字节流;UDP 无连接不可靠数据报,二者适用业务场景差别很大。

想要进一步深入,可以查阅《UNIX 网络编程》以及计算机网络相关教材。

Logo

openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构

更多推荐