linux send 阻塞方式,关于send和sendto阻塞和非阻塞模式的底层细节

对于sendto,Linux 内核最后会调用udp_sendmsg,大概的调用堆栈是

udp_sendmsg

security_socket_sendmsg

__sock_sendmsg

sock_sendmsg

sendto

在sento里面,会根据socket的模式把一个标志传递给内核:

if (sock->file->f_flags & O_NONBLOCK)

flags |= MSG_DONTWAIT;

这个flags的值在后面会有用处

udp_sendmsg的第三个参数msg保存着等待发送的数据,即msg.msg_iov.iov_base,这个指针目前仍然是最上面用户层传递进来的函数临时变量,

udp_sendmsg里面有大量的细节暂且不管,涉及数据拷贝和发动的是ip_append_data,而它调用的是__ip_append_data,ip_append_data会传递一个getfrag函数指针作为回调,getfrag的目的就是执行发送数据的拷贝。getfrag在哪里被执行呢,继续往下看

在__ip_append_data里,代码分成2个执行分支,1

1调用ip_ufo_append_data,

2调用sock_alloc_send_skb

如果走分支2,直接调用sock_alloc_send_skb,sock_alloc_send_skb只有一个调用sock_alloc_send_pskb,sock_alloc_send_pskb是阻塞与非阻塞的关键所在,此函数首先调用timeo = sock_sndtimeo(sk, noblock);

noblock由最初的flags决定,意思是阻塞模式的话会一直等到到超时为止,如果是非阻塞则不等待,那么在哪里等待呢,下面一段代码展示了基本的逻辑

if (atomic_read(&sk->sk_wmem_alloc) >= sk->sk_sndbuf) {

set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);

set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);

err = -EAGAIN;

if (!timeo)

goto failure;

if (signal_pending(current))

goto interrupted;

timeo = sock_wait_for_wmem(sk, timeo);

continue;

}

意思是需要发送的数据长度是大于发送缓存情况下,并且timeo为0,返回EAGAIN给用户层,如果timeo非0,则调用sock_wait_for_wmem一直检查发送缓存的情况,直到发送缓存足够长或者超时。

在非阻塞情况下,sock_alloc_send_skb返回EAGAIN,会跳转到error。而在阻塞情况下如果返回成功,后面才会调用getfrag执行真正的数据拷贝。

补充一下,不管阻塞还是非阻塞模式,在申请skb之前都会调用lock_sock,从而可能进入锁竞争状态,其实也不绝对的非阻塞。

lock_sock这个函数也值得研究,他既调用了自旋锁,又调用了互斥锁。

除了申请kbuffer存在阻塞,发现还需要研究一下真正send数据的时候存在阻塞,发送数据会从udp->ip分层的往下逐层进行调用,最后到达dev,即网络驱动。里面细节太多,主要会调用下面几个函数:

ip_send_skb 》 ip_local_out 》ip_output 》ip_finish_output2 》__dev_queue_xmit

最后调用的是__dev_queue_xmit,这个函数比较复杂,如果想看详细的分析可以看这里http://shaojiashuai123456.iteye.com/blog/842236,

__dev_queue_xmit获取逻辑设备对象,然后判断其是否有发送队列,如果有发送队列则调用__dev_xmit_skb,并依次调用__qdisc_run-》qdisc_restart->sch_direct_xmit->dev_hard_start_xmit

dev_hard_start_xmit 直接调用的是 rc = ops->ndo_start_xmit(skb, dev);

ndo_start_xmit则属于驱动层的调用,典型的是ethernet,对应的函数是cvm_oct_xmit,驱动层会把这个skb放入等待发送队列,然后直接返回NETDEV_TX_OK。

