【问题标题】:c++ server client chatc++ 服务器客户端聊天
【发布时间】:2025-12-01 14:15:02
【问题描述】:

我正在基于 c++ 控制台制作服务器、客户端应用程序。

到目前为止我做了什么:

  • 我可以连接到服务器。
  • 我可以向服务器发送消息。
  • 服务器可以发回消息。

但是我想不通,我怎样才能让服务器也充当客户端,在处理从客户端接收到的消息时向客户端发送消息?

人们也可以用它作为例子:D

好吧,我也将发布部分代码:

服务器:

  #include "stdafx.h"
  using namespace std;
 //our main function
 void main()
     {
int numClients;
long antwoord;
char chatname[100];
char bericht[250]; //messages
char sbericht[250]; //smessages
     //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("192.168.1.103");

//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;
}


//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;
for(;;)
{

        //now we let the socket listen for incoming connections
            //SOMAXCOMM heeft het nut dat het dan voordurend luisterd naar inkomende verbindingen zonder limiet
        listen(sListen, SOMAXCONN);
        while(numClients < SOMAXCONN)
        {
            //if a connection is found: show the message!
            if(sConnect = accept(sListen, (SOCKADDR*)&addr, &addrlen))
            {
                cout << "A Connection was found!" <<endl;

                antwoord = send(sConnect, "Welcome to our chat:", 21,NULL);

                if(antwoord > 1)
                {

                    antwoord = recv(sConnect, sbericht, sizeof(sbericht), NULL);
                    antwoord = recv(sConnect, chatname, sizeof(chatname), NULL);

                        while(antwoord = recv(sConnect, sbericht, sizeof(sbericht), NULL) && (antwoord = recv(sConnect, sbericht, sizeof(sbericht), NULL)) )
                        {
                            antwoord = send(sConnect, sbericht, sizeof(sbericht), NULL);
                            antwoord = send(sConnect, chatname, sizeof(chatname), NULL);    
                        }

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


}
}

客户:

    // ChatServer.cpp : Defines the entry point for the console application.
    //
    //include of the stdafx.h file where importent files are being included

    #include "stdafx.h"

    using namespace std;

    void smessage()
    {

    }
   //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("192.168.1.103");

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, 250);
                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;

            }
        }
    }
}
    }

【问题讨论】:

    标签: c++ client client-server chat winsock


    【解决方案1】:

    您可能必须打开另一个套接字。客户端也必须充当服务器。

    【讨论】:

      【解决方案2】:

      首先:将一个 20mb 的 zip 文件放到网络上以获取大约 4 个有趣的源文件并不是一个好的选择。我们对您的目标文件和调试输出不感兴趣,因为我们想帮助您处理源代码。下次尝试上传仅包含源文件的 zip 文件。

      其次:如果其他人想了解你的源代码并且不熟悉你的母语,他们必须猜测。出于这个原因以及其他各种原因,请尝试使用英语作为源代码语言。

      现在回答你的问题:

      答案已经在您的代码中。目前,服务器一直在循环,直到最大数量的连接、接收输入并发送回答案。所以实际上你已经实现了它。我想如果你想以两种方式发送发起的消息,你必须稍微改变你的软件架构。

      【讨论】:

      • 谢谢,下次我会记住这一点。你是对的,但问题是:当服务器从客户端接收输入时,它在 while 循环中将消息发送回客户端。但我的问题更多的是,当服务器接收消息时,我可以在哪里以及如何在代码中为服务器创建一个输入字段而不中断接收循环?
      【解决方案3】:

      您的代码存在一些基本问题:

      • 服务器一次只能处理一个客户端。如果您的服务器上的用户不止一个(就像聊天服务器一样),您需要能够同时监听多个连接。 select,或WSAEventSelectWaitForMultipleObjects,在这里会有很大帮助。

      • 您假设一次会出现一整条固定大小的消息。 TCP 不能保证这一点(因为“流”概念将数据视为单个字节的潜在无限序列),并且发送一半的消息可能会在等待其余消息时冻结您的服务器。如果这一切都在您的 LAN 上,这没什么大不了的,但是如果您将此服务公开到 Internet,则您要求随机锁定。为了防止这种情况发生,请获取数据并将其放入缓冲区中,仅在您有完整消息时处理它。

      • 对话同步进行。也就是说,客户端发送一条消息,等待响应,然后(只有)期待控制台输入。使用这种设计,每发送一条消息总是会收到一条消息。为了解决这个问题,我通常会为每个方向的数据设置一个线程——一个获取控制台输入并将其发送到服务器,而另一个监听服务器并打印接收到的消息。 (注意,这意味着在你打字的时候可能会收到消息。这有点意思。但它让控制台输入有点烦人。)线程是一个半高级主题——一旦你开始创建新线程,你经常不得不担心同步等。但在这种情况下,它通常比替代方案更干净。

      示例线程代码(非常粗略,因为我手边没有 C++ 编译器):

      const int MessageLength = 250;
      const int NameLength = 250;
      
      char myname[NameLength];
      
      bool sendFully(SOCKET s, char* buffer, size_t buffer_len, int flags)
      {
          char *end = buffer + buffer_len;
          while (buffer != buffer_len)
          {
              int sent = send(s, buffer, end - buffer, flags);
              if (sent == 0) return false;
              buffer += sent;
          }
          return true;
      }
      
      DWORD WINAPI watchConsoleInput(void*)
      {
          char input[MessageLength];
          while (true)
          {
              std::cin.getline(input, MessageLength);
              if (!sendFully(sConnect, input, sizeof(input), 0))
                  break;
              if (!sendFully(sConnect, myname, sizeof(myname), 0))
                  break;
          }
          return 0;
      }
      
      int main()
      {
          char chatname[NameLength];
          char sbericht[MessageLength];
      
          ... get our name in myname ...
      
          ...  do the connect stuff  ...
      
          HANDLE watcher = CreateThread(NULL, 0, watchConsoleInput, NULL, 0, NULL);
      
          while (true)
          {
              // Added MSG_WAITALL to work around the whole-message-at-a-time thing
              if (recv(sConnect, sbericht, sizeof(sbericht), MSG_WAITALL) != sizeof(sbericht))
                  break;
              if (recv(sConnect, chatname, sizeof(chatname), MSG_WAITALL) != sizeof(sbericht))
                  break;
          }
      
          // Don't care about errors; we're just being polite
          shutdown(sConnect, SD_BOTH);
      
          closesocket(sConnect);
          cout << "Connection lost\n";
      
          // ExitProcess rather than just 'return', so we know the watcher thread dies
          ExitProcess(0);
      }
      

      【讨论】:

      • 我完全同意你的观点。我现在正在尝试多线程部分,希望成功吗? :P 你能为我的代码做一个例子吗?所以我可以和你的比较一下,还有参考。?在此先感谢 cHa :)
      • 这个更好用吗? #define MessageLength 250
      • 如果可以的话,通常最好避免使用宏,但在这种情况下,#defineconst 一样有效。但是,一个常见的约定是宏的名称将全部大写,例如MESSAGE_LENGTH
      最近更新 更多