嵌入式学习-网络-Day04

嵌入式学习-网络-Day04

1.IO多路复用

1.1poll

poll同时检测键盘和鼠标事件

1.2epoll

2.服务器模型

2.1循环服务器模型

2.2并发服务器模型

多进程模型

多线程模型

IO多路复用模型

网络聊天室

项目要求

问题思考

程序流程图

1.IO多路复用

1.1poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout);
   参数:
   struct pollfd *fds
     关心的文件描述符数组struct pollfd fds[N];
   nfds:个数
   timeout: 超时检测
    毫秒级的:如果填1000,1秒
     如果-1,阻塞 struct pollfd {
     int   fd;         /* 检测的文件描述符 */
     short events;     /* 检测事件 */
     short revents;    /* 调用poll函数返回填充的事件,poll函数一旦返回,将对应事件自动填充结构体这个成员。只需要判断这个成员的值就可以确定是否产生事件 */
 };
 事件:     POLLIN :读事件
           POLLOUT : 写事件
           POLLERR:异常事件

流程

select

poll

1.建立一个文件描述符的表

fd_set线性表

struct pollfd fds[n]结构体数组

2.将关心的文件描述符加到表中

FD_SET(fd,&readfds)

结构体内容填充fds[m].fd= fd

fds[m].events=POLLIN

3. 然后调用一个函数。 select / poll

4. 当这些文件描述符中的一个或多个已准备好进行I/O操作的时候

该函数才返回(阻塞)

select

poll

5.判断

FD_ISSET

revents==POLLIN

6.相关操作

poll同时检测键盘和鼠标事件

#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <signal.h>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
#include <poll.h>int main(int argc, char const *argv[])
{    // 打开文件
    int fd_mouse = open("/dev/input/mouse0", O_RDONLY);
    if (fd_mouse < 0)
    {
        perror("open err");
        return -1;
    }    // 1.创建表,创建结构体数组
    struct pollfd fds[100] = {};
    // 2.添加关心的文件描述符
    fds[0].fd = 0;
    fds[0].events = POLLIN;    fds[1].fd = fd_mouse;
    fds[1].events = POLLIN;    int last = 1;
    char buf[128] = {};
    // 3.循环调用poll
    int ret;
    while (1)
    {
        ret = poll(fds, last + 1, -1);
        if (ret < 0)
        {
            perror("poll err");
            return -1;
        }
        // 4.检测是哪一个文件描述符产生事件
        for (int i = 0; i <= last; i++)
        {
            if (fds[i].revents == POLLIN)
            {
                if (fds[i].fd == 0) // 键盘事件
                {
                    fgets(buf, sizeof(buf), stdin);
                    if (buf[strlen(buf) - 1] == '\n')
                        buf[strlen(buf) - 1] = '\0';
                    printf("key:%s\n", buf);
                }
                else if (fds[i].fd == fd_mouse)
                {
                    ret = read(fd_mouse, buf, sizeof(buf) - 1);
                    buf[ret] = '\0';
                    printf("buf:%s\n", buf);
                }
            }
        }
    }
    close(fd_mouse);
    return 0;
}

特点:

1.优化文件描述符个数的限制;(根据poll函数第一个参数来定,如果监听的事件为1个,则结构体数组元素个数为1,如果想监听100个,那么这个结构体数组的元素个数就为100,由程序员自己来决定)

2.poll被唤醒之后需要重新轮询一遍驱动的poll函数,效率比较低

3.poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

1.2epoll

特点:

  1. 监听的最大的文件描述符没有个数限制(理论上,取决与你自己的系统)
  2. 异步I/O,Epoll当有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接拿到唤醒的文件描述符,不需要轮询,效率高
  3. epoll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可.

2.服务器模型

2.1循环服务器模型

同一时刻只能响应一个客户端的请求

socket();
bind();
listen();
while(1)
{
    accept();
    while(1)
    {    process();//处理
    }
    close();
}

2.2并发服务器模型

同一时刻能响应多个客户端的请求,常用模型:多进程模型、多线程模型、IO多路复用

