Linux网络编程基础:OSI/TCP‑IP模型、IP协议、Socket‑UDP
标签:#Linux #网络编程 #OSI七层模型 #TCP‑IP #UDP #Socket
摘要:本文整理嵌入式Linux网络通信基础知识点,梳理OSI七层模型、TCP‑IP四层/五层模型、IP地址、子网掩码、端口、网络配置、UDP Socket全套API,修正原始笔记疏漏点,补充原理讲解、大小端字节序原理、可运行UDP服务端客户端代码,适合嵌入式开发、Linux网络编程入门复习。
前言
进程间通信分为两大类:本机IPC(信号、消息队列、共享内存,同一台主机);网络通信(跨主机进程通信)。
网络通信核心目标:在不同主机上找到目标进程完成数据交互。IP地址定位主机,端口号定位主机内部进程,二者唯一确定通信端点。
本文从网络理论模型出发,向下落地到Linux下UDP套接字编程实践。
一、网络分层模型
1.1 OSI七层参考模型(理论模型)
OSI(Open System Interconnect)开放系统互连模型,1977年ISO制定,是理论标准,现实网络没有严格完全按照OSI实现。
| 层级 | 名称 | 核心功能 | 典型设备/协议 |
|---|---|---|---|
| 7 | 应用层 | 为应用程序提供网络服务,业务数据 | FTP、HTTP |
| 6 | 表示层 | 数据格式转换、加密解密、压缩解压 | 数据编解码 |
| 5 | 会话层 | 建立、管理、终止会话通道 | 会话同步 |
| 4 | 传输层 | 端到端传输控制,流量、差错,端口 | TCP、UDP |
| 3 | 网络层 | 寻址、路由选择,跨网段转发 | 路由器、IP、ARP |
| 2 | 数据链路层 | 组帧、差错校验,局域网内点对点传输 | 交换机,MAC帧 |
| 1 | 物理层 | 传输比特流0/1,硬件电气介质 | 网线、光纤、网卡硬件 |
数据封装思想:发送方从上到下,每一层加上本层头部;接收方从下到上逐层剥掉头部。
1.2 TCP‑IP模型(工业现实模型)
TCP‑IP四层模型
把OSI上层三层合并:
- 应用层:合并OSI应用层+表示层+会话层
- 传输层:对应OSI传输层
- 网络层:对应OSI网络层
- 网络接口层:合并OSI数据链路层+物理层
常用五层模型(教学最常用)
- 应用层
- 传输层
- 网络层
- 数据链路层
- 物理层
五层模型常见协议梳理
- 应用层
- HTTP:超文本传输协议;HTTPS:HTTP+SSL/TLS加密
- FTP:文件传输,基于TCP;TFTP:简单文件传输,基于UDP
- DNS:域名解析;MQTT:物联网消息协议
- 传输层
- TCP:传输控制协议,面向连接、可靠
- UDP:用户数据报协议,无连接、不可靠
- 网络层
- IPv4、IPv6、ARP(地址解析:IP→MAC)、RARP逆地址解析
- 数据链路层:MAC帧,以太网协议
- 物理层:比特流传输
💡拓展:ARP协议工作在网络层,完成IP地址解析为物理MAC地址,局域网通信必须依赖ARP。
二、IP协议详解
2.1 IPv4与IPv6
- IPv4:32位二进制,使用点分十进制表示,例:
192.168.1.128 - IPv6:128位,冒分十六进制,解决IPv4地址枯竭。
点分十进制:把32bit分成4个8bit,每一段转十进制,.隔开。
192.168.1.3等价二进制:11000000 10101000 00000001 00000011
2.2 IP地址组成:网络位 + 主机位
IP地址 = 网络位 + 主机位
- 网络位:标识属于哪一个局域网网段
- 主机位:标识网段内部的主机编号
CIDR斜杠表示:
192.168.1.121/24,/24代表前24bit是网络位。
子网掩码
子网掩码配合IP使用:
- 二进制中
1的位置对应IP的网络位 - 二进制中
0的位置对应IP的主机位
示例:255.255.255.0→11111111.11111111.11111111.00000000
网段号
IP网络位不变,主机位全部置0,得到网段号。
例:IP:192.168.1.3,子网掩码255.255.255.0→网段号:192.168.1.0
广播号
IP网络位不变,主机位全部置1,得到广播地址。向广播地址发送数据,局域网内所有主机均可收到报文。
例:IP:192.168.1.3,子网掩码255.255.255.0→广播号:192.168.1.255
网关地址
网关一般是路由器内网口IP,本机访问外网数据包交给网关转发出去;家庭局域网常见网关192.168.1.1。
2.3 IPv4地址分类
| 类别 | 地址范围 | 默认子网掩码 | 用途 | 私有IP段 |
|---|---|---|---|---|
| A类 | 1.0.0.0‑126.255.255.255 | 255.0.0.0 | 大型网络 | 10.0.0.0‑10.255.255.255 |
| B类 | 128.0.0.0‑191.255.255.255 | 255.255.0.0 | 大中型网络 | 172.16.0.0‑172.31.255.255 |
| C类 | 192.0.0.0‑223.255.255.255 | 255.255.255.0 | 中小型局域网 | 192.168.0.0‑192.168.255.255 |
| D类 | 224.0.0.0‑239.255.255.255 | ‑ | 组播通信 | ‑ |
| E类 | 240.0.0.0‑255.255.255.254 | ‑ | 保留实验使用 | ‑ |
回环地址:
127.0.0.0/8,本机内部通信,不经过网卡硬件。
✨概念区分
- 公有IP:运营商分配,互联网路由可达;
- 私有IP:局域网内部使用,不能直接访问公网,依靠NAT地址转换访问互联网,节约公网IP资源。
2.4 端口号
IP定位主机,端口号定位主机上进程。IP+端口号唯一标识网络通信进程端点。
端口号:无符号16位整数,取值0 ~ 65535
端口划分:
- 知名端口 1‑1023:系统服务占用
- HTTP:80;HTTPS:443;FTP:20/21;TFTP:69;MQTT:1883
- 注册端口 1024‑49151:第三方应用注册使用
- 动态/私有端口 49152‑65535:客户端临时随机使用
⚠️易错点:0端口保留,应用程序一般不使用。
三、虚拟机网络配置(桥接模式)
桥接模式下虚拟机和物理主机处于同一个局域网,可以被局域网其他设备访问。
- 虚拟机设置 →网络适配器选择桥接模式
- 虚拟网络编辑器 →更改设置 →VMnet0桥接模式,桥接到本机真实上网网卡
- 修改Ubuntu网卡配置
/etc/network/interfaces
auto lo
iface lo inet loopback
auto ens33
iface ens33 inet dhcp
- 重启网络服务
sudo /etc/init.d/networking restart
# 网络异常可执行系统重启
sudo reboot
- 查看网卡
# Linux
ifconfig
ip addr
# Windows
ipconfig
- 连通性测试
ping www.baidu.com
补充:新版本Ubuntu推荐使用
ip系列命令,ifconfig需要安装net‑tools。
四、UDP协议基础
UDP(User Datagram Protocol)用户数据报协议。
特点:无连接、不可靠、面向报文;不需要建立连接,直接发送数据包;不保证对方一定收到;开销小,适合音视频、物联网MQTT场景。
4.1 软件架构模型
- B/S Browser/Server 浏览器服务器模式
- 使用通用浏览器作为客户端;业务逻辑全部放在服务端;只开发服务端程序。
- C/S Client/Server 客户端服务器模式
- 需要专用客户端程序;客户端服务端都要开发;数据可以两端保存。
UDP通信属于典型C/S模型:
- 服务端:创建socket,bind绑定固定端口,阻塞等待接收客户端报文
- 客户端:创建socket,直接sendto发送报文,客户端端口由操作系统自动分配,不需要bind
4.2 网络字节序(大小端)
⚠️网络传输统一使用大端字节序;X86电脑主机是小端字节序。
- 大端:低地址存放高字节
- 小端:低地址存放低字节
网络传输必须把主机字节序转为网络大端字节序;收到数据转回主机字节序。
字节序转换函数:
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort); //主机短整型 →网络字节序(端口)
uint32_t htonl(uint32_t hostlong); //主机长整型 →网络字节序(IP)
uint16_t ntohs(uint16_t netshort); //网络→主机 short
uint32_t ntohl(uint32_t netlong); //网络→主机 long
地址结构体 IPv4:
struct sockaddr_in {
sa_family_t sin_family; /* 协议族 AF_INET */
in_port_t sin_port; /* 端口,网络字节序 */
struct in_addr sin_addr; /* ip地址结构体 */
};
struct in_addr {
uint32_t s_addr; /* ip,网络字节序 */
};
4.3 UDP核心API详解
socket 创建套接字
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
- domain:协议族
AF_INETIPv4;AF_INET6IPv6 - type:套接字类型
SOCK_DGRAMUDP;SOCK_STREAMTCP - protocol:填0,自动匹配默认协议
返回值:成功返回套接字文件描述符;失败返回‑1。
Socket套接字本质:Linux下一切皆文件,socket返回fd,应用程序读写fd完成网络收发。
bind:绑定IP与端口号
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
功能:将socket fd绑定指定IP地址和端口;服务端必须bind固定端口;客户端一般不bind。
参数:
- sockfd:socket返回描述符
- addr:
struct sockaddr_in强制转换为通用struct sockaddr - addrlen:地址结构体大小
返回:成功0;失败‑1。
sendto:UDP发送报文
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
- sockfd:套接字fd
- buf:待发送数据缓冲区
- len:发送字节长度
- flags:0默认
- dest_addr:目标主机地址(IP+端口)
- addrlen:地址结构体大小
返回值:成功返回发送字节数;失败‑1。
recvfrom:UDP接收报文
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen);
src_addr输出参数:保存发送方的地址信息;addrlen是传入传出参数。
返回值:成功返回收到字节数;‑1失败;0代表收到长度为0的UDP报文。
IP地址转换函数
//点分十进制字符串IP →网络字节序32位整数IP(已废弃,推荐inet_pton)
in_addr_t inet_addr(const char *cp);
//网络字节序in_addr结构体 →点分十进制字符串IP
char *inet_ntoa(struct in_addr in);
💡拓展:
inet_addr存在缺陷,新代码优先使用inet_pton/inet_ntop,支持IPv4与IPv6。
4.4 UDP完整代码示例
udp_server.c UDP服务端
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define PORT 8888
#define BUF_SIZE 1024
int main(void)
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0){
perror("socket");
return -1;
}
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(PORT);
//监听本机所有网卡
ser_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(sockfd, (struct sockaddr *)&ser_addr, sizeof(ser_addr)) < 0){
perror("bind");
close(sockfd);
return -1;
}
char buf[BUF_SIZE] = {0};
struct sockaddr_in cli_addr;
socklen_t cli_len = sizeof(cli_addr);
while(1){
memset(buf, 0, sizeof(buf));
ssize_t n = recvfrom(sockfd, buf, BUF_SIZE, 0,
(struct sockaddr *)&cli_addr, &cli_len);
if(n < 0){
perror("recvfrom");
continue;
}
printf("收到客户端[%s:%d]数据:%s\n",
inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port), buf);
//回射,把收到数据发回客户端
sendto(sockfd, buf, n, 0, (struct sockaddr *)&cli_addr, cli_len);
}
close(sockfd);
return 0;
}
udp_client.c UDP客户端
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#define SER_IP "192.168.1.100"
#define SER_PORT 8888
#define BUF_SIZE 1024
int main(void)
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sockfd < 0){
perror("socket");
return -1;
}
struct sockaddr_in ser_addr;
ser_addr.sin_family = AF_INET;
ser_addr.sin_port = htons(SER_PORT);
ser_addr.sin_addr.s_addr = inet_addr(SER_IP);
char buf[BUF_SIZE];
while(1){
printf("请输入发送数据:");
fgets(buf, sizeof(buf), stdin);
//去掉fgets读取换行符
buf[strcspn(buf,"\n")] = '\0';
sendto(sockfd, buf, strlen(buf), 0,
(struct sockaddr *)&ser_addr, sizeof(ser_addr));
//接收服务端回射
struct sockaddr_in tmp_addr;
socklen_t tmp_len = sizeof(tmp_addr);
memset(buf,0,sizeof(buf));
ssize_t n = recvfrom(sockfd, buf, BUF_SIZE, 0,
(struct sockaddr *)&tmp_addr, &tmp_len);
printf("服务端回送:%s\n", buf);
}
close(sockfd);
return 0;
}
编译运行:
gcc udp_server.c -o udp_server
gcc udp_client.c -o udp_client
#终端1运行服务端
./udp_server
#终端2运行客户端
./udp_client
五、易错点修正总结
inet_addr函数已经标记废弃,新项目建议使用inet_pton;- UDP客户端不需要bind,操作系统自动分配随机端口;只有服务端需要bind固定端口;
recvfrom的最后一个参数addrlen是传入传出指针参数,必须传入地址;- IP、端口放入
sockaddr_in结构体必须转换网络字节序,直接填主机字节序会通信异常; - INADDR_ANY代表监听本机全部网卡,不要硬编码写死本机IP地址;
- UDP是面向报文,不会自动拆包组包;一次recvfrom对应一次sendto报文;
- 广播地址发送报文,需要开启套接字广播选项,默认socket不允许发送广播包。
小结
- OSI七层是理论模型;TCP‑IP四层/五层是工业实现模型;发送数据逐层封装头部,接收逐层解包。
- IPv4依靠IP地址区分主机,子网掩码划分网络位主机位;公有IP访问互联网,私有IP用于局域网。
- IP定位主机,端口号定位进程;二者组合确定网络通信端点。
- UDP无连接不可靠;服务端socket+bind+recvfrom;客户端socket+sendto;收发数据使用sendto/recvfrom。
- 网络统一使用大端字节序,主机小端需要调用
htons/htonl转换。
完整示例代码可直接编译运行,适合嵌入式Linux网络编程课程实验。
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐

所有评论(0)