使用select实现套接字多路复用小记

套接字设置非阻塞,可以对套件字设置非阻塞,这样就可以不必为等待一个套接字而整个程序(进程,线程)阻塞在那里,

设置了套接字不阻塞,则如果此套接字队数据还不可用,则任何阻塞函数调用之都会失败,并且返回一个EWOULDBLOCK错误,每次出错了要判断一下是不是阻塞错误,如果是的话就忽略它,但是这种效率低下,

更好的办法是使用select()函数立即轮询许多套接字集,检测其中套接字是否有活动。

或者还有一种方法,就是使用多线程,每个线程接管一个连接connection,阻塞也是阻塞那个管理connection的线程,并不影响其它线程。

设置非阻塞:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
void Socket::SetBlocking( bool p_blockmode )
{
int err;
#ifdef WIN32
unsigned long mode = !p_blockmode;
err = ioctlsocket( m_sock, FIONBIO, &mode );
#else
// get the flags
int flags = fcntl( m_sock, F_GETFL, 0 );
// set or clear the non-blocking flag
if( p_blockmode == false )
{
flags |= O_NONBLOCK;
}
else
{
flags &= ~O_NONBLOCK;
}
err = fcntl( m_sock, F_SETFL, flags );
#endif
if( err == -1 )
{
throw( Exception( GetError() ) );
}
m_isblocking = p_blockmode;
}

select()函数本质是检查一组套接字集,如果其中有任何一个套接字有活动,它会告诉你。这样就不需要自己去检查每个套接字,以确定它是否获得

select()函数第一个参数是最大的套接字描述符编号。Winsock忽略这一参数,但是UNIX并不忽略此参数。

最笨的方法就是假设此值小于十六进制值0xFFFFFFF,它是int类型的最大值。

但是我看过一个简单的方法就是,利用套接字集中的最后一个套接字的描述符作为最大值,但是实际运用中发现这样并不能涵盖那个最大的描述符的套接字,所以我用最后一个套接字的描述符的值+1作为最大值,实际运用起来很好。

将套接字集的添加删除和select调用封装到一起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
class SocketSet
{
public:
SocketSet();
void AddSocket( const Socket& p_sock );
void RemoveSocket( const Socket& p_sock );
inline int Poll( long p_time = 0 )
{
// this is the time value structure. It will determine how long
// the select function will wait.
struct timeval t = { 0, p_time * 1000 };
// copy the set over into the activity set.
m_activityset = m_set;
#ifdef WIN32
return select( 0, &m_activityset, 0, 0, &t );
#else
if( m_socketdescs.size() == 0 ) return 0;
return select( *(m_socketdescs.rbegin())+1, &m_activityset, 0, 0, &t );
#endif
}
inline bool HasActivity( const Socket& p_sock )
{
return FD_ISSET( p_sock.GetSock(), &m_activityset ) != 0;
//FD_ISSET(int fd,fd_set *fdset);用于测试指定的文件描述符是否在该集合中。
}
protected:
// a set representing the socket descriptors.
fd_set m_set;
// this set will represent all the sockets that have activity on them.
fd_set m_activityset;
#ifndef WIN32
std::set<sock> m_socketdescs;
#endif
};
}

实际运用select监听数据套接字:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
void Listen()//监听每个和客户端成功建立连接的数据套接字传回来的消息
{
// detect if any sockets have action on them
if( m_set.Poll() > 0 )
{
clistitr itr = m_connections.begin();
clistitr c;
// loop through every connection
while( itr != m_connections.end() )
{
// keep the current connection in "c", and move "itr" to
// the next item
c = itr++;
// check if there is any activity
if( m_set.HasActivity( *c ) )
{
try
{
// receive as much data as you can.
c->Receive();
}
catch( ... )//if error
{
Close( c );
}
}
}
}
}

本文标题:使用select实现套接字多路复用小记

文章作者:Yang Shuai

发布时间:2018年06月15日 - 21:06

最后更新:2018年06月15日 - 21:06

原始链接:https://ysbbswork.github.io/2018/06/15/使用select实现套接字多路复用小记/

许可协议: 署名-非商业性使用-禁止演绎 4.0 国际 转载请保留原文链接及作者。

坚持原创技术分享,您的支持将鼓励我继续创作!