C++ мульти клиент / серверный чат

Я достигаю дна, думая о решении проблемы с моим сервером чата и клиентом.

Что предполагается сделать, клиент запрашивает имя пользователя, а затем запрашивает соединение у пользователя с ответом [Да / Нет].

Если нажать "да", клиент должен подключиться к серверу, когда ему это нужно, он должен идти в отдельном потоке (для обработки нескольких клиентов (но моя проблема заключается в том, что когда присоединяется более одного пользователя) (имя пользователя текущего зарегистрированного пользователя изменяется последнему, кто присоединился к чату. Пока это происходит (сервер показывает имя пользователя, в то время как на экране клиента оно исчезает, и не появляются никакие или все странные признаки).

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

Кодовый сервер:

#include "stdafx.h"


long antwoord;
char chatname[100];
char bericht[498];
char sbericht[498];


using namespace std;

DWORD WINAPI SocketHandler(void*);

//our main function
void main()
{
    //here we set the Winsock-DLL to start

    WSAData wsaData;
    WORD DLLVERSION;
    DLLVERSION = MAKEWORD(2,1);

    //here the Winsock-DLL will be started with WSAStartup
    //version of the DLL
    antwoord = WSAStartup(DLLVERSION, &wsaData);

    if(antwoord != 0)
    {
        WSACleanup();
        exit(1);
    }
    else
    {
        cout << "WSA started successfully" <<endl;
        cout << "The status: \n" << wsaData.szSystemStatus <<endl;
    }
    //the DLL is started

    //structure of our socket is being created
    SOCKADDR_IN addr; 

    //addr is our struct

    int addrlen = sizeof(addr);

    //socket sListen - will listen to incoming connections
    SOCKET sListen;
    //socket sConnect - will be operating if a connection is found.
    SOCKET sConnect;

    //setup of our sockets
    //opgezocht op internet - AF_INET bekend dat het lid is van de internet familie
    //Sock_STREAM  betekenend dat onze socket een verbinding georiënteerde socket is.
    sConnect = socket(AF_INET,SOCK_STREAM,NULL);

    //now we have setup our struct

    //inet_addr is our IP adres of our socket(it will be the localhost ip
    //that will be 127.0.0.1

    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    //retype of the family
    addr.sin_family = AF_INET;

    //now the server has the ip(127.0.0.1) 
    //and the port number (4444)
    addr.sin_port = htons(4444);

    //here we will define the setup for the sListen-socket
    sListen = socket(AF_INET,SOCK_STREAM,NULL);

    if (sConnect == INVALID_SOCKET)
    {
        cout << "Error at socket(): \n" << WSAGetLastError() <<endl;
        WSACleanup();
    }
    else
    {
        cout << "Connect socket() is OK!" <<endl;
    }

    if(sListen == INVALID_SOCKET)
    {
        cout << "Error at socket(): \n" << WSAGetLastError() <<endl;
        WSACleanup();
    }
    else
    {
        cout << "Listen socket() is OK!" <<endl;
    }

    //here the sListen-socket will be bind
    //we say that the socket has the IP adress of (127.0.0.1) and is on port (4444)
    //we let the socket become the struct "addr"
    if(bind(sListen, (SOCKADDR*)&addr, sizeof(addr)) == SOCKET_ERROR)
    {
        cout << "bind() failed: \n" << WSAGetLastError() <<endl;
        WSACleanup();
        exit(1);
    }
    else{
        cout << "bind() is OK!" <<endl;
    }

    if(listen( sListen, 10) == -1 ){
        cout << "Error listening %d\n" << WSAGetLastError() <<endl;

    }

    //here we will tell what the server must do when a connection is found
    //therefor we will create an endless loop
    cout << "Waiting for a incoming connection..." <<endl;


    //now we let the socket listen for incoming connections
    //SOMAXCOMM heeft het nut dat het dan voordurend luisterd naar inkomende verbindingen zonder limiet
    int* csock;

    while(true)
    {
        csock = (int*)malloc(sizeof(int));
        //if a connection is found: show the message!
        if((*csock = accept(sListen, (SOCKADDR*)&addr, &addrlen))!= INVALID_SOCKET)
        {
            cout << "A Connection was found with :" << inet_ntoa(addr.sin_addr) <<endl;

            antwoord = send(*csock, "Welcome to our chat:", 21,NULL);
            CreateThread(0,0,&SocketHandler, (void*)csock , 0,0);
            cout << *csock <<endl;

        }
    }

}
//sbericht is the message
DWORD WINAPI SocketHandler(void* lp)
{
    int *csock = (int*)lp;

    for(;;)
    {
        antwoord = recv(*csock, sbericht, sizeof(sbericht), NULL);
        antwoord = recv(*csock, chatname, sizeof(chatname), NULL);

        while(antwoord = recv(*csock, sbericht, sizeof(sbericht), NULL) && (antwoord = recv(*csock, sbericht, sizeof(sbericht), NULL)) )
        {
            printf("%s\: \"%s\"\n", chatname,  sbericht);
            antwoord = send(*csock, sbericht, sizeof(sbericht), NULL);
            antwoord = send(*csock, chatname, sizeof(chatname), NULL);

        }
        return 0;


    }
}

Код клиента:

#include "stdafx.h"

using namespace std;

//our main function
int main()
{
    //here we set the Winsock-DLL to start
    string bevestiging; 

    char chatname[100]; 

    char bericht[250];
    char sbericht[250];

    string strbericht;

    string strsbericht;

    long antwoord;
    //here the Winsock-DLL will be started with WSAStartup
                    //version of the DLL
    WSAData wsaData;
    WORD DLLVERSION;
    DLLVERSION = MAKEWORD(2,1);
    antwoord = WSAStartup(DLLVERSION, &wsaData);
    if(antwoord != 0)
    {
        exit(1);
    }
    else
    {
        cout << "WSA started successfully" <<endl;
        cout << "The status: \n" << wsaData.szSystemStatus <<endl;
    }

    SOCKADDR_IN addr;

    int addrlen = sizeof(addr);

    SOCKET sConnect;

    sConnect = socket(AF_INET, SOCK_STREAM, NULL);

    if (sConnect == INVALID_SOCKET)
    {
        cout << "Error at socket(): \n" << WSAGetLastError() <<endl;
    }
    else
    {
        cout << "socket() is OK!\n" <<endl;
    }


    addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    addr.sin_family = AF_INET;

    addr.sin_port = htons(4444);

    cout << "What is your chat name?" <<endl;

    cin.getline(chatname, 100);


    cout << "Do you want to connect to the server? [Y/N]" <<endl;

    cin >> bevestiging;


    if (bevestiging == "N")
    {
        exit(1);
    }
    else
    {
        if(bevestiging == "Y")
        {

            connect(sConnect, (SOCKADDR*)&addr, sizeof(addr));

            antwoord = recv(sConnect, bericht, sizeof(bericht), NULL);

            strbericht = bericht;

            cout << strbericht << chatname <<endl;

            while(true)
            {
                if(antwoord > 1)
                {

                    cin.clear();
                    cin.sync();
                    cout << chatname << " :" <<endl;
                    cin.getline(sbericht, sizeof(sbericht));
                    antwoord = send(sConnect, sbericht, sizeof(sbericht), NULL);
                    antwoord = send(sConnect, chatname, sizeof(chatname), NULL);

                    while(antwoord = send(sConnect, sbericht, sizeof(sbericht), NULL) && (antwoord = send(sConnect, sbericht, sizeof(sbericht), NULL)))
                    {
                        antwoord = recv(sConnect, sbericht, sizeof(sbericht), NULL);
                        antwoord = recv(sConnect, chatname, sizeof(chatname), NULL);
                        cout << chatname << ":" <<endl;
                        cout << sbericht <<endl;
                        cin.getline(sbericht, 250);

                    }

                }

                else
                {
                cout << "The connection to the server has been lost... \n" << "please exit the client." <<endl;

                }
            }

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

Всегда чему-то учусь (в настоящее время я также занят уроком по сетевому программированию Beejee).

2 ответа

sbericht и chatname - глобальные переменные Ваши два потока работают с этими глобальными буферами одновременно, поэтому один поток перезаписывает данные другого потока

С этим кодом есть несколько проблем, но при запуске сокеты - это непростая вещь. В коде вашего сервера кажется, что многопоточность - ваш настоящий зверь. Обратите внимание, что потоки и сокеты - это очень разные понятия, но они часто используются вместе. Большая проблема (как сказал Эндрю) в том, что у вас есть условия гонки. Вам нужно использовать взаимные исключения, чтобы гарантировать взаимное исключение, если вы пишете в глобальные переменные в нескольких потоках. Например, в SocketHandler вам нужно защитить свою переменную antwoord, Кроме того, используйте new а также delete в С ++, а не malloc() а также free() (они используются в C). Также обратите внимание, что для каждого нового соединения вы перезаписываете значение вашего csock переменная. Вам нужны отдельные переменные для хранения открытого клиентского сокета.

Серверные сокеты в основном работают следующим образом: socket(), bind(), listen(), accept(), Теперь, чтобы сохранить порт, вы bind()'к и listen()открыты для большего количества соединений, accept() перенаправляет ваш клиент и помещает их в другой дескриптор файла сокета (который является возвращаемым значением accept()) так что вы можете продолжать общаться с этим клиентом. Следовательно, для каждого клиента вам нужно держать значение accept() однозначно.

Однако я скептически отношусь к тому, что в вашем клиентском коде вы получаете то, что ожидаете. Ваш протокол где-нибудь гарантирует, что он отправит только имя пользователя, а затем чат? Вам может просто понадобиться один recv() позвоните, чтобы получить все ваши данные. Кроме того, какова актуальность следующей строки?

while(antwoord = send(sConnect, sbericht, sizeof(sbericht), NULL) && (antwoord = send(sConnect, sbericht, sizeof(sbericht), NULL)))

Вы просто выполняете одну и ту же работу дважды - одного раза должно быть достаточно (и, возможно, в этот момент программа может заблокировать и перезаписать данные без вашего ведома). Для простого текстового протокола весьма вероятно, что вы можете создать переменную ~512 байт и получить всю информацию с сервера соответствующим образом за один вызов recv(),

Я пытался выделить большие проблемы в вашем коде, и эти проблемы, более или менее, часто встречаются и в других областях вашего кода. Если вы не знакомы с многопоточностью, решите эту проблему позже. Научитесь делать однопоточные сокеты, а затем изучите многопоточность. Занятие ими обоими укусит вас. Удачи!

Другие вопросы по тегам