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没有打开;