【问题标题】:C++ Socket recv mixed messagesC++ Socket recv 混合消息
【发布时间】:2013-12-13 09:22:00
【问题描述】:

您好,我的套接字服务器和客户端有问题。

问题在于,当我非常快速地发送消息时,它们会混淆。当我向他们发送每秒 1 条消息时,一切运行良好,但是当我每 40 毫秒向他们发送 1 条消息时,它们就会混淆。

这是我的接收代码:

    std::string* AteneaClient::readSocket () {

    std::string finalString = std::string("");
    int size = MSG_SIZE;

    bool receiving = true;
    int timesBufferInc=0;
    while (receiving) {
        std::string temporalString;

        //create an empty buffer
        char* RCV_BUFFER = (char*) malloc (size* sizeof(char));
        for(int i=0;i<size;i++){
            RCV_BUFFER[i]=' ';
        }
        RCV_BUFFER[size-1]='\0';

        int result =  recv(sock,RCV_BUFFER,size-1,NULL);
        if ( result== SOCKET_ERROR ) {
            free(RCV_BUFFER);
            return NULL;
        }
        else if(result<size-1){
            receiving=false;
        }

        temporalString = std::string(RCV_BUFFER);
        finalString+=temporalString;
    }
    return new std::string(finalString);
}

这是我的发送代码:

    int sendThread(void* data){
    SND_THREAD_DATA* parameters =(SND_THREAD_DATA*)data;
    SOCKET* individualSocket = parameters->individualSocket;
    std::string * message = parameters->message;

    char RCV_BUFFER[MSG_SIZE];
    std::string converter;
    std::cout <<"(!)Thread: Iniciando sendThread Individual.."<<std::endl;
    SOCKET default_socket = *individualSocket;

    bool running=true;

    while(running){


        int length=message->length();
        char *cstr = new char[length + 1];
        strcpy(cstr, message->c_str());
        if(::send(*individualSocket,cstr,length + 1,NULL)==SOCKET_ERROR){
            logSendError();
            running=false;
        }
        delete cstr;
        Sleep(SLEEPTIME);
    }

}

这是我设置套接字时的代码:

    void AteneaClient::startUp(){
    int iResult = 0;
    iResult = WSAStartup(MAKEWORD(2, 2), &WinSockData);
    if (iResult != NO_ERROR) {
        wprintf(L"(!)Main:WSAStartup() failed with error: %d\n", iResult);
        return;
    }

    ADDR.sin_addr.s_addr= inet_addr(IP);
    ADDR.sin_family = AF_INET;
    ADDR.sin_port = htons(PORT);
    sock = socket(AF_INET,SOCK_STREAM,0);
    running=true;
}

有人知道为什么套接字消息会混淆吗?

谢谢!


编辑:

这是我目前使用 Maxim cmets 改进的接收方法:

    std::string* AteneaClient::readSocket () {

    int HEADER_SIZE=4;
    std::string finalString = std::string("");
    int sizeFirstBuffer = HEADER_SIZE*sizeof(char);
    char* RCV_BUFFER=(char*) malloc(sizeFirstBuffer+1);

    //clean new buffer
    for(int i=0;i<HEADER_SIZE;i++){
        RCV_BUFFER[i]=' ';
        }
    RCV_BUFFER[sizeFirstBuffer]='\0';



    int result =  recv(sock,RCV_BUFFER,sizeFirstBuffer,NULL);
    //cout << "The Size to read is:" <<RCV_BUFFER << endl;


    //now i create a buffer with that size
    int sizeThatIHaveToRead= atoi(RCV_BUFFER);
    int sizeSecondBuffer = sizeThatIHaveToRead*sizeof(char);
    char* RCV_BUFFER_SECOND=(char*) malloc(sizeSecondBuffer+1);

    //clean new buffer
    for(int i=0;i<sizeSecondBuffer;i++){
        RCV_BUFFER_SECOND[i]=' ';
        }
    RCV_BUFFER_SECOND[sizeSecondBuffer]='\0';



    result =  recv(sock,RCV_BUFFER_SECOND,sizeSecondBuffer,NULL);
    //cout << "RCV_BUFFER_SECOND:" <<RCV_BUFFER_SECOND << endl;
    finalString+=RCV_BUFFER_SECOND;
    return new std::string(finalString);
}