多进程模型

每来一个客户端连接,开一个子进程来专门处理客户端的数据,实现简单,但是系统开销相对较大,更推荐使用线程模型。

伪代码:

socket();
bind();
listen();
while(1)
{
    accept();if(fork()==0)//子进程{ while(1) {process();//处理}close();exit();}else{}}

多进程特点 :

  1. fork之前的代码被复制,但是不会重新执行一遍;fork之后的代码被复制,并且被再次执行一遍
  2. fork之后两个进程相互独立,子进程拷贝了父进程的所有代码,但是内存空间独立
  3. fork之前打开的文件,fork之后拿到的是同一个文件描述符,操作的是同一个文件指针

例:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <sys/types.h>
#include <unistd.h>int main(int argc, char const *argv[])
{
    printf("hello world\n");
    int a = 100;
    int fd = open("./poll.c", O_RDONLY);    pid_t pid = fork();
    if(pid < 0 )
    {
        perror("fork err:");
        return -1;
    }
    else if(pid  == 0)
    {
        char buf[32]="";
        a = 200;
        printf("child:a = %d\n",a);
        read(fd,buf,10);
        printf("buf:%s\n",buf);
        printf("childfd:%d\n",fd);
    }
    else
    {
        sleep(1);
        char buf[32]="";
        printf("father:a=%d\n",a);
        read(fd,buf,10);
        printf("fbuf:%s\n",buf);
        printf("fatherfd:%d\n",fd);
    }
    printf("---------%d\n",a);
    return 0;
}

注意:收到客户端消息后,打印下是来自哪个客户端的数据(来电显示)

使用SIGCHLD来处理子进程结束的信号,信号函数中回收进程资源。

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <signal.h>
#include <sys/wait.h>
#define N 128
void handler(int arg)
{
    waitpid(-1,NULL,WNOHANG);
}
int main(int argc, char const *argv[])
{
    if (argc != 2)
    {
        printf("please input %s  port\n", argv[0]);
        return -1;
    }    // 1.创建套接字         协议族    类型    协议
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err:");
        return -1;
    }
    // 2.填充结构体(ipv4)
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;            // 协议族ipv4
    saddr.sin_port = htons(atoi(argv[1])); // 端口号(网络字节序)
    // saddr.sin_addr.s_addr = inet_addr(argv[1]); // ip地址(网络字节序)
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");    socklen_t len = sizeof(caddr);
    // 3.绑定
    int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (ret < 0)
    {
        perror("bind err:");
        return -1;
    }    // 4.监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err");
        return -1;
    }    signal(SIGCHLD,handler);    while (1)
    {
        // 5.等待连接
        // int acceptfd = accept(sockfd, NULL, NULL);
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd < 0)
        {
            perror("accpet err:");
            return -1;
        }
         printf("acceptfd = %d\n", acceptfd);
        printf("client ip :%s,port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
        pid_t pid =fork();
        if(pid < 0)
        {
            perror("fork err");
            return -1;
        }
        else if(pid == 0)
        {
            close(sockfd);
            char buf[N]={};
            int ret;
            while(1)
            {
                ret  = recv(acceptfd,buf,N,0);
                if(ret  < 0 )
                {
                    perror("recv err");
                    return -1;
                }
                else if(ret  == 0)
                {
                    printf("client exit\n");
                    break;
                }
                else
                {
                    printf("buf:%s\n",buf);
                }
            }
            exit(-1);
        }
        close(acceptfd);
    }
    close(sockfd);
    return 0;
}

多线程模型

每来一个客户端连接,开一个子线程来专门处理客户端的数据,实现简单,占用资源较少,属于使用比较广泛的模型:

伪代码:

socket();
bind();
listen()
while(1)
{
    accept();
    pthread_create();}

多线程实现并发服务器:

#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>#define N 128void * mythread(void * arg)
{
    int  acceptfd = *(int *)arg;
    char buf[N]={};
    int ret;
    while(1)
    {   
        ret = recv(acceptfd,buf,N,0);
        if(ret < 0)
        {
            perror("recv err:");
            break;
        }
        else if(ret == 0)
        {
            printf("client exit\n");
            close(acceptfd);
            break;
        }
        else
        {
            printf("%d said:%s\n",acceptfd,buf);
        }    }
    pthread_exit(NULL);
}
int main(int argc, char const *argv[])
{
     if (argc != 2)
    {
        printf("please input %s  port\n", argv[0]);
        return -1;
    }    // 1.创建套接字         协议族    类型    协议
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("socket err:");
        return -1;
    }
    // 2.填充结构体(ipv4)
    struct sockaddr_in saddr, caddr;
    saddr.sin_family = AF_INET;            // 协议族ipv4
    saddr.sin_port = htons(atoi(argv[1])); // 端口号(网络字节序)
    // saddr.sin_addr.s_addr = inet_addr(argv[1]); // ip地址(网络字节序)
    saddr.sin_addr.s_addr = inet_addr("0.0.0.0");    socklen_t len = sizeof(caddr);
    // 3.绑定
    int ret = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));
    if (ret < 0)
    {
        perror("bind err:");
        return -1;
    }    // 4.监听
    if (listen(sockfd, 5) < 0)
    {
        perror("listen err");
        return -1;
    }    while (1)
    {
        // 5.等待连接
        // int acceptfd = accept(sockfd, NULL, NULL);
        int acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);
        if (acceptfd < 0)
        {
            perror("accpet err:");
            return -1;
        }
        printf("acceptfd = %d\n", acceptfd);
        printf("client ip :%s,port:%d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
        pthread_t tid;        pthread_create(&tid,NULL,mythread,&acceptfd);
        pthread_detach(tid);
    }
    close(sockfd);
    return 0;
}

IO多路复用模型

借助select、poll、epoll机制,将新连接的客户端描述符增加到描述符表中,只需要一个线程即可处理所有的客户端连接,在嵌入式开发中应用广泛,不过代码写起来稍显繁琐。

网络聊天室

项目要求

利用UDP协议,实现一套聊天室软件。服务器端记录客户端的地址,客户端发送消息后,服务器群发给各个客户端软件。

问题思考

  • 客户端会不会知道其它客户端地址?

UDP客户端不会直接互连,所以不会获知其它客户端地址,所有客户端地址存储在服务器端。

  • 有几种消息类型?

登录:服务器存储新的客户端的地址。把某个客户端登录的消息发给其它客户端。

聊天:服务器只需要把某个客户端的聊天消息转发给所有其它客户端。

退出:服务器删除退出客户端的地址,并把退出消息发送给其它客户端。

  • 服务器如何存储客户端的地址?

链表

链表节点结构体:
struct node{struct sockaddr_in addr;struct node *next;
}消息对应的结构体(同一个协议)
typedef struct msg_t
{int type;//L  M  Q  char name[32];//用户名char text[128];//消息正文
}MSG_t;int memcmp(void *s1,void *s2,int size)
功能:比较两个空间内的值是否完全相同
  • 客户端如何同时处理发送和接收?

客户端不仅需要读取服务器消息,而且需要发送消息。读取需要调用recvfrom,发送需要先调用gets,两个都是阻塞函数。所以必须使用多任务来同时处理,可以使用多进程或者多线程来处理。

程序流程图

客户端

伪代码:

服务器端://1.创建服务器流程//2.创建空链表//3.循环接受消息//根据消息类型调用函数login:
    1.将登录信息发送给所有已经登录的客户端(链表,sockfd,msg)
    2.将新登录的客户端插入链表(caddr)quit:
    1.将谁退出的信息发送给所有登录的客户端(遍历链表)
    2.将退出的客户端信息删除chat:
    将聊天的内容转发给已经登录的客户端客户端:
1.客户端创建流程
2.登录(输入名字,发送给服务器)login
3.创建子进程(循环接收服务器的信息并打印)chat
4.父进程
    终端输入信息并发送给服务器(注意发送的是否为quit,区分正常消息和退出消息)

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

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

