Linux C/C++多线程代码中的socket发送

10

在多线程代码中,如果有多个线程同时尝试向一个TCP套接字发送数据,会发生什么情况?它们的数据会混合在一起还是不同的线程最终会逐个发送数据?


你是如何向套接字写入数据的?你使用的是 write(2) 还是一些 C++ 抽象层? - kdgregory
谢谢,请看下面我的评论,发送数据包用send/sendto,大小约100字节。 - Daniel
2个回答

10

这取决于您使用哪些基元来将数据提交到套接字。

如果您正在使用write(2)send(2)sendto(2)sendmsg(2),并且消息的大小足够小,可以完全适应套接字的内核缓冲区,则整个写操作将作为一个块发送,而不会出现其他数据插入其中。

如果您正在使用fwrite(3)(或任何其他更高级别的缓冲IO抽象),则有可能您的数据将被发送而没有其他数据插入其中,但我不会依赖这种行为。

我无法说明sendfile(2)的行为。我想说sendfile(2)操作会在该套接字上的任何其他write(2)请求之前“写入”文件的全部内容,但是我读过的文档没有提到这一点,因此最好不要做任何意义上的“原子性”假设。

最安全的机制是仅有单个线程提交数据到套接字。


我正在使用send/sendto,每次线程尝试发送一些数据时,数据大小很短,大约<200字节。目前我正在使用mutex_lock在调用send之前同步不同的线程,我正在尝试找出是否可以删除mutex_lock以提高性能。 - Daniel
POSIX文档对这些调用的线程安全性并没有提供很多信息,除了阻塞/非阻塞IO的行为。有一个关于在Linux内核中使用多线程调用write(2)与文件相关的错误报告,但我不知道是否已经修复。但是,无论如何,系统调用提供比它们上面的任何东西更高的原子性保证(暗示或不暗示)。 - kdgregory
话虽如此,互斥锁是比较便宜的,而且不应该增加太多争用情况,超出了你因依赖内核被正确地单线程运行所获得的争用情况。 - kdgregory
1
更正我之前的评论:POSIX要求除了指定集合以外的所有系统调用都是线程安全的:http://pubs.opengroup.org/onlinepubs/007908799/xsh/threads.html - kdgregory
1
PIPE_BUF仅在写入管道时才相关。如果您正在写入套接字,则没有真正的保证。请使用互斥锁。 - nos
显示剩余2条评论

-1

正如前面的回答所述:“最安全的机制是只允许单个线程向套接字提交数据。”

但如果你想让多个线程调用read/write,你必须自己保证线程安全。我已经写了一个包装器来处理这个问题,它可以在send和recv周围做到这一点。

在“发送”和“接收”方面,它们是线程安全的,因为它们不会导致崩溃,但不能保证该数据不会与其他线程发送的数据“混合”。由于我们很可能不希望出现这种情况,我们必须阻塞直到所有线程的数据被确认发送或接收完成,或者发生错误。这样一个长时间的阻塞调用可能会有问题,所以请确保你在能够处理长时间阻塞操作的线程上执行此操作。

对于linux:

#include <sys/types.h>
#include <sys/socket.h>
#include <map>
#include <mutex>



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

    static std::map<int, std::mutex> mtx;

    std::lock_guard<std::mutex> lock(mtx[sock]);

    int bytes_received = 0;

    while (bytes_received != len){

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

        //error check
        if (bytes == -1){
            throw std::runtime_error("Network Exception");
        }

        bytes_received += bytes;

    } 
}



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

    static std::map<int, std::mutex> mtx;

    std::lock_guard<std::mutex> lock(mtx[sock]);

    int bytes_sent = 0; 

    while (bytes_sent != len){

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

        if (bytes_sent == -1) {
            throw std::runtime_error("Network Exception");
        }

        bytes_sent += bytes_s0;

    }
}

1
抛出异常并使用mutex.lock()?如果您抛出异常,此代码将无法再次运行!请使用lock_guard。 - Aaditya Kalsi
啊,我确实错过了一个解锁。lock_guards 是一个伟大的发明。 - randyrand
在持有互斥锁的同时调用阻塞 I/O 函数可能会导致您的程序在某些情况下长时间无响应(例如,如果远程客户端没有响应,则 recv() 调用可能会阻塞很长时间,在此期间调用线程当然会被阻塞,任何试图获取互斥锁的其他线程也会被阻塞)。 - Jeremy Friesner
如果您没有专用的UI线程,它将使程序无响应。 - randyrand
我理解你不想让这些调用的阻塞时间比它们已经长了更久,但 OP 正在询问关于多线程套接字的问题,可以假设他已经有一个专门的 UI 线程了。 - randyrand

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