网络编程day04(UDP、Linux IO 模型)

news2024/12/23 8:45:35

目录

【1】UDP

1》通信流程

2》函数接口 

1> recvfrom

2> sendto

 3》代码展示

1> 服务器代码

2> 客户端代码

 【2】Linux IO 模型

场景假设一

 1》阻塞式IO:最常见、效率低、不耗费CPU

2》 非阻塞 IO:轮询、耗费CPU,可以处理多路IO

 设置非阻塞的方式

1> 通过函数自带参数设置

2> 通过设置文件描述符的属性,把文件描述符的属性设置为非阻塞 

3》信号驱动IO/异步IO:异步通知方式,需要底层驱动的支持

 4》三种模型对比


【1】UDP

1》通信流程

服务器----------------------------------------------------------------------------》短信的接收方

  1. 创建数据报套接字(socket)------------------》有手机
  2. 指定网络信息--------------------------------------》有号码
  3. 绑定套接字(bind)------------------------------》绑定手机
  4. 接收、发送消息(recvfrom sendto)-------》收短信
  5. 关闭套接字(close)----------------------------》接收完毕

客户端---------------------------------------------------------------------------》短信的发送方

  1. 创建数据报套接字(socket)------------------》有手机
  2. 指定网络信息--------------------------------------》有对方号码
  3. 接收、发送消息(recvfrom sendto)-------》发短信
  4. 关闭套接字(close)----------------------------》发送完毕

2》函数接口 

1> recvfrom

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,

struct sockaddr *src_addr, socklen_t *addrlen);

功能:接收数据

参数:

sockfd:套接字描述符

buf:接收缓存区的首地址

len:接收缓存区的大小

flags:0

src_addr:发送端的网络信息结构体的指针

addrlen:发送端的网络信息结构体的大小的指针

返回值:

成功接收的字节个数

失败:-1

0:客户端退出

2> sendto

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

功能:发送数据

参数:

sockfd:套接字描述符

buf:发送缓存区的首地址

len:发送缓存区的大小

flags:0

src_addr:接收端的网络信息结构体的指针

addrlen:接收端的网络信息结构体的大小

返回值:

成功发送的字节个数

失败:-1

 3》代码展示

1> 服务器代码

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

int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    int ret;
    // 1.创建数据报套接字(socket)------------------》有手机
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {

        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    // 2.指定网络信息--------------------------------------》有号码
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_I
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = INADDR_ANY;
    int len = sizeof(caddr);

    // 3.绑定套接字(bind)------------------------------》绑定手机
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind okk\n");
    // 4.接收、发送消息(recvfrom sendto)-------》收短信
    while (1)
    {
        // 最后两个参数存放:发送消息的人的信息
        ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &len);
        if (ret < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        else
        {
            printf("ip:%s port:%d buf:%s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port), buf);
            memset(buf, 0, sizeof(buf));
        }
    }

    // 5.关闭套接字(close)----------------------------》接收完毕
    close(sockfd);
    return 0;
}

2> 客户端代码

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

int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    int ret;
    // 1.创建数据报套接字(socket)------------------》有手机
    int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {

        perror("socket err");
        return -1;
    }
    printf("sockfd:%d\n", sockfd);
    // 2.指定网络信息--------------------------------------》有号码
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = inet_addr("192.168.253.145");
    int len = sizeof(caddr);

    // 4.接收、发送消息(recvfrom sendto)-------》收短信
    while (1)
    {
        fgets(buf, sizeof(buf), stdin);
        if (buf[strlen(buf) - 1] == '\n')
            buf[strlen(buf) - 1] = '\0';
        if (strcmp(buf, "quit") == 0)
        {
            break;
        }
        sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&saddr, sizeof(saddr));
        memset(buf,0,sizeof(buf));
    }

    // 5.关闭套接字(close)----------------------------》接收完毕
    close(sockfd);
    return 0;
}

注意:

1.对于TCP是先运行服务器,客户端才能运行

2.对于UDP来说,服务器和客户端运行顺序没有先后,因为是无连接的,所以服务器和客户端谁先开始,没有关系

3.一个服务器可以同时连接多个客户端,想知道是哪个客户端登录,可以在服务器代码里加上打印IP和端口号的代码

4.UDP,客户端当使用send的时候,上面要加上connect,这个connect不是代表连接的作用,而是指定客户端即将要发送给谁数据,这样就不需要使用sendto而是用send就可以了

5.在TCP里,也可以使用recvfrom和sendto,使用时将后面的两个参数都写为NULL即OK

 【2】Linux IO 模型

4种:阻塞IO、非阻塞IO、信号驱动IO(异步IO)、IO多路复用

场景假设一

假设妈妈有一个孩子,孩子在房间里睡觉,妈妈需要及时获知孩子是否醒了,如何做?

  1. 一直看着他,一直在一个房间呆着:不累,但是不能处理其他的事情
  2. 时不时的进房间看看:累,但是可以处理其他事情
  3. 睡觉,听孩子哭不哭:互不耽误

 1》阻塞式IO:最常见、效率低、不耗费CPU

阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。

缺省情况下(及系统默认状态),套接字建立后所处于的模式就是阻塞I/O 模式。

学习的读写函数在调用过程中会发生阻塞相关函数如下:

•读操作中的read、recv、recvfrom

读阻塞--》需要读缓冲区中有数据可读,读阻塞解除

•写操作中的write、send

写阻塞--》阻塞情况比较少,主要发生在写入的缓冲区的大小小于要写入的数据量的情况下,写操作不进行任何拷贝工作,将发生阻塞,一旦缓冲区有足够的空间,内核将唤醒进程,将数据从用户缓冲区拷贝到相应的发送数据缓冲区。

注意:sendto没有写阻塞

1)无sendto函数的原因:

sendto不是阻塞函数,本身udp通信不是面向链接的,udp无发送缓冲区,即sendto没有发送缓冲区,send是有发送缓存区的,即sendto不是阻塞函数。

2)UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在缓冲区满的情况,在UDP套接字上进行写操作永远不会阻塞。

•其他操作:accept、connect

udp丢包

 tcp粘包

tcp拆包 

 TCP粘包、拆包发生原因:

发生TCP粘包或拆包有很多原因,常见的一下几点:

1.要发送的数据大于TCP发送缓存区剩余空间大小,将发生拆包

2.待发送数据大于MSS(传输层的最大报文长度),将进行拆包(到网络层拆包-idipflags)

3.要发送的数据小于TCP发送缓存区的大小,TCP将多次写入缓存区的数据一次发送出去,将会发生粘包

4.接收数据端的应用层没有及时读取缓冲区中的数据,将发生粘包


粘包解决方法:

解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下:

1.发送端给每个数据包添加首部,首部中应该至少包含数据包的长度,这样接收端在接受到数据后,通过读取包首部的长度字段,便可以知道每一个数据报的实际长度了

2.发送端将每个数据包封装为固定长度,这样接收端每次从接受缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

3.可以再数据包之间设置边界,如添加特殊符号,这样接收端通过这个边界既可以将不同的数据包拆分开来。

4.延时发送

2》 非阻塞 IO:轮询、耗费CPU,可以处理多路IO

•当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”

•当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。

•应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。

•这种模式使用中不普遍。

 

 设置非阻塞的方式

1> 通过函数自带参数设置

2> 通过设置文件描述符的属性,把文件描述符的属性设置为非阻塞 

int fcntl(int fd, int cmd, ... /* arg */ );

功能:设置文件描述符属性

参数:

fd:文件描述符

cmd:设置方式 - 功能选择

F_GETFL 获取文件描述符的状态信息 第三个参数化忽略

F_SETFL 设置文件描述符的状态信息 通过第三个参数设置

O_NONBLOCK 非阻塞

O_ASYNC 异步

O_SYNC 同步

arg:设置的值 in

返回值:

特殊选择返回特殊值 - F_GETFL 返回的状态值(int)

其他:成功0 失败-1,更新errno

使用:0为例

0-原本:阻塞、读权限 修改或添加非阻塞

int flags=fcntl(0,F_GETFL);//1.获取文件描述符原有的属性信息

flags = flags | O_NONBLOCK;//2.修改添加权限

fcntl(0,F_SETFL,flags); //3.将修改好的权限设置回去

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{
    //1.获取文件描述符的原有属性
    int flags = fcntl(0,F_GETFL);
    //2.修改文件描述符的属性
    flags = flags | O_NONBLOCK;
    //3.设置文件描述符的属性
    fcntl(0,F_SETFL,flags);
    char buf[32];
    while(1)
    {
        if(fgets(buf,sizeof(buf),stdin) == NULL)
        {
            perror("err\n");
        }
        else
        {
            printf("buf: %s\n",buf);
        }
        sleep(1);
    }
    return 0;
}

3》信号驱动IO/异步IO:异步通知方式,需要底层驱动的支持

异步通知:异步通知是一种非阻塞的通知机制,发送方发送通知后不需要等待接收方的响应或确认。确认发送后,发送方可以继续执行其他操作,而无需等待接收方处理通知

1.通过信号方式,当内核检测到设备数据后,会主动给应用发送信号SIGIO

2.应用程序收到信号后做异步处理即可

应用程序需要把自己的进程号告诉内核,并打开异步通知机制

//1.设置将文件描述符和进程号提交给内核驱动

//一旦fd有事件响应, 则内核驱动会给进程号发送一个SIGIO的信号

fcntl(fd,F_SETOWN,getpid());

//2.设置异步通知

int flags;

flags = fcntl(fd, F_GETFL); //获取原属性

flags |= O_ASYNC; //给flags设置异步 O_ASUNC 通知

fcntl(fd, F_SETFL, flags); //修改的属性设置进去,此时fd属于异步

//3.signal捕捉SIGIO信号 --- SIGIO:内核通知会进程有新的IO信号可用

//一旦内核给进程发送sigio信号,则执行handler

signal(SIGIO,handler);

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <signal.h>

int fd;

void handler(int sig)
{
    char buf[32];
    printf("------------------------\n");
    read(fd, buf, sizeof(buf));
    printf("mouse: %s\n", buf);
}

int main(int argc, char const *argv[])
{
    fd = open("/dev/input/mouse1", O_RDONLY);
    if (fd < 0)
    {
        perror("fd err\n");
        return -1;
    }
    // 1.将进程号和文件描述符交给内核
    fcntl(fd, __F_SETOWN, getpid());
    // 2.设置异步通知
    int flags = fcntl(fd, F_GETFL);
    flags = flags | O_ASYNC;
    fcntl(fd, F_SETFL, flags);
    // 3.捕捉信号,做逻辑处理
    signal(SIGIO, handler);

    while (1)
    {
        printf("111\n");
        sleep(1);
    }
    return 0;
}

 4》三种模型对比

 

阻塞 IO(Blocking IO)非阻塞 IO(Non-blocking IO)信号驱动 IO(Signal-driven IO)
同步性同步非同步异步
描述调用IO操作的线程会被阻塞,直到操作完成调用IO操作时,如果不能立即完成操作,会立即返回,线程可以继续执行其他操作当IO操作可以进行时,内核会发送信号,通知进程
特点最常见、效率低、不耗费CPU轮询、耗费CPU,可以处理多路IO,效率高

异步通知方式,需要底层驱动的支持

适应场景小规模IO操作,对性能要求不高高并发网络服务器,减少线程阻塞时间实时性要求高的应用,避免轮询开销

 今天的分享就到这里结束啦,如果有哪里写的不好的地方,请指正。
如果觉得不错并且对你有帮助的话点个关注支持一下吧!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2119375.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【C++ 面试 - 新特性】每日 3 题(三)

