WinSock2 客户端/服务器通信:发送和接收字符串

WinSock2 client/server communication: send & receive strings

本文关键字:字符串 客户端 服务器 通信 WinSock2      更新时间:2023-10-16

我有一点困难试图编码这个,因为我不知道很多。我有两台可以互相通信的电脑。它可以正常工作,但它只能互相发送单个字符。如果命令执行时没有IP地址参数,那么一台PC就像服务器一样,而另一台如果给定服务器IP地址,就像客户端一样连接到服务器。

代码都在这里:

// Quick and dirty - error checks omitted for brevity.
#include <iostream>
#include <string>
#include <conio.h>
#include <WinSock2.h>
#include <ws2tcpip.h>
using namespace std;
void chat (int socket_d)
{
    while (true)
    {
        if (_kbhit ())
        {
            char ch;
            ch = _getche();
            int n;
            n = send (socket_d, &ch, 1, 0);
            if (ch == 'r')
            {
                cout << "n";
            }
        }
        int n;
        int count = 0;
        char byte; // Read one byte at a time - is this efficient?
        n = recv (socket_d, &byte, 1, 0);
        if (n <= 0)
        {
            if (WSAGetLastError() != WSAEWOULDBLOCK) // A real problem - not just avoiding blocking.
            {
                cout << "Terminated " << WSAGetLastError() << "n";
                return;
            }
        }
        else
        {
            cout << (char)byte;
            if ((char) byte == 'r')
                cout << "n";
        }
    }
}
int main (int argc, char * argv [])
{
    // Messy process with windows networking - "start" the networking API.
    WSADATA wsaData;
    int result = WSAStartup (MAKEWORD (2, 2), &wsaData);
    unsigned short port = 25565;
    // If argument is IP address - be a client and connect to it. Otherwise
    // be a server.
    if (argc > 1)
    {
        int socket_d;
        socket_d = socket (AF_INET, SOCK_STREAM, 0);
        // be a client.
        struct sockaddr_in server_addr;
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = inet_addr (argv[1]); // Parse the string and create the 32 bit address.
        server_addr.sin_port = htons (port); // Watch out for the endian conversion!
        connect (socket_d, (sockaddr *) &server_addr, sizeof (server_addr));
        u_long iMode=1;
        ioctlsocket (socket_d, FIONBIO, &iMode); // put the socket into non-blocking mode.
        chat (socket_d);
        closesocket (socket_d);
    }
    else
    {
        // be a server
        int listen_socket_d;
        int connection_socket_d;
        listen_socket_d = socket (AF_INET, SOCK_STREAM, 0);
        struct sockaddr_in server_addr;
        server_addr.sin_family = AF_INET;
        server_addr.sin_addr.s_addr = INADDR_ANY; // A placeholder that will be replaced with my own address.
        server_addr.sin_port = htons (port); // Watch out for the endian conversion!
        bind (listen_socket_d, (struct sockaddr *) &server_addr, sizeof (server_addr));
        int backlog = 5;
        listen (listen_socket_d, backlog);
        // take only the first connection.
        struct sockaddr_storage their_addr; 
        int their_addr_size = sizeof(their_addr);
        connection_socket_d = accept (listen_socket_d, (struct sockaddr *) &their_addr, &their_addr_size);
        u_long iMode=1;
        ioctlsocket (connection_socket_d, FIONBIO, &iMode); // put the socket into non-blocking mode.
        chat (connection_socket_d);
        closesocket (connection_socket_d);
    }
    return 0;
}

我想要实现的是能够发送字符串而不是单个字符。我希望这个工作的方式是增加它发送的字节大小,而不是目前的单个字节。我假设它可以工作的方式,让我们假设一次发送和接收的总大小为64字节。

从这里并不清楚您的问题所在。

我的解决方案如下。基本思想是分配大小相等的发送和接收缓冲区,在键盘输入时向发送缓冲区添加字符,并在缓冲区满了或用户按回车键时传输缓冲区。

非阻塞send函数可能不会一次发送整个缓冲区(参见文档)。我决定继续在这里阻塞,直到整个缓冲区被发送,这样我就不必跟踪单独的输入和传输缓冲区。我相信你可以在这方面做得更好。

接收部分只是回显接收到的任何字节。一般来说,没有办法知道其他用户是否"完成"发送数据,所以我们只打印第一次调用recv后我们收到的数据。

我总是在每次发送和接收后将缓冲区memset为所有零,以防止丢失null终止符的奇怪行为。简单地在当前字符串的末尾附加一个空字符会更优化,但我有时会偏执。

下面是我的代码:

#define BUFFER_SIZE 64

void chat (int socket_d) 
{
  char sendBuffer[BUFFER_SIZE] = { 0 };
  char receiveBuffer[BUFFER_SIZE] = { 0 };
  int bufferPosition = 0;
  int charsSent = 0;
  int charsReceived = 0;
  while (true)
  {
    if (_kbhit ())
    {
      sendBuffer[bufferPosition++] = _getche();
      if (sendBuffer[bufferPosition - 1] == 'r' || bufferPosition == BUFFER_SIZE - 1)
      {
        // This defeats the purpose of a non-blocking socket, I know.
        // You can do better by keeping separate buffers for sending and
        // collecting input.
        while (charsSent < bufferPosition)
        {
          charsSent += send(
            socket_d,
            &sendBuffer[charsSent], // Treats the address of a character as a string.
            bufferPosition - charsSent, // Only send the part that hasn't been sent yet.
            0);
        }
        memset(sendBuffer, 0, bufferPosition); // Paranoid.
        bufferPosition = charsSent = 0;
        cout << "n";
      }
    }
    charsReceived = recv (socket_d, receiveBuffer, BUFFER_SIZE, 0);
    if (charsReceived <= 0)
    {
      if (WSAGetLastError() != WSAEWOULDBLOCK) // A real problem - not just avoiding blocking.
      {
        cout << "Terminated " << WSAGetLastError() << "n";
        return;
      }
    }
    else
    {
      cout << receiveBuffer;
      if (receiveBuffer[charsReceived - 1] == 'r')
        cout << "n";
      memset(receiveBuffer, 0, charsReceived); // Super paranoid.
    }
  }
}

现在,在它支持UTF-8或至少wchar_t之前,我不会真正认为这是"好的"。ASCII缺少很多人们期望能够在真正的聊天应用程序中使用的字符。

PS -根据Visual Studio 2013, inet_addr函数已弃用。我用inet_ptons代替。下面是Winsock实现的文档