C++网络编程


849 浏览 5 years, 7 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] );
    }