Linux学习-通信(网络通信)


一、Linux 应用软件编程 - 网络编程 整体框架

涵盖 文件操作、多任务并发 基础,核心聚焦 "不同主机进程间通信" ,解决 物理网络连通 + 软件进程互通 问题。

二、网络通信核心概念

1. 网络标识体系
标识类型 作用 特点
IP 地址 软件地址,标识主机 分公网(直连互联网)、私网(局域网内)
MAC 地址 硬件地址,固定标识设备 全球唯一
端口号 标识主机内不同网络进程 16 位整数(0~65535)
2. 网络协议与分层模型
  • OSI 七层模型 (理论参考):从应用层到物理层定义通信标准,核心关注 传输层(TCP/UDP)、网络层(IP 路由)
TCP/IP 五层模型详细说明表
层级(从下到上) 核心功能 常见协议 典型设备 关键技术/标识 应用场景示例
1. 物理层 定义物理设备标准,处理物理介质上的原始比特流传输,规定电气、机械、接口特性 无独立协议(依赖硬件标准) 集线器、中继器、网线、光纤、无线AP 比特流、物理接口(RJ-45、LC) 以太网物理连接、Wi-Fi信号传输
2. 数据链路层 封装原始比特流为"帧",实现相邻节点间可靠传输,处理帧同步、差错控制、MAC寻址 以太网协议(Ethernet)、PPP(点到点协议)、HDLC(高级数据链路控制协议) 交换机、网卡 帧、MAC地址(设备硬件地址) 局域网内设备间数据转发
3. 网络层 实现跨网络的"数据包"路由与转发,通过IP地址定位主机,选择最优传输路径 IP(IPv4/IPv6)、ICMP(控制报文协议,如ping)、ARP(IP转MAC)、RARP(MAC转IP) 路由器、三层交换机 数据包、IP地址(网络逻辑地址) 不同局域网间数据跨网段传输
4. 传输层 提供端到端通信服务,控制数据传输的可靠性/效率,处理顺序、流量、差错控制 TCP(面向连接、可靠传输)、UDP(无连接、高效传输) 无(依赖主机操作系统) 字节流(TCP)、数据报(UDP)、端口号(进程标识) 文件传输(TCP)、视频会议(UDP)
5. 应用层 直接为用户应用程序提供服务,定义应用间通信的协议与数据格式 HTTP(网页浏览)、HTTPS(安全网页)、FTP(文件传输)、SMTP(邮件发送)、DNS(域名解析) 计算机、服务器(运行应用程序) 应用数据(如HTML、文件、邮件内容) 网页访问、文件上传下载、邮件发送
3. IP 地址深度解析
  • 结构IP = 网络位 + 主机位(通过子网掩码区分),例:192.168.0.121/24 中,24 表示网络位占 24 位。

  • IP地址分类

    类别 范围 子网掩码 应用场景
    A 1.0.0.0~126.255.255.255 255.0.0.0 大规模网络
    B 128.0.0.0~191.255.255.255 255.255.0.0 中大规模网络
    C 192.0.0.0~223.255.255.255 255.255.255.0 中小规模网络
    D 224.0.0.0~239.255.255.255 - 组播/广播
    E 240.0.0.0~255.255.255.254 - 实验用途
  • 特殊地址

    • 私网 IP(如 10.0.0.0/8192.168.0.0/16):局域网内使用,无法直连互联网。
    • 回环地址(127.0.0.0/8):用于本机进程间通信。
4. 端口号

16位整形数据(unsigned short)0 ~65535

功能:标记同一主机的不同网络进程

范围 类型 说明 典型协议/应用
1~1023 知名端口 系统/通用服务保留 HTTP(80)、FTP(20/21)、HTTPS(443)、TFPT(69)
1024~49151 注册端口 特殊服务使用(需 IANA 分配) MQTT(1883/8883)
49152~65535 动态/私有端口 应用临时分配 客户端随机端口

三、网络调试与配置工具

工具 作用 示例命令
ping 检测网络连通性 ping www.baidu.com
ifconfig Linux 查看/配置 IP ifconfig eth0
ipconfig Windows 查看 IP ipconfig /all
虚拟机网络配置 桥接模式(直连局域网)、NAT(共享主机网络) VMware 中设置"桥接模式"

