网络通信之多路复用和并发服务器

什么是多路复用:类似C语言的switch。一句话简单讲就是:单线程或单进程同时监测若干个文件描述符是否可以执行IO操作的能力。用多路复用可以实现监听多个客户端操作。

原理:

        创建一个集合,这个集合保存你关心的文件描述符。用相关函数select对这个集合进行阻塞。当集合中任意一个文件描述符就绪的话,就解除阻塞,向下运行。

1、select函数

#include <sys/select.h>

        /* According to earlier standards */
        #include <sys/time.h>
        #include <sys/types.h>
        #include <unistd.h>

        int select(int nfds, fd_set *readfds, fd_set *writefds,
		
                  fd_set *exceptfds, struct timeval *timeout);

        功能:
            多路复用,用来监听文件描述符
        
        参数: 
            nfds:最大文件描述符值+1
            readfds:所有要读的文件文件描述符的集合(读集合) 
            writefds:所有要的写文件文件描述符的集合(写集合)
            exceptfds:其他要向我们通知的文件描述符(异常集合)
            timeout:超时时间,结构体指针,设置为NULL阻塞
            
         struct timeval {
               long    tv_sec;         /* seconds */秒1
               long    tv_usec;        /* microseconds */微秒
           };
           
        返回值:    
            成功返回所有文件描述符
            失败返回-1,并设置错误码
            
        宏:
        void FD_CLR(int fd, fd_set *set); //将文件描述符fd移出fd_set集合
        int  FD_ISSET(int fd, fd_set *set);//判断fd是否在那个fd_set集合
        void FD_SET(int fd, fd_set *set);//将fd加入到fd_set集合
        void FD_ZERO(fd_set *set);//清空fd_set集合

示例:

//多路复用select
    fd_set readfds;//多路复用函数要用到的读集合
    int max = 0;//用来存储文件描述符最大值
    char buf[50];//让我们可以看见效果
    int cfd;
    while(1){
        FD_ZERO(&readfds);//清空fd_set集合
        
        FD_SET(serfd,&readfds);//将serfd放进来,看有没有客户端连接
        FD_SET(0,&readfds);//将文件描述符0,也就是键盘拿来测试是否多路
        max = serfd;
        ret = select(max+1,&readfds,NULL,NULL,NULL);
            if(ret<0)//返回值判断
            {
				perror("select");
				return -1;
            }
        
       if(FD_ISSET(0, &readfds)){//判断fd是否在那个fd_set集合
            scanf("%s",buf);
            printf("键盘输入:%s\n",buf);					
       }else if(FD_ISSET(serfd, &readfds)){//判断fd是否在那个fd_set集合
            struct sockaddr_in cli_addr;
            int addlen = sizeof(cli_addr);
            cfd = accept(serfd,(struct sockaddr *)&cli_addr,&addlen);
            if(cfd<0)//返回值判断
            {
				perror("accept");
				return -1;
            }
            printf("客户%d连接进来了!\n",cfd);
        }
    }

2、poll函数

 poll
    #include <poll.h>

    int poll(struct pollfd *fds, nfds_t nfds, int timeout);
    功能:
        监听文件描述符哪个被触发,将fds结构体中revents改变
    参数:
        fds:监听的文件描述符“数组”
        nfds:文件描述符的个数
        timeout:超时时间   >0:阻塞对应时间  == 0:不阻塞  -1:一直阻塞
    返回值:
        成功返回0
        失败返回-1,并设置错误码
        
    struct pollfd {
          int   fd;         /* file descriptor */     0号事件  (是否键盘输入)
          short events;     /* requested events */    POLLIN(希望它被触发)
          short revents;    /* returned events */  (poll)触发了,置为POLLIN,0
    };

示例:

int cfd; // 客户端socket描述符
struct pollfd fds[2]; // pollfd 结构体数组
char buf[50]; // 缓冲区

// 设置0号描述符为标准输入,1号描述符为服务器socket描述符
fds[0].fd = 0;
fds[0].events = POLLIN; // 监听可读事件
fds[1].fd = serfd;
fds[1].events = POLLIN; // 监听可读事件

