嵌入式解谜日志-网络编程(udp,tcp,(while循环原理))

一、OSI 模型的核心思想

  1. 分层解耦:将网络通信拆解为 7 个独立层,每层仅关注自身功能(如数据封装、路由选择、流量控制),降低复杂度。
  2. 标准化接口:每层通过 "服务访问点(SAP)" 为上层提供服务,同时通过 "协议" 与对等层(另一台设备的同一层)通信,屏蔽底层实现细节。
  3. 数据封装与解封装:发送端从应用层到物理层,每层为数据添加 "首部(Header)" 或 "尾部(Trailer)";接收端从物理层到应用层,逐层剥离首部,最终获取原始数据。

二、OSI 7 层模型详解(从顶层到底层)


应用层:为网络用户提供各种服务,例如电子邮件、文件传输等。

提供服务

  1. 应用层(Application Layer,第 7 层)
  • 核心功能:为应用程序提供网络服务接口,是用户与网络的直接交互层。
  • 典型协议:HTTP(网页访问)、FTP(文件传输)、SMTP(邮件发送)、DNS(域名解析)、Telnet(远程登录)。
  • 数据单位:用户数据(如网页内容、文件数据)。
  • 示例:浏览器通过 HTTP 协议向服务器请求网页,邮件客户端通过 SMTP 发送邮件。

表示层:为不同主机间的通信提供统一的数据表示形式

(压缩,加密)

  1. 表示层(Presentation Layer,第 6 层)
  • 核心功能:处理数据的 "格式转换与加密",确保发送端与接收端能理解数据格式。
  • 主要任务
    • 数据编码 / 解码(如 ASCII、UTF-8);
    • 数据压缩 / 解压缩(如 ZIP、GZIP);
    • 数据加密 / 解密(如 SSL/TLS 中的加密处理)。
  • 数据单位:PDU(Protocol Data Unit,协议数据单元,此处为 "表示层 PDU")。
  • 示例:发送端将文本数据从 UTF-8 转换为网络通用格式,接收端再转换回本地格式;网银登录时对密码进行加密传输。

会话层:负责信息传输的组织和协调,管理进程会话过程。

网络链接的状态信息

  1. 会话层(Session Layer,第 5 层)
  • 核心功能:建立、管理和终止 "会话连接"(应用程序之间的通信会话),确保通信双方的会话同步。
  • 主要任务
    • 会话建立(如三次握手类似的会话初始化);
    • 会话维护(如心跳检测、会话恢复);
    • 会话终止(释放会话资源)。
  • 数据单位:会话层 PDU。
  • 示例:视频会议中建立双方的实时会话,若网络中断,尝试恢复会话而非重新建立;数据库连接中的会话管理(如 MySQL 的连接会话)。

传输层:管理网络通信两端的数据传输,提供可靠或不可靠的传输服务

udp(用户数据包),tcp(传输控制协议)。数据以什么方式传输(可靠与否)

  1. 传输层(Transport Layer,第 4 层)
  • 核心功能:提供 "端到端的可靠数据传输",负责流量控制、差错控制和端口寻址(标识主机中的进程)。
  • 典型协议
    • TCP(Transmission Control Protocol):面向连接、可靠的字节流传输(重传丢失数据、按序交付);(文件传输)
    • UDP(User Datagram Protocol):无连接、不可靠的数据报传输(速度快,适合实时场景)。(视频和音频),网络开销小
  • 数据单位:TCP 段(Segment)、UDP 数据报(Datagram)。
  • 关键机制
    • 端口号(0-65535,如 80 端口对应 HTTP);
    • TCP 的流量控制(滑动窗口)、差错控制(ACK 确认、重传)。
  • 示例:浏览器(客户端进程,随机端口)通过 TCP 连接服务器的 80 端口,确保网页数据不丢失;视频通话通过 UDP 传输实时数据,容忍少量丢包。

网络层:负责数据传输的路由选择和网际互连。

IP地址(广域网)

  1. 网络层(Network Layer,第 3 层)
  • 核心功能:实现 "跨网络的路由选择",将数据从源主机发送到目标主机(可能经过多个路由器)。
  • 典型协议:IP(Internet Protocol,IPv4/IPv6)、ICMP(网络控制消息,如 ping 命令)、OSPF(路由协议)、ARP(地址解析,IP→MAC)。
  • 数据单位:数据包(Packet)。
  • 关键机制
    • IP 地址(标识网络中的主机,如 192.168.1.1);
    • 路由选择(路由器通过路由表决定数据包的下一跳);
    • 拥塞控制(避免网络过载)。
  • 示例:发送端通过 IP 协议为数据添加目标 IP 地址,路由器根据 IP 地址和路由表将数据包转发到目标网络;ping 命令通过 ICMP 协议检测主机可达性。

数据链路层,负责物理相邻(通过网络介质相连)的主机间的数据传输,主要作用包括物理地址寻址、数据帧封装、差错控制等。该层可分为逻辑链路控制子层(LLC)和介质访问控制子层(MAC)

租建链路(局域网),组织01数据成位帧(一包),校验

  1. 数据链路层(Data Link Layer,第 2 层)
  • 核心功能:处理 "物理层的原始数据帧",实现同一局域网内的可靠传输,负责 MAC 地址寻址、帧同步和差错检测。
  • 典型协议:Ethernet(以太网)、PPP(点对点协议,如拨号上网)、ARP(底层地址解析)。
  • 数据单位:帧(Frame)。
  • 关键机制
    • MAC 地址(硬件地址,如 00:1A:2B:3C:4D:5E,标识网卡);
    • 差错检测(如 CRC 校验,检测帧在传输中是否损坏);
    • 冲突避免(以太网的 CSMA/CD 机制,避免同一局域网内数据冲突)。
  • 示例:局域网内的主机通过 MAC 地址通信,路由器将 IP 数据包封装为以太网帧,通过 MAC 地址发送到下一跳设备;接收端通过 CRC 校验判断帧是否损坏,损坏则丢弃。

物理层,负责把主机中的数据转换成电信号,再通过网络介质(双绞线、光纤、无线信道等)来传输。该层描述了通信设备的机械、电气、功能等特性。

  1. 物理层(Physical Layer,第 1 层)
  • 核心功能:定义 "物理介质的电气特性和信号传输规则",将数据链路层的帧转换为物理信号(如电信号、光信号)在物理介质上传输。
  • 主要任务
    • 物理介质(如双绞线(短距离)、光纤(长距离)、无线电波((WiFi 2.4G和5G频段),无线局域网和 无线广域网));
    • 信号类型(如数字信号、模拟信号);
    • 引脚定义(如网线的 T568A/B 线序)、传输速率(如 100Mbps(双绞线)、10Gbps(光纤))。
  • 数据单位:比特(Bit,0 或 1)。
  • 示例:网线传输电信号,光纤传输光信号,WiFi 通过无线电波传输信号;网卡将帧转换为电信号,通过网线发送到交换机。