【问题讨论】:

  • 套接字是 TCP (SOCK_STREAM) 还是 UDP (SOCK_DGRAM) 套接字?它是阻塞套接字还是非阻塞套接字?
  • 它是一个 TCP 套接字。 SOCK_STREAM
  • 你没有使用recv的返回值,这是一个常见的错误。
  • 返回值表示接收到的确切字节数。 linux.die.net/man/2/recv 如果不使用该数字,您将无法知道获得了多少字节。如果你刚刚得到第一个字节怎么办?
  • 我不完全确定“混合”的定义是什么,但是要在 TCP 上执行基于消息的 I/O(而不是基于流的 I/O),您需要对消息进行框架化,这是一个非常简单的示例:blogs.msdn.com/b/joncole/archive/2006/03/20/555721.aspx

标签: c++ multithreading sockets winsock winsock2


【解决方案1】:

您正在通过流套接字发送字符串,并期望它们以原子方式发送和接收,例如要么不发送/接收任何内容,要么发送/接收整个字符串。这不是流套接字的工作方式。

流套接字通常只发送您的部分数据,因此您需要继续发送,直到所有数据都发送完毕。接收也是一样。

您还需要以某种方式分隔消息,否则在接收时您将不知道消息何时结束以及下一个消息何时开始。两种最常见的方式是 a) 为其大小添加前缀消息,b) 使用消息分隔符(例如换行符)。

ZeroMQ 可以为您完成这两项任务:您的应用程序最终会发送和接收完整的消息,而无需您在字节级别实现消息帧和发送/接收。


更新后的代码仍然不能正确使用sendrecv 调用。

这里是发送和接收std::string的函数的正确用法:

#include <stdexcept>
#include <stdint.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>

ssize_t recv_all(int fd, void* buf, size_t buf_len) {
    for(size_t len = buf_len; len;) {
        ssize_t r = ::recv(fd, buf, len, 0);
        if(r <= 0)
            return r;
        buf = static_cast<char*>(buf) + r;
        len -= r;
    }
    return buf_len;
}

ssize_t send_all(int fd, void const* buf, size_t buf_len) {
    for(size_t len = buf_len; len;) {
        ssize_t r = ::send(fd, buf, len, 0);
        if(r <= 0)
            return r;
        buf = static_cast<char const*>(buf) + r;
        len -= r;
    }
    return buf_len;
}

void send_string(int fd, std::string const& msg) {
    ssize_t r;
    // Send message length.
    uint32_t len = msg.size();
    len = htonl(len); // In network byte order.
    if((r = send_all(fd, &len, sizeof len)) < 0)
        throw std::runtime_error("send_all 1");
    // Send the message.
    if((r = send_all(fd, msg.data(), msg.size())) < 0)
        throw std::runtime_error("send_all 2");
}

std::string recv_string(int fd) {
    ssize_t r;
    // Receive message length in network byte order.
    uint32_t len;
    if((r = recv_all(fd, &len, sizeof len)) <= 0)
        throw std::runtime_error("recv_all 1");
    len = ntohl(len);
    // Receive the message.
    std::string msg(len, '\0');
    if(len && (r = recv_all(fd, &msg[0], len)) <= 0)
        throw std::runtime_error("recv_all 2");
    return msg;
}

【讨论】:

  • 我会尝试按照您所说的发送前缀消息并回复您。谢谢@Maxim Yegorushkin
  • @chelo_c 您的另一个选择是尝试使用zeromq。它通过流套接字等为您构建消息。
  • 您的解决方案似乎可以解决问题。消息现在非常好。但是随着缓冲区的增加,recv 方法会读取更大的缓冲区并花费更长的时间,因此它开始延迟程序。这正常吗?
  • @chelo_c 可能不会。请显示新的发送/接收代码。
  • 我已经在问题中粘贴了我的新代码,它不会丢失消息并且它们不会混淆。但随着缓冲区变大,它开始延迟。
猜你喜欢
  • 2016-12-22
  • 1970-01-01
  • 2011-05-09
  • 2021-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-14
  • 2012-06-14
相关资源
最近更新 更多