简体   繁体   English

如何在 UNIX 中通过接收/发送交换数据时正确使用缓冲区?

[英]How to properly use buffer while exchanging data via recv/send in UNIX?

I am making a simple chat that has to send text message made by one member to all other members.我正在做一个简单的聊天,必须将一个成员发出的短信发送给所有其他成员。 The format of the message that everybody has to receive is "[IP]: hello.": Also the server has to notify everybody when somebody connects or disconnects.每个人都必须收到的消息格式是“[IP]: hello.”:服务器还必须在有人连接或断开连接时通知每个人。 "[IP] has connected" and "[IP] is doosconnected" respectively.分别为“[IP] 已连接”和“[IP] 已连接”。

Here is a piece of code that implements this function of the server.这里有一段代码实现了服务器的这个function。 You can start looking since the line that has "THE PROBLEM IS HERE" comment:您可以从有“问题在这里”注释的行开始查找:

while (true)
{

// select() for reading

static constexpr int bufferSize = 1024;
static char buffer[bufferSize];

// this is for getting IP-address of sender
static sockaddr_in sockAddr;
static socklen_t sockAddrSize;

if (FD_ISSET(masterSocket, &set)) // masterSocket is a socket that establishes connections
{
    sockAddrSize = sizeof(sockAddr);
    int slaveSocket = accept(masketSocket, &sockAddr, &sockAddrSize); // slaveSocket is a client socket

    // setting a slaveSocket non-blocking

    sprintf(buffer, "[%d.%d.%d.%d] has connected\n", 
        (sockAddr.sin_addr.s_addr & 0x000000FF), 
        (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8, 
        (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16, 
        (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24);

    for (const auto &socket : slaveSockets)
        send(socket, buffer, strlen(buffer), MSG_NOSIGNAL);

    slaveSockets.insert(slaveSocket);
}

for (const auto &socket : slaveSockets)
{
    if (FD_ISSET(socket, &set))
        continue;

    static int recvSize = recv(socket, buffer, bufferSize, MSG_NOSIGNAL);

    if (recvSize == 0 && errno != EAGAIN)
    {
        sockAddrSize = sizeof(sockAddr);
        getsockname(socket, (sockaddr *) &sockAddr, &sockAddrSize);
        sprintf(buffer, "[%d.%d.%d.%d] has disconnected\n", 
                    (sockAddr.sin_addr.s_addr & 0x000000FF), 
                    (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8, 
                    (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16, 
                    (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24);

        shutdown(socket, SHUT_RDWR);
        close(socket);
        slaveSockets.erase(socket);

        for (const auto &socket : slaveSockets)
            send(socket, buffer, strlen(buffer), MSG_NOSIGNAL);
    }
    else if (recvSize > 0) // THE PROBLEM IS HERE
    {
        static char reply[bufferSize];
        sockAddrSize = sizeof(&sockAddr);
        getsocklen(socket, (sockaddr *) &sockAddr, &sockAddrSize);
        sprintf(reply, "[%d.%d.%d.%d]: %s\n",
            (sockAddr.sin_addr.s_addr & 0x000000FF),
            (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
            (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
            (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24,
            buffer);

        int senderSocket = socket;
        for (const auto &socket : slaveSockets)
        {
            if (socket == senderSocket)
                continue;

            send(socket, reply, strlen(reply), MSG_NOSIGNAL); // even tried the "strlen(reply) + 1"
        }
    }
}

}

The problem is that receivers have each message incorrectly outputed: it is outputed completely but also has the end of old values of buffer in the end.问题是接收者错误地输出了每条消息:它被完全输出,但最后也有缓冲区旧值的结尾。 For example:例如:

Client A has connected.客户端 A 已连接。

Client B has connected.客户端 B 已连接。 Client A has received "[127.0.0.1] has connected".客户端 A 收到“[127.0.0.1] 已连接”。

Client A has sent "hello".客户端 A 已发送“你好”。 Client B received "[127.0.0.1]: hello\n0.1] has connected\n".客户端 B 收到“[127.0.0.1]: hello\n0.1] 已连接\n”。

Client B has sent "what's up?".客户端 B 已发送“what's up?”。 Client A has received "[127.0.0.1]: what's up?\nhas connected\n".客户端 A 收到“[127.0.0.1]:怎么了?\n已连接\n”。

Client A has disconnected.客户端 A 已断开连接。 Client B has received "[127.0.0.1] has disconnected".客户端 B 收到“[127.0.0.1] 已断开连接”。

As you can see connection/disconnection information is always outputed correctly but chatting is works wrong: it contains parts of connection/disonnection information in its end.如您所见,连接/断开连接信息始终正确输出,但聊天工作错误:它最后包含连接/断开连接信息的一部分。

I sincerely beleive that I use buffers properly and can not understand what I am doing wrong.我真诚地相信我正确使用缓冲区并且无法理解我做错了什么。

The buffer is not a null-terminated C string after recv returns.recv返回后, buffer不是以 null 结尾的 C 字符串。 This is logical - what if you transfer binary data?这是合乎逻辑的——如果你传输二进制数据怎么办? Then you would want to recv exactly (message-length) bytes and don't append any zero bytes.然后你会想要准确地接收(消息长度)字节并且不要recv任何零字节。

Note that sending terminating null byte in send is the wrong thing to do - your receiver relies on sender to append this zero byte, but if sender is malicious, then he may not append zero byte and cause all sorts of bugs and vulnerabilities - including DoS attacks and remote code execution. Note that sending terminating null byte in send is the wrong thing to do - your receiver relies on sender to append this zero byte, but if sender is malicious, then he may not append zero byte and cause all sorts of bugs and vulnerabilities - including DoS攻击和远程代码执行。

You may still rely on sender appending zero byte, but then you should pass bufferSize-1 as buffer length to recv , and after a call to recv set the reply[bufferSize-1]=0 .您可能仍然依赖 sender 附加零字节,但是您应该将bufferSize-1作为缓冲区长度传递给recv ,并在调用recv后设置reply[bufferSize-1]=0 But maybe it is still not the best thing to do: one of multitude of other options is to pass a "message length" as a 32-bit unsinged integer, check for maximum length (say, no message is bigger than 1024 chars, and if is, don't receive anything and just close the socket), and recv exactly the passed "message length" bytes to buffer.但也许它仍然不是最好的做法:众多其他选项之一是将“消息长度”作为 32 位未分割 integer 传递,检查最大长度(例如,没有消息大于 1024 个字符,并且如果是,则不接收任何内容并关闭套接字),并recv接收传递的“消息长度”字节到缓冲区。 You'll still need to append the terminating null byte if you intend to use the buffer as a C-style string.如果您打算将缓冲区用作 C 样式字符串,您仍然需要 append 终止 null 字节。

Edit: IMPORTANT, If you use TCP (SOCK_STREAM): always use message length: the message might (and someday will) be read by recv in fragments.编辑:重要,如果您使用 TCP (SOCK_STREAM):始终使用消息长度: recv可能(并且有一天会)在片段中读取消息。 You absolutely should concatenate them into whole message by yourself.您绝对应该自己将它们连接成整个消息。

Just to add to smitsyn's answer, you should use the recvSize (that's grater than 0 in the branch with the issue - since you've received something from one of the clients) to set the '\0' inside buffer.只是为了添加到 smitsyn 的答案中,您应该使用recvSize (在有问题的分支中大于 0 - 因为您从其中一个客户端收到了一些东西)来设置缓冲区内的 '\0' 。

Your static variables got initialized to 0 (default) all over so your buffer did contain a '\0' immediately after the longest message that you got (since the rest was overwritten) and you got lucky that sprintf found it and did not go search for it somewhere else in your program's memory (out of bounds even). Your static variables got initialized to 0 (default) all over so your buffer did contain a '\0' immediately after the longest message that you got (since the rest was overwritten) and you got lucky that sprintf found it and did not go search在程序的 memory 的其他地方(甚至超出范围)。

Something along these lines should make it work (for a simple case):这些方面的东西应该使它工作(对于一个简单的案例):

else if (recvSize > 0) // THE PROBLEM IS HERE
    {
     ...

     buffer[recvSize] = '\0'; // of course make sure it fits! could use a std::min(recvSize, bufferSize - 1)
     sprintf(reply, "[%d.%d.%d.%d]: %s\n",
            (sockAddr.sin_addr.s_addr & 0x000000FF),
            (sockAddr.sin_addr.s_addr & 0x0000FF00) >> 8,
            (sockAddr.sin_addr.s_addr & 0x00FF0000) >> 16,
            (sockAddr.sin_addr.s_addr & 0xFF000000) >> 24,
            buffer); // now this print should work as expected 

Edit: because I can't post comments:(编辑:因为我不能发表评论:(

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM