首页 > 其他分享 >多线程select并发

多线程select并发

时间:2023-11-07 23:44:21浏览次数:35  
标签:info include fdinfo maxfd 并发 描述符 pthread 多线程 select

目录

单纯select的问题

之前的代码中,建立连接和接收数据是线性执行的关系,也就是说,建立连接时不能接收,接收时不能建立连接,所以效率仍然不够高

解决方法

主线程中一直执行select函数,检测文件描述符的状态,让子线程去进行通信

建立子线程的位置

需要在接收到客户端连接后,用子线程去处理这个文件描述符

检测select函数的返回值之后,进行子线程的创建
同理,通信中也需要用到子线程

多线程的共享资源

在此情况下,共享资源为maxfd和rdset

所以在修改时需要加锁

代码

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>
#include <iostream>
#include <pthread.h>
#include <sys/select.h>
#include <mutex>

pthread_mutex_t mtx;

struct fdinfo
{
    int fd;
    int* maxfd;
    fd_set* rdset;
};
void* acceptConn(void* arg)
{
    fdinfo* info = (fdinfo*)arg;
    // 接受连接请求, 这个调用不阻塞
    struct sockaddr_in cliaddr;
    int cliLen = sizeof(cliaddr);
    int cfd = accept(info->fd, (struct sockaddr*)&cliaddr, (socklen_t*)&cliLen);

    // 得到了有效的文件描述符
    // 通信的文件描述符添加到读集合
    // 在下一轮select检测的时候, 就能得到缓冲区的状态
    pthread_mutex_lock(&mtx);
    FD_SET(cfd, info->rdset);
    // 重置最大的文件描述符
    *info->maxfd = cfd > *info->maxfd ? cfd : *info->maxfd;
    pthread_mutex_unlock(&mtx);
    delete info;
    return NULL;

}
void* communication(void* arg)
{
    fdinfo* info = (fdinfo*)arg;
    // 接收数据
    char buf[100];
    // 一次只能接收10个字节, 客户端一次发送100个字节
    // 一次是接收不完的, 文件描述符对应的读缓冲区中还有数据
    // 下一轮select检测的时候, 内核还会标记这个文件描述符缓冲区有数据 -> 再读一次
    // 	循环会一直持续, 知道缓冲区数据被读完位置
    int len = read(info->fd, buf, sizeof(buf));
    if (len == 0)
    {
        printf("客户端关闭了连接...\n");
        // 将检测的文件描述符从读集合中删除
        pthread_mutex_lock(&mtx);
        FD_CLR(info->fd, info->rdset);
        pthread_mutex_unlock(&mtx);
        close(info->fd);
        delete info;
        return NULL;
    }
    else if (len > 0)
    {
        // 收到了数据
        std::cout << "client says: " << buf << std::endl;
        // 发送数据
        write(info->fd, buf, strlen(buf) + 1);
    }
    else
    {
        // 异常
        perror("read");
        delete info;
        return NULL;
    }
    delete info;
    return NULL;
}

int main()
{
    pthread_mutex_init(&mtx,NULL);

    std::cout<<"START SUCCESS"<<std::endl;
    // 1. 创建监听的fd
    int lfd = socket(AF_INET, SOCK_STREAM, 0);

    // 2. 绑定
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(9999);
    addr.sin_addr.s_addr = INADDR_ANY;
    bind(lfd, (struct sockaddr*)&addr, sizeof(addr));

    // 3. 设置监听
    listen(lfd, 128);

    // 将监听的fd的状态检测委托给内核检测
    int maxfd = lfd;
    // 初始化检测的读集合
    fd_set rdset;
    fd_set rdtemp;
    // 清零
    FD_ZERO(&rdset);
    // 将监听的lfd设置到检测的读集合中
    FD_SET(lfd, &rdset);
    // 通过select委托内核检测读集合中的文件描述符状态, 检测read缓冲区有没有数据
    // 如果有数据, select解除阻塞返回
    // 应该让内核持续检测
    while (1)
    {
        // 默认阻塞
        // rdset 中是委托内核检测的所有的文件描述符
        pthread_mutex_lock(&mtx);
        rdtemp = rdset;
        pthread_mutex_unlock(&mtx);
        int num = select(maxfd + 1, &rdtemp, NULL, NULL, NULL);
        // rdset中的数据被内核改写了, 只保留了发生变化的文件描述的标志位上的1, 没变化的改为0
        // 只要rdset中的fd对应的标志位为1 -> 缓冲区有数据了
        // 判断
        // 有没有新连接
        if (FD_ISSET(lfd, &rdtemp))
        {
            
            //子线程
            pthread_t tid;
            fdinfo *info=new fdinfo;
            info->maxfd = &maxfd;
            info->fd = lfd;
            info->rdset = &rdset;
            pthread_create(&tid, NULL, acceptConn, info);
            pthread_detach(tid);

        }

        // 没有新连接, 通信
        for (int i = 0; i < maxfd + 1; ++i)
        {
            // 判断从监听的文件描述符之后到maxfd这个范围内的文件描述符是否读缓冲区有数据
            if (i != lfd && FD_ISSET(i, &rdtemp))
            {
                //子线程
                pthread_t tid;
                fdinfo* info = new fdinfo;
                info->fd = lfd;
                info->rdset = &rdset;
                pthread_create(&tid, NULL, communication, info);
                pthread_detach(tid);
                
            }
        }
    }
    pthread_mutex_destroy(&mtx);
    return 0;
}

