C/C++:写入和读取套接字

Ern*_*ani 1 c c++ unix sockets

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

int wr_bytes = write(sock, msg.c_str(), msg.length());
Run Code Online (Sandbox Code Playgroud)

并收到这样的消息:

int rd_bytes = read(msgsock, buf, SOCKET_BUFFER_SIZE);
Run Code Online (Sandbox Code Playgroud)

这段代码与数千个字节完美配合,我不明白的是,read当其他部分完成发送消息时,函数如何知道?我试着阅读阅读文档,根据我的理解,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 );
}
Run Code Online (Sandbox Code Playgroud)

这是我的服务器功能(稍微复杂一点,类型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);
}
Run Code Online (Sandbox Code Playgroud)

Joh*_*ger 5

我不明白的是,读取功能如何知道其他部分何时完成发送消息?

对于流式插槽(例如您正在使用),它不会.对于数据报类型的套接字,通信被分成不同的块,但如果消息跨越多个数据报,则答案再次是"它没有".这确实是了解read()write()(send()recv())函数以及更具体的套接字的关键事项之一.

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

我试着阅读阅读文档,根据我的理解,读取一旦达到EOF或SOCKET_BUFFER_SIZE就会返回,是否正确?

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

所以我猜测当我将我的字符串赋给write函数时,它会在我的内容末尾添加一个EOF,因此read函数知道何时停止.

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

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

或多或少,因为你很幸运,但你使用UNIX域套接字(而不是网络套接字)的事实有所帮助.您的数据通过内核从发送过程非常有效地传输到接收过程,并且writes()单个read()s 接收大量数据并不特别令人惊讶.但是,你无法安全地依赖它.

  • @ErnanideSãoThiago,不客气.对于它的价值,应用程序级协议不需要复杂.最简单的方法之一是将消息打包为固定宽度(2或4字节)的二进制长度,然后是该数量的字节数据.接收器知道要读取多少字节来获取长度,并告诉它需要读取多少字节才能获得完整的消息. (2认同)