当前位置: 首页 > news >正文

Linux----网络通信

一、IP地址详解

(一)核心概念

概念说明
IP地址网络设备的唯一逻辑标识符,相当于网络世界的"门牌号"
主机任何接入网络的终端设备(计算机/手机/服务器等)
核心作用① 设备标识 ② 路由寻址 ③ 数据传输

(二)技术特性

1. 基础结构

         32位IPv4地址结构
+----------------+----------------+
|   网络号        |    主机号       |
+----------------+----------------+
2. 关键组成
组成部分作用
网络号标识所属网络(类似城市+街道)
主机号标识具体设备(类似门牌号)

3. 存储方式

  • 数值形式:32位无符号整数(范围:0 ~ 2³²-1)
  • 存储格式:大端序(Big-Endian)
    // 示例:192.168.1.1的存储方式
    0xC0 (192) 0xA8 (168) 0x01 (1) 0x01 (1)
    

(三)地址分类(经典分类)

1. 类型划分

类别首位格式网络号范围主机号范围典型示例
A类0XXXXXXX前8位后24位1.0.0.1 ~ 126.255.255.254
B类10XXXXXX前16位后16位128.0.0.1 ~ 191.255.255.254
C类110XXXXX前24位后8位192.0.0.1 ~ 223.255.255.254
D类1110XXXX组播地址224.0.0.0 ~ 239.255.255.255
E类1111XXXX保留地址240.0.0.0 ~ 255.255.255.254

2. 特殊地址

地址类型范围用途
私有地址10.0.0.0/8, 172.16.0.0/12, 192.168.0.0/16局域网内部使用
环回地址127.0.0.0/8本机测试
自动配置地址169.254.0.0/16DHCP失败时临时使用

(四)现代划分方式(CIDR)

1. 表示方法

格式:IP地址/前缀长度
示例:192.168.1.0/24
  • 前缀长度:网络号的位数(范围:0-32)
  • 子网掩码:网络号全1,主机号全0
    /24 → 255.255.255.0
    /16 → 255.255.0.0
    

2. 地址计算示例

给定地址:192.168.1.100/24

网络地址:192.168.1.0    (IP & 子网掩码)
广播地址:192.168.1.255  (网络地址 | 主机位全1)
可用主机范围:192.168.1.1 ~ 192.168.1.254
最大主机数:2^8 - 2 = 254

(五)协议版本对比

特性IPv4IPv6
地址长度32位128位
地址表示点分十进制(192.168.1.1)冒号分隔十六进制(2001:db8::1)
地址空间约42.9亿3.4×10³⁸
包头复杂度简单扩展头部机制

二、OSI七层模型(理论框架)

OSI层级名称核心功能典型协议/技术数据单元
7应用层用户接口与应用服务HTTP、FTP、SMTP、DNS消息(Message)
6表示层数据格式转换、加密/解密、压缩SSL/TLS、JPEG、ASCII、GZIP
5会话层建立/管理/终止会话连接RPC、PPTP、NetBIOS
4传输层端到端连接、流量控制TCP、UDP、SCTP段(Segment)
3网络层寻址与路由选择IP、ICMP、NAT、OSPF包(Packet)
2数据链路层帧封装、MAC地址访问、差错校验Ethernet、PPP、VLAN、ARP帧(Frame)
1物理层比特流传输、物理介质规范RS-232、1000BASE-T、Wi-Fi比特(Bit)

关键技术示例:

应用层:浏览器访问网页时使用的HTTP协议
表示层:HTTPS通信中的TLS加密过程
会话层:VPN连接使用的PPTP协议
传输层:TCP的三次握手建立连接
网络层:路由器使用OSPF协议计算最优路径
数据链路层:交换机的MAC地址表学习
物理层:光纤传输的激光信号调制

(二)TCP/IP四层模型(实际标准)

TCP/IP层级对应OSI层核心功能典型协议设备示例
应用层5-7整合应用服务与网络接口HTTP、DNS、SMTP、MQTT网关设备
传输层4端到端数据传输管理TCP、UDP、QUIC负载均衡器
网络层3数据包路由与地址管理IP、ICMP、BGP、IPSec路由器
网络接口层1-2物理连接与本地网络传输Ethernet、Wi-Fi、PPP交换机/网卡
协议栈工作流程:
sequenceDiagram
    应用层->>传输层: 封装应用数据(如HTTP请求)
    传输层->>网络层: 添加TCP头+端口号
    网络层->>网络接口层: 添加IP头+IP地址
    网络接口层->>物理介质: 封装帧头+MAC地址

(三)关键对比分析

对比维度OSI模型TCP/IP模型
设计定位理论参考模型实际应用标准
层间关系严格分层松散分层
协议支持未绑定具体协议绑定TCP/IP协议族
普及程度主要用于教学互联网实际标准
地址管理网络层独立寻址IP地址统一寻址
校验机制每层独立校验端到端校验为主

(四)核心协议详解

1. 帧结构示例(Ethernet II)

+---------------+---------------+-----------------+---------------+
| 目标MAC(6B)   | 源MAC(6B)     | 类型(2B)        | 数据(46-1500B)| FCS(4B)       |
+---------------+---------------+-----------------+---------------+

2. IP数据包分片

原始包:总长度5000B(超过MTU 1500B)
分片1:1480B数据 + 20B头(偏移0,MF=1)
分片2:1480B数据 + 20B头(偏移185,MF=1)
分片3:40B数据 + 20B头(偏移370,MF=0)

3. TCP连接管理

三次握手:
Client → SYN → Server
Client ← SYN+ACK ← Server
Client → ACK → Server

四次挥手:
Client → FIN → Server
Client ← ACK ← Server
Client ← FIN ← Server
Client → ACK → Server

三、客户端/服务器(C/S)模型与传输协议详解

(一)C/S架构核心特性

特性说明
角色划分客户端(请求方)与服务端(响应方)
通信模式1:N 服务模式(单个服务端服务多个客户端)
网络依赖基于网络协议栈实现远程通信
协议支持可基于TCP/UDP等不同传输层协议实现