Linux下:

修改网络配置文件:sudo vim/etc/network/interfaces

重启网络服务:sudo /etc/init.d/networking restart

测试: ping www.baidu.com

四、网络编程实践(UDP 为例)

1. C/S 模型对比
模型 客户端特点 开发场景 资源加载方式
B/S 通用(浏览器) 主要开发服务端 全依赖服务端加载
C/S 专用客户端 服务端 + 客户端都需开发 本地可缓存资源 ,无需所有数据都请求服务器
2. UDP 编程流程(C/S 架构)
  • 服务端socket()bind()recvfrom()sendto()close()
  • 客户端socket()sendto()recvfrom()close()

五、关键函数解析

函数 功能 核心参数说明
socket() 创建套接字 domain(AF_INET=IPv4)、type(SOCK_DGRAM=UDP)
bind() 绑定 IP+端口 需指定 struct sockaddr_in(含 IP、端口)
sendto() 发送数据 需传入对端地址(dest_addr
recvfrom() 接收数据 可获取发送端地址(src_addr
socket:创建套接字
c 复制代码
int socket(int domain, int type, int protocol);
  • 功能:创建一个网络套接字(通信端点),返回用于后续网络操作的文件描述符。
  • 参数
    • domain:地址族(协议族),指定网络通信的地址类型。常用值:AF_INET(IPv4)、AF_INET6(IPv6)。
    • type:套接字类型,指定通信方式。常用值:SOCK_STREAM(TCP,面向连接、可靠传输)、SOCK_DGRAM(UDP,无连接、不可靠传输)。
    • protocol:具体协议,通常填 0 表示使用 type 对应的默认协议(如 SOCK_STREAM 对应 TCP,SOCK_DGRAM 对应 UDP)。
  • 返回 :成功返回非负套接字描述符(sockfd),失败返回 -1(需检查 errno)。
bind:绑定 IP + 端口
c 复制代码
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
  • 功能 :将套接字 sockfd 与指定的 IP + 端口 绑定,让系统知道该套接字监听哪个地址。
  • 参数
    • sockfd:套接字(socket() 返回的文件描述符)。
    • addr:要绑定的地址(需强转为 struct sockaddr *,通常用 struct sockaddr_in 填充)。
    • addrlen:地址结构体的大小(sizeof(struct sockaddr_in))。
  • 返回 :成功返回 0,失败返回 -1(需检查 errno)。
sendto:发送 UDP 数据到指定地址

(补充说明,与之前知识联动)

c 复制代码
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
               const struct sockaddr *dest_addr, socklen_t addrlen);
  • 功能 :向指定地址(dest_addr)发送 UDP 数据。
  • 参数
    • dest_addr:接收方的地址(struct sockaddr_in 填充 IP + 端口)。
recvfrom:接收 UDP 数据并获取发送方地址
c 复制代码
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                 struct sockaddr *src_addr, socklen_t *addrlen);
  • 功能 :从套接字 sockfd 接收数据,同时获取 发送方的地址信息src_addr)。
  • 参数
    • sockfd:套接字。
    • buf:存储接收数据的缓冲区。
    • len:希望接收的最大字节数。
    • flags:通常填 0(阻塞接收)。
    • src_addr:用于存储 发送方地址 (需用 struct sockaddr_in 解析)。
    • addrlen:地址结构体的大小(传入 sizeof(src_addr) 的指针)。
  • 返回 :成功返回实际接收的字节数,失败返回 -1
