高并发服务器 poll模型 非阻塞 代码已跑

这篇具有很好参考价值的文章主要介绍了高并发服务器 poll模型 非阻塞 代码已跑。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

经过十几次修改终于发现了问题!

现在代码完全可以运行了!排除BUG太麻烦了

之前错误现象

开了N个客户端,排查netstat -a | grep 8888 全部establilsed了,网络连接正常!但是仅仅有一个客户端正常反应,别的客户端仿佛被堵住了!只有那个正常的客户端发一句,好像才能把被堵住的字母流出来似的!

找到原因: 是在遍历数组所有元素的时候,只找到一个不是-1的fd 就开始read起来!!完全没判断它的revent 是不是POLLIN ,所以有一个客户端的read 就把别人的read全阻塞了

所以在

//为什么不从0开始遍历?0是你的监听描述符
       for (i =1;i <1024;i++){

这里面加了一句 

 if (fdarray[i].revents ==POLLIN){ 

但是!!!!神奇的是,异常现象仍然存在!!! 

跟老师的代码已经完全一样了!怎么办!只能自己蒙着加了一个else . 也就是在if ( fdarray[0].fd 不等于 -1的情况下,再去判断 if ( revents == POLLIN ) 

也就是说!! revents == POLLIN的时候那个对应的fd还可能是-1 !!虽然不知道怎么搞的但是这下改动后代码可以跑了!!

B站就业班视频代码搬运

对应课程

1.函数原型

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

 poll 模型和select模型的作用相似,用于I/O 多路复用

1.1 函数参数;

1.1.1    struct pollfd *   fds

是结构体pollfd 组成的 数组的首地址,

struct pollfd

{

int fd;

short events;

short revents;

};

(1)int fd :文件描述符。如果fd == -1 表明内核不再监控。

(2)short events :输入参数,让内核去监视的事件,就像是 select 中让内核去监视读事件,写事件,异常事件

(3)short revents :输出参数,表示告诉内核,有哪些事件发生了改变。

调用 poll 函数,当监视的文件描述符有事件发生,内核修改的是 revents,而不修改 events 和 fd,这样做events 和 fd 就可以重用;poll 返回的是 revents 。

1.1.2   int ndfs:

表示内核监控的范围,具体:数组最大下标  +1

也就是说,现在数组开头6个文件描述符,  0  , 100, 200, 0,300, 400,然后后面的文件描述符都是0 ,ndfs就是 5 ,因为最大的不是0的文件描述符400的下标是5 ,表示poll监视着6个的变化。所以这个参数的含义是:第一个参数中,让内核监控的文件描述符的个数

1.1.3 timeout 单位是毫秒:  

=0  不阻塞,立刻返回

 -1  表示一直阻塞,直到有事件发生,

 > 0 表示阻塞时长,在时长范围内如果有事件发生会立刻返回,如果超时,也会立刻返回,

POLLIN  表示监控读事件

POLLPRI 紧急事件去读

POLLOUT  可写事件

POLLERR  错误事件。但是仅仅返回在revents , 在events 中错误被忽略了。

读事件,客户端有连接过来,才能读, 而写事件基本上用不着监控(主动写发送的),除非写的太多把缓冲区写满了。。

1.2.   返回值

一个正数, 代表revents 不是0的 个数,表示的意思是有事件返回或者有错误返回的文件描述符的总数。

如果返回0,表示没有任何文件描述符有变化。

如果返回-1 ,表明有一个错误errno没有被正确设置。

2.使用pol l 模型实现服务器的流程

  1. 创建socket ,得到监听文件描述符 lisenfd;     --socket()
  2. 设置端口复用  --setsockopt();
  3. 绑定 --bind ()
  4. 定义pollfd结构体的数组fdarray[1024]

老师设置了一个变量maxindex, 表示poll函数的第一个参数中,不为-1的 文件描述符的那个最大位置的下标,例如, 这里有8个文件描述符, 

-1, 20,-1,-1,10, 1, -1,    3,  -1那么maxindex =7, 指向那个3  。

maxindex +1 就是poll函数的第二个参数,写进去。表示内核监控 这8个文件描述符。最后那个-1 不管( 数组里,都是下标从0 开始,最后一个 - 1  下标是 8)

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <errno.h>
#include <poll.h>
int main() {
    int sfd = socket(AF_INET, SOCK_STREAM, 0); 
    //设置端口复用
    int opt =1;
    setsockopt ( sfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(int));
   
    //定义一个服务器地址的 结构体,利用本机任意网络地址接口,端口为8888
    struct sockaddr_in servad;
    bzero(&servad, sizeof(servad));
    servad.sin_family =AF_INET;
    servad.sin_port =htons(8888);
    servad.sin_addr.s_addr = htonl (INADDR_ANY);
 
    int ret = bind(sfd, (struct sockaddr*)&servad, sizeof( servad));
 
    //将socket从主动变为被动(服务器必备),监听来自客户的请求
    listen(sfd,128);  
 
    //定义pollfd 结构体,并建立一个1024长度的数组fdarray ,
    struct pollfd fdarray[1024];
 
    //把all文件描述符都变成-1
    int i =0;
    for (i =0; i<1024; i++){
        fdarray[i].fd = -1;
    }
 
    fdarray[0].fd = sfd;    
    fdarray[0].events = POLLIN;
    //zhuy注意别写成revents了,现在监听呢,没有什么返回,没有revents
 
    int newfd ;
    int nready =0;
    int sockfd;
    int maxindex =0;
    while (1) {
        //因为超时时间设置为NULL ,所以进程会无限阻塞在这一步,除非有了变化才往下走
        nready = poll (fdarray, maxindex+1, -1);
 
        //第二个参数每次有变化的描述符+1 ,第二个参数也跟着加1, 表明客户端连接来的
         //越多,内核监控的描述符跟着增多 
        if (nready < 0) {
	        if (errno == EINTR){
	           continue;
	        }
	        break;
	    }
        //第一种情况,有客户端的新请求fdarray[0].返回事件等于可写事件
        //但是我现在不懂为什么新连接请求是pollin .新链接=可写事件??
	    if (fdarray[0].revents == POLLIN) {
	        newfd = accept (sfd,NULL, NULL);
            //寻找在fdarray中最靠近的不是-1的位置方下newfd
	        for (i =1;i<1024;i++){
	            if( fdarray[i].fd ==-1){
	                fdarray[i].fd = newfd;
	                fdarray[i].events = POLLIN;
	                break;
	            }
	        }
	        if (i==1024){
	            close (newfd);
	            continue;
 	        }
            if (maxindex <i ){
                maxindex =i;
            }
	        //这个if nreday等于1 不写也行,就是减少循环,只有一个连接那就赶紧下一轮
	        if (--nready ==0){
		        continue;
	        }
        }
 
        //第二种情况,眼前的描述符,有可读事件发生 
        //为什么不从0开始遍历?0是你的监听描述符
        for (i =1;i <1024;i++){
            if (fdarray[i].fd ==-1 ){
	            continue;
            }
	        else{
	            if (fdarray[i].revents ==POLLIN){
                    // i如果不能使用是要关闭的,为了避免代码变化,用sockfd代替i 
                   sockfd = fdarray[i].fd;
                   char buf[256];
                   //读数据
                   memset(buf,0x00,sizeof(buf));
                   int n =read (sockfd, buf, sizeof(buf));
                   if (n <=0){ 
                       printf("这里是服务器端, read error or client close\n"); 
                       close (sockfd);
		               fdarray[i].fd =-1;
	               }
                   else {
                       printf ("这里是服务器端n =%d ,读到的是%s \n", n,buf);
                       //将小写字母都转化wie大写
                       for (int j =0; j<n; j++) { 
                           buf[j] =toupper (buf[j]); 
                       }    
                       //发送数据
                       write (sockfd, buf, n);
                   }
               }
           }
       }//这是第二种情况下for循环的大括号
    }//这是while 1的大括号
close (sfd);
return 0;
}

 附上客户端代码,这边就比较简单 了,,,,跟前几篇文章的都一样。

//这是poll客户端的 代码
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include<netinet/in.h>
#include <sys/socket.h>
#include <arpa/inet.h>


int main() {
    int sfd2 = socket(AF_INET, SOCK_STREAM, 0);
    if (sfd2 <0){
        printf("socket fun error/n");
        return -1;
    }
    //定义一个服务器地址的结构体。
    struct sockaddr_in seradd; 
    //清空  memory
    bzero(&seradd, sizeof(seradd));
    seradd.sin_family =AF_INET;
    seradd.sin_port = htons(8888);
    //int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    //参数1:目的服务器的socket描述符;
    //参数2:服务器端的IP地址和端口号的地址结构体指针;
    //但是,服务器的IP 现在是一个字符串,我们必须用一个函数,把它转化为网络大端格式,
    //然后,存储到seradd的sin_addr的s_addr中,
    int s = inet_pton( AF_INET,"127.0.0.1",&seradd.sin_addr.s_addr);
    if (s <= 0) {
        if (s == 0)
            fprintf(stderr, "Not in presentation format");
        else
            perror("inet_pton");
        exit(EXIT_FAILURE);
    }

    int ret333 = connect(sfd2,(struct sockaddr* )&seradd,sizeof(seradd));
    if (ret333<0){
	printf("connect error\n");
	return -1;
    }
    int n1 =0, n2=0;
    char buf2[256];
    while (1 ){
        memset(buf2,0x00,sizeof(buf2));
	//从电脑键盘输入数据
	printf("这里是客户端type in a word\n");
        n1 =read (STDIN_FILENO,buf2,sizeof(buf2));
	//发送数据给服务器
	write(sfd2,buf2,n1);

	//读取服务器返回的数据
	memset(buf2,0x00,sizeof(buf2));
        n2 = read (sfd2,buf2,sizeof(buf2));
	printf("这里是客户端,接受大写字母为%s\n", buf2);
        if (n2<=0){
            printf("这里是客户端 server已关闭,或者读到的字符为0\n");
	    break;
        }
    }
    close(sfd2);
return 0;
}

3. 自带手册里的代码

#include <poll.h>
#include <fcntl.h>
#include <sys/types.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#define errExit(msg)    do { perror(msg); exit(EXIT_FAILURE); \
                               } while (0)

int main(int argc, char *argv[]){
    int nfds, num_open_fds;
    struct pollfd *pfds;
    if (argc < 2) {
        fprintf(stderr, "Usage: %s file...\n", argv[0]);
        exit(EXIT_FAILURE);
    }
    num_open_fds = nfds = argc - 1;
    pfds = calloc(nfds, sizeof(struct pollfd));
    if (pfds == NULL){
               errExit("malloc");
    }
           /* Open each file on command line, and add it 'pfds' array */
    for (int j = 0; j < nfds; j++) {
        pfds[j].fd = open(argv[j + 1], O_RDONLY);
        if (pfds[j].fd == -1){
            errExit("open");
        }
        printf("Opened \"%s\" on fd %d\n", argv[j + 1], pfds[j].fd);
        pfds[j].events = POLLIN;
    }

      /* Keep calling poll() as long as at least one file descriptor is open */
    while (num_open_fds > 0) {
        int ready;
        printf("About to poll()\n");
        ready = poll(pfds, nfds, -1);
        if (ready == -1){
            errExit("poll");
	    }
        printf("Ready: %d\n", ready);
        /* Deal with array returned by poll() */
        for (int j = 0; j < nfds; j++) {
            char buf[10];
            if (pfds[j].revents != 0) {
                printf("  fd=%d; events: %s%s%s\n", pfds[j].fd,
                  (pfds[j].revents & POLLIN)  ? "POLLIN "  : "",
                  (pfds[j].revents & POLLHUP) ? "POLLHUP " : "",
                  (pfds[j].revents & POLLERR) ? "POLLERR " : "");
	        }
            if (pfds[j].revents & POLLIN) {
                ssize_t s = read(pfds[j].fd, buf, sizeof(buf));
                if (s == -1)
                               errExit("read");
                printf(" read %zd bytes: %.*s\n",s, (int) s, buf);
            }
	        else {                /* POLLERR | POLLHUP */
                printf("    closing fd %d\n", pfds[j].fd);
                if (close(pfds[j].fd) == -1)
                      errExit("close");
                num_open_fds--;
            }
        }
    }
    printf("All file descriptors closed; bye\n");
    exit(EXIT_SUCCESS);
}

/* Suppose we run the program in one terminal, asking it to open a FIFO:

           $ mkfifo myfifo
           $ ./poll_input myfifo

       In a second terminal window, we then open the FIFO for writing, write some data to  it,  and  close  the
       FIFO:

           $ echo aaaaabbbbbccccc > myfifo

       In the terminal where we are running the program, we would then see:
*/

这段代码首先定义了 2个整数 nfds  和 num_open_fds ,让它们等于你写的参数的个数,。也就是说它默认你必须在命令行写出   ./脚本名   文件名  (文件名可以不止一个),你写几个文件,这篇代码就打开几个文件,然后把open函数返回的 fd 文件描述符,全部纳入内核的监控,然后让poll 函数管理。

       然后当这些描述符有变化时,先判断事件类型

 (pfds[j].revents & POLLIN)  ? "POLLIN "  : "",

这行的意思是,返回结果revents和POLLIN这个宏,做“ 按位与” 运算,如果结果是POLLIN ,说明revents 就是POLLIN, 那么这行字符串结果就是 'POLLIN'   不然这个字符串就是 “”

,然后当revents 就是POLLIN时,开始读取,你刚才在另一终端写的东西都会体现在当前终端的屏幕上。文章来源地址https://www.toymoban.com/news/detail-824397.html

到了这里,关于高并发服务器 poll模型 非阻塞 代码已跑的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • Linux搭建Web服务器(一)——阻塞与非阻塞、同步与异步、Linux五种IO模型

    目录 0x01 阻塞与非阻塞、同步与异步 阻塞与非阻塞 同步与异步 总结 0x02 Unix、Linux上的五种IO模型 阻塞(blocking) 非阻塞(non-blocking——NIO) IO复用(IO multiplexing) 信号驱动(signal-driven) 异步(asynchronous) 为了理清楚这几个概念,我们可以从 数据就绪 以及 数据读写 层面

    2023年04月10日
    浏览(65)
  • 常见网络服务器并发模型

    近些年,随着互联网的大发展,高并发服务器技术也快速进步,从简单的循环服务器模型处理少量网络并发请求,演进到解决C10K,C10M问题的高并发服务器模型。本文主要以TCP为例,总结了几种常见的网络服务器模型的实现方式,优缺点,以及应用实例。 单线程循环 单线程循

    2024年02月08日
    浏览(51)
  • 使用select实现TCP并发服务器模型

    本期主要分享的是对于select的使用,使用select实现TCP并发服务器模型,由于之前所用到的技术知识只能够支撑我们进行单个访问,但是有了select之后呢,我们就能够实现多用户进行访问;这也是非常符合客观需求的; 这次呢我们重点来使用一下select; 用到的头文件如下: 我

    2024年02月08日
    浏览(47)
  • 网络编程(8.14)TCP并发服务器模型

    作业: 1. 多线程中的newfd,能否修改成全局,不行,为什么? 2. 多线程中分支线程的newfd能否不另存,直接用指针间接访问主线程中的newfd,不行,为什么? 多线程并发服务器模型原代码: 1.将newfd改成全局变量效果:  答:不行,因为newfd是全局变量的话,客户端连接后生成

    2024年02月13日
    浏览(49)
  • 网络socket服务器开发几种并发模型详解

    目录 一、socket创建流程。 二、I/O多路复用 三、服务器开发常见的并发模型 1、模型一:单线程——无IO复用 1.1 模型分析 2、模型二:单线程accept + 多线程读写业务(无IO复用) 模型分析 3、模型三:单线程多路IO复用 模型分析  4、模型四:单线程多路IO复用 + 多线程业务工作

    2024年02月11日
    浏览(37)
  • 计算机网络编程 | 并发服务器代码实现(多进程/多线程)

    欢迎关注博主 Mindtechnist 或加入【Linux C/C++/Python社区】一起学习和分享Linux、C、C++、Python、Matlab,机器人运动控制、多机器人协作,智能优化算法,滤波估计、多传感器信息融合,机器学习,人工智能等相关领域的知识和技术。 专栏:《网络编程》 当涉及到构建高性能的服务

    2024年02月08日
    浏览(81)
  • Linux中 socket编程中多进程/多线程TCP并发服务器模型

    一次只能处理一个客户端的请求,等这个客户端退出后,才能处理下一个客户端。 缺点:循环服务器所处理的客户端不能有耗时操作。 模型 源码 可以同时处理多个客户端请求 父进程 / 主线程专门用于负责连接,创建子进程 / 分支线程用来与客户端交互。 模型 源码 模型 源

    2024年02月12日
    浏览(42)
  • 多进程并发TCP服务器模型(含客户端)(网络编程 C语言实现)

    摘要 :大家都知道不同pc间的通信需要用到套接字sockte来实现,但是服务器一次只能收到一个客户端发来的消息,所以为了能让服务器可以接收多个客户端的连接与消息的传递,我们就引入了多进程并发这样一个概念。听名字就可以知道--需要用到进程,当然也有多线程并发

    2024年02月17日
    浏览(67)
  • 多路IO—POll函数,epoll服务器开发流程

    \\\"在计算机网络编程中,多路IO技术是非常常见的一种技术。其中,Poll函数和Epoll函数是最为常用的两种多路IO技术。这两种技术可以帮助服务器端处理多个客户端的并发请求,提高了服务器的性能。本文将介绍Poll和Epoll函数的使用方法,并探讨了在服务器开发中使用这两种技

    2024年02月06日
    浏览(42)
  • 用Rust设计一个并发的Web服务:常用Rust库如Tokio、Hyper等,基于TCP/IP协议栈,实现了一个简单的并发Web服务器,并结合具体的代码讲解如何编写并发Web服务器的程序

    作者:禅与计算机程序设计艺术 1994年,互联网泡沫破裂,一批优秀的程序员、工程师纷纷加入到web开发领域。而其中的Rust语言却备受瞩目,它是一种现代系统编程语言,专注于安全和并发。因此,Rust在当下成为最流行的编程语言之一,很多框架也开始使用Rust重构,这使得

    2024年02月06日
    浏览(62)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包