(二)TCP协议深度解析

1. 核心特性

graph TD
    A[面向连接] --> B[三次握手建立连接]
    A --> C[四次挥手断开连接]
    D[可靠传输] --> E[序列号机制]
    D --> F[确认应答机制]
    D --> G[超时重传机制]
    H[字节流模式] --> I[无边界数据流]
    H --> J[数据顺序保证]

2. 协议头结构(20字节基础头)

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+---------------+-----------------+
|         源端口               |       目标端口                |
+-------------------------------+---------------+-----------------+
|                       序列号(SEQ)                           |
+---------------------------------------------------------------+
|                     确认号(ACK)                            |
+-----+---------+-----+-----------------------------------------+
|首部长度| 保留 |控制位|                 窗口大小                |
+-----+---------+-----+-----------------------------------------+
|        校验和        |          紧急指针(URG)                |
+---------------------+-----------------------------------------+

3. 典型应用场景

1. Web服务(HTTP/HTTPS)
2. 文件传输(FTP/SFTP)
3. 电子邮件(SMTP/POP3)
4. 数据库连接(MySQL/Oracle)
5. 远程登录(SSH/Telnet)

(三)UDP协议深度解析

1. 核心特性

graph TD
    A[无连接] --> B[无需建立连接]
    C[不可靠传输] --> D[无确认机制]
    C --> E[可能丢包]
    F[数据报模式] --> G[独立数据包]
    F --> H[保留边界]

2. 协议头结构(8字节固定头)

 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-------------------------------+---------------+-----------------+
|         源端口               |       目标端口                |
+-------------------------------+---------------+-----------------+
|           数据长度           |           校验和              |
+-------------------------------+---------------+-----------------+

3. 典型应用场景

1. 实时视频/音频传输(Zoom/WebRTC)
2. DNS域名解析
3. 网络监控(SNMP)
4. 物联网通信(MQTT-SN)
5. 在线游戏(部分实时对战)


(四)协议对比分析

对比维度TCPUDP
连接方式面向连接(三次握手)无连接
可靠性可靠传输(确认+重传)尽最大努力交付
数据边界字节流(无边界)数据报(保留边界)
传输效率较低(有连接开销)较高(无控制开销)
头部开销20-60字节固定8字节
拥塞控制完善机制(滑动窗口等)无控制
适用场景需可靠传输的应用实时性要求高的应用

四、socket() 函数详解

(一)函数原型

#include <sys/socket.h>

int socket(int domain, int type, int protocol);

(二)核心功能

  • 创建通信端点:在内核中创建套接字数据结构
  • 分配资源:建立协议控制块(PCB)
  • 返回描述符:类似文件描述符的操作接口

(三)参数详解

1. domain(地址族)
常用值说明典型应用
AF_INETIPv4网络协议互联网通信(TCP/IP)
AF_INET6IPv6网络协议下一代互联网通信
AF_UNIX本地通信(文件系统路径)同主机进程间高效通信
AF_PACKET底层包接口网络嗅探器、协议分析工具

注意AF_*(地址族)与PF_*(协议族)在Linux中已统一,但推荐使用AF_前缀

2. type(套接字类型)
类型标志特性对应协议
SOCK_STREAM面向连接、可靠传输、字节流TCP
SOCK_DGRAM无连接、不可靠、数据报UDP
SOCK_RAW原始网络层访问IP/ICMP
SOCK_SEQPACKET有序分组服务SCTP
3. protocol(协议选择)
取值说明
0自动选择默认协议(推荐)
IPPROTO_TCP显式指定TCP协议(=6)
IPPROTO_UDP显式指定UDP协议(=17)
IPPROTO_RAW原始IP协议(需要权限)

标准协议组合

// TCP套接字
socket(AF_INET, SOCK_STREAM, 0);

// UDP套接字
socket(AF_INET, SOCK_DGRAM, 0);

// 原始ICMP套接字
socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);

(四)返回值处理

返回值说明
正整数新创建的套接字描述符(>=3,0-2为stdin/stdout/stderr)
-1创建失败,检查errno:<br>• EACCES(权限不足)<br>• EMFILE(描述符耗尽)<br>• EPROTONOSUPPORT(协议不支持)

(五)使用示例

1. 创建TCP套接字
int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
if (tcp_sock == -1) {
    perror("TCP socket creation failed");
    exit(EXIT_FAILURE);
}
2. 创建UDP套接字
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_sock == -1) {
    perror("UDP socket creation failed");
    exit(EXIT_FAILURE);
}
3. 创建原始套接字(需root权限)
int raw_sock = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
if (raw_sock == -1) {
    perror("Raw socket creation failed");
    exit(EXIT_FAILURE);
}

五、connect() 函数详解

(一)函数原型

#include <sys/types.h>
#include <sys/socket.h>

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

(二)核心功能

  • 建立连接:在客户端与目标服务器之间建立通信链路(TCP三次握手)
  • 地址绑定:对于UDP可预先指定默认接收地址
  • 错误检测:验证网络可达性与服务可用性

(三)参数解析

参数类型说明
sockfdintsocket()创建的套接字描述符
addrstruct sockaddr*目标服务器地址结构(需转换为实际协议地址结构)
addrlensocklen_t地址结构体的实际长度(字节数)

(四)地址结构示例(IPv4)

struct sockaddr_in {
    sa_family_t    sin_family;  // 地址族(AF_INET)
    in_port_t      sin_port;    // 端口号(网络字节序)
    struct in_addr sin_addr;   // IP地址(网络字节序)
    unsigned char  sin_zero[8]; // 填充字段(全0)
};

struct in_addr {
    in_addr_t s_addr;          // 32位IPv4地址
};

(五)使用流程

sequenceDiagram
    Client->>Socket: 1. socket()
    Client->>Socket: 2. connect()
    Note right of Socket: 触发三次握手
    Socket-->>Client: 连接成功(返回0)
    or Socket-->>Client: 连接失败(返回-1)

(六)返回值处理