三,TCP/IP 模型(TCP/IP协议栈)(Transmission Control Protocol/Internet Protocol Model)是互联网的核心通信架构

一、TCP/IP 模型的分层结构(4 层划分)

TCP/IP 模型从顶层到底层分为以下 4 层,每层负责特定的通信功能,通过协议协作完成端到端的数据传输:

  1. 应用层(Application Layer)
  • 核心功能:为用户应用程序提供网络服务接口,直接处理用户数据。
  • 包含协议 :整合了 OSI 模型中应用层、表示层和会话层的功能,常见协议有:
    • HTTP/HTTPS:网页访问(超文本传输协议);
    • FTP:文件传输协议;
    • SMTP/POP3:邮件发送与接收;
    • DNS:域名解析(将域名转换为 IP 地址);
    • SSH/Telnet:远程登录。
  • 数据单位:用户数据(如网页内容、文件、邮件正文)。
  • 示例:浏览器通过 HTTP 协议向服务器请求网页,邮件客户端通过 SMTP 发送邮件。
  1. 传输层(Transport Layer)
  • 核心功能 :提供端到端的可靠数据传输,负责数据的分段、重组、流量控制和差错控制。
  • 核心协议
    • TCP(Transmission Control Protocol):面向连接、可靠的字节流传输。通过三次握手建立连接,四次挥手断开连接,使用确认机制、重传机制和滑动窗口实现流量控制,确保数据不丢失、不重复、按序到达。适用于文件传输、网页加载等对可靠性要求高的场景。
    • UDP(User Datagram Protocol):无连接、不可靠的数据报传输。无需建立连接,传输速度快,但不保证数据送达顺序和完整性。适用于视频通话、实时游戏、DNS 查询等对实时性要求高的场景。
  • 数据单位:TCP 称为 "段(Segment)",UDP 称为 "数据报(Datagram)"。
  • 关键机制 :通过端口号(0-65535)标识主机中的进程(如 80 端口对应 HTTP 服务,443 端口对应 HTTPS 服务)。
  1. 网络层(Internet Layer)
  • 核心功能 :实现跨网络的路由选择,将数据从源主机发送到目标主机(可能经过多个路由器)。
  • 核心协议
    • IP(Internet Protocol):为主机分配唯一的 IP 地址(如 IPv4 的 192.168.1.1,IPv6 的 2001:db8::1),定义数据包的格式和路由规则。
    • ICMP(Internet Control Message Protocol):用于网络诊断和控制(如 ping 命令检测主机可达性,traceroute 追踪路由路径)。
    • ARP(Address Resolution Protocol):将 IP 地址转换为物理 MAC 地址(同一局域网内通信需要)。
    • 路由协议:如 OSPF、RIP,用于路由器之间交换路由信息,生成路由表。
  • 数据单位:数据包(Packet)。
  • 关键机制:路由器根据 IP 地址和路由表,决定数据包的下一跳转发路径。
  1. 网络接口层(Network Interface Layer)
  • 核心功能 :处理物理层的原始数据,实现同一局域网内的帧传输,负责 MAC 地址寻址和数据链路管理。
  • 包含内容 :对应 OSI 模型的数据链路层和物理层,涉及:
    • 数据链路层协议:如以太网(Ethernet)、PPP(点对点协议),定义帧的格式(包含 MAC 地址、校验码等)。
    • 物理层规范:如网线、光纤等物理介质的电气特性,信号传输规则(如比特流转换为电信号 / 光信号)。
  • 数据单位:帧(Frame)。
  • 关键机制:通过 MAC 地址(硬件地址,如 00:1A:2B:3C:4D:5E)标识局域网内的设备,使用 CRC 校验检测帧传输是否损坏。

http:超文本传输协议

1.DNS(Domain Name System,域名系统)域名解析服务

