Linux套接字和多线程程序

4

我需要编写一个简单的客户端-服务器应用程序,它使用Unix数据报套接字。客户端可以在服务器请求时向服务器发送信息,也可以在自己的请求下从服务器接收信息。

我的想法是,一个线程将等待用户输入以确定我们要向服务器发送什么请求,另一个线程将只等待来自服务器的套接字消息,如果它是我们请求的消息,它将将其写入标准输出,如果是服务器请求线程将写入服务器请求的内容。我将使用互斥锁,以便两个线程不会同时向同一套接字写入。

我的问题是,如果一个线程从某个套接字读取数据,同时另一个线程正在使用相同的套接字发送数据,套接字会如何表现?这样安全吗?或者我也应该为这种情况使用互斥锁?


我认为这是安全的,但我不知道要查阅哪些文档来支持这一说法。 - Ringding
这就是问题所在,无论我到哪里都能找到人说“很安全”,但没有人能引用一些文献或书籍。 - Andna
请查看我的第二次编辑。 - Fingolfin
2个回答

10

内核结构通常以线程安全的方式构建,套接字也不例外。如果您应该担心任何事情,那么不是使用套接字和线程的安全性,而是您程序的逻辑问题。

此外,我想提到流套接字是全双工的,这意味着读/写都能够同时安全地进行,这是如何实现的呢?内核为您锁定或确保您可以同时执行发送和接收操作。

关于全双工的论点:
http://www.kernel.org/doc/man-pages/online/pages/man2/socket.2.html 关于内核结构是线程安全的:
我找不到支持此说法的链接,但我对此有99%的把握。

P.S 如果有疑问,测试一下可能会有帮助
编辑:
在对此进行投票之前,请评论指出我所说的任何错误。


编辑2:
在这里,您可以找到POSIX标准规定其所有函数都必须是线程安全的,除了第2.9.1节中定义的一些函数
http://pubs.opengroup.org/onlinepubs/9699919799/functions/V2_chap02.html


1
在 Linux 和 Windows 中,send() 和 recv() 调用(通常用于 TCP,但我认为也可以与 UDP 一起使用)可能会提前返回,而没有发送您想要发送的全部数据。这将导致线程问题,因为您的消息将被拆分。
我发现最简单的方法是使用以下属性包装 recv() 和 send() 函数:
只在所有请求的数据都写入套接字后返回 线程安全
然后,在所有我原本会使用 send/recv 的地方使用这些包装器。
这是包装器的代码(请随意更改错误处理):
//linux (must change SOCKET types to int)
//#include <sys/socket.h>

//windows 
//#include <Winsock2.h>
//#include <ws2tcpip.h>

//blocks until the full amount of bytes requested are read
//thread safe
//throws exception on error
void recv_bytes(SOCKET socket, char* buf, int len, int flags){

    static std::mutex mtx;

    mtx.lock();

    int bytes_received = 0;

    while (bytes_received != len){

        int bytes = recv(socket, buf + bytes_received, len - bytes_received, flags);

        //error check
        if (bytes == 0){
            throw std::exception("Network Exception");
        }

        bytes_received += bytes;

    }

    mtx.unlock();

}



//blocks until the full amount of bytes requested are sent
//thread safe
//throws exception on error
void send_bytes(SOCKET socket, char* buf, int len, int flags){

    static std::mutex mtx;

    mtx.lock();

    int bytes_sent = 0; 

    while (bytes_sent != len){

        int bytes_s0 = send(socket, buf, len, flags);

        if (bytes_sent == SOCKET_ERROR) {
            mtx.unlock();
            throw std::exception("Network Exception");
        }

        bytes_sent += bytes_s0;

    }

    mtx.unlock();
}

recv_bytes函数中,你在离开函数时没有解锁互斥锁。我建议你自己做个好事,使用std::lock_guard来持有锁,直到函数离开。这样,你就不需要在函数的每个可能的路径上手动解锁互斥锁,而只需在本地std::lock_guard在函数离开时被销毁时,让编译器插入这个逻辑... - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接