while(1) // 无限循环
{
    if(poll(fds, 2, -1) < 0) // 调用poll函数,监听两个描述符的事件,-1代表超时时间设置为无穷大
    {
        perror("poll"); // 出错处理
        return -1;
    }

    if(fds[0].revents == POLLIN) // 检查0号描述符的事件是否为可读事件
    {
        printf("0号异步事件被触发了!\n");
        scanf("%s", buf); // 从标准输入读取输入
        printf("键盘输入:%s\n", buf);
        fds[0].revents = 0; // 将0号描述符的revents置为0,表示处理完事件
    }

    if(fds[1].revents == POLLIN) // 检查1号描述符的事件是否为可读事件
    {
        struct sockaddr_in cli_addr; // 客户端地址结构体
        int addlen = sizeof(cli_addr);
        cfd = accept(serfd, (struct sockaddr *)&cli_addr, &addlen); // 接受客户端连接
        if(cfd < 0) // 判断连接是否成功
        {
            perror("accept"); // 连接失败处理
            return -1;
        }
        printf("客户端%d连接进来了!\n", cfd);
        fds[1].revents = 0; // 将1号描述符的revents置为0,表示处理完事件;不置位则表示继续监听可读事件
    }
}

3、并发服务器

 并发服务器:

 真正意义上做到一对多,但是,对系统的负担很大。
进程并发服务器:
                    服务器通过进程的方式开展可以存储很多客户端的信息,相比线程,开销比较大,每连接一个客户进来,就需要服务器开多一个进程存储该客户端
                  fork

线程并发服务器:
                   占用资源比较小,代码维护起来困难  pthread_create自动收尸,pthread_detach  不自动,pthread_join

扫描二维码关注公众号,回复: 16769424 查看本文章

测试:

        fork函数执行并发服务器:

int ret;
void *recv_msg(int *cfd)//recv_msg = (*start_rtn)        void *(*)(void *)
{
    int fd = *cfd;//万能指针先强转成int *,再解引用
    char buf[300];//接收每一个对应客户的信息
    char buf1[100]="迎面走来的你让我如此蠢蠢欲动";
    //将传入的参数存下来,通过它向客户端发送信息
    while(1){
        bzero(buf,sizeof(buf));
        ret = recv(fd,buf,sizeof(buf),0);
        if(ret==0)
        {
            printf("客户%d似乎不满意,离开了\n",fd);
            close(fd);
            break;
        }else{
            printf("客户%d说:%s\n",fd,buf);
            send(fd,buf1,strlen(buf),0);//阻塞
         }
    }
}



//等待客户端连接
    int cfd; 
    struct sockaddr_in cli_addr;
    int addlen = sizeof(cli_addr);
    while(1)
    {
        cfd = accept(serfd,(struct sockaddr *)&cli_addr,&addlen);
         if(cfd<0)
         {
            perror("accept");
            return -1;
         }
			 printf("欢迎客户%d来访!\n",cfd);	
			 printf("客户的IP地址是 %s 端口号是 %d\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port));
         if(fork()==0)//开辟子进程,不影响主进程
         {
            recv_msg(&cfd); //在子进程里执行需要操作的函数
         }
        
    }

线程执行并发服务器:

int ret;
void *recv_msg(void *cfd)//recv_msg = (*start_rtn)        void *(*)(void *)
{
    int fd = *(int *)cfd;//万能指针先强转成int *,再解引用
    char buf[300];//接收每一个对应客户的信息
    char buf1[100]="迎面走来的你让我如此蠢蠢欲动";
    //将传入的参数存下来,通过它向客户端发送信息
    while(1){
        bzero(buf,sizeof(buf));
        ret = recv(fd,buf,sizeof(buf),0);
        if(ret==0)
        {
            printf("客户%d似乎不满意,离开了\n",fd);
            close(fd);
            break;
        }else{
            printf("客户%d说:%s\n",fd,buf);
            send(fd,buf1,strlen(buf),0);//阻塞
         }
    }
}




 //等待客户端连接
    int cfd;
    pthread_t tid;//开辟线程,保证while(1)中一直在做接收客户端连接的请求
    //当连接进来一个客户端,我们就开辟线程为其提供服务,不影响主进程accept
    while(1)
    {
        cfd = accept(serfd,NULL,NULL);
         if(cfd<0)
         {
            perror("accept");
            return -1;
         }
         printf("欢迎客户%d来访!\n",cfd);
         printf("88号服务员为您提供服务!\n");
         if(pthread_create(&tid,NULL,recv_msg,&cfd)<0)//开辟线程服务对应的客户
         {
            perror("pthread_create");
            return -1;
         }
         pthread_detach(tid);  //自动收尸 
     
        
    }

猜你喜欢

转载自blog.csdn.net/apple_71040140/article/details/132823557