Linux非阻塞IO(五)使用poll实现非阻塞的回射服务器客户端

前面几节我们讨论了非阻塞IO的基本概念、Buffer的设计以及非阻塞connect的实现,现在我们使用它们来完成客户端的编写。

我们在http://www.cnblogs.com/inevermore/p/4049165.html中提出过,客户端需要监听stdin、stdout和sockfd。

这里需要注意的是

只有缓冲区可写的时候,才去监听sockfd和stdin的读事件。

过去在阻塞IO中,我们总是监听sockfd的读事件,因为每当sockfd可读,我们就去调用用户的回调函数处理read事件,在回调函数中需要用户手工read缓冲区的数据。 换句话说,接收数据是用户的责任,poll模型只需要提醒用户去接收即可。

而在非阻塞IO中,因为poll采用的是水平触发,如果缓冲区满了,每次read等于无效操作,那么数据始终堆积在内核中,poll会不停的被触发。这在某种程度上等于轮询。所以我们只在缓冲区可用的情况下监听sockfd的读事件。

只有缓冲区可读的时候,才去监听sockfd和stdout的写事件。因为没有数据可写,监听write事件除了不停的触发poll之外,没有实际意义。

所以每次执行poll之前,需要重新装填poll的events数组。

完整的代码如下:

#include "sysutil.h"
#include "buffer.h"

int main(int argc, char const *argv[])
{
    //创建client套接字
    int sockfd = tcp_client(8934);
    //调用非阻塞connect函数
    int ret = nonblocking_connect(sockfd, "192.168.44.136", 9981, 5000);
    if(ret == -1)
    {
        fprintf(stderr, "Timeout .\n");
        exit(EXIT_FAILURE);
    }

    //将三个fd设置为Non-Blocking
    activate_nonblock(sockfd);
    activate_nonblock(STDIN_FILENO);
    activate_nonblock(STDOUT_FILENO);

    buffer_t recvbuf; //sockfd -> Buffer -> stdout
    buffer_t sendbuf; //stdin -> Buffer -> sockfd

    //初始化缓冲区
    buffer_init(&recvbuf);
    buffer_init(&sendbuf);

    struct pollfd pfd[10];

    while(1)
    {
        //初始化
        int ix;
        for(ix = 0; ix != 3; ++ix)
        {
            pfd[ix].fd = -1;
            pfd[ix].events = 0;
        }

        //重新装填events数组
        if(buffer_is_readable(&sendbuf))
        {
            pfd[0].fd = sockfd;
            pfd[0].events |= kWriteEvent;
        }
        if(buffer_is_writeable(&sendbuf))
        {
            pfd[1].fd = STDIN_FILENO;
            pfd[1].events |= kReadEvent;
        }
        if(buffer_is_readable(&recvbuf))
        {
            pfd[2].fd = STDOUT_FILENO;
            pfd[2].events |= kWriteEvent;
        }
        if(buffer_is_writeable(&recvbuf))
        {
            pfd[0].fd = sockfd;
            pfd[0].events |= kReadEvent;
        }

        //监听fd数组
        int nready = poll(pfd, 3, 5000);
        if(nready == -1)
            ERR_EXIT("poll");
        else if(nready == 0)
        {
            printf("timeout\n");
            continue;
        }
        else
        {
            int i;
            for(i = 0; i < 3; ++i)
            {
                int fd = pfd[i].fd;
                if(fd == sockfd && pfd[i].revents & kReadEvent)
                {
                    //从sockfd接收数据到recvbuf
                    if(buffer_read(&recvbuf, fd) == 0)
                    {
                        fprintf(stderr, "server close.\n");
                        exit(EXIT_SUCCESS);
                    }
                }

                if(fd == sockfd && pfd[i].revents & kWriteEvent)
                    buffer_write(&sendbuf, fd); //将sendbuf中的数据写入sockfd

                if(fd == STDIN_FILENO && pfd[i].revents & kReadEvent)
                {
                    //从stdin接收数据写入sendbuf
                    if(buffer_read(&sendbuf, fd) == 0)
                    {
                        fprintf(stderr, "exit.\n");
                        exit(EXIT_SUCCESS);
                    }
                }

                if(fd == STDOUT_FILENO && pfd[i].revents & kWriteEvent)
                    buffer_write(&recvbuf, fd); //将recvbuf中的数据输出至stdout
            }
        }
    }

}

从以上的代码可以看出,大部分操作被封装进了buffer的实现中。

 

测试服务器,我暂时使用muduo库编写一个,代码如下:

#include <muduo/net/TcpServer.h>
#include <muduo/net/InetAddress.h>
#include <muduo/net/TcpConnection.h>
#include <muduo/base/Timestamp.h>
#include <muduo/net/EventLoop.h>
#include <muduo/base/Logging.h>
using namespace muduo;
using namespace muduo::net;

void onMessage(const TcpConnectionPtr &conn, Buffer *buf, Timestamp t)
{
    string s(buf->retrieveAllAsString());
    LOG_INFO << "recv msg : " << s.size() << " at: " << t.toFormattedString();
    conn->send(s);
}

int main(int argc, char const *argv[])
{
    EventLoop loop;
    InetAddress addr("192.168.44.136", 9981);
    TcpServer server(&loop, addr, "EchoServer");
    server.setMessageCallback(&onMessage);
    server.start();

    loop.loop();

    return 0;
}

读者如果使用上述的代码需要安装muduo网络库。

采用以下命令编译:

g++ server.cpp  -lmuduo_net -lmuduo_base -lpthread -o server

 

下文用poll实现非阻塞的服务器端。

时间: 2024-07-31 13:10:02

Linux非阻塞IO(五)使用poll实现非阻塞的回射服务器客户端的相关文章

转一贴,今天实在写累了,也看累了--【Python异步非阻塞IO多路复用Select/Poll/Epoll使用】

下面这篇,原理理解了, 再结合 这一周来的心得体会,整个框架就差不多了... http://www.haiyun.me/archives/1056.html 有许多封装好的异步非阻塞IO多路复用框架,底层在linux基于最新的epoll实现,为了更好的使用,了解其底层原理还是有必要的.下面记录下分别基于Select/Poll/Epoll的echo server实现.Python Select Server,可监控事件数量有限制: 1 2 3 4 5 6 7 8 9 10 11 12 13 14

Linux非阻塞IO(八)使用epoll重新实现非阻塞的回射服务器

本文无太多内容,主要是几个前面提到过的注意点: 一是epoll的fd需要重新装填.我们将tcp_connection_t的指针保存在数组中,所以我们以这个数组为依据,重新装填fd的监听事件. //重新装填epoll内fd的监听事件 int i; for(i = 0; i < EVENTS_SIZE; ++i) { if(connsets[i] != NULL) { int fd = i; //fd tcp_connection_t *pt = connsets[i]; //tcp conn ui

Linux下使用epoll函数同时处理TCP请求和UDP请求的回射服务器

#include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <assert.h> #include <stdio.h> #include <unistd.h> #include <errno.h> #include <string.h> #include

Linux非阻塞IO(三)非阻塞IO中缓冲区Buffer的实现

本文我们来实现回射服务器的Buffer.   Buffer的实现   上节提到了非阻塞IO必须具备Buffer.再次将Buffer的设计描述一下: 这里必须补充一点,writeIndex指向空闲空间的第一个位置. 这里有三个重要的不变式: 1. 0 <= readIndex <= writeIndex <= BUFFER_SIZE 2. writeIndex – readIndex 为可以从buffer读取的字节数 3. BUFFER_SIZE – writeIndex 为buffer还

Linux下的非阻塞IO(一)

非阻塞IO是相对于传统的阻塞IO而言的. 我们首先需要搞清楚,什么是阻塞IO.APUE指出,系统调用分为两类,低速系统调用和其他,其中低速系统调用是可能会使进程永远阻塞的一类系统调用.但是与磁盘IO有关的系统调用是个例外. 我们以read和write为例,read函数读取stdin,如果是阻塞IO,那么: 如果我们不输入数据,那么read函数会一直阻塞,一直到我们输入数据为止. 如果是非阻塞IO,那么: 如果存在数据,读取然后返回,如果没有输入,那么直接返回-1,errno置为EAGAIN 我们

实例浅析epoll的水平触发和边缘触发,以及边缘触发为什么要使用非阻塞IO

一.基本概念                                                          我们通俗一点讲: Level_triggered(水平触发):当被监控的文件描述符上有可读写事件发生时,epoll_wait()会通知处理程序去读写.如果这次没有把数据一次性全部读写完(如读写缓冲区太小),那么下次调用 epoll_wait()时,它还会通知你在上没读写完的文件描述符上继续读写,当然如果你一直不去读写,它会一直通知你!!!如果系统中有大量你不需要读写的就

{python之IO多路复用} IO模型介绍 阻塞IO(blocking IO) 非阻塞IO(non-blocking IO) 多路复用IO(IO multiplexing) 异步IO(Asynchronous I/O) IO模型比较分析 selectors模块

阅读目录 一 IO模型介绍 二 阻塞IO(blocking IO) 三 非阻塞IO(non-blocking IO) 四 多路复用IO(IO multiplexing) 五 异步IO(Asynchronous I/O) 六 IO模型比较分析 七 selectors模块 一 IO模型介绍 同步(synchronous) IO和异步(asynchronous) IO,阻塞(blocking) IO和非阻塞(non-blocking)IO分别是什么,到底有什么区别?这个问题其实不同的人给出的答案都可能

从操作系统内核看Java非阻塞IO事件检测

非阻塞服务器模型最重要的一个特点是,在调用读取或写入接口后立即返回,而不会进入阻塞状态.在探讨单线程非阻塞IO模型前必须要先了解非阻塞情况下Socket事件的检测机制,因为对于非阻塞模式最重要的事情是检测哪些连接有感兴趣的事件发生,一般会有如下三种检测方式. 应用程序遍历socket检测 如图所示,当多个客户端向服务器请求时,服务器端会保存一个socket连接列表,应用层线程对socket列表进行轮询尝试读取或写入.对于读取操作,如果成功读取到若干数据则对读取到的数据进行处理,读取失败则下个循环

非阻塞IO模式原理

与阻塞模式对应的另一种模式叫非阻塞IO模式,在整个通信过程中读和写操作不会阻塞,当前处理线程不存在阻塞情况.从A机器到B机器它的通信过程是:A机器一条线程将通道设置为写事件后往下执行,而另外一条线程遍历到此通道有字节要写并往socket写数据,B机器一条线程遍历到此通道有字节要读,交给另外一条线程对socket读数据,处理完又把通道设置为写事件,遍历线程遍历到此通道有字节要写,又往socket写数据传往A机器,不断往下循环此操作直到完成通信.这个过程每台机器都有两类主要线程,一类是负责逻辑处理且