相关文章

Java 面向对象基础

目录 1. 面向对象2. 类与对象3. 面向对象在内存中的执行原理4. 类和对象注意事项5. this 关键字6. 构造器6.1 什么是构造器?6.2 构造器作用6.3 构造器应用场景 7. 封装性7.1 什么是封装&#xff1f;7.2 封装的设计规范7.3 封装的书写 8. 实体JavaBean 正文开始 1. 面向对象 …

《华为云主机:1024的惊喜馈赠》

《华为云主机&#xff1a;1024的惊喜馈赠》 一、1024 华为送云主机之缘起&#xff08;一&#xff09;特殊日子的馈赠意义&#xff08;二&#xff09;华为云主机活动初衷 二、华为云主机领取攻略&#xff08;一&#xff09;领取条件全解析&#xff08;二&#xff09;具体领取步骤…

第10章 自定义控件

第 10 章 自定义控件 bilibili学习地址 github代码地址 本章介绍App开发中的一些自定义控件技术&#xff0c;主要包括&#xff1a;视图是如何从无到有构建出来的、如何改造已有的控件变出新控件、如何通过持续绘制实现简单动画。然后结合本章所学的知识&#xff0c;演示了一个…

开始使用HBuilderX开发网页

1 给我一个用hbuilderx的理由 首先看一个截图&#xff1a; 现在技术更新太快了&#xff0c;大家伙儿也都用windows10甚至了11了&#xff0c;而我们还在使用熟悉的windows7&#xff0c;这对于编程桌面端没问题的&#xff0c;但是网络编程真实够费劲的了&#xff0c;或者用pytho…

ffmpeg视频滤镜:添加边框-drawbox

滤镜介绍 drawbox 官网链接 > FFmpeg Filters Documentation 这个滤镜会给视频添加一个边框。 滤镜使用 参数 x <string> ..FV.....T. set horizontal position of the left box edge (default "0")y <string&…

单向数据流在 React 中的作用

文章目录 单向数据流在 React 中的作用什么是单向数据流&#xff1f;单向数据流的优势如何实现单向数据流1. 父组件传递 props2. 状态提升 结论 单向数据流在 React 中的作用 什么是单向数据流&#xff1f; 单向数据流是指数据在应用程序中只按照一个方向流动。在 React 中&a…

uniapp学习(008-2 图片模块和分享模块)

零基础入门uniapp Vue3组合式API版本到咸虾米壁纸项目实战&#xff0c;开发打包微信小程序、抖音小程序、H5、安卓APP客户端等 总时长 23:40:00 共116P 此文章包含第93p-第p103的内容 文章目录 详情页图片问题storage缓存图片网络消耗问题使用计算属性获取详细信息 保存壁纸到…

双十一宠物空气净化器决胜局,希喂、安德迈哪款性价比更高?

秋天到了&#xff0c;新一轮的猫咪换毛季又来了。尽管每天下班很累&#xff0c;但也不得不花上不少时间清理。有时候想偷懒&#xff0c;但身体是第一个反对的。要知道&#xff0c;长期堆积的猫毛除了会破坏家中的干净整洁外&#xff0c;浮毛还会随呼吸进入我们体内&#xff0c;…

工程项目智能化管理平台,SpringBoot框架智慧工地源码,实现工程建设施工可视化、智能化的全过程闭环管理。

智慧工地管理系统的建设以“1个可扩展性平台2个应用端3方数据融合N个智能设备”为原则。以“智、保、安、全”为导向&#xff0c;与工程建设管理信息系统、综合安防平台深度集成&#xff0c;构建统一的标准化工地平台&#xff0c;实现现场人员、车辆、项目、安全、进度等方面的…

springboot 自定义错误页面

自定义错误页面 背景&#xff1a;当我们访问应用程序不存在的接口路径或者参数传递不规范时&#xff0c;springboot 默认提示 如下页面 该页面对用户不友好&#xff0c;我们可以自定义展示错误页来改善。 优化后的简洁效果&#xff0c;可对 html 页面进一步美化&#xff0c;…