返回值说明
0连接建立成功
-1连接失败,检查errno:<br>• ECONNREFUSED(目标拒绝)<br>• ETIMEDOUT(超时)<br>• ENETUNREACH(网络不可达)

(七)典型应用场景

1. TCP客户端连接
int main() {
    // 创建套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd == -1) {
        perror("Socket creation failed");
        exit(EXIT_FAILURE);
    }

    // 配置服务器地址
    struct sockaddr_in serv_addr = {
        .sin_family = AF_INET,
        .sin_port = htons(8080),
        .sin_addr.s_addr = inet_addr("192.168.1.100")
    };

    // 建立连接
    if (connect(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
        perror("Connection failed");
        close(sockfd);
        exit(EXIT_FAILURE);
    }

    // 数据传输...
    close(sockfd);
    return 0;
}
2. UDP预连接(非必须)
// UDP套接字创建
int udp_sock = socket(AF_INET, SOCK_DGRAM, 0);

// 指定默认接收地址
struct sockaddr_in serv_addr = {...};
connect(udp_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr));

// 后续可直接使用send()代替sendto()
send(udp_sock, buffer, strlen(buffer), 0);

六、网络字节序转换与IP地址处理

(一)字节序转换函数

1. 核心函数列表
函数原型功能描述常用场景
uint32_t htonl(uint32_t hostlong)主机到网络序(32位)IP地址转换
uint16_t htons(uint16_t hostshort)主机到网络序(16位)端口号转换
uint32_t ntohl(uint32_t netlong)网络到主机序(32位)接收IP地址解析
uint16_t ntohs(uint16_t netshort)网络到主机序(16位)接收端口号解析
2. 使用示例
// 转换端口号
uint16_t port = 8080;
uint16_t net_port = htons(port);

// 转换IPv4地址
uint32_t ip = 0xC0A80001;  // 192.168.0.1
uint32_t net_ip = htonl(ip);

(二)IP地址转换函数

1. 传统方法(IPv4)
#include <arpa/inet.h>

// 字符串转网络字节序(已过时但常见)
in_addr_t inet_addr(const char *cp);

特性

  • 支持点分十进制格式(如"192.168.0.1")
  • 失败返回INADDR_NONE(通常是0xFFFFFFFF)
  • 不支持IPv6地址

示例

struct sockaddr_in addr;
addr.sin_addr.s_addr = inet_addr("192.168.1.100");
if (addr.sin_addr.s_addr == INADDR_NONE) {
    perror("Invalid IP address");
}
2. 现代方法(推荐)
// 通用地址转换(支持IPv4/IPv6)
int inet_pton(int af, const char *src, void *dst);
参数说明
af地址族(AF_INET/AF_INET6)
src点分地址字符串
dst存储转换结果的缓冲区

返回值

  • 1:成功
  • 0:输入格式错误
  • -1:系统错误

示例

struct in_addr ipv4_addr;
if (inet_pton(AF_INET, "10.0.0.1", &ipv4_addr) <= 0) {
    perror("Address conversion failed");
}

(三)地址转换对照表

字符串地址十六进制值(网络序)十进制值(主机序)
127.0.0.10x7F0000012130706433
192.168.1.1000xC0A801643232235876
255.255.255.2550xFFFFFFFF4294967295

(四)错误处理指南

错误现象可能原因解决方案
inet_addr返回INADDR_NONE地址格式错误使用正则表达式验证输入
inet_pton返回0地址与协议族不匹配检查AF_INET/AF_INET6
转换后数值异常字节序转换错误确认使用ntohl/ntohs

七、bind() 函数详解

(一)函数原型

#include <sys/socket.h>

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

(二)核心功能

  • 地址绑定:将套接字与指定的网络地址/端口绑定
  • 资源分配:声明对特定网络接口的控制权
  • 服务标识:为客户端提供可连接的目标端点

(三)参数解析

参数类型说明
sockfdintsocket()创建的套接字描述符
addrstruct sockaddr*包含绑定地址信息的结构体指针
addrlensocklen_t地址结构体的实际长度(字节数)

(四)使用场景对比

场景服务器端客户端
必要性必须调用(需固定服务端口)通常省略(系统自动分配临时端口)
典型地址明确指定IP和端口(如0.0.0.0:80)可指定为INADDR_ANY+0(自动选择)
错误处理严格检查EADDRINUSE(端口占用)关注EACCES(特权端口权限)

(五)服务器端典型用法

// 创建TCP套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// 配置绑定地址
struct sockaddr_in serv_addr = {
    .sin_family = AF_INET,
    .sin_port = htons(8080),             // 明确指定端口
    .sin_addr.s_addr = htonl(INADDR_ANY) // 监听所有接口
};

// 绑定地址
if (bind(sockfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1) {
    perror("Bind failed");
    close(sockfd);
    exit(EXIT_FAILURE);
}

(六)客户端特殊用法

// 创建UDP套接字
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);

// 指定源端口(特殊需求)
struct sockaddr_in cli_addr = {
    .sin_family = AF_INET,
    .sin_port = htons(54321),            // 固定客户端端口
    .sin_addr.s_addr = inet_addr("192.168.1.100")
};

if (bind(sockfd, (struct sockaddr*)&cli_addr, sizeof(cli_addr)) == -1) {
    perror("Client bind failed");
    close(sockfd);
    exit(EXIT_FAILURE);
}

(七)错误处理指南

错误码原因描述解决方案
EADDRINUSE地址/端口已被占用更换端口或等待释放
EACCES无权限绑定特权端口(<1024)使用sudo运行或改用非特权端口
EINVAL套接字已绑定过地址检查是否重复调用bind()
ENOTSOCK文件描述符不是套接字验证sockfd来源

八、listen() 函数详解

(一)函数原型

#include <sys/socket.h>

int listen(int sockfd, int backlog);

(二)核心功能

  • 监听模式:将套接字设置为被动接收连接状态
  • 队列管理:建立连接请求缓冲队列
  • 服务准备:为后续accept()调用提供就绪环境

(三)参数解析