服务器是互联网的核心基础设施之一,其核心作用是将人类可读的域名(如 www.baidu.com)转换为计算机可识别的 IP 地址(如 202.108.2.147,解决 "记域名易、记 IP 难" 的问题,同时实现域名与 IP 地址的动态映射,支撑互联网服务的灵活部署。


2.DHCP(Dynamic Host Configuration Protocol,动态主机配置协议)

是一种运行在应用层的网络协议,主要用于自动为局域网内的设备分配 IP 地址及其他网络配置参数(如子网掩码、网关、DNS 服务器等),避免了手动配置 IP 的繁琐和地址冲突问题,是局域网中不可或缺的基础设施。


ip:互联网协议

icmp:互联网控制管理协议,网络诊断

ARP(Address Resolution Protocol)地址转换协议

是 TCP/IP 协议族中网络层的核心协议,用于将 IP 地址转换为物理 MAC 地址(Media Access Control Address),解决 "同一局域网内设备如何通过 IP 地址找到对方物理地址" 的问题,是局域网通信的基础。

RARP:将物理MAC地址转换成IP地址


OSI 模型(7 层) TCP/IP 模型(4 层) 核心对应关系 典型协议 / 功能
应用层 应用层 直接对应,包含 OSI 的 3 层功能 HTTP、FTP、DNS、SMTP
表示层 应用层 合并到应用层 数据加密、格式转换(如 SSL)
会话层 应用层 合并到应用层 会话管理(如 HTTP 会话)
传输层 传输层 直接对应 TCP、UDP
网络层 网络层 直接对应 IP、ICMP、OSPF
数据链路层 网络接口层 合并物理层和数据链路层 Ethernet、PPP、ARP
物理层 网络接口层 合并物理层和数据链路层 网线、光纤、信号传输

IP 地址(Internet Protocol Address)是互联网中标识唯一标识设备的逻辑地址

(1)IP地址的组成:网络位+主机位

复制代码
sudo vim/etc/network/interfaces 网络配置文件,配置网卡分配ip地址的方式。

(2)IP地址的分类:点分十进制 .ipv4

  1. A 类地址(超大型网络)
  • 核心特征
    二进制前 1 位固定为 0(前 4 位范围:0000-0111),网络位长度为 8 位 ,主机位长度为 24 位
  • 地址范围
    十进制 1.0.0.0 ~ 126.255.255.255(注:0.0.0.0 表示本机未分配 IP,127.0.0.0 为回环地址,均不属于 A 类可用地址)。
  • 子网掩码 :默认 255.0.0.0(二进制 11111111.00000000.00000000.00000000)。
  • 可容纳主机数
    主机位共 24 位,理论可容纳 2²⁴ - 2 = 16777214 台设备(减 2 是排除 "网络地址" 和 "广播地址")。
  • 适用场景:早期互联网主干网、超大型机构(如早期的 IBM、AT&T),目前 A 类地址已基本分配完毕。
  1. B 类地址(大中型网络)
  • 核心特征
    二进制前 2 位固定为 10(前 4 位范围:1000-1011),网络位长度为 16 位 ,主机位长度为 16 位
  • 地址范围
    十进制 128.0.0.0 ~ 191.255.255.255
  • 子网掩码 :默认 255.255.0.0(二进制 11111111.11111111.00000000.00000000)。
  • 可容纳主机数
    主机位共 16 位,理论可容纳 2¹⁶ - 2 = 65534 台设备。
  • 适用场景:中型企业、高校、运营商的二级网络(如某省电信的城域网)。
  1. C 类地址(小型网络)
  • 核心特征
    二进制前 3 位固定为 110(前 4 位范围:1100-1101),网络位长度为 24 位 ,主机位长度为 8 位
  • 地址范围
    十进制 192.0.0.0 ~ 223.255.255.255
  • 子网掩码 :默认 255.255.255.0(二进制 11111111.11111111.11111111.00000000)。
  • 可容纳主机数
    主机位共 8 位,理论可容纳 2⁸ - 2 = 254 台设备(最适合小型局域网)。
  • **私有:**192.168.0.0 - 192.168.255.255
  • 静态路由
    192.168.0.0
    192.168.0.1网关
    192.168.0.255
  • 适用场景:家庭 WiFi、小型办公室、商铺等设备数量较少的网络(如家庭中 10 台以内的手机、电脑、智能设备)。
  1. D 类地址(组播地址)
  • 核心特征
    二进制前 4 位固定为 1110(前 4 位范围:1110),无网络位和主机位之分,不用于单播通信,仅用于 "组播"(一对多通信)。
  • 地址范围
    十进制 224.0.0.0 ~ 239.255.255.255
  • 典型用途
    视频会议(向多个参会设备同步发送视频流)、IPTV(向多个用户推送电视信号)、路由器之间的路由协议通信(如 OSPF)。
  • 示例224.0.0.1 是 "所有主机" 的组播地址,224.0.0.2 是 "所有路由器" 的组播地址。
  1. E 类地址(保留地址)
  • 核心特征
    二进制前 4 位固定为 1111(前 4 位范围:1111),无网络位和主机位之分,不对外开放使用
  • 地址范围
    十进制 240.0.0.0 ~ 255.255.255.255(注:255.255.255.255 是全局广播地址,属于特殊用途,不归属 E 类可用地址)。
  • 用途:仅用于科研、实验或未来扩展(如 IPv4 向 IPv6 过渡的技术测试),目前无实际商用场景。

1.配置网络设置:

命令:

①ifconfig:查询网络地址

ip: ifconfig ethX X.X.X.X/24 up ifconfig ens33 192.168.0.13/24 up 255.255.255.0

网关 :route add default gw x.x.x.x

DNS : vi /etc/resolv.conf ==>nameserver 8.8.8.8

测试 : ping www.baidu.com

②ping 网络诊断

③netstat -anp:查看本机所有的网络连接信息

2.网络接口:

**1.socket 套接字:**用于网络通信的一组接口函数。

本质:文件描述符(网络设备的)

2.ip+port:地址+端口:

ip地址用来识别主机

port端口用来识别应用程序(查找进程)

port(端口):分为TCP port/UDP port

范围:1-65535

1000以内的端口为系统使用

3.网络字节序:①pc,arm : 都是小端存储设备(数据的地位存放在低地址)

②网络:大端存储

四,udp,不可靠(丢包)低延迟 网络开销小,无链接,一包正文相对多

数据报:

1.数据与数据之间有边界

2.发送的次数和接收的次数需要对应

3.如果数据发送太快,就会丢包

(1)server(服务端)

1.socket:创建套接字,打开网路设备

2.bind :给套接字,设置ip+port(地址+端口)

3.recvform :先阻塞等待客服端发送文件
4. sendto:送回客服端

cs 复制代码
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include<time.h>
typedef struct sockaddr * (SA);
int	main(int argc, char **argv)
{
    //创建套接字sockfd
    //AF_INET为互联网类型网络
    //SOCK_DGRAM为UDP端口类型(不可靠)
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(-1==sockfd)
    {
        perror("socket");
        return 1;
    }
    //man 7 ip查看地址结构体
    struct sockaddr_in ser,cli;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(50000);
    ser.sin_addr.s_addr=inet_addr("192.168.1.23");
    int ret=bind(sockfd,(SA)&ser,sizeof(ser));
    if(-1==ret)
    {
        perror("bind");
        return 1;
    }
    time_t tm;
    socklen_t len=sizeof(cli);
    while (1)
    {
        char buf[512]={0};
        //等待接收客户端
        time(&tm);
        recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);
        sprintf(buf,"%s %s",buf,ctime(&tm));
        //向客户端发送
        printf("recv %s\n",buf);
        sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len);
    }
    //system("pause");
    return 0;
}

(2)client(客户端)

1.socket,打开网络设备

2. sendto:向服务端发送信息

3.recvform:接受服务端传回的信息

cs 复制代码
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include<time.h>
typedef struct sockaddr * (SA);
int	main(int argc, char **argv)
{
    //创建套接字,打开网络
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(-1==sockfd)
    {
        perror("socket");
        return 1;
    }
    //
    struct sockaddr_in ser;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(50000);
    ser.sin_addr.s_addr=inet_addr("192.168.1.23");
    int i=10;
    while (i--)//循环发送
    {
        char buf[512]="hello,白海英";
        //发送信息
        sendto(sockfd,buf,strlen(buf),0,(SA)&ser,sizeof(ser));
        //将buf清空,准备接收服务端
        bzero(buf,sizeof(buf));
        //接收服务端信息
        recvfrom(sockfd,buf,sizeof(buf),0,NULL,NULL);
        printf("from ser:%s\n",buf);
        sleep(1);
    }
    close(sockfd);
    //system("pause");
    return 0;
}

做服务端的ip地址是自己的,

做客户端的ip地址是服务端的。
复制图片

客户端:

cs 复制代码
​
#include<arpa/inet.h>   // 提供IP地址转换函数(如inet_addr)和网络数据结构
#include<stdio.h>        // 标准输入输出函数(如printf、perror)
#include<stdlib.h>       // 标准库函数(如exit,确保兼容性)
#include<string.h>       // 字符串操作函数(如bzero,可扩展使用)
#include<sys/types.h>    // 定义系统数据类型(如ssize_t、socklen_t)
#include<sys/socket.h>   // 提供套接字操作函数(如socket、sendto、recvfrom)
#include<unistd.h>       // 提供文件关闭函数(close)和睡眠函数(sleep)
#include <netinet/in.h>  // 定义网络地址结构(如struct sockaddr_in)
#include <netinet/ip.h>  // 提供IP协议相关定义(用于兼容性)
#include <fcntl.h>       // 提供文件打开控制选项(如O_RDONLY、O_WRONLY)

