C++网络编程
1080 浏览 6 years, 4 months
2.3 因特网服务器2 - select
版权声明: 转载请注明出处 http://www.codingsoho.com/在服务器1中,用的是accept阻塞方式来等待数据的,这种方式显然不能用于多用户处理,一旦某个用户处理慢了,其他所有用户都会受到影响。
下面方法可以解决这些问题
- 让套接字不阻塞,如果队列中的数据不能够使用,则任何阻塞函数都将失败,这种方法太浪费CPU的利用率,用不停轮询每一个套接字,所以会浪费CPU的时间。
- select()函数轮询多个套接字,检查这些套接字中是否有活动,这种方法使用于单线程
- 流行的方式是使用多线程,让每一个阻塞发生在它自己的线程里,这样不会中断其他的线程。
本节来探讨如何用select()函数处理多个套接字。
select()函数本质上时检查一组套接字,以确定这些套接字中是否任何一个套接字有活动
select(0和fd_set
套接字列表
#include <vector>
int main()
{
vector<int> socketlist; // list of sockets
定义套接字列表容器,开始时为空,当检测到套接字有活动时,会将活动套接字放入
监听套接字
int lsock; // listening socket
// create a socket
lsock = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );
// check if socket was created
if( lsock == -1 )
{
cout << "Socket creation error!" << endl;
return 0;
}
cout << "Socket created!" << endl;
// create a sockaddr_in for binding, listening on port 4000
struct sockaddr_in socketaddress;
socklen_t sa_size = sizeof( struct sockaddr_in );
socketaddress.sin_family = AF_INET;
socketaddress.sin_port = htons( 4000 );
socketaddress.sin_addr.s_addr = htonl( INADDR_ANY );
memset( &(socketaddress.sin_zero), 0, 8 );
// bind the socket
err = bind( lsock, (struct sockaddr*)&socketaddress, sa_size );
if( err == -1 )
{
cout << "Socket binding error!" << endl;
return 0;
}
cout << "Socket bound!" << endl;
// listen on the socket
err = listen( lsock, 16 );
if( err == -1 )
{
cout << "Socket listening error!" << endl;
return 0;
}
cout << "Socket listening, waiting for connection..." << endl;
这个跟前面一样,lsock
用于监听
数据接收
struct timeval zerotime;
zerotime.tv_usec = 0;
zerotime.tv_sec = 0;
char buffer[128]; // used for getting messages
bool done = false; // used for quitting
vector<int>::iterator itr;
// now begin the main loop.
while( !done )
{
// clear the set
FD_ZERO( &rset );
// add the listening socket
FD_SET( lsock, &rset );
// add all of the data sockets
for( itr = socketlist.begin(); itr != socketlist.end(); itr++ )
{
FD_SET( *itr, &rset );
}
// find out if there is any activity on any of the sockets.
i = select( 0x7FFFFFFF, &rset, NULL, NULL, &zerotime );
if( i > 0 )
{
if( FD_ISSET( lsock, &rset ) )
{
// incomming connection
cout << "Incomming connection..." << endl;
int dsock = accept( lsock,
(struct sockaddr*)&socketaddress,
&sa_size );
if( dsock == -1 )
{
cout << "Socket accepting error!" << endl;
return 0;
}
cout << "Socket " << dsock << " accepted." << endl;
// add the socket to the list
socketlist.push_back( dsock );
}
// loop through each socket and see if it has any activity
for( itr = socketlist.begin(); itr != socketlist.end(); itr++ )
{
if( FD_ISSET( *itr, &rset ) )
{
// incomming data
cout << "receiving data from socket ";
cout << *itr << "..." << endl;
err = recv( *itr, buffer, 128, 0 );
if( err == -1 )
{
cout << "Socket receiving error!" << endl;
return 0;
}
if( err == 0 )
{
cout << "Socket " << *itr << " closed" << endl;
shutdown( *itr, 2 );
CloseSocket( *itr );
socketlist.erase( itr );
itr--;
}
else
{
cout << "Data: " << buffer << endl;
// if the message was "servquit", then quit the server.
if( strcmp( buffer, "servquit" ) == 0 )
done = true;
}
}
}
}
}
详细描述下各个步骤:
- 读集
fd_set rset;
,套接字会添加到这个集合里 - 清除集合
FD_ZERO( &rset );
- 首先添加监听套接字,这个是固定存在的
FD_SET( lsock, &rset );
- 添加所有的数据套接字
for( itr = socketlist.begin(); itr != socketlist.end(); itr++ )
{
FD_SET( *itr, &rset );
}
- 用select()函数 检测套接字活动
i = select( 0x7FFFFFFF, &rset, NULL, NULL, &zerotime );
,这个检测包括监听套接字和数据套接字;返回结果大于0表明有活动的套接字,如果有活动,则检查确定是否监听套接字上有流入连接 - 监听套接字流入连接,并将套接字添加到容器socketlist
if( FD_ISSET( lsock, &rset ) )
{
// incomming connection
cout << "Incomming connection..." << endl;
int dsock = accept( lsock,
(struct sockaddr*)&socketaddress,
&sa_size );
if( dsock == -1 )
{
cout << "Socket accepting error!" << endl;
return 0;
}
cout << "Socket " << dsock << " accepted." << endl;
// add the socket to the list
socketlist.push_back( dsock );
}
- 数据套接字流入数据处理,轮询所有的数据套接字,并确定这些套接字上是否有活动。如果有活动,则试图接收数据
err = recv( *itr, buffer, 128, 0 );
。err为接收到的字节数,如果套接字被关闭,则error值为0,如果有错误,则error值为-1.
// loop through each socket and see if it has any activity
for( itr = socketlist.begin(); itr != socketlist.end(); itr++ )
{
if( FD_ISSET( *itr, &rset ) )
{
// incomming data
cout << "receiving data from socket ";
cout << *itr << "..." << endl;
err = recv( *itr, buffer, 128, 0 );
if( err == -1 )
{
cout << "Socket receiving error!" << endl;
return 0;
}
if( err == 0 )
{
cout << "Socket " << *itr << " closed" << endl;
shutdown( *itr, 2 );
CloseSocket( *itr );
socketlist.erase( itr );
itr--;
}
else
{
cout << "Data: " << buffer << endl;
// if the message was "servquit", then quit the server.
if( strcmp( buffer, "servquit" ) == 0 )
done = true;
}
}
}
如果有错误,则退出;如果返回0,则停止和关闭被断开的套接字。同时,将它从套接字容器里删除socketlist.erase( itr );
。删完之后,迭代器往回移itr--;
如果输入servquit,则退出程序
- 最后关闭套接字,包括监听套接字和数据套接字,方法跟前面一样
shutdown( lsock, 2 );
CloseSocket( lsock );
for( i = 0; i < socketlist.size(); i++ )
{
shutdown( socketlist[i], 2 );
CloseSocket( socketlist[i] );
}