【问题标题】:C/C++: Write and Read SocketsC/C++:写入和读取套接字
【发布时间】:2017-07-22 20:07:48
【问题描述】:

我正在使用 unix 套接字发送和接收信息,但我不完全理解它是如何工作的。基本上,我会发送这样的消息:

int wr_bytes = write(sock, msg.c_str(), msg.length());

并收到这样的消息:

int rd_bytes = read(msgsock, buf, SOCKET_BUFFER_SIZE);

这段代码在数千字节的情况下完美运行,我不明白的是,read 函数如何知道另一部分何时完成发送消息?我尝试阅读read documentation,据我了解,read 将在到达EOFSOCKET_BUFFER_SIZE 后返回,对吗?

所以我猜当我将字符串提供给write 函数时,它会在我的内容末尾添加一个EOF,以便read 函数知道何时停止。

我问这个问题是因为,我没有添加任何代码来检查另一部分是否完成发送消息,但是,我收到大消息(数千字节)没有任何问题,为什么会发生这种情况,为什么我只收到部分消息?

这是我用来向 unix 套接字服务器发送消息的完整函数:

string sendSocketMessage(string msg) {
    int sock;
    struct sockaddr_un server;
    char buf[1024];

    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock < 0) {
        throw runtime_error("opening stream socket");
    }
    server.sun_family = AF_UNIX;
    strcpy(server.sun_path, "socket");

    if (connect(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un)) < 0) {
        close(sock);
        throw runtime_error("connecting stream socket");
    }
    if (write(sock, msg.c_str(), msg.length()) < 0){
        throw runtime_error("writing on stream socket");
        close(sock);
    }
    bzero(buf, sizeof(buf));
    int rval = read(sock, buf, 1024);
    return string( reinterpret_cast< char const* >(buf), rval );
}

这是我的服务器函数(稍微复杂一点,类型vSocketHandler代表我调用来处理请求的函数):

void UnixSocketServer::listenRequests(vSocketHandler requestHandler){
    int sock, msgsock, rval;
    struct sockaddr_un server;
    char buf[SOCKET_BUFFER_SIZE];

    sock = socket(AF_UNIX, SOCK_STREAM, 0);
    if (sock < 0) {
        throw runtime_error("opening stream socket");
    }
    server.sun_family = AF_UNIX;
    strcpy(server.sun_path, SOCKET_FILE_PATH);
    if (bind(sock, (struct sockaddr *) &server, sizeof(struct sockaddr_un))) {
        throw runtime_error("binding stream socket");
    }
    listen(sock, SOCKET_MAX_CONNECTIONS);
    while(true) {
        msgsock = accept(sock, 0, 0);
        if (msgsock == -1){
            throw runtime_error("accept socket");
        } else {
            bzero(buf, sizeof(buf));
            if((rval = read(msgsock, buf, SOCKET_BUFFER_SIZE)) < 0)
                throw runtime_error("reading stream message");
            else if (rval == 0){
                //do nothing, client closed socket
                break;
            } else {
                string msg = requestHandler(string( reinterpret_cast< char const* >(buf), rval ));
                if(write(msgsock, msg.c_str(), msg.length()) < 0)
                    throw runtime_error("sending stream message");
            }
            close(msgsock);
        }
    }
    close(sock);
    unlink(SOCKET_FILE_PATH);
}

【问题讨论】:

    标签: c++ c sockets unix


    【解决方案1】:

    我不明白的是,read函数怎么知道对方什么时候发送完消息?

    对于流式套接字,例如您正在使用的,它没有。对于数据报类型的套接字,通信被分成不同的块,但如果一条消息跨越多个数据报,那么答案又是“它没有”。这确实是了解read()write()(以及send()recv())功能的关键之一,更具体地说,是关于套接字。

    对于这个答案的其余部分,我将专注于面向流的套接字,因为这就是您正在使用的。我还假设套接字不是非阻塞模式。如果您打算将通过此类套接字传输的数据分解为不同的消息,则由您来实现另一端可以识别消息边界的应用程序级协议。

    我尝试阅读 read 文档,据我了解,read 一旦达到 EOF 或 SOCKET_BUFFER_SIZE 就会返回,对吗?

    不完全是。 read() 在到达文件末尾时返回,这发生在对等方关闭其套接字(或至少关闭它的写入端)时,以确保不再有数据将被发送。 read() 也会在出现各种错误情况时返回。并且read() 可能会在其他未指定的情况下返回,前提是它已经传输了至少一个字节。在实践中,如果套接字缓冲区已满,通常会调用最后一种情况,但也可能在其他情况下调用,例如缓冲区清空时。

    所以我猜测当我将字符串提供给写入函数时,它会在我的内容末尾添加一个 EOF,以便读取函数知道何时停止。

    不,它没有这样的事情。成功时,write() 函数会发送您要求它发送的部分或全部字节,仅此而已。请注意,即使发送 all 请求的字节也不能保证;它的返回值告诉你它实际发送了多少。如果这少于“全部”,那么通常您应该简单地执行另一个write() 来转移其余部分。您可能需要多次执行此操作才能发送整个消息。无论如何,只会发送您指定的字节。

    我问这个问题是因为,我没有添加任何代码来检查另一部分是否完成发送消息,但是,我收到大消息(数千字节)没有任何问题,为什么会发生这种情况,为什么我只收到部分消息?

    或多或少是因为您很幸运,但是您使用 UNIX 域套接字(而不是网络套接字)这一事实会有所帮助。您的数据通过内核非常有效地从发送进程传输到接收进程,单个read()s 接收到大writes() 也就不足为奇了。但是,您不能安全地依靠总是发生这种情况。

    【讨论】:

    • 谢谢@John Bollinger,这是一个非常完整的答案,我基本上是想通过假设读写函数可以处理发送完整消息的问题来逃避构建协议,但是,我看那是不可能的。再次感谢
    • @ErnanideSãoThiago,不客气。对于它的价值,应用程序级协议不需要很复杂。最简单的方法之一是将消息打包为固定宽度(2 或 4 字节)的二进制长度,后跟该字节数的数据。接收者知道要读取多少字节才能获得长度,并告诉它还要读取多少字节才能获得完整的消息。
    猜你喜欢
    • 2013-05-06
    • 1970-01-01
    • 2021-10-12
    • 2013-11-27
    • 2019-03-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多