// 类型重定义:将struct sockaddr*简化为SA,减少代码冗余
typedef struct sockaddr * (SA);

int	main(int argc, char **argv)
{
    // 创建UDP套接字(打开网络通信通道)
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (-1 == sockfd)
    {
        perror("socket");  // 打印套接字创建失败原因
        return 1;
    }

    // 初始化服务端地址结构(指定数据发送目标)
    struct sockaddr_in ser;
    ser.sin_family = AF_INET;        // 使用IPv4协议
    ser.sin_port = htons(50000);     // 服务端端口(网络字节序)
    ser.sin_addr.s_addr = inet_addr("192.168.1.123");  // 服务端IP地址

    // 打开源文件(待发送的图片)和目标文件(接收后保存的图片)
    int fd = open("1.png", O_RDONLY);                 // 只读打开源文件
    int fd_s = open("2.png", O_WRONLY | O_CREAT | O_TRUNC, 0666);  // 创建/打开目标文件
    if (fd == -1 || fd_s == -1) 
    {
        perror("文件打开失败");  // 打印文件操作失败原因
        close(fd);
        close(fd_s);
        close(sockfd);
        return 1;
    }

    char buf[1024];          // 数据缓冲区(每次最多处理1024字节)
    ssize_t n;               // 存储实际读取/发送的字节数(支持负数表示错误)
    socklen_t ser_len = sizeof(ser);  // 服务端地址结构长度

    /****************************************************************************
     * 核心循环:分块读取文件 → 发送数据 → 接收反馈 → 写入目标文件
     * 循环逻辑:"读一点,发一点,收一点,写一点"(而非一次性处理整个文件)
     * 适用场景:大文件传输(避免占用过多内存)、网络分片传输(符合UDP特性)
     ***************************************************************************/
    // read函数:从源文件fd读取数据到buf,最多读sizeof(buf)=1024字节
    // 循环条件:n > 0 → 读取到有效数据,继续处理;n ≤ 0 → 文件读完或出错,退出循环
    while ((n = read(fd, buf, sizeof(buf))) > 0) 
    {
        /****************** 发送阶段:将刚读取的一块数据发送给服务端 ******************/
        // sendto参数n:使用实际读取的字节数(而非缓冲区大小),避免发送无效空数据
        // 例如:文件只剩500字节时,n=500,仅发送500字节有效数据
        sendto(sockfd, buf, n, 0, (SA)&ser, sizeof(ser));
        //清空buf,为下一次循环做准备
        bzero(buf, sizeof(buf));
        /****************** 接收阶段:等待服务端回传数据(确认或原数据) ******************/
        // 覆盖buf中的发送数据(因发送数据已通过网络发出,缓冲区可复用)
        recvfrom(sockfd, buf, sizeof(buf), 0, NULL, NULL);

        /****************** 写入阶段:将服务端回传的数据写入目标文件 ******************/
        // 写入字节数与发送时的n一致,确保每块数据大小匹配,最终文件完整
        write(fd_s, buf, n);
    }

    // 释放资源(关闭所有打开的文件和套接字,避免资源泄漏)
    close(sockfd);
    close(fd);
    close(fd_s);

    return 0;
}

​

服务端:

cs 复制代码
#include<arpa/inet.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include<time.h>
#include <fcntl.h>
typedef struct sockaddr * (SA);
int	main(int argc, char **argv)
{
    //创建套接字sockfd
    //AF_INET为互联网类型网络
    //SOCK_DGRAM为UDP端口类型(不可靠)
    int sockfd=socket(AF_INET,SOCK_DGRAM,0);
    if(-1==sockfd)
    {
        perror("socket");
        return 1;
    }
    //man 7 ip查看地址结构体
    struct sockaddr_in ser,cli;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(50000);
    ser.sin_addr.s_addr=inet_addr("192.168.1.213");
    int ret=bind(sockfd,(SA)&ser,sizeof(ser));
    if(-1==ret)
    {
        perror("bind");
        return 1;
    }
    //int fd_s = open("received_from_client.png", O_WRONLY | O_CREAT | O_TRUNC, 0666); // 保存接收文件
    //if (fd_s == -1) {
      //  perror("服务端目标文件创建失败");
        //close(sockfd);
       // return 1;
    //}
    ssize_t n;
    char buf[1024]={0};
    socklen_t len=sizeof(cli);
    const char *end_flag = "FILE_TRANSFER_COMPLETE";
    while (1)
    {

        n=recvfrom(sockfd,buf,sizeof(buf),0,(SA)&cli,&len);
       /* if (n == 0) 
        { // 接收到结束标记
            break;
        }*/
       //write(fd_s,buf,n);
        sendto(sockfd,buf,n,0,(SA)&cli,len);
        break;
    }
    //printf("hello\n");
    //system("pause");
   // close(fd_s);
    //close(sockfd);
    return 0;
}

udp聊天。可以连续收发。其中#quit,c,s都退出。

客户端:

cs 复制代码
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
typedef struct sockaddr * (SA);
int sockfd;
struct sockaddr_in ser;
socklen_t len = sizeof(ser);
void *recv_thread(void *arg) 
{
   char buf[1024];
    while (1)
    {
        bzero(buf, sizeof(buf));
        // 阻塞接收服务端消息(在独立线程中不影响主线程发送)
        recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&ser, &len);
        
        // 检测服务端退出指令
        if (strcmp(buf, "#quit") == 0) 
        {
            printf("\n服务端已退出聊天\n");
            close(sockfd);
            exit(0);  // 退出整个程序
        }
        // 显示收到的消息,并提示用户输入
        printf("\nxxy:%s\n", buf);
        printf("wjk: ");
        fflush(stdout);  // 刷新输出缓冲区,确保提示正常显示
    }
}
int main(int argc, char **argv)
{
  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == sockfd)
  {
    perror("socket");
    return 1;
  }
   pthread_t tid;
   if (pthread_create(&tid, NULL, recv_thread, NULL) != 0) 
   {
        perror("pthread_create failed");
        close(sockfd);
        return 1;
    }
  // man 7 ip
  ser.sin_family = AF_INET;
  // host to net short
  ser.sin_port = htons(50000);
  ser.sin_addr.s_addr = inet_addr("192.168.1.23");
  while (1)
  {
    char buf[1024]={0};
    printf("wjk:");
    fgets(buf, sizeof(buf), stdin);
    buf[strcspn(buf, "\n")] = '\0';
    sendto(sockfd,buf,strlen(buf),0,(SA)&ser,sizeof(ser));
    if (strcmp(buf, "#quit") == 0) 
    {
            printf("已退出聊天\n");
            close(sockfd);
            return 0;
    }
  } 
  close(sockfd);
  return 0;
}