标签:info,include,fdinfo,maxfd,并发,描述符,pthread,多线程,select
From: https://www.cnblogs.com/liviayu/p/17816366.html

相关文章

  • 基于select进行并发处理
    目录处理流程服务端代码处理流程创建监听的套接字lfd=socket();将监听的套接字和本地的IP和端口绑定bind()给监听的套接字设置监听listen()创建一个文件描述符集合fd_set,用于存储需要检测读事件的所有的文件描述符通过FD_ZERO()初始化通过FD_SET()将监听的文......
  • 11月7日form表单与input框以及select标签
    目录form表单与input框form表单input标签input其它属性说明:form与其它标签以及input的应用再来展示一下禁用以及隐藏的属性select标签form表单与input框form表单功能:表单用于向服务器传输数据,从而实现用户与web服务器的交互表单能够包含input系列标签,比如文本字段、复选框、......
  • Gin+Vue+微服务打造秒杀商城,打造高并发抢购平台
    gin+vue实战后端:用户管理用户列表登录/登出商品管理商品的增上改查活动管理商品关联成功率redis队列,不成功的回到队列继续,成功的从队列删除结束难点:代码和部署完全隔离怎么避免雪崩根据后端承载能力,进行限流和过载保护使用redis承载海量QPSmysql性......
  • 数据库系列:InnoDB下实现高并发控制
    数据库系列:MySQL慢查询分析和性能优化数据库系列:MySQL索引优化总结(综合版)数据库系列:高并发下的数据字段变更数据库系列:覆盖索引和规避回表数据库系列:数据库高可用及无损扩容数据库系列:使用高区分度索引列提升性能数据库系列:前缀索引和索引长度的取舍数据库系列:MySQL引擎My......
  • vue 大文件分片上传(断点续传、并发上传、秒传)
    对于大文件的处理,无论是用户端还是服务端,如果一次性进行读取发送、接收都是不可取,很容易导致内存问题。所以对于大文件上传,采用切块分段上传,从上传的效率来看,利用多线程并发上传能够达到最大效率。本文是基于springboot+vue实现的文件上传,本文主要介绍vue实现文件上传的步骤......
  • Java 并发多线程面试题及答案
    1、并发编程三要素?(1)原子性原子性指的是一个或者多个操作,要么全部执行并且在执行的过程中不被其他操作打断,要么就全部都不执行。(2)可见性可见性指多个线程操作一个共享变量时,其中一个线程对变量进行修改后,其他线程可以立即看到修改的结果。(3)有序性有序性,即程序的执行顺序......
  • .NET(C#) Linq Concat和Union以及Select和SelectMany的使用及区别
    1、Concat操作符Concat操作符用于连接两个序列,生成一个新序列。usingSystem;usingSystem.Collections.Generic;usingSystem.Linq;usingSystem.Text;usingSystem.Threading.Tasks;namespaceConsoleApplication{classProgram{staticvoidMain(s......
  • js获去select选中值
    我想获取select选中的value,或者text,或者……比如这个:第一个option第二个option一:JavaScript原生的方法1:拿到select对象:`varmyselect=document.getElementById("select");2:拿到选中项的索引:varindex=myselect.selectedIndex;//selectedIndex代表的是你所选中项的ind......
  • 多线程学习笔记
    **Process与Thread**说起进程,就不得不说下程序。程序是指令和数据的有序集合,其本身没有任何运行的含义,是一个静态的概念。而进程则是执行程序的一次执行过程,它是一个动态的概念。是系统资源分配的单位。通常在一个进程中可以包含若干个**线程**,当然一个进程中至少有一个线程,不......
  • JAVA多线程并发查询百万数据的内存占用问题?
    在Java中使用多线程并发查询百万数据时,内存占用是一个需要考虑的重要问题。以下是一些解决该问题的方案:分批查询:将数据分成较小的批次进行查询,而不是一次性加载全部数据。这样可以减少每个线程需要处理的数据量,降低内存占用。可以将查询结果分组或按需加载,以保持内存占用的合理范......