首页 > 编程知识 正文

socket send函数返回值,sendrecv

时间:2023-05-06 15:58:00 阅读:167377 作者:2392

1、发送函数

intsend(sockets,const char FAR *buf,int len,int flags );

无论是客户端还是服务器APP应用程序,都使用send函数将数据发送到TCP连接的另一端。

客户端通常使用send函数向服务器发送请求,服务器通常使用send函数向客户端发送响应。

(1)最初的参数指定发送方套接字描述符;

2 )第二个参数指示包含APP应用程序发送的数据的缓冲区

)第三个参数指示实际发送的数据的字节数。

(4)第四个参数一般取0。

这里只说明同步套接字send函数的执行流程。 在调用该函数时,send首先把要发送的数据的长度len与套接字s的发送缓冲区的长度进行比较,如果len大于s的发送缓冲区的长度,则返回SOCKET_ERROR; 如果len小于s的发送缓冲区长度,send首先检查协议是否发送了s的发送缓冲区中的数据,如果是,则等待协议发送完数据。 在协议还没有开始发送s的发送缓冲区中的数据时,或者s的发送缓冲区中没有数据时,send比较s的发送缓冲区的空闲容量和len。 如果len大于空闲区域大小send,则继续等待协议发送完s的发送缓冲器内的数据,如果len小于空闲区域大小send,则只将buf中的数据copy发送到空闲区域(send将s的发送缓冲器内的数据发送到连接的另一侧)

如果send函数的copy数据成功,则返回实际的copy字节数;如果send在copy数据中发生错误,则send返回SOCKET_ERROR; 如果在等待协议传输数据时网络断开,send函数会返回SOCKET_ERROR。

请注意,在send函数成功将buf中的数据复制到s发送缓冲区的剩馀空间之后,它会返回。 但是,此时,这些数据并不一定会立即传递到连接的另一端。 如果在后续传输过程中协议出现网络错误,以下套接字函数将返回SOCKET_ERROR。 每个非send套接字函数都在执行之前等待协议传输套接字发送缓冲区中的数据。 如果在等待过程中发生网络错误,则套接字函数会返回SOCKET_ERROR。

注:在Unix系统上,如果send在等待协议发送数据时断开与网络的连接,则调用send的进程将接收SIGPIPE信号,并且该信号的缺省进程将终止

2、recv函数

intrecv(sockets,char FAR *buf,int len,int flags );

无论是客户端还是服务器APP应用程序,都使用recv函数从TCP连接的另一端接收数据。

(1)最初的参数指定接收方套接字描述符;

)第二个参数指示存储recv函数接收的数据的缓冲区。

)第三个参数表示buf的长度。

(4)第四个参数一般取0。