参数类型说明
sockfdint已绑定的流式套接字描述符(需先调用bind()
backlogint等待连接队列的最大长度(详见下文说明)

(四)队列机制详解

graph LR
    A[Client SYN] --> B{SYN队列<br>(未完成握手)}
    B -->|完成三次握手| C[ACCEPT队列<br>(已建立连接)]
    C --> D[accept()取出]
    
    style B fill:#f9d5e5,stroke:#f25287
    style C fill:#d5e8d4,stroke:#82b366
  • SYN队列(半连接队列):存储未完成三次握手的请求
  • ACCEPT队列(全连接队列):存储已完成握手的连接
  • backlog参数:通常指ACCEPT队列的最大长度(不同系统实现有差异)

(五)使用规范

1. 前置条件
// 创建套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);

// 绑定地址(必须步骤)
struct sockaddr_in addr = {...};
bind(sockfd, (struct sockaddr*)&addr, sizeof(addr));

// 开始监听
listen(sockfd, SOMAXCONN);  // SOMAXCONN为系统允许的最大值
2. 推荐配置
操作系统最大backlog值说明
Linux/proc/sys/net/core/somaxconn默认值通常为4096
FreeBSDkern.ipc.soacceptqueue默认值通常为128
通用方案使用SOMAXCONN宏定义自动适配系统最大值

(六)错误处理

错误码原因描述解决方案
EADDRINUSE地址/端口已被占用检查端口冲突,使用netstat -tulnp排查
EBADF无效套接字描述符验证sockfd是否已正确创建
EINVAL套接字未绑定或类型错误确保先调用bind()且为流式套接字
ENOTSOCK文件描述符不是套接字检查sockfd来源

九、accept() 函数详解

(一)函数原型

#include <sys/socket.h>

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

(二)核心功能

  • 连接接入:从已完成连接队列中取出首个连接
  • 创建新套接字:生成专用于该连接的通信套接字
  • 客户端信息获取(可选):记录客户端地址信息

(三)参数解析

参数类型说明
sockfdint处于监听状态的流式套接字描述符
addrstruct sockaddr*客户端地址信息存储缓冲区(可NULL)
addrlensocklen_t*输入:缓冲区大小<br>输出:实际地址长度(值-结果参数)

(四)使用流程

sequenceDiagram
    Client->>Server: SYN
    Server->>Client: SYN-ACK
    Client->>Server: ACK
    Note right of Server: 连接进入ACCEPT队列
    Server->>Server: accept()
    Server-->>Client: 建立数据通道

(五)典型用法

1. 基础用法(不获取客户端信息)
int new_sock = accept(listen_sock, NULL, NULL);
if (new_sock == -1) {
    perror("Accept failed");
    // 错误处理
}
2. 获取客户端信息
struct sockaddr_in client_addr;
socklen_t addr_len = sizeof(client_addr);

int new_sock = accept(listen_sock, 
                     (struct sockaddr*)&client_addr,
                     &addr_len);

if (new_sock != -1) {
    printf("Client from %s:%d\n",
           inet_ntoa(client_addr.sin_addr),
           ntohs(client_addr.sin_port));
}

(六)返回值说明

返回值说明
正整数新通信套接字描述符(>=3)
-1失败,检查errno:<br>• EAGAIN/EWOULDBLOCK(非阻塞模式无连接)<br>• ECONNABORTED(连接中止)<br>• EINTR(被信号中断)

十、send() 与 recv() 函数

(一)函数原型

#include <sys/socket.h>

// 发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

// 接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);

(二)核心功能对比

特性send()recv()
传输方向主动发送数据到对端被动接收来自对端的数据
缓冲区管理从应用缓冲区复制到内核发送缓冲区从内核接收缓冲区复制到应用缓冲区
阻塞行为等待内核缓冲区空间等待网络数据到达
连接状态检测通过返回值检测连接异常返回0表示对端已关闭连接

(三)参数详解

通用参数:
参数说明
sockfd已建立连接的流式套接字描述符(TCP)
buf数据缓冲区指针
len数据长度(send为待发送长度,recv为缓冲区容量)
flags控制标志(见下文详解)
标志位说明:
标志值适用函数说明
MSG_OOB两者处理带外数据(最高优先级数据)
MSG_DONTWAIT两者非阻塞操作(等价于设置O_NONBLOCK
MSG_PEEKrecv查看数据但不移除接收队列
MSG_WAITALLrecv等待直到填满缓冲区(可能被信号中断)

MSG_NOSIGNAL

send

禁止生成SIGPIPE信号

0表示阻塞接受,通常可以设置为 0。

(四)返回值处理指南

send() 返回值:
返回值状态说明
>0成功发送的字节数(可能小于len
0对端正常关闭连接(仅非阻塞模式可能)
-1错误发生,检查errno:<br>• EAGAIN/EWOULDBLOCK(非阻塞未就绪)<br>• ECONNRESET(连接重置)<br>• EPIPE(对端关闭连接)
recv() 返回值:
返回值状态说明
>0实际接收的字节数
0对端已正常关闭连接
-1错误发生,检查errno:<br>• EAGAIN/EWOULDBLOCK(非阻塞无数据)<br>• ECONNRESET(连接被重置)

(五)模式对比

阻塞模式 vs 非阻塞模式
特性阻塞模式非阻塞模式
send() 行为等待直到所有数据进入内核缓冲区立即返回已发送数据量
recv() 行为等待直到有数据到达立即返回当前可用数据量
错误处理简化编程逻辑需要处理EAGAIN/EWOULDBLOCK
适用场景简单客户端/低并发服务器高并发服务器(配合I/O多路复用)

综合应用:

 client //作为主动的角色 

#include<stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>


//./client port ip

int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("Usage: %s <port> <ip>\n",argv[0]);
		return -1;
	}
	int fd = socket(AF_INET,SOCK_STREAM,0);

	if(fd < 0)
	{
		perror("socket fail\n");
		return -1;
	}

	struct sockaddr_in seraddr;
	bzero(&seraddr,sizeof(seraddr));

	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);

	if(connect(fd,(struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
	{
		perror("connect fail");
		return -1;
	}

	printf("connect success!\n");
	
	char buf[1024];
	while(1)
	{
		printf(">");
		fgets(buf,sizeof(buf),stdin);
		send(fd,buf,strlen(buf)+1,0);
		
		if(strncmp(buf,"quit",4) == 0)
		{
			break;
		}

		recv(fd,buf,sizeof(buf),0);
		printf("s: %s",buf);
		
	}

	close(fd);
	return 0;
}

server //被动角色 

#include<stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
 
int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("Usage: %s <port> <ip>\n",argv[0]);
		return -1;
	}
 
	//1.socket 创建通信一端
	int fd = socket(AF_INET,SOCK_STREAM,0);
 
	if(fd < 0)
	{
		perror("socket fail\n");
		return -1;
	}
 
	struct sockaddr_in seraddr;
	bzero(&seraddr,sizeof(seraddr));
 
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);
 
	//2.bind--绑定服务端的地址信息
	if(bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
	{
		perror("bind fail");
		return -1;
	}
 
	//3.listen 监听
	if(listen(fd,5) < 0)
	{
		perror("listen fail");
		return -1;
	}
 
	//4.accept
#if 0
	int connfd = accept(fd,NULL,NULL);
 
	if(connfd < 0)
	{
		perror("accept fail");
		return -1;
	}
	printf("-----client----connectted\n");
#endif
	struct sockaddr_in cliaddr;
	bzero(&cliaddr,sizeof(cliaddr));
	socklen_t len = sizeof(cliaddr);
	int connfd = 0;
	if ((connfd = accept(fd,(struct sockaddr*)&cliaddr,&len)) < 0)
	{
		perror("accept fail");
		return -1;
	}

	printf("-------client-----info-----\n");
	printf("ip  : %s\n",inet_ntoa(cliaddr.sin_addr));
	printf("port: %d\n",ntohs(cliaddr.sin_port));
	printf("---------------------------\n");

	char buf[1024];
	char sbuf[1024];
	while(1)
	{
		recv(connfd,buf,sizeof(buf),0);
 
		printf("c: %s\n",buf);
 
		if(strncmp(buf,"quit",4) == 0)
		{
			break;
		}
		sprintf(sbuf,"server + %s\n",buf);
		send(connfd,sbuf,strlen(sbuf)+1,0);
	}
 
	close(fd);
	close(connfd);
	return 0;
}

十一、TCP粘包问题及解决方案详解

(一)粘包现象本质

1. 产生原因
graph LR
    A[发送方] -->|连续小包| B[TCP发送缓冲区]
    B -->|合并发送| C[网络传输]
    C -->|整块接收| D[接收方缓冲区]
    D -->|单次读取| E[接收方应用]
2. 典型场景
  • 快速连续发送小数据包(如心跳包)
  • 接收缓冲区大于单个数据包
  • 网络延迟导致数据堆积

(二)解决方案对比

方案类型实现方式优点缺点典型应用场景
分隔符法自定义特殊字符作为消息边界实现简单需处理转义问题文本协议(如HTTP)
长度前缀法数据前添加长度头处理高效需约定长度格式二进制协议
固定长度法所有消息统一长度解析简单空间浪费金融领域
复合结构法结构体封装元数据扩展性强需处理对齐问题复杂业务系统

(三)核心解决方案实现

1. 分隔符方案(以\r\n为例)
// 发送端
const char* msg = "Hello World\r\n";
send(sockfd, msg, strlen(msg), 0);

// 接收端处理逻辑
char buffer[4096];
size_t recv_pos = 0;

while(1) {
    ssize_t n = recv(sockfd, buffer + recv_pos, sizeof(buffer)-recv_pos, 0);
    if(n <= 0) break;
    
    recv_pos += n;
    char* end = strstr(buffer, "\r\n");
    while(end) {
        *end = '\0';
        process_message(buffer);  // 处理完整消息
        size_t remain = recv_pos - (end - buffer + 2);
        memmove(buffer, end+2, remain);
        recv_pos = remain;
        end = strstr(buffer, "\r\n");
    }
}
2. 长度前缀法(4字节网络序头)
// 发送端封装
void send_packet(int sockfd, const void* data, size_t len) {
    uint32_t net_len = htonl(len);
    send(sockfd, &net_len, 4, MSG_MORE);  // 发送长度头
    send(sockfd, data, len, 0);          // 发送数据体
}

// 接收端处理
enum { HEAD_SIZE = 4 };
size_t need_len = HEAD_SIZE;
size_t recv_pos = 0;
uint32_t body_len = 0;
char buffer[4096];

while(1) {
    ssize_t n = recv(sockfd, buffer + recv_pos, need_len, 0);
    if(n <= 0) break;
    
    recv_pos += n;
    if(recv_pos >= need_len) {
        if(need_len == HEAD_SIZE) {
            body_len = ntohl(*(uint32_t*)buffer);
            need_len = body_len;
            recv_pos = 0;
        } else {
            process_message(buffer, body_len);  // 处理完整消息
            need_len = HEAD_SIZE;
            recv_pos = 0;
        }
    }
}
3. 复合结构法(协议头+数据体)
#pragma pack(push, 1)  // 禁用内存对齐
struct PacketHeader {
    uint16_t version;   // 协议版本
    uint32_t cmd;       // 操作指令
    uint32_t checksum;  // 校验和
    uint32_t body_len;  // 数据体长度
};
#pragma pack(pop)

// 发送封装函数
void send_packet(int sockfd, uint32_t cmd, const void* data, size_t len) {
    struct PacketHeader header = {
        .version = 0x0101,
        .cmd = htonl(cmd),
        .body_len = htonl(len)
    };
    header.checksum = calc_checksum(&header, data);
    
    send(sockfd, &header, sizeof(header), MSG_MORE);
    send(sockfd, data, len, 0);
}

练习二、客户端向服务器传输一个文件(粘包)

cilent

#include<stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

typedef struct
{
	int size;
	char data[1024];
}msg_t;

//./client port ip

int main(int argc, const char *argv[])
{
#if 1
	if(argc != 4)
	{
		printf("Usage: %s <port> <ip> <filename>\n",argv[0]);
		return -1;
	}
#endif
	int fd = socket(AF_INET,SOCK_STREAM,0);

	if(fd < 0)
	{
		perror("socket fail\n");
		return -1;
	}

	struct sockaddr_in seraddr;
	bzero(&seraddr,sizeof(seraddr));

	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);

	if(connect(fd,(struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
	{
		perror("connect fail");
		return -1;
	}

	//名字
	msg_t msg;
	msg.size = -1;
	strcpy(msg.data,argv[3]);
	send(fd,&msg,sizeof(msg),0);
	//文件内容
	int fd_s = open(argv[3],O_RDONLY);
	if(fd_s < 0)
	{
		perror("open fail");
		return -1;
	}
	while(1)
	{
		int ret = read(fd_s,msg.data,sizeof(msg.data));
		msg.size = ret;
		send(fd,&msg,sizeof(msg),0);
		if(ret ==0)
				break;
	}
	
	close(fd_s);
	close(fd);
	return 0;
}

server

#include<stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <strings.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <sys/stat.h>
#include <fcntl.h>

typedef struct
{
	int size;
	char data[1024];
}msg_t;


int main(int argc, const char *argv[])
{
	if(argc != 2)
	{
		printf("Usage: %s <port>\n",argv[0]);
		return -1;
	}

	//1.socket 创建通信一端
	int fd = socket(AF_INET,SOCK_STREAM,0);

	if(fd < 0)
	{
		perror("socket fail\n");
		return -1;
	}

	struct sockaddr_in seraddr;
	bzero(&seraddr,sizeof(seraddr));

	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = INADDR_ANY;//绑定所有IP

	//2.bind--绑定服务端的地址信息
	if(bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
	{
		perror("bind fail");
		return -1;
	}


	//3.listen 监听
	if(listen(fd,5) < 0)
	{
		perror("listen fail");
		return -1;
	}

	//4.accept
	int connfd = accept(fd,NULL,NULL);

	if(connfd < 0)
	{
		perror("accept fail");
		return -1;
	}

	char buf[1024];
	msg_t msg;
	int ret = recv(connfd,&msg,sizeof(msg),0);

	int fd_dest = open(msg.data,O_WRONLY|O_CREAT|O_TRUNC,0666);

	if(fd < 0)
	{
		perror("open fail");
		return -1;
	}

	while(1)
	{
		ret = recv(connfd,&msg,sizeof(msg),0);
	
		if(msg.size == 0)
			break;
		write(fd_dest,msg.data,msg.size);
	}
	
	return 0;
}

十二、UDP C/S 模型实现详解

(一)协议特性对比

特性TCPUDP
连接方式面向连接无连接
可靠性可靠传输尽最大努力交付
数据边界字节流保留数据报边界
传输效率较低(有控制开销)较高
应用场景文件传输、Web访问实时视频、DNS查询

(二)核心函数说明

1. sendto()
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
              const struct sockaddr *dest_addr, socklen_t addrlen);
  • 功能:发送数据报到指定地址
  • 参数
    • dest_addr:目标地址结构指针
    • addrlen:地址结构长度
2. recvfrom()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                struct sockaddr *src_addr, socklen_t *addrlen);
  • 功能:接收数据报并获取来源地址
  • 参数
    • src_addr:来源地址结构指针(可NULL)
    • addrlen:输入时为缓冲区大小,输出时为实际地址长度

(三)完整实现示例

一对一通信(进程):

头文件

#ifndef _HEAD_H_
#define _HEAD_H_

#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#endif

UDP 服务器端

#include "head.h"

void handler(int signo)
{
	printf("%d exit....\n",getpid());
	exit(0);
}


//./cli ip port
int main(int argc, const char *argv[])
{
	if (argc != 3)
	{
		printf("Usage: %s <ip> <port>\n",argv[0]);
		return -1;
	}

	//1.socket 
	int fd = socket(AF_INET,SOCK_DGRAM,0);

	if (fd < 0)
	{
		perror("socket fail");
		return -1;
	}


	struct sockaddr_in seraddr;
	bzero(&seraddr,sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);

	//2.bind
	if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
	{
		perror("bind fail");
		return -1;
	}
	struct sockaddr_in cliaddr;
	bzero(&cliaddr,sizeof(cliaddr));
	socklen_t len = sizeof(cliaddr);

    char buf[1024] = {0};
	recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&cliaddr,&len);

	printf("cli: %s \n",buf);	

	signal(SIGUSR1,handler);
	pid_t pid = fork();

	if (pid < 0)
	{
		perror("fork fail");
		return -1;
	}
	if (pid > 0)
	{
		while (1)
		{
			printf(">");
			fgets(buf,sizeof(buf),stdin);
			sendto(fd,buf,strlen(buf)+1,0,(const struct sockaddr*)&cliaddr,sizeof(cliaddr));

			if (strncmp(buf,"quit",4) == 0)
			{
				kill(pid,SIGUSR1);
				wait(NULL);
				exit(0);
			}
			
		}
	}else if (pid == 0)
	{
		while (1)
		{
			recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
			printf("buf = %s\n",buf);
			if (strncmp(buf,"quit",4) == 0)
			{
				kill(getppid(),SIGUSR1);
				exit(0);
			}
		}

	}
	


	return 0;
}

UDP 客户端

#include "head.h"
void handler(int signo)
{
	exit(0);
}
//./cli ip port
int main(int argc, const char *argv[])
{
	if (argc != 3)
	{
		printf("Usage: %s <ip> <port>\n",argv[0]);
		return -1;
	}

	//1.socket 
	int fd = socket(AF_INET,SOCK_DGRAM,0);

	if (fd < 0)
	{
		perror("socket fail");
		return -1;
	}


	struct sockaddr_in seraddr;
	bzero(&seraddr,sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);

	signal(SIGUSR1,handler);
	pid_t pid = fork();

	if (pid < 0)
	{
		perror("fork fail");
		return -1;
	}
	char buf[1024] = {0};
	if (pid > 0)
	{
		while (1)
		{
			printf(">");
			fgets(buf,sizeof(buf),stdin);
			sendto(fd,buf,strlen(buf)+1,0,(const struct sockaddr*)&seraddr,sizeof(seraddr));

			if (strncmp(buf,"quit",4) == 0)
			{
				kill(pid,SIGUSR1);
				wait(NULL);
				exit(0);
			}
		}
	}else if (pid == 0)
	{
		while (1)
		{
			recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
			printf("buf = %s\n",buf);
			if (strncmp(buf,"quit",4) == 0)
			{
				kill(getppid(),SIGUSR1);
				exit(0);
			}
		}

	}
	


	return 0;
}
一对一通信(线程):

UDP 客户端

#include "head.h"

void *do_read(void *arg)
{
	int fd = *(int *)arg;
	char buf[1024] = {0};
	while (1)
	{
		recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
		printf("buf = %s\n",buf);
		if (strncmp(buf,"quit",4) == 0)
		{
			exit(0);
		}
	}
	return NULL;
}

struct sockaddr_in seraddr;
void * do_write(void *arg)
{
	int fd = *(int *)arg;
	char buf[1024] = {0};
	while (1)
	{
		printf(">");
		fgets(buf,sizeof(buf),stdin);
		sendto(fd,buf,strlen(buf)+1,0,(const struct sockaddr*)&seraddr,sizeof(seraddr));

		if (strncmp(buf,"quit",4) == 0)
		{
			exit(0);
		}
	}
}


//./cli ip port
int main(int argc, const char *argv[])
{
	if (argc != 3)
	{
		printf("Usage: %s <ip> <port>\n",argv[0]);
		return -1;
	}

	//1.socket 
	int fd = socket(AF_INET,SOCK_DGRAM,0);

	if (fd < 0)
	{
		perror("socket fail");
		return -1;
	}


	bzero(&seraddr,sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);

	pthread_t tid[2];

	int ret = pthread_create(&tid[0],NULL,do_read,&fd);
	if (ret != 0)
		handle_error_en(ret,"pthread_create fail");

	ret = pthread_create(&tid[1],NULL,do_write,&fd);
	if (ret != 0)
		handle_error_en(ret,"pthread_create fail");


	printf("-------------main-------\n");

	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);

	return 0;
}