✍个人博客&#xff1a;Pandaconda-CSDN博客 &#x1f4e3;专栏地址&#xff1a;http://t.csdnimg.cn/fYaBd &#x1f4da;专栏简介&#xff1a;在这个专栏中&#xff0c;我将会分享 C 面试中常见的面试题给大家~ ❤️如果有收获的话&#xff0c;欢迎点赞&#x1f44d;收藏&…

RPKI应急管控网络拓扑搭建

应急管控网络拓扑搭建 一、网络拓扑图 二、拓扑配置 1.资源库批量导入roas 在rpki.qcl.edu.cn服务器上的/usr/local/rpki/目录下执行脚本 sh roa_get.sh add#!/bin/dash# TODO Aadd Rremove start10000 sum254 run(){for i in seq 1 20dofor j in seq 1 250doas_numberexpr…

正点原子阿尔法ARM开发板-IMX6ULL(三)——汇编LED驱动实验-上

文章目录 一、原理分析1.1 对于IMX6ULL的IO初始化1.2 IO的复用&#xff08;MUX&#xff09;1.3 电气属性寄存器&#xff08;PAD&#xff09;1.3.1 SRE(bit0)1.3.2 DSE(bit5:3)1.3.3 SPEED(bit7:6)1.3.4 ODE(bit11)1.3.5 PKE(bit12)1.3.6 PUE(bit13)1.3.7 PUS(bit15:14)1.3.8 HY…

6.5椒盐噪声

在OpenCV中联合C给一张图片加上椒盐噪声&#xff08;Salt and Pepper Noise&#xff09;可以通过随机选择像素点并将其置为黑色&#xff08;0&#xff09;或白色&#xff08;255&#xff09;来实现。椒盐噪声是一种随机噪声&#xff0c;通常表现为图像中的孤立黑点&#xff08;…

Windows环境下 VS2022 编译 LAME 源码

LAME LAME 是一个非常流行的开源 MP3 编码器库&#xff0c;它的全称是 “LAME Ain’t an MP3 Encoder”&#xff0c;这是一个带有讽刺意味的名字&#xff0c;因为 LAME 实际上是一个功能强大的 MP3 编码器。LAME 的开发始于 1998 年&#xff0c;目的是创建一个开放源代码的库&a…

AIPaperGPT写论文靠谱吗?

AIPaperGPT&#xff0c;论文写作神器~ https://www.aipapergpt.com/ 在信息爆炸的今天&#xff0c;学术写作的挑战日益增加&#xff0c;而AIPaperGPT作为一款旨在提升写作效率的工具&#xff0c;其可靠性自然成为了用户关注的焦点。本文将从多个维度对AIPaperGPT进行全面评估&…

(java+Seleniums3)自动化测试实战

一.web自动化测试基础 密码的加密处理--是在前端JavaScript 二.selenium IDE录制 打开火狐浏览器&#xff1a; 点击 寻找更多附加组件 输入&#xff1a; 选择&#xff1a; 跳转&#xff1a;点击 安装完成&#xff0c;打开之后是这个页面&#xff1a; 录制一个新的测试用例在一个…

黑马点评17——多级缓存-Lua语法

文章目录 Lua语法初始Lua变量和循环条件控制、函数 变量和循环函数和条件控制 Lua语法 初始Lua https://www.lua.org/ 魔兽的一些插件就是用lua开发的。 centOs已经装好了lua&#xff0c;直接用~ 变量和循环 条件控制、函数 变量和循环 函数和条件控制

python项目无法启动?在终端运行 manage.py runserver 8000 没反应该怎么解决

运行 manage.py runserver 8000 没反应&#xff0c;出现提示语句&#xff1a; 根据提示修改命令再次运行 .\manage.py runserver 8000 仍然没反应 解决办法&#xff1a; 1、添加当前目录到 PATH&#xff1a;临时将当前目录添加到 PATH 环境变量中。使用以下命令&#xff1a; …

如何理解有效值电流?电流的均方根值

电流的有效值就是电流的均方根。 有效值电流定义&#xff1a;将一直流电与一交流电分别通过相同阻值的电阻&#xff0c;如果相同时间内两电流通过电阻产生的热量相同&#xff0c;就说这一直流电的电流值是这一交流电的有效值。 如果说电流就是直流电&#xff0c;那么电流的有效…