服务端:

cs 复制代码
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
typedef struct sockaddr * (SA);
int sockfd;
struct sockaddr_in ser, cli;
socklen_t len =sizeof(cli);;
void *recv_thread(void *arg) 
{
    char buf[1024] = {0};
    while (1) {
        // 接收客户端消息
        ssize_t n = recvfrom(sockfd, buf, sizeof(buf), 0, (SA)&cli, &len);
        if (n <= 0) {
            continue;  // 接收失败则继续等待
        }

        // 检测退出指令
        if (strcmp(buf, "#quit") == 0) {
            printf("\n客户端已退出聊天\n");
            close(sockfd);
            exit(0);  // 退出整个程序
        }

        // 显示收到的消息
        printf("\nwjk:%s\n", buf);
        printf("xxy: ");  // 提示输入回复
        fflush(stdout);  // 刷新输出缓冲区,确保提示正常显示
    }
}
int main(int argc, char **argv)
{
  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (-1 == sockfd)
  {
    perror("socket");
    return 1;
  }
    pthread_t tid;
   if (pthread_create(&tid, NULL, recv_thread, NULL) != 0) 
   {
        perror("pthread_create failed");
        close(sockfd);
        return 1;
    }
  // man 7 ip
  ser.sin_family = AF_INET;
  // host to net short
  ser.sin_port = htons(50000);
  ser.sin_addr.s_addr = inet_addr("192.168.1.23");
  int ret = bind(sockfd,(SA) &ser, sizeof(ser));
  if (-1 == ret)
  {
    perror("bind");
    return 1;
  }
  socklen_t len = sizeof(cli);
  while (1)
  {
    char buf[1024] = {0};
    printf("xxy:");
    fgets(buf, sizeof(buf), stdin);
    buf[strcspn(buf, "\n")] = '\0';
    sendto(sockfd,buf,strlen(buf),0,(SA)&cli,len);
    if (strcmp(buf, "#quit") == 0) 
    {
            printf("已退出\n");
            close(sockfd);
            return 0;
        }
  }

  return 0;
}

①两端通信为服务端先运行,

②采用线程(pthread_create)的目的:同时处理接收消息和发送消息

③采用while(1)循环 目的:是为了实现可以持续接收和发送信息

总结:线程与循环的配合逻辑

  • 接收线程 :用while(1)循环 "专职" 接收客户端消息,确保消息不被遗漏,且不阻塞主线程的输入操作。
  • 主线程 :用while(1)循环 "专职" 处理用户输入和发送消息,确保可以连续发送多条消息。

重点理解while循环:

核心逻辑是 while ((n = read(fd, buf, sizeof(buf))) > 0) 循环 ------ 它的行为是 "读一点、发一点、收一点、写一点",即每次从文件中读取一块数据(最多 1024 字节),立即发送给服务端,再接收服务端的反馈,最后将反馈数据写入目标文件,直到文件全部读完。

循环自动执行下一次的原理

循环的核心是条件判断数据读取的连续性

  1. 每次循环结束后,自动回到条件判断

    当循环体内的代码(发送、接收、写入)执行完毕后,程序会自动回到循环开头,重新执行 n = read(fd, buf, sizeof(buf))

  2. read 函数会 "记住" 上一次的读取位置

    操作系统会为打开的文件(fd)维护一个 "文件指针",记录当前读取到的位置。每次 read 后,指针会自动移动到下一个未读取的字节。

    • 例如:第一次读取 1024 字节后,指针指向第 1025 字节;
    • 下一次 read 会从第 1025 字节开始读取,直到文件末尾。
  3. 循环终止条件

    read 返回 0(文件读完)或 -1(读取错误)时,n > 0 条件不成立,循环自动退出,不会进行下一次。

while 循环的自动迭代特性和文件指针的自动移动,实现了 "读完一块数据后,自动进行下一次循环处理下一块数据",直到整个文件传输完成。无需添加额外代码控制 "下一次循环",循环会根据文件读取状态自动执行或终止。


五,服务器 - 客户端(Server-Client)模型:

C/S(Client/Server,客户端 / 服务器) 和 B/S(Browser/Server,浏览器 / 服务器) 是两种

(1)C/S 模型(Client/Server,客户端 / 服务器)

C/S 是传统的服务器 - 客户端架构,其核心是 "专用客户端软件 "------ 用户必须在设备上安装专门开发的客户端程序,才能与服务器进行交互。

1. 核心结构

C/S 架构由 "客户端""服务器" 两个独立组件构成,二者分工明确:

  • 客户端(Client) :安装在用户本地设备(PC、手机、嵌入式设备等)的专用软件,负责用户交互、本地数据处理、请求发起。例如:QQ 客户端、微信 PC 版、本地数据库客户端(Navicat)、游戏客户端(英雄联盟客户端)。
  • 服务器(Server) :部署在远程机房 / 云端的高性能设备 / 程序,负责核心业务逻辑处理、数据存储与管理、响应客户端请求。例如:QQ 消息服务器、游戏后台服务器、企业数据库服务器。

(2)B/S 模型(Browser/Server,浏览器 / 服务器)

B/S 是基于互联网发展的轻量化架构,其核心是 "浏览器作为通用客户端 "------ 用户无需安装专用软件,只需通过浏览器(如 Chrome、Edge、Safari)即可访问服务器,本质是 "C/S 的特殊形态(浏览器 = 通用客户端)"。


1. 核心结构

B/S 架构由 "浏览器(通用客户端)""Web 服务器""应用服务器 / 数据库服务器" 三层构成(部分场景 Web 服务器与应用服务器合并):

  • 浏览器(Browser) :用户本地设备上的通用软件(无需额外开发),负责用户交互、HTML/CSS/JS 渲染、请求发起。例如:用 Chrome 打开百度、淘宝、企业官网。
  • Web 服务器:负责接收浏览器的 HTTP/HTTPS 请求,返回静态资源(HTML、CSS、JS、图片)。例如:Nginx、Apache 服务器。
  • 应用服务器 / 数据库服务器:负责处理动态业务逻辑(如用户登录验证、订单计算)、数据存储与查询。例如:Java 的 Tomcat(应用服务器)、MySQL(数据库服务器)。

(3)C/S 与 B/S 核心差异对比