从这里可以看到,不管阻塞还是非阻塞send,都会经历漫长的调用堆栈,知道驱动层把skb放入发送队列,才会返回,其中会有多次对rcu锁的申请。

  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,以下是一份简单的 C++ Windows 下 UDP 非阻塞发送和接收示例代码: ```c++ #include <iostream> #include <WinSock2.h> #include <WS2tcpip.h> #pragma comment(lib, "Ws2_32.lib") int main() { // 初始化 Winsock 库 WSADATA wsaData; int result = WSAStartup(MAKEWORD(2, 2), &wsaData); if (result != 0) { std::cerr << "WSAStartup failed: " << result << std::endl; return 1; } // 创建 UDP 套接字 SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sock == INVALID_SOCKET) { std::cerr << "Failed to create socket: " << WSAGetLastError() << std::endl; WSACleanup(); return 1; } // 设置套接字为非阻塞模式 u_long nonBlocking = 1; if (ioctlsocket(sock, FIONBIO, &nonBlocking) != 0) { std::cerr << "Failed to set non-blocking mode: " << WSAGetLastError() << std::endl; closesocket(sock); WSACleanup(); return 1; } // 绑定套接字到本地端口 sockaddr_in localAddr; localAddr.sin_family = AF_INET; localAddr.sin_port = htons(12345); // 绑定到端口 12345 localAddr.sin_addr.s_addr = INADDR_ANY; if (bind(sock, reinterpret_cast<sockaddr*>(&localAddr), sizeof(localAddr)) == SOCKET_ERROR) { std::cerr << "Failed to bind socket: " << WSAGetLastError() << std::endl; closesocket(sock); WSACleanup(); return 1; } // 发送数据 sockaddr_in destAddr; destAddr.sin_family = AF_INET; destAddr.sin_port = htons(12345); inet_pton(AF_INET, "127.0.0.1", &destAddr.sin_addr); // 发送到本地回环地址 const char* message = "Hello, UDP!"; int messageLen = strlen(message); int bytesSent = sendto(sock, message, messageLen, 0, reinterpret_cast<sockaddr*>(&destAddr), sizeof(destAddr)); if (bytesSent == SOCKET_ERROR) { int error = WSAGetLastError(); if (error != WSAEWOULDBLOCK) { std::cerr << "Failed to send data: " << error << std::endl; closesocket(sock); WSACleanup(); return 1; } } else { std::cout << "Sent " << bytesSent << " bytes of data." << std::endl; } // 接收数据 char buffer[1024]; sockaddr_in srcAddr; int srcAddrLen = sizeof(srcAddr); int bytesReceived = recvfrom(sock, buffer, sizeof(buffer), 0, reinterpret_cast<sockaddr*>(&srcAddr), &srcAddrLen); if (bytesReceived == SOCKET_ERROR) { int error = WSAGetLastError(); if (error != WSAEWOULDBLOCK) { std::cerr << "Failed to receive data: " << error << std::endl; closesocket(sock); WSACleanup(); return 1; } } else { buffer[bytesReceived] = '\0'; std::cout << "Received " << bytesReceived << " bytes of data: " << buffer << std::endl; } // 关闭套接字并清理 Winsock 库 closesocket(sock); WSACleanup(); return 0; } ``` 在本示例代码中,我们首先初始化了 Winsock 库,然后创建了一个 UDP 套接字,并将其设置为非阻塞模式。接着,我们将套接字绑定到本地端口,然后发送了一条 UDP 数据报到本地回环地址。最后,我们接收了一条 UDP 数据报,并将其输出到控制台。 需要注意的是,由于套接字被设置为非阻塞模式,所以在发送和接收数据时,我们需要检查返回值并根据返回值判断操作是否成功。如果返回值为 SOCKET_ERROR,则说明操作失败,此时我们需要通过 WSAGetLastError() 函数获取错误码,并根据错误码判断错误类型。如果错误类型为 WSAEWOULDBLOCK,则说明套接字处于非阻塞模式,暂时无法进行发送或接收操作,可以继续等待后续操作。 希望这份示例代码能对你有所帮助!

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值