C ++ Winsock服务器向每个客户端发送消息

时间:2018-03-12 14:47:01

标签: c++ multithreading server winsock

如何将此echo服务器转换为聊天服务器?我希望这个程序要做的是让服务器将每个收到的消息发送给每个客户端。我想应该有某种队列,但我对这种实现毫无头绪。代码示例会有很大帮助。这是代码:

服务器:

#include <winsock2.h>
#include <iostream>
using namespace std;

struct CLIENT_INFO
{
    SOCKET hClientSocket ;
    struct sockaddr_in clientAddr ;
} ;

char szServerIPAddr[ ] = "192.168.1.221" ;     // Put here the IP address of the server
int nServerPort = 5050 ;                    // The server port that will be used by
                                            // clients to talk with the server

bool InitWinSock2_0( ) ;
BOOL WINAPI ClientThread( LPVOID lpData ) ;

int main( )
{
    if ( ! InitWinSock2_0( ) )
    {
        cout << "Unable to Initialize Windows Socket environment" << WSAGetLastError( ) << endl ;
        return -1 ;
    }

    SOCKET hServerSocket ;

    hServerSocket = socket(
        AF_INET,        // The address family. AF_INET specifies TCP/IP
        SOCK_STREAM,    // Protocol type. SOCK_STREM specified TCP
        0               // Protoco Name. Should be 0 for AF_INET address family
        ) ;
    if ( hServerSocket == INVALID_SOCKET )
    {
        cout << "Unable to create Server socket" << endl ;
        // Cleanup the environment initialized by WSAStartup()
        WSACleanup( ) ;
        return -1 ;
    }

    // Create the structure describing various Server parameters
    struct sockaddr_in serverAddr ;

    serverAddr . sin_family = AF_INET ;     // The address family. MUST be AF_INET
    serverAddr . sin_addr . s_addr = inet_addr( szServerIPAddr ) ;
    serverAddr . sin_port = htons( nServerPort ) ;

    // Bind the Server socket to the address & port
    if ( bind( hServerSocket, ( struct sockaddr * ) &serverAddr, sizeof( serverAddr ) ) == SOCKET_ERROR )
    {
        cout << "Unable to bind to " << szServerIPAddr << " port " << nServerPort << endl ;
        // Free the socket and cleanup the environment initialized by WSAStartup()
        closesocket( hServerSocket ) ;
        WSACleanup( ) ;
        return -1 ;
    }

    // Put the Server socket in listen state so that it can wait for client connections
    if ( listen( hServerSocket, SOMAXCONN ) == SOCKET_ERROR )
    {
        cout << "Unable to put server in listen state" << endl ;
        // Free the socket and cleanup the environment initialized by WSAStartup()
        closesocket( hServerSocket ) ;
        WSACleanup( ) ;
        return -1 ;
    }

    // Start the infinite loop
    while ( true )
    {
        // As the socket is in listen mode there is a connection request pending.
        // Calling accept( ) will succeed and return the socket for the request.
        SOCKET hClientSocket ;
        struct sockaddr_in clientAddr ;
        int nSize = sizeof( clientAddr ) ;

        hClientSocket = accept( hServerSocket, ( struct sockaddr *) &clientAddr, &nSize ) ;
        if ( hClientSocket == INVALID_SOCKET )
        {
            cout << "accept( ) failed" << endl ;
        }
        else
        {
            HANDLE hClientThread ;
            struct CLIENT_INFO* clientInfo = (struct CLIENT_INFO*) malloc(sizeof(struct CLIENT_INFO)) ;
            DWORD dwThreadId ;
            clientInfo -> clientAddr = clientAddr ;
            clientInfo -> hClientSocket = hClientSocket ;

            cout << "Client connected from " << inet_ntoa( clientAddr . sin_addr ) << endl ;

            // Start the client thread
            hClientThread = CreateThread( NULL, 0,
                ( LPTHREAD_START_ROUTINE ) ClientThread,
                ( LPVOID ) &(*clientInfo), 0, &dwThreadId ) ;
            if ( hClientThread == NULL )
            {
                cout << "Unable to create client thread" << endl ;
            }
            else
            {
                CloseHandle( hClientThread ) ;
            }
        }
    }

    closesocket( hServerSocket ) ;
    WSACleanup( ) ;
    return 0 ;
}

bool InitWinSock2_0( )
{
    WSADATA wsaData ;
    WORD wVersion = MAKEWORD( 2, 0 ) ;

    if ( ! WSAStartup( wVersion, &wsaData ) )
        return true ;

    return false ;
}