对比维度 C/S 模型 B/S 模型
客户端形态 专用软件(需安装) 通用浏览器(无需安装)
开发成本 高(多端客户端开发) 低(仅服务器端开发)
维护成本 高(客户端需逐个升级) 低(仅升级服务器)
交互效率 高(自定义协议,本地处理) 中等(HTTP 协议,依赖网络)
跨设备兼容性 差(需适配不同系统) 好(浏览器跨设备通用)
本地功能支持 强(可调用本地硬件:摄像头、打印机) 弱(受浏览器沙箱限制,需特殊权限)
典型代表 QQ、微信 PC 版、游戏客户端、Navicat 淘宝网页版、百度、企业官网、

(4)总结:如何选择 C/S 还是 B/S?

  • C/S:当场景需要 "高交互效率、强本地功能(如调用硬件)、高安全性",且用户设备相对固定(如企业内部员工设备)时,优先用 C/S(如工业控制软件、专业设计工具)。
  • B/S:当场景需要 "跨设备访问、低开发维护成本、用户无需安装软件",且对本地功能要求不高时,优先用 B/S(如互联网公共服务、轻量企业系统)。

六,Tcp(传输控制协议):流式套接字,数据传输连续,可靠传输,应答,自动重传,有链路,全双工通信

因为tcp协议为流式套接字,会存在数据粘包的问题:

**数据粘包:**发送方发送数据,接收方无法解析数据。

解决方法:1.人为设置数据边界

2.固定一次接收数据大小

3.自定义协议。

(1)TCP 的核心特性

TCP 的设计围绕 "可靠性" 和 "有序性" 展开,核心特性可概括为以下 5 点:

特性 核心作用 实现机制简要说明
面向连接 确保通信双方 "准备就绪" 后再传输数据,避免无效发送 需通过 "三次握手" 建立连接,"四次挥手" 关闭连接,连接状态保存在双方的 TCP 协议栈中
可靠传输 保证数据 "不丢失、不重复、无差错" 到达接收方 1. 确认应答(ACK):接收方收到数据后回复确认; 2. 超时重传:发送方未按时收到 ACK 则重发
有序传输 保证数据按发送顺序到达(TCP 是 "字节流协议",需维护字节的顺序) 1. 序号(Sequence Number):为每个字节分配唯一序号; 2. 确认号(Acknowledgment Number):告知发送方 "已收到到哪个序号的数据"
流量控制 避免接收方因处理能力不足导致数据溢出("接收方跟不上发送方") 滑动窗口(Sliding Window):接收方通过 TCP 头部的 "窗口大小" 字段,告知发送方可连续发送的字节数
拥塞控制 避免网络因数据量过大导致拥堵("发送方超过网络承载能力") 4 种算法:慢启动、拥塞避免、快重传、快恢复,动态调整发送方的 "拥塞窗口" 大小

(2)TCP 的关键工作流程:三次握手和四次挥手

在 TCP(传输控制协议)的通信流程中,

三次握手(Three-Way Handshake) 是建立可靠连接的核心过程

四次挥手(Four-Way Wavehand) 是关闭连接的标准流程。

两者均基于 "请求 - 应答" 机制,确保通信双方的状态同步,是 TCP 实现 "可靠、面向连接" 特性的关键。


1、三次握手:建立 TCP 连接

TCP 是面向连接的协议,通信前必须先建立双向连接(客户端和服务器都确认 "能发也能收"),三次握手的本质是 "双方交换初始序列号(ISN)并确认对方可达"。

核心背景
  • 通信双方:通常是发起连接的客户端(Client) 和等待连接的服务器(Server)
  • 关键标志位:TCP 头部中的 3 个控制位用于握手:
    • SYN(Synchronize):请求同步,用于发起连接、传递初始序列号。
    • ACK(Acknowledgment):确认应答,用于告知对方 "已收到你的数据",附带确认号(收到数据的下一个期望序号)。
  • 初始序列号(ISN):TCP 为每个连接分配随机的初始序列号(避免历史数据干扰),后续数据的序号从 ISN 开始递增。
三次握手具体流程(步骤拆解)
步骤 发起方 接收方 传递的关键信息(TCP 头部) 核心目的
1 客户端 服务器 SYN=1,ISN=C(客户端初始序号) 客户端向服务器 "请求建立连接",告知自己的初始序列号,此时客户端进入 SYN_SENT 状态。
2 服务器 客户端 SYN=1,ACK=1,ISN=S(服务器初始序号),确认号 = C+1 服务器 "应答客户端的连接请求": 1. 用 SYN=1 告知客户端自己的初始序列号; 2. 用 ACK=1 和确认号 C+1 表示 "已收到客户端的 SYN 包",此时服务器进入 SYN_RCVD 状态。
3 客户端 服务器 ACK=1,确认号 = S+1 客户端 "确认收到服务器的 SYN+ACK 包",用确认号 S+1 告知服务器 "你的初始序号我已接收",此时客户端进入 ESTABLISHED(连接建立)状态;服务器收到该 ACK 后,也进入 ESTABLISHED 状态,双方可开始传输数据。
为什么需要 "三次"?(而非两次)

核心是确保双方的 "发送能力" 和 "接收能力" 都正常

  • 若只做 "两次握手"(客户端发 SYN → 服务器发 SYN+ACK,连接直接建立),服务器无法确认 "客户端是否能收到自己的 SYN+ACK"------ 如果客户端的 ACK 丢失,服务器会误以为连接已建立,持续等待数据,造成资源浪费;
  • 第三次握手的 ACK,能让服务器明确 "客户端既能发(第一步 SYN),也能收(第二步 SYN+ACK)",双方状态完全同步,避免无效连接。

2、四次挥手:关闭 TCP 连接

当通信结束后,双方需通过四次交互关闭连接(因 TCP 是全双工通信,双方需分别关闭 "发送通道" 和 "接收通道")。

关键标志位

除了握手时的 SYN、ACK,关闭连接新增 1 个控制位:

  • FIN(Finish):表示 "我已无数据要发送,请求关闭我的发送通道"。
四次挥手具体流程(步骤拆解)

假设客户端先发起关闭请求(实际双方均可主动发起),流程如下:

步骤 发起方 接收方 传递的关键信息(TCP 头部) 核心目的
1 客户端 服务器 FIN=1,ACK=1,确认号 = S_last+1(S_last 是服务器最后发的序号) 客户端 "请求关闭自己的发送通道":告知服务器 "我不再发数据了,但还能收你的数据",此时客户端进入 FIN_WAIT_1 状态。
2 服务器 客户端 ACK=1,确认号 = C_last+1(C_last 是客户端最后发的序号) 服务器 "确认收到客户端的关闭请求":告知客户端 "我知道你要关发送通道了,我会继续发剩余数据",此时服务器进入 CLOSE_WAIT 状态;客户端收到 ACK 后,进入 FIN_WAIT_2 状态(等待服务器的剩余数据)。
3 服务器 客户端 FIN=1,ACK=1,确认号 = C_last+1 服务器 "发送完所有数据后,请求关闭自己的发送通道":告知客户端 "我也没数据要发了,我的发送通道也关了",此时服务器进入 LAST_ACK 状态(等待客户端的最终确认)。
4 客户端 服务器 ACK=1,确认号 = S_last_new+1(S_last_new 是服务器 FIN 包的序号) 客户端 "确认收到服务器的关闭请求":告知服务器 "我知道你也关了发送通道,双方通道均关闭",此时客户端进入 TIME_WAIT 状态;服务器收到 ACK 后,立即进入 CLOSED 状态(连接完全关闭)。
关键细节:TIME_WAIT 状态的作用

客户端发送第四次 ACK 后,不会立即关闭,而是进入 TIME_WAIT 状态(默认时长为 2 倍的 TCP 最大段生命周期,即 2MSL,通常约 1-4 分钟),目的是:

  1. 确保服务器能收到第四次 ACK:若第四次 ACK 丢失,服务器会重发 FIN 包,客户端在 TIME_WAIT 内可再次发送 ACK,避免服务器因未收到确认而持续等待;
  2. 避免历史连接干扰新连接:TIME_WAIT 期间,客户端会丢弃该连接端口上的所有旧数据报,防止旧连接的残留数据被新连接误接收。

3、三次握手 vs 四次挥手:核心差异

对比维度 三次握手(建立连接) 四次挥手(关闭连接)
核心目的 建立双向连接,同步初始序列号 关闭双向通道,释放资源
交互次数 3 次 4 次(因 FIN 和 ACK 需分开发送)
关键标志位 主要用 SYN、ACK 主要用 FIN、ACK
状态流转终点 双方均进入 ESTABLISHED(连接就绪) 双方均进入 CLOSED(连接释放)
是否有 TIME_WAIT

七、TCP 与 UDP 的核心区别

TCP 和 UDP 同属传输层协议,但设计目标完全不同,实际应用中需根据需求选择。两者的核心区别如下表:

对比维度 TCP(传输控制协议) UDP(用户数据报协议)
连接类型 面向连接(需三次握手建立连接) 无连接(直接发送,无需建立连接)
传输可靠性 可靠(不丢失、不重复、有序) 不可靠(可能丢失、重复、无序)
传输方式 字节流(无数据边界,需应用层自行处理) 数据报(有明确边界,一次发送一个数据报)
流量控制 / 拥塞控制 支持(滑动窗口 + 拥塞窗口) 无拥塞控制(发送方无限制发送,易导致网络拥堵)
头部开销 较大(固定 20 字节,可选扩展) 较小(固定 8 字节)
适用场景 需可靠传输的场景(HTTP/HTTPS、FTP、SSH) 需低延迟 / 高并发的场景(DNS、直播、游戏)
  • sizeof(buf)

    • 运算符 (不是函数),用于计算变量 / 类型所占用的内存字节数
    • 计算的是分配给变量的总内存空间,与内存中实际存储的数据无关。
  • strlen(buf)

    • 库函数 (需要包含 <string.h>),用于计算字符串的长度
    • 计算的是从首地址开始到第一个 '\0'(字符串结束符)之间的字符数 ,不包含 '\0' 本身。

以复制一个图片为例:

(1)server(服务端)

1.socket:创建套接字,打开网路设备

2.bind :给套接字,设置ip+port(地址+端口)

3.listen:(创建监听套接字)使套接字进入监听

4.accept:阻塞等待与接收客户端产生链接(三次握手)

5.recvform :接收客服端发送文件

6. sendto:送回客服端

服务端先运行的到accept位置等待接受请求,在接受到客户端请求后,运行后面的接收,发送

cs 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include<arpa/inet.h>
#include<fcntl.h>
typedef struct sockaddr * (SA);
int	main(int argc, char **argv)
{
    //监听套接字
    //AF_INET为互联网类型网络
    //SOCK_STREAM为tcp端口类型(可靠)
    int listfd=socket(AF_INET,SOCK_STREAM,0);
    if(-1==listfd)
    {
        perror("socket error\n");
        return 1;
    }
    int fd_s=open("5.png", O_WRONLY | O_CREAT | O_TRUNC, 0666);
    if(-1==fd_s)
    {
        perror("open error\n");
        return 1;
    }
    //man 7 ip 地址结构体
    struct sockaddr_in ser,cli;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(50000);
    //ser.sin_addr.s_addr=inet_addr("127.0.0.1");
    ser.sin_addr.s_addr=INADDR_ANY;
    //创建ip和端口
    int ret=bind(listfd,(SA)&ser,sizeof(ser));
    if(-1==ret)
    {
        perror("bind");
        return 1;
    }
    //第二个参数不是可以链接客户端的个数,而是三次握手一次能接纳客户端的排队个数
    listen(listfd,3);
    socklen_t len=sizeof(cli);
    //通信套接字conn;
    //accept阻塞等待接收客户端的链接
    //几个客户端就有几个套接字
    int conn=accept(listfd,(SA)&cli,&len);
    if(-1==conn)
    {
        perror("accept");
        return 1;
    }
    char buf[1024]={0};
    while(1)
    {
        //sizeof():buf占用内存字节
        int ret=recv(conn,buf,sizeof(buf),0);
        if(ret<=0)
        {
            break;
        }
        write(fd_s,buf,ret);
        //sprintf(buf,"%s %s",buf);
        //strlen():字符串大小
        bzero(buf,sizeof(buf));

        send(conn,buf,ret,0);
    }
    close(listfd);
    close(conn);



    //system("pause");
    return 0;
}

(2)client(客户端)

1.socket,打开网络设备

2.connect:主动发起链接(三次握手)

3. sendto:向服务端发送信息

4.recvform:接受服务端传回的信息

cs 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<unistd.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include<arpa/inet.h>
#include<time.h>
#include<fcntl.h>
typedef struct sockaddr * (SA);
int	main(int argc, char **argv)
{
    int conn=socket(AF_INET,SOCK_STREAM,0);
    if(-1==conn)
    {
        perror("socket");
        return 1;
    }
    struct sockaddr_in ser,cli;
    ser.sin_family=AF_INET;
    ser.sin_port=htons(50000);
    //ser.sin_addr.s_addr=inet_addr("127.0.0.1");
    ser.sin_addr.s_addr=INADDR_ANY;
    int ret=connect(conn,(SA)&ser,sizeof(ser));
    if(ret==-1)
    {
        perror("connect error\n");
        return 1;
    }
    int fd=open("3.png",O_RDONLY);
    if(-1==fd)
    {
        perror("open error\n");
        return 1;
    }
    ssize_t n;
    char buf[1024]={0};
    while((n=read(fd,buf,sizeof(buf)))>0)
    {
        if(n<=0)
        {
            break;
        }
        send(conn,buf,n,0);
        bzero(buf,sizeof(buf));
        //小阻塞
        recv(conn,buf,sizeof(buf),0);
    }
    printf("接收完成\n");
    close(conn);
    //system("pause");
    return 0;
}