SpringBoot 集成RabbitMQ 实现钉钉日报定时发送功能

文章目录 一、RabbitMq 下载安装二、开发步骤&#xff1a;1.MAVEN 配置2. RabbitMqConfig 配置3. RabbitMqUtil 工具类4. DailyDelaySendConsumer 消费者监听5. 测试延迟发送 一、RabbitMq 下载安装 官网&#xff1a;https://www.rabbitmq.com/docs 二、开发步骤&#xff1a;…

低代码统一待办:提升任务管理效率的新模式

低代码平台的魔力 低代码平台通过图形化用户界面和简化开发流程&#xff0c;让用户无需具备深厚的编程知识也能快速构建应用程序。这种技术不仅加速了应用开发速度&#xff0c;还大幅降低了开发成本和复杂度&#xff0c;适合各种规模的企业。 构建统一待办系统的优势 集中化管…

itertools模块的combinations很牛

在 Python 中&#xff0c;combinations 是 itertools 模块提供的一个非常有用的函数&#xff0c;用于生成给定序列的所有可能的组合&#xff08;不考虑顺序&#xff09;。combinations 函数可以生成从长度为 r 的所有子集&#xff0c;其中 r 是一个指定的正整数&#xff0c;表示…

React中管理state的方式

使用useState 使用useReducer 既然已经有了useState&#xff0c;为什么还需要useReducer呢&#xff1f; 那么useReducer是如何将解决这些问题的呢&#xff1f; reducer是如何更新state的呢&#xff1f; reducer的工作方式非常类似JavaScript中的reduce方法&#xff0c;随着时…

CSS网页布局综合练习(涵盖大多CSS知识点)

该综合练习就是为这个学校静态网页设置CSS样式&#xff0c;使其变成下面的模样 其基本骨架代码为&#xff1a; <!DOCTYPE html> <html lang"zh"> <head> <meta charset"UTF-8"> <meta name"viewport" content…

放大器的保护机制

在工作中&#xff0c;使用功率放大器或高压放大器这类精密仪器时&#xff0c;为了保护设备不受伤害&#xff0c;确保设备的稳定性和安全性&#xff0c;在设备上需要设置保护机制。保护机制起着至关重要的作用&#xff0c;可以防止设备因过流、过压、过热等因素而受损。放大器的…

JavaSE笔记4】API、包、String类、Object类

目录 一、API 二、包 2.导入不同包下的同名程序 三、String 1. String类是什么&#xff1f; 2. 如何创建String对象?(常用的四种方法&#xff09; 3. String API a. 遍历字符串 b. 判断字符串内容是否相等&#xff1a; c. 截取子串 d. 替换部分内容 e. 匹配子串 f. 匹配开头字…

「C/C++」C/C++ 之 判断语句

✨博客主页何曾参静谧的博客&#x1f4cc;文章专栏「C/C」C/C程序设计&#x1f4da;全部专栏「VS」Visual Studio「C/C」C/C程序设计「UG/NX」BlockUI集合「Win」Windows程序设计「DSA」数据结构与算法「UG/NX」NX二次开发「QT」QT5程序设计「File」数据文件格式「PK」Parasoli…

nrm的使用

在安装nrm之前&#xff0c;要先完成node.js的安装。 1、nrm的介绍 ‌nrm&#xff08;npm registry manager&#xff09;是一个npm源管理器&#xff0c;允许用户在不同npm源之间快速切换。 关于npm和nvm的介绍&#xff0c;详见文章nvm的使用-CSDN博客。 解释&#xff1a;比如…

芯片上音频相关的验证

通常芯片设计公司&#xff08;比如QUALCOMM&#xff09;把芯片设计好后交由芯片制造商&#xff08;比如台积电&#xff09;去生产&#xff0c;俗称流片。芯片设计公司由ASIC部门负责设计芯片。ASIC设计的芯片只有经过充分的验证&#xff08;这里说的验证是FPGA&#xff08;现场…