一、数据结构和算法概述

文章目录 一、数据结构的介绍二、线性结构和非线性结构 一、数据结构的介绍 二、线性结构和非线性结构

Numpy中常用的数学方法

目录 1、数学运算符2、比较运算符3、常用的数学函数4、常用的统计函数 1、数学运算符 import numpy as npa np.array([10,4,6,7]) b np.arange(4) # 两数组值相加 cab # 数组的值平方 db**2 # 两数组对应的值相乘 ea*b # 两数组对应的值相除 fc/a # 两数组对应的值取余 gc…

JDBC的介绍续

四 JDBC的事务支持 4.1 银行转账案例演示 4.4.1 案例分析&#xff1a; 1.需求&#xff1a;一个账号fromAccount向另一个账号toAccount转入money元钱 2.分析&#xff1a; - 检查两个账号是否存在&#xff0c;不存在的话&#xff0c;结束转账行为 - 检查转出账号的里金…

N 皇后

题目 n 皇后问题 研究的是如何将 n 个皇后放置在 nn 的棋盘上&#xff0c;并且使皇后彼此之间不能相互攻击。 给你一个整数 n &#xff0c;返回所有不同的 n 皇后问题 的解决方案。 每一种解法包含一个不同的 n 皇后问题 的棋子放置方案&#xff0c;该方案中 ‘Q’ 和 ‘.’…

RP2040 C SDK 64位定时器功能使用

RP2040 C SDK 64位定时器功能使用 &#x1f9e8;RP2040的64位定时器功能介绍参见&#xff1a;https://www.raspberrypi.com/documentation/pico-sdk/hardware.html#group_hardware_timer &#x1f389;RP2040有一个单64位计数器&#xff0c;每微秒递增一次看起来很复杂&#xf…

[基于 Vue CLI 5 + Vue 3 + Ant Design Vue 3 搭建项目] 02 配置 nodejs 淘宝镜像仓库

文章目录 为什么要配置淘宝镜像仓库呢如何查看镜像仓库如何配置镜像仓库 为什么要配置淘宝镜像仓库呢 主要是因为默认的镜像仓库是国外的&#xff0c;当我们使用 npm 安装依赖的时候会很慢或者失败&#xff0c;我们配置国内的镜像仓库这样就可以加速我们安装镜像的过程&#x…

这些加密软件功能你都知道吗?

1.透明加密与无感操作&#xff1a; 透明加密是许多现代加密软件的核心功能之一&#xff0c;它允许用户在无感知的情况下对文件进行加密和解密。这意味着用户无需改变日常操作习惯&#xff0c;加密和解密过程在后台自动完成&#xff0c;确保了数据的安全性同时不影响工作效率。…

【阿雄不会写代码】全国职业院校技能大赛GZ036第十套

也不说那么多了&#xff0c;要用到这篇博客&#xff0c;肯定也知道他是干嘛的&#xff0c;给博主点点关注点点赞&#xff01;&#xff01;&#xff01;这样博主才能更新更多免费的教程&#xff0c;不然就直接丢付费专栏里了&#xff0c;需要相关文件请私聊

关于OceanBase MySQL 模式中全局索引 global index 的常见问题

在OceanBase的问答区和开源社区钉钉群聊中&#xff0c;时常会有关于全局索引 global index的诸多提问&#xff0c;因此&#xff0c;借这篇博客&#xff0c;针对其中一些普遍出现的问题进行简要的解答。 什么是 global index &#xff1f; 由于 MySQL 不具备 global index 的概…

利用TCP编程实现FTP功能

模拟FTP核心原理&#xff1a;客户端连接服务器后&#xff0c;向服务器发送一个文件。文件名可以通过参数指定&#xff0c;服务器端接收客户端传来的文件&#xff08;文件名随意&#xff09;&#xff0c;如果文件不存在自动创建文件&#xff0c;如果文件存在&#xff0c;那么清空…