标签:#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上层三层合并:

  1. 应用层:合并OSI应用层+表示层+会话层
  2. 传输层:对应OSI传输层
  3. 网络层:对应OSI网络层
  4. 网络接口层:合并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.011111111.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.255255.0.0.0大型网络10.0.0.0‑10.255.255.255
B类128.0.0.0‑191.255.255.255255.255.0.0大中型网络172.16.0.0‑172.31.255.255
C类192.0.0.0‑223.255.255.255255.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. 知名端口 1‑1023:系统服务占用
    • HTTP:80;HTTPS:443;FTP:20/21;TFTP:69;MQTT:1883
  2. 注册端口 1024‑49151:第三方应用注册使用
  3. 动态/私有端口 49152‑65535:客户端临时随机使用

⚠️易错点:0端口保留,应用程序一般不使用。


三、虚拟机网络配置(桥接模式)

桥接模式下虚拟机和物理主机处于同一个局域网,可以被局域网其他设备访问。

  1. 虚拟机设置 →网络适配器选择桥接模式
  2. 虚拟网络编辑器 →更改设置 →VMnet0桥接模式,桥接到本机真实上网网卡
  3. 修改Ubuntu网卡配置 /etc/network/interfaces
auto lo
iface lo inet loopback

auto ens33
iface ens33 inet dhcp
  1. 重启网络服务
sudo /etc/init.d/networking restart
# 网络异常可执行系统重启
sudo reboot
  1. 查看网卡
# Linux
ifconfig
ip addr
# Windows
ipconfig
  1. 连通性测试
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_INET IPv4;AF_INET6 IPv6
  • type:套接字类型 SOCK_DGRAM UDP;SOCK_STREAM TCP
  • 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

五、易错点修正总结

  1. inet_addr函数已经标记废弃,新项目建议使用inet_pton
  2. UDP客户端不需要bind,操作系统自动分配随机端口;只有服务端需要bind固定端口;
  3. recvfrom的最后一个参数addrlen传入传出指针参数,必须传入地址;
  4. IP、端口放入sockaddr_in结构体必须转换网络字节序,直接填主机字节序会通信异常;
  5. INADDR_ANY代表监听本机全部网卡,不要硬编码写死本机IP地址;
  6. UDP是面向报文,不会自动拆包组包;一次recvfrom对应一次sendto报文;
  7. 广播地址发送报文,需要开启套接字广播选项,默认socket不允许发送广播包。

小结

  1. OSI七层是理论模型;TCP‑IP四层/五层是工业实现模型;发送数据逐层封装头部,接收逐层解包。
  2. IPv4依靠IP地址区分主机,子网掩码划分网络位主机位;公有IP访问互联网,私有IP用于局域网。
  3. IP定位主机,端口号定位进程;二者组合确定网络通信端点。
  4. UDP无连接不可靠;服务端socket+bind+recvfrom;客户端socket+sendto;收发数据使用sendto/recvfrom。
  5. 网络统一使用大端字节序,主机小端需要调用htons/htonl转换。

完整示例代码可直接编译运行,适合嵌入式Linux网络编程课程实验。

Logo

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

更多推荐