bamshad1986 Ответов: 1

Tcp echo chat server для нескольких клиентов в windows.


I have a problem in case of multiple clients socket programming in c++. Actually I have downloaded a source code for server side that echo the message to the clients. Also I have a simple client code. Everything works perfectly as long as there is only one client connected to the server. But when the other client connects, the server echo the message to both of them but, in the client side it prints in different order. Indeed it wait for user to enter the input message and then print out the received message. But I want it to be print it as soon as the message is received. For better description of the problem, the output of chat between two clients connected to the server is mentioned bellow. Also the codes of server and client are attached at the end.

in the Bob cmd:

Pleas insert your message: Bob: hello alice.
The recieved message:Bob: hello alice.
Pleas insert your message: Bob: how is everything?
The recieved message:Alice: hello bob.
Pleas insert your message: Bob: everything is perfect!
The recieved message:Bob: how is everything?Alice: it is fine, and you?
Pleas insert your message:

in the alice cmd:

Pleas insert your message: Alice: hello bob.
The recieved message:Bob: hello alice.
Pleas insert your message: Alice: it is fine, and you?
The recieved message:Alice: hello bob.Bob: how is everything?
Pleas insert your message: Alice: cool!
The recieved message:Alice: it is fine, and you?Bob: everything is perfect!
Pleas insert your message:


Что я уже пробовал:


/*
    TCP Echo server in winsock
    
*/
#include<stdio.h>
#include<winsock2.h>

#pragma comment(lib, "ws2_32.lib") //Winsock Library

int main(int argc , char *argv[])
{
    WSADATA wsa;
    SOCKET master , new_socket , client_socket[30] , s;
    struct sockaddr_in server, address;
    int max_clients = 30 , activity, addrlen, i, valread;

    //size of our receive buffer, this is string length.
    int MAXRECV = 1024;
    //set of socket descriptors
    fd_set readfds;
    //1 extra for null character, string termination
    char *buffer;
    char msg[10] = "salam";
    buffer =  (char*) malloc((MAXRECV + 1) * sizeof(char));

    for(i = 0 ; i < 30;i++)
    {
        client_socket[i] = 0;
    }

    printf("\nInitialising Winsock...");
    if (WSAStartup(MAKEWORD(2,2),&wsa) != 0)
    {
        printf("Failed. Error Code : %d",WSAGetLastError());
        exit(EXIT_FAILURE);
    }

    printf("Initialised.\n");

    //Create a socket
    if((master = socket(AF_INET , SOCK_STREAM , 0 )) == INVALID_SOCKET)
    {
        printf("Could not create socket : %d" , WSAGetLastError());
        exit(EXIT_FAILURE);
    }

    printf("Socket created.\n");

    //Prepare the sockaddr_in structure
    server.sin_family = AF_INET;
    server.sin_addr.s_addr = INADDR_ANY;
    server.sin_port = htons( 8888 );

    //Bind
    if( bind(master ,(struct sockaddr *)&server , sizeof(server)) == SOCKET_ERROR)
    {
        printf("Bind failed with error code : %d" , WSAGetLastError());
        exit(EXIT_FAILURE);
    }

    puts("Bind done");

    //Listen to incoming connections
    listen(master , 3);

    //Accept and incoming connection
    puts("Waiting for incoming connections...");

    addrlen = sizeof(struct sockaddr_in);

    while(TRUE)
    {
        //clear the socket fd set
        FD_ZERO(&readfds);

        //add master socket to fd set
        FD_SET(master, &readfds);

        //add child sockets to fd set
        for (  i = 0 ; i < max_clients ; i++)
        {
            s = client_socket[i];
            if(s > 0)
            {
                FD_SET( s , &readfds);
            }
        }

        //wait for an activity on any of the sockets, timeout is NULL , so wait indefinitely
        activity = select( 0 , &readfds , NULL , NULL , NULL);

        if ( activity == SOCKET_ERROR )
        {
            printf("select call failed with error code : %d" , WSAGetLastError());
            exit(EXIT_FAILURE);
        }

        //If something happened on the master socket , then its an incoming connection
        if (FD_ISSET(master , &readfds))
        {
            if ((new_socket = accept(master , (struct sockaddr *)&address, (int *)&addrlen))<0)
            {
                perror("accept");
                exit(EXIT_FAILURE);
            }

            //inform user of socket number - used in send and receive commands
            printf("New connection , socket fd is %d , ip is : %s , port : %d \n" , new_socket , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));


            //send( new_socket , msg , valread , 0 );
            //send( new_socket, msg, (int)strlen(msg), 0 );

            //add new socket to array of sockets
            for (i = 0; i < max_clients; i++)
            {
                if (client_socket[i] == 0)
                {
                    client_socket[i] = new_socket;
                    printf("Adding to list of sockets at index %d \n" , i);
                    break;
                }
            }
        }

        //else its some IO operation on some other socket :)
        for (i = 0; i < max_clients; i++)
        {
            s = client_socket[i];
            //if client presend in read sockets
            if (FD_ISSET( s , &readfds))
            {
                //get details of the client
                getpeername(s , (struct sockaddr*)&address , (int*)&addrlen);

                //Check if it was for closing , and also read the incoming message
                //recv does not place a null terminator at the end of the string (whilst printf %s assumes there is one).
                valread = recv( s , buffer, MAXRECV, 0);

                if( valread == SOCKET_ERROR)
                {
                    int error_code = WSAGetLastError();
                    if(error_code == WSAECONNRESET)
                    {
                        //Somebody disconnected , get his details and print
                        printf("Host disconnected unexpectedly , ip %s , port %d \n" , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));

                        //Close the socket and mark as 0 in list for reuse
                        closesocket( s );
                        client_socket[i] = 0;
                    }
                    else
                    {
                        printf("recv failed with error code : %d" , error_code);
                    }
                }
                if ( valread == 0)
                {
                    //Somebody disconnected , get his details and print
                    printf("Host disconnected , ip %s , port %d \n" , inet_ntoa(address.sin_addr) , ntohs(address.sin_port));

                    //Close the socket and mark as 0 in list for reuse
                    closesocket( s );
                    client_socket[i] = 0;
                }

                //Echo back the message that came in
                else
                {
                    //add null character, if you want to use with printf/puts or other string handling functions
                    buffer[valread] = '\0';
                    printf("%s:%d - %s \n" , inet_ntoa(address.sin_addr) , ntohs(address.sin_port), buffer);
                    //for (int j=0;j<4;j++)	{
                    send( client_socket[0] , buffer , valread , 0 );
                    send( client_socket[1] , buffer , valread , 0 );
                    //printf("%d",client_socket[j]);
                    //}
                    memset(buffer,'\0',sizeof(buffer));
                }
            }
        }
    }

    closesocket(s);
    WSACleanup();

    return 0;
}