网络地址结构体(struct sockaddr_in

用于描述 IPv4 网络地址,是 UDP/TCP 编程的基础数据结构。

c 复制代码
struct sockaddr_in {
    sa_family_t  sin_family;    // 地址族(固定为 AF_INET 表示 IPv4)
    in_port_t    sin_port;      // 端口号(网络字节序)
    struct in_addr sin_addr;    // IP 地址(网络字节序)
};

// IP 地址的二进制形式(网络字节序)
struct in_addr {
    uint32_t s_addr; 
};
参数:
  1. sin_family
    • 必须填 AF_INET(表示 IPv4 地址族),否则通信失败。
  2. sin_port
    • 存储 端口号 ,但必须用 网络字节序 (通过 htons 转换)。
  3. sin_addr
    • 存储 IP 地址 (二进制形式,网络字节序),常用 inet_addrinet_pton 转换字符串 IP。
字节序转换(网络大端 vs 主机小端 )

网络通信要求 端口号、IP 地址 必须用 网络字节序(大端) ,而主机 CPU 可能是 小端,因此需要转换。

1. 核心函数
函数 功能 适用场景
htons(host_short) 主机短整型 → 网络字节序 端口号转换(16 位)
ntohs(net_short) 网络字节序 → 主机短整型 端口号转换(16 位)
htonl(host_long) 主机长整型 → 网络字节序 IP 地址转换(32 位)
ntohl(net_long) 网络字节序 → 主机长整型 IP 地址转换(32 位)
2. 示例(端口号转换)
c 复制代码
uint16_t host_port = 50000;   // 主机字节序(小端)
uint16_t net_port = htons(host_port); // 转换为网络字节序(大端)
3. 字节序直观对比
  • 主机小端(x86 架构):低字节存低地址,例:0x1234 存储为 0x34 0x12
  • 网络大端:高字节存低地址,例:0x1234 存储为 0x12 0x34
IP 地址字符串 ↔ 二进制转换
  • inet_addr(const char *cp)

    • 功能:将字符串 IP(如 "192.168.0.171")转换为 网络字节序的二进制 IPuint32_t)。

    • 示例:

      c 复制代码
      struct in_addr ip;
      ip.s_addr = inet_addr("192.168.0.171"); // 转换为网络字节序
  • inet_ntoa(struct in_addr in)

    • 功能:将二进制 IP(网络字节序)转换为 字符串 IP (如 "192.168.0.171")。

    • 示例:

      c 复制代码
      struct in_addr ip = {.s_addr = inet_addr("192.168.0.171")};
      char *ip_str = inet_ntoa(ip); // 转换为字符串 "192.168.0.171"

六、练习(UDP 全双工通信)

  • 服务端(server.c

    c 复制代码
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    
    int main() {
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建 UDP 套接字
        struct sockaddr_in addr = {.sin_family = AF_INET, 
                                   .sin_port = htons(50000), 
                                   .sin_addr.s_addr = INADDR_ANY};
        bind(sockfd, (struct sockaddr*)&addr, sizeof(addr)); // 绑定端口
    
        char buf[1024];
        struct sockaddr_in client_addr;
        socklen_t len = sizeof(client_addr);
    
        while (1) {
            // 接收客户端数据 + 获取客户端地址
            ssize_t cnt = recvfrom(sockfd, buf, sizeof(buf), 0, 
                                 (struct sockaddr*)&client_addr, &len);
            buf[cnt] = '\0';
            printf("收到来自 %s:%d 的数据:%s\n", 
                   inet_ntoa(client_addr.sin_addr), 
                   ntohs(client_addr.sin_port), buf);
            
            // 回复客户端
            sendto(sockfd, "已收到", 6, 0, 
                  (struct sockaddr*)&client_addr, len);
        }
        close(sockfd);
        return 0;
    }
  • 客户端(client.c

    c 复制代码
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <string.h>
    
    int main() {
        int sockfd = socket(AF_INET, SOCK_DGRAM, 0); // 创建 UDP 套接字
        struct sockaddr_in server_addr = {.sin_family = AF_INET, 
                                          .sin_port = htons(50000), 
                                          .sin_addr.s_addr = inet_addr("192.168.0.171")};
    
        char buf[1024];
        while (1) {
            fgets(buf, sizeof(buf), stdin); // 从终端读入数据
            sendto(sockfd, buf, strlen(buf), 0, 
                  (struct sockaddr*)&server_addr, sizeof(server_addr)); // 发数据
    
            // 接收服务端回复
            ssize_t cnt = recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);
            buf[cnt] = '\0';
            printf("服务端回复:%s\n", buf);
        }
        close(sockfd);
        return 0;
    }