搜索
您的当前位置:首页正文

Unix下基于SCTP socket的通信:实现I/O复用

来源:二三娱乐

1. 场景需求

最近在写一个小的程序,验证一下SCTP连接过程中的一些行为:

  • 程序分为Server端和Client端;
  • 两个程序都需要能在监听对端的数据的同时,能够监听用户的键盘输入(用户可以控制是否发送消息给对端,或者推出应用程序);

2. 解决方案

2.1 多线程

第一个想到的解决方法就是多线程,

  • 监听用户输入一个线程,阻塞监听标准输入;
  • 监听socket上的消息,也是阻塞监听;

这样实现的应用程序有一些弊端

  • 多线程势必带来一些额外的开销用于线程切换;
  • 由于线程可以互相打断,程序设计的时候有些地方需要考虑互斥;
  • 控制台的提示信息不好打印

2.2 使用Poll函数实现I/O复用

好在我们有Poll函数可以实现I/O复用,这样我们就可以把两个线程合成一个;在单线程上实现同时监听标准输入和sctp socket,首先实现一个用于IO复用的类

class IoMultiplex
{
public:
...
    void RegisterFd(int fd, CallBack cb);
    void Poll();    
private:
    void PollEventHandler();
    
    std::vector<pollfd> poll_items;
    std::vector<CallBack> cb_lists;
};

2.2.1 IoMultiplex成员函数

第一个成员函数是RegisterFd,用于提供给调用者注册新的文件描述符(比如说socket文件描述符),和它相应的回调函数,

void IoMultiplex::RegisterFd(int fd, CallBack cb)
{
    pollfd item;
    item.fd = fd;
    item.events = POLLIN;
    poll_items.push_back(std::move(item));
    
    cb_lists.push_back(std::move(cb));
}

不同的poll事件会保存到成员变量中,一一对应的会保存这些事件对应的回调函数。

第二个成员函数是Poll,用来提供给调用者启动poll过程,poll是一个阻塞的函数调用,

 void IoMultiplex::Poll()
{
    switch(poll(poll_items.data(), poll_items.size(), -1))
    {
        ...
        default:
            PollEventHandler();
            // new message
            break;
    }
}

单线程程序会阻塞在poll函数,等待注册事件的发生,一旦有新的事件,会调用注册的回调函数。

2.2.2 实现标准输入监听

标准输入的文件描述符是0,

io_multi->RegisterFd(STDIN, [this](int fd)
          { ReadUserCmd(fd); });

ReadUserCmd读取用户输入并执行相应操作。

2.2.3 事件SCTP socket监听

io_multi->RegisterFd(sock_op->socket_fd(), [this](int fd)
            { SctpMsgHandler(fd); });

3 Poll事件

poll可以监听的事件不单单只有POLLIN,还有如下这些

#define POLLIN      0x0001
#define POLLPRI     0x0002
#define POLLOUT     0x0004
#define POLLERR     0x0008
#define POLLHUP     0x0010
#define POLLNVAL    0x0020
  • POLLIN:有数据读取;
  • POLLPRI:有紧急数据要读取;比如说TCP socket上的out-of-band数据
  • POLLOUT:有写出数据;
  • POLLERR:出错;
  • POLLHUP:挂起;
  • POLLNVAL:非法请求:fd没有打开;
Top