/*
the client side:
 */

#define WIN32_LEAN_AND_MEAN

#define _WIN32_WINNT  0x501

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <iostream>
using namespace std;



// Need to link with Ws2_32.lib, Mswsock.lib, and Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")


#define DEFAULT_BUFLEN 512
#define DEFAULT_PORT "8888"

int __cdecl main(int argc, char **argv)
{
    WSADATA wsaData;
    SOCKET ConnectSocket;
    struct addrinfo *result = NULL,
                    *ptr = NULL,
                    hints;
    char sendbuf[1000];
    char recvbuf[DEFAULT_BUFLEN];
    int iResult, activity;
    int recvbuflen = DEFAULT_BUFLEN;

    // Validate the parameters
    if (argc != 2) {
        printf("usage: %s server-name\n", argv[0]);
        return 1;
    }

    // Initialize Winsock
    iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
    if (iResult != 0) {
        printf("WSAStartup failed with error: %d\n", iResult);
        return 1;
    }

    ZeroMemory( &hints, sizeof(hints) );
    hints.ai_family = AF_UNSPEC;
    hints.ai_socktype = SOCK_STREAM;
    hints.ai_protocol = IPPROTO_TCP;

    // Resolve the server address and port
    iResult = getaddrinfo(argv[1], DEFAULT_PORT, &hints, &result);
    if ( iResult != 0 ) {
        printf("getaddrinfo failed with error: %d\n", iResult);
        WSACleanup();
        return 1;
    }

    // Attempt to connect to an address until one succeeds
    for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {

        // Create a SOCKET for connecting to server
        ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
        //printf("%d\n",ConnectSocket);
        if (ConnectSocket == INVALID_SOCKET) {
            printf("socket failed with error: %ld\n", WSAGetLastError());
            WSACleanup();
            return 1;
        }

        // Connect to server.
        iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
        if (iResult == SOCKET_ERROR) {
            closesocket(ConnectSocket);
            ConnectSocket = INVALID_SOCKET;
            continue;
        }
        break;
    }

    freeaddrinfo(result);

    if (ConnectSocket == INVALID_SOCKET) {
        printf("Unable to connect to server!\n");
        WSACleanup();
        return 1;
    }

    while(TRUE)	{
        cout << "Pleas insert your message: ";
        cin.getline(sendbuf,1000);
        send( ConnectSocket, sendbuf, (int)strlen(sendbuf), 0 );
        memset(sendbuf,'\0',sizeof(sendbuf));
        recv(ConnectSocket, recvbuf, recvbuflen, 0);
        std::cout << "The recieved message:" << recvbuf <<endl;
        memset(recvbuf,'\0',sizeof(recvbuf));
    }

    return 0;
}





1 Ответов

Рейтинг:
2

Jochen Arndt

Это потому, что cin.getline() звонок блокируется. Вы должны выбрать неблокирующий метод, чтобы вы могли обрабатывать входящие сообщения.

Это может быть реализовано путем ожидания нажатия клавиши события и наличия сообщения.

Когда придет сообщение, примите его и распечатайте.

После нажатия клавиши вызовите getline() Это будет снова блокироваться до тех пор, пока не будет введена клавиша возврата, но обычно это делается для того, чтобы избежать нарушения ввода на экране при печати нового сообщения. В качестве альтернативы вы должны организовать вывод на экран (например, вставляя новые сообщения над строкой ввода в ожидании клавиши ENTER).


bamshad1986

Привет :)
спасибо за помощь. да, похоже, проблема в cin. getline (). Я новичок в c++, так что у вас есть какие-нибудь технические предложения? Я нашел SDL, но есть ли какая-нибудь внутренняя библиотека в c++? или любой другой вариант?

Jochen Arndt

Вы можете использовать _kbhit () или GetAsyncKeyState (), чтобы проверить, доступен ли символ в буфере ввода клавиатуры.

Но вам нужен метод ожидания, который возвращается, когда либо нажата клавиша, либо поступают новые данные. Самым простым подходом было бы что-то вроде (псевдокод):

while (!KeyPressed() && !NewDataAvailable())
    SleepForVeryShortTime();
if (KeyPressed())
    GetLineAndSend();
else
    ReceiveDataAndPrint();

Это называется "опросом", и его следует избегать, поскольку он создает высокую нагрузку на систему.

Лучшим решением было бы использование WaitForMultipleObjects () с дескрипторами для нажатий клавиш (GetStdHandle(STD_INPUT_HANDLE)) и событий сокета или WSAWaitForMultipleEvents () (где я на самом деле не уверен, что это может обрабатывать события клавиатуры).

Но это уже продвинутая тема.