这里只说明同步套接字recv函数的执行流程。 当APP调用recv函数时,recv首先等待通过协议传输s的发送缓冲器中的数据,如果协议在传输s的发送缓冲器中的数据时发生网络错误,则recv函数返回SOCKET_ERROR 或者通过协议成功发送数据后,recv首先检查套接字s的接收缓冲区,如果s的接收缓冲区没有数据,或者如果协议正在接收数据,则在协议接收完数据后,recv函数返回s的接收缓冲区(因为协议接收到的数据有可能比buf的长度大,所以要注意在这种情况下要调用recv函数几次将s的接收缓冲区的数据复制完毕。 recv函数只是一个拷贝数据(真正的接收数据是通过协议实现的),recv函数返回实际拷贝的字节数。 如果在复制期间recv出错,则返回SOCKET_ERROR; 如果网络在等待协议接收数据时中断,recv函数将返回0。

注:在Unix系统上,如果recv函数在等待协议接收数据时断开网络连接,则调用recv的进程将接收SIGPIPE信号,并且该进程的缺省处理过程将终止。

tcp协议本身是可靠的,APP应用通过tcp发送数据未必可靠。不管有无拥塞,send发送的大小都不表示端到端recv多少数据。

在阻塞模式下,send函数的过程是将APP应用程序请求发送的数据复制到发送缓存中并发送,然后经过确认再返回。 但是,由于发送缓存的存在,如果发送缓存的大小大于请求的大小,则send函数会立即返回,同时将数据发送到网络; 否则,send会将缓存中无法容纳的数据发送到网络,等待对方确认后再返回。 (接收方在接收缓存中接收到数据后进行确认。 不一定要等待APP应用程序调用recv。 )

在无阻塞模式下,send函数的进程只是将数据复制到协议栈的缓存区域,如果缓存空间没有足够的空间,它会尽可能地进行复制并返回成功副本的大小。 如果缓冲区可用空间为0,则返回-1,并将errno设置为EAGAIN。

3 .高速缓存大小显示被更改

linux上可用的sysctl -a | grep net.ipv

4.tcp_wmem查看系统默认的发送缓存大小:

net.ipv4.tcp_wmem = 4096 16384 81920

这有三个值,第一个值是socket的发送缓存区分配的最少字节数,第二个值是默认值(该值会被net.core.wmem_default覆盖),缓存区在系统负载不重的情况下可以增长到这个值,第三个值是发送缓存区空间的最大字节数(该值会被net.core.wmem_max覆盖).

根据实际测试,如果手工更改了net.ipv4.tcp_wmem的值,则会按更改的值来运行,否则在默认情况下,协议栈通常是按net.core.wmem_default和net.core.wmem_max的值来分配内存的.

应用程序应该根据应用的特性在程序中更改发送缓存大小:

socklen_t sendbuflen = 0;socklen_t len = sizeof(sendbuflen);getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);printf("default,sendbuf:%dn", sendbuflen);sendbuflen = 10240;setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len);getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len);printf("now,sendbuf:%dn", sendbuflen);需要注意的是,虽然将发送缓存设置成了10k,但实际上,协议栈会将其扩大1倍,设为20k.

4、实例分析

在实际应用中,如果发送端是非阻塞发送,由于网络的阻塞或者接收端处理过慢,通常出现的情况是,发送应用程序看起来发送了10k的数据,但是只发送了2k到对端缓存中,还有8k在本机缓存中(未发送或者未得到接收端的确认).那么此时,接收应用程序能够收到的数据为2k.假如接收应用程序调用recv函数获取了1k的数据在处理,在这个瞬间,发生了以下情况之一,双方表现为:

A. 发送应用程序认为send完了10k数据,关闭了socket:

发送主机作为tcp的主动关闭者,连接将处于FIN_WAIT1的半关闭状态(等待对方的ack),并且,发送缓存中的8k数据并不清除,依然会发送给对端.如果接收应用程序依然在recv,那么它会收到余下的8k数据(这个前题是,接收端会在发送端FIN_WAIT1状态超时前收到余下的8k数据.), 然后得到一个对端socket被关闭的消息(recv返回0).这时,应该进行关闭.

B. 发送应用程序再次调用send发送8k的数据:

假 如发送缓存的空间为20k,那么发送缓存可用空间为20-8=12k,大于请求发送的8k,所以send函数将数据做拷贝后,并立即返回8192;

假如发送缓存的空间为12k,那么此时发送缓存可用空间还有12-8=4k,send()会返回4096,应用程序发现返回的值小于请求发送的大小值后,可以认为缓存区已满,这时必须阻塞(或通过select等待下一次socket可写的信号),如果应用程序不理会,立即再次调用send,那么会得到-1的值, 在linux下表现为errno=EAGAIN.

C. 接收应用程序在处理完1k数据后,关闭了socket:

接收主机作为主动关闭者,连接将处于FIN_WAIT1的半关闭状态(等待对方的ack).然后,发送应用程序会收到socket可读的信号(通常是select调用返回socket可读),但在读取时会发现recv函数返回0,这时应该调用close函数来关闭socket(发送给对方ack);