BOOL WINAPI ClientThread( LPVOID lpData )
{
    CLIENT_INFO *pClientInfo = ( CLIENT_INFO * ) lpData ;
    char szBuffer[ 1024 ] ;
    int nLength ;

    while ( 1 )
    {
        nLength = recv( pClientInfo -> hClientSocket, szBuffer, sizeof( szBuffer ), 0 ) ;
        if ( nLength > 0 )
        {
            szBuffer[ nLength ] = '\0' ;
            cout << "Received " << szBuffer << " from " << inet_ntoa( pClientInfo -> clientAddr . sin_addr ) << endl ;

            // Convert the string to upper case and send it back, if its not QUIT
            strupr( szBuffer ) ;
            if ( strcmp( szBuffer, "QUIT" ) == 0 )
            {
                closesocket( pClientInfo -> hClientSocket ) ;
                return TRUE ;
            }
            // send( ) may not be able to send the complete data in one go.
            // So try sending the data in multiple requests
            int nCntSend = 0 ;
            char *pBuffer = szBuffer ;

            while ( ( nCntSend = send( pClientInfo -> hClientSocket, pBuffer, nLength, 0 ) != nLength ) )
            {
                if ( nCntSend == -1 )
                {
                    cout << "Error sending the data to " << inet_ntoa( pClientInfo -> clientAddr . sin_addr ) << endl ;
                    break ;
                }
                if ( nCntSend == nLength )
                    break ;

                pBuffer += nCntSend ;
                nLength -= nCntSend ;
            }
        }
        else
        {
            cout << "Error reading the data from " << inet_ntoa( pClientInfo -> clientAddr . sin_addr ) << endl ;
        }
    }

    return TRUE ;
}

客户端:

#include <winsock2.h>
#include <iostream>
using namespace std;

char szServerIPAddr[ 20 ] = "192.168.1.221" ;     // Put here the IP address of the server
int nServerPort = 5050 ;                    // The server port that will be used by                                            // clients to talk with the server

bool InitWinSock2_0( ) ;

int main( )
{
    cout << "Enter the server IP Address: " ;
    cin >> szServerIPAddr ;
    cout << "Enter the server port number: " ;
    cin >> nServerPort ;

    if ( ! InitWinSock2_0( ) )
    {
        cout << "Unable to Initialize Windows Socket environment" << WSAGetLastError( ) << endl ;
        return -1 ;
    }

    SOCKET hClientSocket ;

    hClientSocket = socket(
        AF_INET,        // The address family. AF_INET specifies TCP/IP
        SOCK_STREAM,    // Protocol type. SOCK_STREM specified TCP
        0               // Protoco Name. Should be 0 for AF_INET address family
        ) ;
    if ( hClientSocket == INVALID_SOCKET )
    {
        cout << "Unable to create Server socket" << endl ;
        // Cleanup the environment initialized by WSAStartup()
        WSACleanup( ) ;
        return -1 ;
    }


    // Create the structure describing various Server parameters
    struct sockaddr_in serverAddr ;

    serverAddr . sin_family = AF_INET ;     // The address family. MUST be AF_INET
    serverAddr . sin_addr . s_addr = inet_addr( szServerIPAddr ) ;
    serverAddr . sin_port = htons( nServerPort ) ;

    // Connect to the server
    if ( connect( hClientSocket, ( struct sockaddr * ) &serverAddr, sizeof( serverAddr ) ) < 0 )
    {
        cout << "Unable to connect to " << szServerIPAddr << " on port " << nServerPort << endl ;
        closesocket( hClientSocket ) ;
        WSACleanup( ) ;
        return -1 ;
    }

    char szBuffer[ 1024 ] = "" ;

    while ( strcmp( szBuffer, "QUIT" ) != 0 )
    {
        cout << "Enter the string to send (QUIT) to stop: " ;
        cin >> szBuffer ;

        int nLength = strlen( szBuffer ) ;

        // send( ) may not be able to send the complete data in one go.
        // So try sending the data in multiple requests
        int nCntSend = 0 ;
        char *pBuffer = szBuffer ;

        while ( ( nCntSend = send( hClientSocket, pBuffer, nLength, 0 ) != nLength ) )
        {
            if ( nCntSend == -1 )
            {
                cout << "Error sending the data to server" << endl ;
                break ;
            }
            if ( nCntSend == nLength )
                break ;

            pBuffer += nCntSend ;
            nLength -= nCntSend ;
        }

        strupr( szBuffer ) ;
        if ( strcmp( szBuffer, "QUIT" ) == 0 )
        {
            break ;
        }

        nLength = recv( hClientSocket, szBuffer, sizeof( szBuffer ), 0 ) ;
        if ( nLength > 0 )
        {
            szBuffer[ nLength ] = '\0' ;
            cout << "Received " << szBuffer << " from server" << endl ;
        }
    }

    closesocket( hClientSocket ) ;
    WSACleanup( ) ;
    return 0 ;
}

bool InitWinSock2_0( )
{
    WSADATA wsaData ;
    WORD wVersion = MAKEWORD( 2, 0 ) ;

    if ( ! WSAStartup( wVersion, &wsaData ) )
        return true ;

    return false ;
}

1 个答案:

答案 0 :(得分:0)

只是为了提出这个想法(如果您需要进一步的帮助,请在本网站上实施您的服务器并提出新的更具体的问题):

  • 首先,创建一个合适的消息队列。旁注:std::vector的缺点是,无论是插入还是删除消息,您都会一直在移动数据(具体取决于您的实现方式)。某种环形缓冲区更有效 - 它可以基于std :: vector实现。
  • 创建包含所有已连接客户端的另一个队列。要么重复使用你的环形缓冲区(它们使它成为一个模板),要么变化不那么频繁,只需使用std :: vector。
  • 创建一个线程,该线程从队列中的第一条消息中读取并将其发送到列表中的所有客户端。您可以使用std::condition_variable让您的线程在队列为空时完全休眠,并在插入一些数据后立即唤醒。
  • 对于每个accept ed连接,创建一个从套接字读取的新线程,并将接受的数据放入队列中。

请注意,您需要保护消息队列和客户端列表不受竞争条件影响,因为两者都可以从多个线程访问!