程序:tcp 聊天。实时收发,#quit 双方都能退出。

服务端:

cs 复制代码
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
typedef struct sockaddr * (SA);
int listfd;
int conn;
struct sockaddr_in ser, cli;
socklen_t len =sizeof(cli);;
void *recv_thread(void *arg) 
{
    char buf[1024] = {0};
    while (1) {
        // 接收客户端消息
        ssize_t n = recvfrom(conn, buf, sizeof(buf), 0, (SA)&cli, &len);
        if (n <= 0) {
            continue;  // 接收失败则继续等待
        }

        // 检测退出指令
        if (strcmp(buf, "#quit") == 0) {
            printf("\n客户端已退出聊天\n");
            close(conn);
            exit(0);  // 退出整个程序
        }

        // 显示收到的消息
        printf("\nwjk:%s\n", buf);
        printf("xxy: ");  // 提示输入回复
        fflush(stdout);  // 刷新输出缓冲区,确保提示正常显示
    }
}
int main(int argc, char **argv)
{
  listfd = socket(AF_INET, SOCK_STREAM, 0);
  if (-1 == listfd)
  {
    perror("socket");
    return 1;
  }
  // man 7 ip
  ser.sin_family = AF_INET;
  // host to net short
  ser.sin_port = htons(50000);
  ser.sin_addr.s_addr=INADDR_ANY;
  int ret = bind(listfd,(SA) &ser, sizeof(ser));
  if (-1 == ret)
  {
    perror("bind");
    return 1;
  }
  listen(listfd,3);
  socklen_t len = sizeof(cli);
  conn=accept(listfd,(SA)&cli,&len);
  if(-1==conn)
  {
    perror("accept");
    return 1;
  }
  pthread_t tid;
   if (pthread_create(&tid, NULL, recv_thread, NULL) != 0) 
   {
        perror("pthread_create failed");
        close(conn);
        return 1;
    }
  while (1)
  {
    char buf[1024] = {0};
    printf("xxy:");
    fgets(buf, sizeof(buf), stdin);
    buf[strcspn(buf, "\n")] = '\0';
    sendto(conn,buf,strlen(buf),0,(SA)&cli,len);
    if (strcmp(buf, "#quit") == 0) 
    {
            printf("已退出\n");
            close(conn);
            return 0;
        }
  }

  return 0;
}

客户端:

cs 复制代码
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#include <pthread.h>
typedef struct sockaddr * (SA);
int conn;
struct sockaddr_in ser;
socklen_t len = sizeof(ser);
void *recv_thread(void *arg) 
{
   char buf[1024];
    while (1)
    {
        bzero(buf, sizeof(buf));
        // 阻塞接收服务端消息(在独立线程中不影响主线程发送)
        recvfrom(conn, buf, sizeof(buf), 0, (SA)&ser, &len);
        
        // 检测服务端退出指令
        if (strcmp(buf, "#quit") == 0) 
        {
            printf("\n服务端已退出聊天\n");
            close(conn);
            exit(0);  // 退出整个程序
        }
        // 显示收到的消息,并提示用户输入
        printf("\nxxy:%s\n", buf);
        printf("wjk: ");
        fflush(stdout);  // 刷新输出缓冲区,确保提示正常显示
    }
}
int main(int argc, char **argv)
{
  conn= socket(AF_INET, SOCK_STREAM, 0);
  if (-1 == conn)
  {
    perror("socket");
    return 1;
  }
   pthread_t tid;
   if (pthread_create(&tid, NULL, recv_thread, NULL) != 0) 
   {
        perror("pthread_create failed");
        close(conn);
        return 1;
    }
  // man 7 ip
  ser.sin_family = AF_INET;
  // host to net short
  ser.sin_port = htons(50000);
  ser.sin_addr.s_addr=INADDR_ANY;
    int ret=connect(conn,(SA)&ser,sizeof(ser));
    if(ret==-1)
    {
        perror("connect error\n");
        return 1;
    }
  while (1)
  {
    char buf[1024]={0};
    printf("wjk:");
    fgets(buf, sizeof(buf), stdin);
    buf[strcspn(buf, "\n")] = '\0';
    sendto(conn,buf,strlen(buf),0,(SA)&ser,sizeof(ser));
    if (strcmp(buf, "#quit") == 0) 
    {
            printf("已退出聊天\n");
            close(conn);
            return 0;
    }
  } 
  close(conn);
  return 0;
}

①两端通信为服务端先运行,

②采用线程(pthread_create)的目的:同时处理接收消息和发送消息

③采用while(1)循环 目的:是为了实现可以持续接收和发送信息

总结:线程与循环的配合逻辑

  • 接收线程 :用while(1)循环 "专职" 接收客户端消息,确保消息不被遗漏,且不阻塞主线程的输入操作。
  • 主线程 :用while(1)循环 "专职" 处理用户输入和发送消息,确保可以连续发送多条消息。
相关推荐
Want5954 小时前
C/C++哆啦A梦
c语言·开发语言·c++
Nexmoe5 小时前
我踩过最深的 React 数据沉钻坑,以及我现在偷懒写法
开发语言·javascript·ecmascript
野犬寒鸦6 小时前
力扣hot100:缺失的第一个正数(哈希思想)(41)
java·数据结构·后端·算法·leetcode·哈希算法
守.护6 小时前
云计算学习笔记——Linux系统网络配置与远程管理(ssh)篇
linux·运维·服务器·ssh·linux网络配置
计算机毕业设计木哥6 小时前
计算机Python毕业设计推荐:基于Django的酒店评论文本情感分析系统【源码+文档+调试】
开发语言·hadoop·spring boot·python·spark·django·课程设计
津津有味道7 小时前
15693协议ICODE SLI 系列标签应用场景说明及读、写、密钥认证操作Qt c++源码,支持统信、麒麟等国产Linux系统
linux·c++·qt·icode·sli·15693
闪电麦坤957 小时前
数据结构:开放散列(Open Hashing)
数据结构·算法·哈希算法·散列表
一枝小雨7 小时前
【C++】编写通用模板代码的重要技巧:T()
开发语言·c++·笔记·学习笔记
Lynnxiaowen8 小时前
今天我们继续学习shell编程语言的内容
linux·运维·学习·云计算·bash