如果发送应用程序没有处理这个可读的信号,而是在send,那么这要分两种情况来考虑,假如是在发送端收到RST标志之后调用send,send将返回-1,同时errno设为ECONNRESET表示对端网络已断开,但是,也有说法是进程会收到SIGPIPE信号,该信号的默认响应动作是退出进程,如果忽略该信号,那么send是返回-1,errno为EPIPE(未证实);如果是在发送端收到RST标志之前,则send像往常一样工作;

以上说的是非阻塞的send情况,假如send是阻塞调用,并且正好处于阻塞时(例如一次性发送一个巨大的buf,超出了发送缓存),对端socket关闭,那么send将返回成功发送的字节数,如果再次调用send,那么会同上一样.

D. 交换机或路由器的网络断开:

接收应用程序在处理完已收到的1k数据后,会继续从缓存区读取余下的1k数据,然后就表现为无数据可读的现象,这种情况需要应用程序来处理超时.一般做法是设定一个select等待的最大时间,如果超出这个时间依然没有数据可读,则认为socket已不可用.

发送应用程序会不断的将余下的数据发送到网络上,但始终得不到确认,所以缓存区的可用空间持续为0,这种情况也需要应用程序来处理.

如果不由应用程序来处理这种情况超时的情况,也可以通过tcp协议本身来处理,具体可以查看sysctl项中的:

net.ipv4.tcp_keepalive_intvl
net.ipv4.tcp_keepalive_probes
net.ipv4.tcp_keepalive_time

5、send函数特点及相关问题收藏

在send函数的help里面看到

The successful completion of a send call does not indicate that the data was successfully delivered.

send成功完成并不代表数据已经成功送达。

If no buffer space is available within the transport system to hold the data to be transmitted, send will block unless the socket has been placed in nonblocking mode.

如果没有缓冲存储待发送的数据,send会阻塞直到socket被设置为非阻塞模式,

On nonblocking stream-oriented sockets, the number of bytes written can be between 1 and the requested length, depending on buffer availability on both client and server machines.

在非阻塞流模式socket中,写入的字节可以是1到需要的长度,依赖于客户端和服务器的缓冲。

The select or WSAEventSelect function can be used to determine when it is possible to send more data.

select 或WSAEventSelect 函数可以用于决定什么时候可以继续发送数据

阻塞模式下send并不是说直到你发送数据到对方机器才返回的意思,它是说把你要发送的数据放入发送缓冲后,就直接返回。而不是阻塞时,如发送缓冲区没有了,他就直接返回,而阻塞时会等待发送缓冲区有空间。

先看看在阻塞模式下send的表现吧(注意缓冲区的大小,我这里是16k)

(1)发送一个小于16k的数据,send马上就返回了

也就说是,send把待发送的数据放入发送缓冲马上就返回了,前提是发送的数据字节数小于缓冲大小

(2)发送一个大于16k的数据,send没有马上返回,阻塞了一下

send一定要把所有数据放入缓冲区才会返回,假设我们发32k的数据,当send返回的时候,有16k数据已经到达另一端,剩下16k还在缓冲里面没有发出去

在阻塞模式下

如果发送成功,返回的nBytes一定等于len

nBytes = send(m_socket,buf,len,0);

也就是在上面代码中那个发送循环其实是没有必要的

再看看在非阻塞模式下的情况吧

(1)发送一个小于16k的数据,send马上返回了,而且返回的字节长度是等于发送的字节长度的,情况和阻塞模式是向相同的

(2)发送一个大于16k的数据,send也是马上就返回了,返回的nByte小于待发送的字节数

来模拟一下实际情况,假设我们有32k的数据要发送,

第一次send,返回16384字节(16k),也就是填满了缓冲区

第二次send,在这之前sleep了1000毫秒,这段时间可能已经有5000字节从缓冲区发出,到达另外一端了,于是缓冲区空了5000字节出来,相应的,这次返回的是5000,表示新放入了5000字节到缓冲区

第三次send ,和第二次相同,又放了6000字节

最后一次send,放入了剩下的字节数,这个时候缓冲还是有数据的。

再发送大于16k数据的情况下,那个send发送循环就是必须的了

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。