UDP 服务器端

#include "head.h"


void *do_read(void *arg)
{
	int fd = *(int *)arg;
	char buf[1024] = {0};
	while (1)
	{
		recvfrom(fd,buf,sizeof(buf),0,NULL,NULL);
		printf("buf = %s\n",buf);
		if (strncmp(buf,"quit",4) == 0)
		{
			exit(0);
		}
	}
	return NULL;
}

struct sockaddr_in cliaddr;
void * do_write(void *arg)
{
	int fd = *(int *)arg;
	char buf[1024] = {0};
	while (1)
	{
		printf(">");
		fgets(buf,sizeof(buf),stdin);
		sendto(fd,buf,strlen(buf)+1,0,(const struct sockaddr*)&cliaddr,sizeof(cliaddr));

		if (strncmp(buf,"quit",4) == 0)
		{
			exit(0);
		}
	}
}
//./cli ip port
int main(int argc, const char *argv[])
{
	if (argc != 3)
	{
		printf("Usage: %s <ip> <port>\n",argv[0]);
		return -1;
	}

	//1.socket 
	int fd = socket(AF_INET,SOCK_DGRAM,0);

	if (fd < 0)
	{
		perror("socket fail");
		return -1;
	}


	struct sockaddr_in seraddr;
	bzero(&seraddr,sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);

	//2.bind
	if (bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) < 0)
	{
		perror("bind fail");
		return -1;
	}
	
	bzero(&cliaddr,sizeof(cliaddr));
	socklen_t len = sizeof(cliaddr);

    char buf[1024] = {0};
	recvfrom(fd,buf,sizeof(buf),0,(struct sockaddr*)&cliaddr,&len);

	printf("cli: %s \n",buf);	

	pthread_t tid[2];

	int ret = pthread_create(&tid[0],NULL,do_read,&fd);
	if (ret != 0)
		handle_error_en(ret,"pthread_create fail");

	ret = pthread_create(&tid[1],NULL,do_write,&fd);
	if (ret != 0)
		handle_error_en(ret,"pthread_create fail");


	printf("-------------main-------\n");

	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);

	return 0;
}
UDP聊天室

