2011-12-18 5 views
0

나는 채팅 서버와 클라이언트에서 겪고있는 문제에 대한 해결책을 생각하면서 최악의 상황에 직면하고 있습니다.C++ 멀티 클라이언트/서버 채팅

클라이언트는 사용자 이름을 묻고 답을 [Y/N]으로하여 사용자에게 연결을 요청합니다.

예를 들자면 클라이언트가 서버에 연결해야합니다. 클라이언트가 별도의 스레드 (여러 클라이언트를 처리하기 위해 필요합니다.하지만 내 문제는 하나 이상의 사용자가 가입 할 때 (현재 사용자의 사용자 이름 로그인하면 마지막으로 채팅에 참여한 사람으로 변경됩니다. 그 동안 (서버에 사용자 이름이 표시되고 클라이언트 화면에는 사라지고 이상한 징후가 나타나지 않거나 전혀 표시되지 않습니다.)

내가 필요한 것도 (사용자 자신을 제외하고) 연결된 다른 클라이언트에게 메시지를 배포하는 것입니다. (사용자 제외)

코드 서버 :

#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의 네트워크 프로그래밍 튜토리얼로도 바쁘다).

+0

이 질문은 매우 광범위합니다. 사실 몇 가지 질문이 있습니다. 한 번에 한 가지만 물어보고 특정 질문을 다루는 최소한의 예에 제공된 코드를 자르십시오. 좋은 SO 질문에 대한 더 많은 힌트는 http://tinyurl.com/sohints에서 찾을 수 있습니다. –

+0

나는이 프로젝트를 두 부분으로 나눌 것을 권한다 : '비 스레드 (non-threaded)'서버로 네트워킹 측면을 완전히 이해하고, stdio/iostream과 완전히 다른 '스레딩 샌드 박스'를 사용해야한다. 당신이 그들을 개별적으로 잘 이해하지 못한다면, 당신은 악몽을 꾼다. – kfmfe04

+0

좋아, 이제이 문제에 이르렀다. [링크] (http://i42.tinypic.com/awq81h.jpg) –

답변

0

sbericht 및 chatname - 동시에 에서이 글로벌 버퍼 작업 전역 변수 당신이 개 스레드 그래서 하나 개의 스레드가이 코드에 몇 가지 문제가 있습니다

+0

스레드에 버퍼를 넣는 것도 좋은 생각입니까? –

+0

매우 크지 않은 경우 - 예. 각 스레드는 스택을 가지고 있습니다. 기본적으로 (Windows에서) 스레드의 스택 크기는 1 메가 바이트입니다. 귀하의 상황에서 스레드에 버퍼를 넣으려면 필요합니다. – Andrew

0

다른 스레드의 데이터를 다시 작성하지만, 소켓은 끈적 끈적한 것입니다 당신이 시작할 때 당신의 머리를 감싸는 것. 그것은 서버 코드에서 멀티 쓰레딩이 실제 짐승 인 것처럼 보입니다. 스레드와 소켓은 매우 다른 개념이지만 종종 함께 사용됩니다. 커다란 이슈 (Andrew가 말했듯이)는 경쟁 조건이 있다는 것입니다. 여러 스레드에서 전역 변수에 쓰는 경우 뮤텍스를 사용하여 상호 배제를 보장해야합니다. 예를 들어 SocketHandler에서 변수 antwoord을 보호해야합니다. 또한 malloc()free()이 아닌 C++에서 newdelete을 사용하십시오 (이들은 C에서 사용됩니다). 또한 새로 연결될 때마다 csock 변수의 값을 덮어 씁니다. 각 클라이언트의 열린 연결 소켓을 보유하려면 별도의 변수가 필요합니다.

서버 소켓은 기본적으로 다음과 같이 작동합니다. socket(), bind(), listen(), accept(). 이제 포트를 유지하기 위해 bind()을 수정하고 더 많은 연결을 위해 listen()을 열어두면 클라이언트를 재 라우팅하여 다른 소켓 파일 설명자 (즉, accept()의 반환 값)에 넣습니다. 이 클라이언트와 계속 통신하십시오. 따라서 각 클라이언트에 대해 accept()의 값을 고유하게 유지해야합니다.

그러나 나는 회의적입니다. 그러나 클라이언트 코드에서 기대 한 바를 받고 있습니다. 귀하의 프로토콜은 어딘가에 사용자 이름과 채팅 이름을 보낼 것을 보장합니까? 모든 데이터를 검색하려면 단일 recv() 호출이 필요할 수 있습니다. 또한 다음 줄의 관련성은 무엇입니까?

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

당신은 단순히 두 번 같은 일을하고있는 - 한 시간이 충분해야한다 (그리고 아마도 프로그램이 시점에서 차단하고 모르게 데이터를 덮어 쓸 수 있습니다). 간단한 텍스트 프로토콜의 경우 ~ 512 바이트를 생성하고 recv()에 대한 단일 호출에서 서버의 모든 정보를 적절히 수신 할 가능성이 매우 높습니다.

코드에서 큰 문제를 골라 내려고 시도했지만 이러한 문제는 코드의 다른 영역에서도 자주 발생합니다. 멀티 스레딩에 익숙하지 않은 경우 나중에 문제를 공격하십시오. 단일 쓰레드 소켓을 수행 한 다음 멀티 쓰레드를 배우는 법을 배웁니다. 한 번에 둘 다 태클하면 당신을 물 것입니다. 행운을 빕니다!

+0

SocketHandler에서 charname을 정의했는데 이름이 바뀌 었습니다. –

+0

OK (서버에서 제대로 작동하면서 다른 이름 클라이언트 올바르게) –