Socket编程-IO模型

news2024/11/19 5:41:51

1、首先IO模型的内容。

        感觉可以简单理解为:我们写代码时,在基础的  IO 操作上做了一些其他的策略,根据策略的不同,一般有阻塞IO和非阻塞IO

1、阻塞IO

        就是在操作的时候,比如网络通信中,某一线程使用下面这三个函数接收数据的时候,都有flags参数,就可以设定成非阻塞 MSG_DONTWAIT,这样就不会将本线程的运行卡在这个函数这里,可以进行其他的操作了。

 ssize_t recv(int sockfd, void *buf, size_t len, int flags);

       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

       ssize_t recvmsg(int sockfd, struct msghdr *msg, int flags);

       这也有问题,因为本线程既然调用这个函数了,就是用它获取信息的,现在设定成非阻塞后。没有信息直接就做别的事情,怎么去继续读取信息呢?

        这就只能来回的循环这一步,来进行查询信息是否已经从内核态读取到了用户态,这样次数多了,也是浪费CPU资源。所以用select和epoll等来解决这个问题。

        (这个IO的话,我理解上它是我们调用函数比如recv,read这样的系统调用函数时,会对内核进行访问读取数据,当有数据准备好时,从内核空间拷贝到用户空间或者说会直接读取到我们在程序中自己设定的存储空间中。

简单说就是这样的过程   : 从    磁盘---》内核空间 -----》用户空间(程序存储区)

比如  read( int fd, void *buf  , size_t count);    //buf  就是我们设定的程序存储空间。fd的话,就是磁盘中的文件对应的文件描述符 / 或者网络通信链接的connfd等

        2、怎么实现线程安全

1、首先,线程安全这个问题(基本上就是指对共享资源的访问是安全的),只要提到就一定是发生在多线程的情况下,并且这个问题是针对多个线程访问同一共享资源的情况(线程共享堆区,静态变量,全局变量,还有文件系统的东西等), 当多个线程对同一资源进行“写”操作的时候,或者说对同一资源进行改动的时候,会不会出现问题。(而我们使用多线程就是为了能够执行更多的任务,做更多的功能,提高效率,且线程之间一定需要有隔离,不能乱掺和其他线程的工作,

        比如我们其中一个线程对配置文件进行读取操作,读取后使用配置信息进行工作。

但是,在我们读取完之前,另外一个线程对配置文件进行了更新迭代!!!,这种情况下,其他正在读取配置文件的线程,可能就读取不到需要的信息,也就不能进行接下来的工作,,这多线程不就崩了(其他能解决的方法很多,比如循环读取验证配置信息,但是这样不是更加浪费时间资源在这个配置文件的读取校验上)。

       互斥锁解决线程安全的问题: 

        使用互斥锁就是挺好使的一个机制(使用互斥锁对共享资源进行上锁,或者说,更细节一些的,我们在每一个可能会导致多线程访问共享资源的点上都使用互斥锁去给共享资源上个锁,这样就安全了,一定记得解锁

接Socket网络编程-池化的思路-CSDN博客

        互斥锁的使用信息上篇内容写过,需要注意的一个点就是,当上锁后,这个锁到解锁的代码区域里,如果访问到贡献资源,就是自己单独访问,其他线程都需要等待本线程访问完释放这个共享资源才行。

        其他的线程如果访问不到,运行到pthread_cond_wait时,这个函数本身就会自动给这个线程解锁,被条件变量阻塞在这里,等到被唤醒后,再次去进行资源访问,(如果还不行可能就还需要阻塞在这里,等下一次唤醒),不过一般这时候就已经可以访问贡献资源了。

2、对栈区和寄存器的访问是独有的。

2、Reactor模型

C++实现算法的一些巧妙点-CSDN博客

1、Socket函数的具体应用过程

这里以读事件做示例,核心功能就是使用服务器实现读操作,使用 epoll_wait  监听socket等就绪事件(一般就是文件描述符,sockfd关联的客户端链接)

        (1)、其中比较特别的就是,我们使用线程时,进行任务处理的就是pthread_create 的回调函数;这里我们就需要注册这个函数。

        还需要注册监听的事件(将相应的事件加到epollfd中,epoll_ctl())。

(注册,简单说提前设定好函数功能,和监听的事件,当代码运行到相应的位置时,会去调用或者处理这些事件)下面文章也有介绍Linux 信号和信号量小记-CSDN博客

         (2)另外呢就是accept这个函数,之前也写过;这个函数只从listen 管理的两个队列(全链接和半链接队列)中,从全连接队列取出一个已经链接的客户端(fd),生成一个新的文件描述符,具体如下。

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

        accept()系统调用:

        用于基于连接的套接字类型(SOCK_STREAM, SOCK_SEQPACKET)。它从监听套接字sockfd的阻塞连接队列中提取第一个连接请求,创建一个新的连接套接字,并返回一个引用该套接字的新文件描述符。新创建的套接字未处于监听状态。原始套接字sockfd不受此调用的影响。(从sockfd关联的的队列中,提取第一个链接请求  再创建新的套接字,这个新的也需要我们设置添加进监听集合中,这个新的套接字就代表原来的链接请求。)

        select ()函数:

select()允许程序监视多个文件描述符,直到一个或多个文件描述符“准备好”进行某类I/O操作。如果可以执行相应的I/O操作(例如,read(2)或足够小的write(2))而不阻塞,则认为文件描述符已准备就绪(到底什么时候算就绪,后面还得研究清楚

        这个应该是和内核空间(链接的文件描述符的读写缓冲区的设定有关,达到条件可读,可写)接Socket网络编程-池化的思路-CSDN博客这篇里面有写,每个sockfd创建的时候都有读写缓冲区。(类似我们写一个char * buf[  1024]   ,可以设定字节的大小,比如内容超过50字节可读,内容小于100字节,可写入等)----> 这个具体问题,后面找找源码看看。

 2、select示例程序

        这是一个服务器简单代码,只是将一个客户端发过来的消息,转发给其他的客户端(类似我们的消息群发)

int main(int argc, char *argv[]){
    // ./server
    ARGS_CHECK(argc,3);
    int sockFd = socket(AF_INET,SOCK_STREAM,0);
    ERROR_CHECK(sockFd,-1,"socket");

    int optval = 1;
    int ret = setsockopt(sockFd,SOL_SOCKET,SO_REUSEADDR,&optval,sizeof(int));
    ERROR_CHECK(ret,-1,"setsockopt");

    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2]));
    addr.sin_addr.s_addr = inet_addr(argv[1]);
    ret = bind(sockFd,(struct sockaddr *)&addr,sizeof(addr));
    ERROR_CHECK(ret,-1,"bind");

    ret = listen(sockFd,10);
    ERROR_CHECK(ret,-1,"listen");

    fd_set rdset;//单纯地去保存就绪的fd
    fd_set monitorSet;//使用一个单独的监听集合
    FD_ZERO(&monitorSet);
    FD_SET(sockFd,&monitorSet);
    char buf[4096] = {0};
    int netFdArr[10] = {0};
    int curConn = 0;

    //服务端不再监听标准输入 
    //服务端的功能:处理新的客户端 监听sockFd
    //处理客户端发送的消息的转发 
//这里使用monitor做所有文件描述符的监听集合,使用rdset 只监听已经就绪的链接fd(就绪的标准)
//就是accept从listen的已连接队列中取到的链接fd,然后将其加入select监听中,看看是可读还是可写
    while(1){
        memcpy(&rdset,&monitorSet,sizeof(fd_set));
        select(20,&rdset,NULL,NULL,NULL);
//select  能够监视我们需要监视的文件描述符的变化情况——可读写或是异常。
        if(FD_ISSET(sockFd,&rdset)){
           netFdArr[curConn]= accept(sockFd,NULL,NULL); 
            //此时从listen的全链接队列取出一个,生成新的netfdAddr,这时这个链接的fd还不知道有没有就绪
           ERROR_CHECK(netFdArr[curConn],-1,"accept");
           FD_SET(netFdArr[curConn],&monitorSet);  //这里加入总的监听集合中,因为rdset,是从总的监听集合的来的,所以需要先加进去,再转移给rdset,使用select再进行监听。
           printf("new connect is accepted!,curConn = %d\n", curConn);
           ++curConn; 
        }
        for(int i = 0;i < curConn; ++i){
            if(FD_ISSET(netFdArr[i],&rdset)){   //这个循环只是查找已经读写就绪的fd,之后循环将数据转发给除了发送方以外的客户端
                bzero(buf,sizeof(buf));
                recv(netFdArr[i],buf,sizeof(buf),0);//这里从当前的链接客户端 读取信息
                for(int j = 0; j < curConn; ++j){     //这里循环查询,将信息转发给除了自己以外的客户端
                    if(j == i){
                        continue;
                    }
                    send(netFdArr[j],buf,strlen(buf),0);
                }
            }
        }
    }
    close(sockFd);
}

3、模型代码使用epoll

EPOLLIN  : 可读

EPOLLOUT:可写

过程就是 

                使用epoll_ctl将服务端关联的sockfd 添加监听的时候,就相当于将所有的链接请求加入到了监听中。而epoll_wait 就是返回这些被监听的链接中,已经就绪的链接(是一个链表结构);这些链接fd也可以使用,就是需要判断是不是sockfd链接,如果是别的文件描述符,就出错了。

             这是一个简单的测试案例,

#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <error.h>
#include <sys/epoll.h>

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

#include <iostream>
#include <string>

using std::string;
using std::cin;
using std::cout;
using std::endl;

void test()
{
    // 1、创建监听服务器的套接字
    int listenfd = socket(AF_INET,SOCK_STREAM, 0);

    if(listenfd==-1){
        perror("socket");
        return;
    }
    struct sockaddr_in serveraddr;
    memset(&serveraddr, 0, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    serveraddr.sin_port = htons(8888);

    socklen_t length = sizeof(serveraddr);
    
    //端口重用
    int opt = 1;
    int setAddResusetRet = setsockopt(listenfd, SOL_SOCKET,SO_REUSEADDR, &opt, sizeof(opt));
    if(-1 == setAddResusetRet){
        perror("set sockoptADDR is error");
        return ;
    }
    int setPortReuseRet = setsockopt(listenfd, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt));
    if(-1 == setPortReuseRet){
        perror("set sockoptPORT is error");
        return ;
    }
    //2、服务器绑定网络地址信息
    if(::bind(listenfd,(struct sockaddr *)&serveraddr, sizeof(serveraddr))<0){
        perror("bind");
        return;
    }
    printf("server is listenning ...\n");

  
    //3、让服务器开始监听
    //listenfd 跟所有的新连接打交道
    if(::listen(listenfd, 128) < 0){
        perror("listen");
        close(listenfd);
        return;
    }
    int efd = epoll_create1(0);   //底层实现实现使用红黑树,还会有个就绪链表
    struct epoll_event ev;
    ev.events = EPOLLIN | EPOLLOUT;   //注册的epoll事件类型,可读和可写
    ev.data.fd = listenfd;
    //epoll 要进行监听操作:对listenfd的读写事件进行监听
    //
    //Reactor :注册读就绪事件
    int ret = ::epoll_ctl(efd,EPOLL_CTL_ADD,listenfd,&ev);    //将socket的listenfd加入epoll的监听中(efd)
                                                              //不过这个接口套接字添加到监听中后是在什么地位还不清楚
                                                              //更像是加入了一个接口(门户),可以通过它可以链接各个客户端的sockfd
                                                              //accept从listen的全链接队列取出已经链接的请求事件
    if(ret < 0){
        perror("epoll_ctl1");
        close(listenfd);
        close(efd);
        return ;
    }
    //就绪事件队列设置;
    struct epoll_event * evtList = (struct epoll_event *)malloc(1024 *sizeof(struct epoll_event)); 

    while(1){
        int ready = ::epoll_wait(efd,evtList,1024,3000);    //evtlist 就是epoll_wait 执行后,得到的可用客户端链接文件描述符的列表,
                                                            //和select功能一样,起到一个监听的作用
                                                            //在整个结构中,这里是只起到监听功能的和用链表结构表示出来已经就绪的客户端链接

        for(int idx = 0; idx < ready; ++idx){
            if((evtList[idx].data.fd == listenfd) && (evtList[idx].events & EPOLLIN)){  //判断是否时sockfd就绪,还是其他的文件描述符就绪
                //这里表示有就绪的链接fd,使用accept获取新连接,且可读(这里有个问题,就是evtlist 就是返回的可用文件描述符,
                //为什么不直接拿来用呢,非要经过accept的处理,还没有具体弄清楚过程后面再写)
                
                int peerfd = accept(listenfd,NULL,NULL);  
                //TCPConnection connect(peerfd)


                //将新链接添加到epoll的监听实例中
                struct epoll_event evt;
                evt.data.fd = peerfd;
                evt.events = EPOLLIN | EPOLLOUT |EPOLLERR;
                ret = ::epoll_ctl(efd,EPOLL_CTL_ADD,peerfd,&evt);
                if(ret < 0){
                    perror("epoll_ctl");
                    continue;
                }
                
                //新链接到来之后的处理
                printf(">> new connection has connected , fd = %d\n",peerfd);
                
                //>> 此处可以记录日志,使用log4CPP 完成
                //个性化定制 ==》事件处理器
                //也可以调用线程去安排处理别的任务等

            }
            else{
                char buff[128] = {0};
                if(evtList[idx].events& EPOLLIN){
                    int fd = evtList[idx].data.fd;
                    int ret = ::recv(fd,buff,sizeof(buff),0);
                    if(ret > 0)
                    {
                        //表示获取到的数据大于0,可能是序列化的数据
                        //对应用层数据进行分析
                        //拿到最终数据后,进行业务逻辑处理
                        //
                        //处理完后,是否需要返回给客户
                        ret = send(fd ,buff, strlen(buff),0);

                    }
                    else if(ret == 0){
                        printf("connection has closed \n");
                        struct epoll_event ev;
                        ev.data.fd = fd;

                        epoll_ctl(efd,EPOLL_CTL_DEL,fd, &ev);    //将fd和其事件信息从监听结构中删除

                        //可以记录日志信息
                        //或者其他工作信息
                    }
                }
            }
        }
    }
    close(listenfd);
}

int main()
{
    test();
    return 0;
}

   //本来想这一次写详细些Reactor模型的池化和其他东西,但是越写思考的东西越多,下一篇再写

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

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

相关文章

【Web】CTFSHOW PHP特性刷题记录(全)

知其然知其所以然&#xff0c;尽量把每种特性都详细讲明白。 目录 web89 web90 web91 web92 web93 web94 web95 web96 web97 web98 web99 web100 web101 web102 web103 web104 web105 web106 web107 web108 web109 web110 web111 web112 web113 web…

【QML COOK】- 007-Item对象、信号和槽

信号&#xff08;signal&#xff09;和槽&#xff08;slot&#xff09;是Qt的独特的设计&#xff0c;自然在QML中也被支持。 Item是QML所有类型的基类&#xff0c;Item类型不会显示在窗口上&#xff0c;但是可以支持信号和槽。本节就用Item编写一个信号和槽的实例。 1. 创建Q…

【Spring 篇】走进SpringMVC的世界:舞动Web的激情

嗨&#xff0c;亲爱的小白们&#xff01;欢迎来到这篇关于SpringMVC的博客&#xff0c;让我们一起探索这个舞动Web的框架&#xff0c;感受它带来的激情和便利。在这个世界里&#xff0c;我们将学到SpringMVC的概述、开发步骤以及如何快速入门&#xff0c;一切都是如此的令人兴奋…

数据分析实战丨基于flask+pygal可视化分析sqlite中的数据

文章目录 写在前面实验目标项目框架实验内容1.配置实验环境2.查看sqlite3数据库的数据3.创建项目文件4.编写代码5.运行项目 运行结果写在后面 写在前面 本期内容&#xff1a; 基于FlaskPygal可视化分析Sqlite3中的数据 实验环境&#xff1a; pythonpygalflask 项目下载地址…

Cocos 使用VsCode调试-跨域问题

解决方案&#xff1a; 在添加完debug配置后 在项目文件夹中打开vscode然后找到launch.json 这个runtimeArgs参数在原本的配置中是没有的,给他添加上去 "runtimeArgs": ["--disable-web-security" ] 原理: 禁用浏览器跨域检查&#xff08;仅用于调试&…

msvcp140.dll丢失的常见问题,msvcp140.dll丢失的几种解决办法分享

在电脑系统中&#xff0c;msvcp140.dll是一个重要的系统文件&#xff0c;其作用是为应用程序提供所需的功能和支持。然而&#xff0c;有时候我们可能会遇到msvcp140.dll文件丢失的情况&#xff0c;导致我们无法正常使用某些程序或游戏。本文将介绍msvcp140.dll丢失的常见问题、…

polar CTF 写shell

一、题目 <?php /*PolarD&N CTF*/highlight_file(__FILE__);file_put_contents($_GET[filename],"<?php exit();".$_POST[content]);?>二、解题 payload ?filenamephp://filter/convert.base64-decode/resourceshell.php #<?eval($_POST[1]);…

openGauss学习笔记-197 openGauss 数据库运维-常见故障定位案例-分析查询语句是否被阻塞

文章目录 openGauss学习笔记-197 openGauss 数据库运维-常见故障定位案例-分析查询语句是否被阻塞197.1 分析查询语句是否被阻塞197.1.1 问题现象197.1.2 原因分析197.1.3 处理办法 openGauss学习笔记-197 openGauss 数据库运维-常见故障定位案例-分析查询语句是否被阻塞 197.…

【LV12 DAY17-18 中断处理】

GPX1_1是外部中断9 EINT9 查询可知其中断ID是57 所以需要进行人为修正lr的地址 sub lr&#xff0c;lr&#xff0c;#4 //iqr异常处理程序 irq_handler: //IRQ异常后LR保存的地址是被IRQ打断指令的下一条再下一条指令的地址&#xff0c;所以我们需要人为进行修正一下sub LR,L…

Linux下如何快速调试I2C设备

Linux下如何快速调试I2C设备 目录 1 什么场景下需要快速调试I2C设备 2 如何快速调试I2C设备 3 如何获取I2C Tools工具集 3.1 获取I2C Tools工具集源码 3.2 编译I2C Tools工具集源码 3.3 为设备添加I2C Tools工具集 4 如何使用I2C Tools工具集 5 小结 1 什么场景下需要快…

vue2配置教程

5.12.3 Vue Cli 文档地址: https://cli.vuejs.org/zh/ IDEA 打开项目&#xff0c;运行项目

堆排序——高效解决TOP-K问题

. 个人主页&#xff1a;晓风飞 专栏&#xff1a;数据结构|Linux|C语言 路漫漫其修远兮&#xff0c;吾将上下而求索 文章目录 引言什么是堆&#xff1f;建堆堆排序&#xff1a;排序的最终结果 堆排序实现函数声明交换函数 Swap下沉调整 DnAdd堆排序函数 HeapSort主函数 文件中找…

day-09 删除排序链表中的重复元素

思路 从前往后遍历链表&#xff0c;当当前节点的值与下一个节点值相等时&#xff0c;删除下一节点&#xff1b;否则向后移动一个节点&#xff0c;继续遍历 解题方法 while(p!null&&p.next!null){ if(p.next.valp.val)p.nextp.next.next;//当前节点的值与下一个节点值相…

WorkPlus卓越的即时通讯工具,助力企业提升工作效率

在当今快节奏的商业环境中&#xff0c;高效沟通和协作是企业成功的关键。而即时通讯作为实现高效沟通的利器&#xff0c;成为了现代企业不可或缺的一部分。作为一款领先的即时通讯工具&#xff0c;WorkPlus以其卓越的性能和独特的功能&#xff0c;助力企业打造高效沟通和协作的…

Docker实战07|Docker增加容器资源限制

上一篇文章中&#xff0c;讲解了Docker run的具体流程以及Docker是如何改变PID为1的底层原理。 具体文章可见《Docker就应该这么学-06》 有需要的小伙伴可以回顾一下。 接下来本文会详细介绍一下Docker 是如何增加容器的资源限制 增加容器的资源限制 获取代码 git clone …

Python之循环判断语句

一、if判断语句 1. if...else if 条件: 满足条件时要做的事情1 满足条件时要做的事情2 ...... else: 不满足条件时要做的事情1 不满足条件时要做的事情2 ...... # -*- coding:utf-8 -*- age input("请输入年龄:") age int(age) if age > 18:print("已经成…

C# 导出EXCEL 和 导入

使用winfrom简单做个界面 选择导出路径 XLSX起名字 打开导出是XLSX文件 // 创建Excel应用程序对象Excel.Application excelApp new Excel.Application();excelApp.Visible false;// 创建工作簿Excel.Workbook workbook excelApp.Workbooks.Add(Type.Missing);Excel.Works…

【PlantUML】- 时序图

写在前面 本篇文章&#xff0c;我们来介绍一下PlantUML的时序图。这个相对类图来讲&#xff0c;比较简单&#xff0c;也不需要布局。读完文章&#xff0c;相信你就能实际操作了。 目录 写在前面一、基本概念二、具体步骤1.环境说明2.元素3.语法4.示例 三、参考资料写在后面系列…

DNS分离解析

一、介绍 分离解析的域名服务器实际也是主域名服务器&#xff0c;这里主要是指根据不同的客户端提供不同的域名解析记录。比如来自内网和外网的不同网段地址区域的客户机请求解析同一域名时&#xff0c;为其提供不同的解析结果&#xff0c;得到不同的IP地址。 DNS的分离…

基于Java SSM框架实现企业车辆管理系统项目【项目源码】

基于java的SSM框架实现企业车辆管理系统演示 JSP技术 JSP技术本身是一种脚本语言&#xff0c;但它的功能是十分强大的&#xff0c;因为它可以使用所有的JAVA类。当它与JavaBeans 类进行结合时&#xff0c;它可以使显示逻辑和内容分开&#xff0c;这就极大的方便了运动员的需求…