头文件

#ifndef _HEAD_H_
#define _HEAD_H_

#include <stdio.h>
#include <sys/types.h>	       /* See NOTES */
#include <sys/socket.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <stdlib.h>
#include <strings.h>
#include <string.h>
#include <signal.h>
#include <sys/wait.h>
#include <pthread.h>
#include <errno.h>

enum MSG_TYPE
{
	LOGIN =1,
	CHAT,
	QUIT,
};

typedef struct msg
{
	enum MSG_TYPE type;
	unsigned char name[20];
	unsigned char text[256];
}msg_t;

#define handle_error_en(en, msg) \
	do { errno = en; perror(msg); exit(EXIT_FAILURE); } while (0)

#define handle_error(msg) \
	do { perror(msg); exit(EXIT_FAILURE); } while (0)


#

客户端

#include "head.h"

struct sockaddr_in seraddr;
msg_t msg;

void *do_read(void *arg)
{
	int fd = *(int *)arg;
	msg_t msg;// 局部变量,用于接收消息(注意这里和全局变量名相同,但作用域不同)
	while(1)
	{
		recvfrom(fd,&msg,sizeof(msg_t),0,NULL,NULL);// 从socket接收客户端消息
		printf("[%s]:%s",msg.name,msg.text);
	}
	return NULL;
}

void *do_write(void *arg)
{
	int fd = *(int *)arg;
	printf("char>:");

	while(1)
	{
		fgets(msg.text,sizeof(msg.text),stdin);
		
		if(strncmp(msg.text,"quit",4) == 0)
		{
			msg.type = QUIT;
			sendto(fd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));
			exit(0);
		}else
		{
			msg.type = CHAT;
			sendto(fd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));
		}
	}
	return NULL;
}


int main(int argc, const char *argv[])
{
	if(argc != 3)
	{
		printf("Usage: %s <port> <ip>\n",argv[0]);
		return -1;
	}

	int fd = socket(AF_INET,SOCK_DGRAM,0);
	if(fd < 0)
	{
		perror("socket fail");
		return -1;
	}
	
	bzero(&seraddr,sizeof(seraddr));
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);

	msg.type = LOGIN;
	printf("input your name>: ");
	fgets(msg.name,sizeof(msg.name),stdin);
	msg.name[strlen(msg.name)-1] = '\0';
	
	sprintf(msg.text,"[%s] is login!\n",msg.name);// 构造登录消息
	sendto(fd,&msg,sizeof(msg),0,(const struct sockaddr*)&seraddr,sizeof(seraddr));

	pthread_t tid[2];

	int ret = pthread_create(&tid[0],NULL,do_read,&fd);
	if(ret != 0)
		handle_error_en(ret,"pthread_create fail");
	
	ret = pthread_create(&tid[1],NULL,do_write,&fd);
	if(ret != 0)
		handle_error_en(ret,"pthread_create fail");

	pthread_join(tid[0],NULL);
	pthread_join(tid[1],NULL);
	return 0;
}

UDP 服务器端

#include "head.h"
#define N 10

// 定义一个结构体,用于存储客户端地址和状态
typedef struct
{
	struct sockaddr_in addr;//客户端地址
	enum MSG_TYPE stat;//客户端状态
}cliaddr_t;

cliaddr_t peer_addr[N];// 存储已登录客户端信息的数组
struct sockaddr_in cliaddr;// 用于临时存储接收到的客户端地址

// 广播消息给所有已登录的用户(除了发送者)
void broadcast_msg(int fd,msg_t *msg,struct sockaddr_in *cliaddr)
{
	int i = 0;
	for(i = 0;i < N;++i)
	{
		if(peer_addr[i].stat == LOGIN && memcmp(&peer_addr[i].addr,cliaddr,sizeof(struct sockaddr_in)) != 0)
		{
			sendto(fd,msg,sizeof(msg_t),0,(const struct sockaddr*)&peer_addr[i],sizeof(struct sockaddr_in));

		}
	}
}

int main(int argc, const char *argv[])
{
	if (argc != 3)
	{
		printf("Usage: %s <port> <ip>\n",argv[0]);
		return -1;
	}

	int fd = socket(AF_INET,SOCK_DGRAM,0);
	if(fd < 0)
	{
		perror("socket fail");
		return -1;
	}

	struct sockaddr_in seraddr;
	bzero(&seraddr,sizeof(socket));

	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(atoi(argv[1]));
	seraddr.sin_addr.s_addr = inet_addr(argv[2]);

	if(bind(fd,(const struct sockaddr*)&seraddr,sizeof(seraddr)) != 0)
	{
		perror("bind fail");
		return -1;
	}

	bzero(&cliaddr,sizeof(cliaddr));
	socklen_t len = sizeof(cliaddr);// 用于recvfrom函数获取客户端地址长度

	msg_t msg;// 用于接收消息的结构体
	int i = 0;// 用于遍历peer_addr数组的索引
	int j = 0;// 在QUIT情况下用于遍历peer_addr数组的索引

	while(1)
	{
		recvfrom(fd,&msg,sizeof(msg),0,(struct sockaddr*)&cliaddr,&len);
		
		printf("cli %s text:%s\n",msg.name,msg.text);
		switch(msg.type)
		{
		case LOGIN:
			peer_addr[i].addr = cliaddr;// 存储客户端地址
			peer_addr[i].stat = LOGIN;// 设置状态为登录
			++i;
			broadcast_msg(fd,&msg,&cliaddr);// 广播登录消息
			break;
		case CHAT:
			broadcast_msg(fd,&msg,&cliaddr);// 广播聊天信息
			break;
		case QUIT:
			broadcast_msg(fd,&msg,&cliaddr);// 广播退出消息
			for(j = 0;j < N;j++)
			{
				if(memcmp(&cliaddr,&peer_addr[i].addr,sizeof(struct sockaddr_in)) == 0)// 找到匹配项
				{
					bzero(&peer_addr[i],sizeof(peer_addr[i]));// 清除用户信息
				}
			}
				break;
		}
	}
	
	return 0;
}

相关文章:

  • WPS AI+office-ai的安装、使用
  • Linux查看TP6 command定时任务并重启
  • 一、Prometheus架构
  • table 拖拽移动
  • 广域互联网关键技术详解(GRE/LSTP/IPsec/NAT/SAC/SPR)
  • 文件上传复现
  • Office 2021 Mac Office办公
  • 【银河麒麟高级服务器操作系统实际案例分享】数据库资源重启现象分析及处理全过程
  • HTML5+CSS3+JS小实例:带缩略图的焦点图
  • vue组合式API中prop
  • 深入 Vue.js 组件开发:从基础到实践
  • 《张一鸣,创业心路与算法思维》
  • 准确--Centos最小化安装通过命令去修改ip和dns
  • 本地部署Dify及避坑指南
  • 【powerjob】 powerjobserver注册服务IP错误
  • uniapp+vue3搭建项目
  • ESP32-P4 支持哪些 RISC-V 汇编指令?
  • 前缀和矩阵
  • 人工智能技术的广阔前景
  • verilog 基本语法结构与定义
  • wordpress换页/长沙seo网站优化公司
  • 宿州哪有做网站的/关键词抓取工具都有哪些
  • 无极app定制开发公司网站模板/网络推广哪家做得比较好
  • 移动网站 案例/郑州网络营销推广公司
  • 找大学生做家教的网站/天津seo托管
  • 代办公司注册商务服务/网站排名优化需要多久