int send( SOCKET s, const char FAR *buf, int len, int flags );
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。
客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
该函数的第一个参数指定发送端套接字描述符;
第二个参数指明一个存放应用程序要发送数据的缓冲区;
第三个参数指明实际要发送的数据的字节数;
第四个参数一般置0。
这里只描述同步Socket的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仅仅是把buf中的数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy数据成功,就返回实际copy的字节数,如果send在copy数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
要注意send函数把buf中的数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些数据并不一定马上被传到连接的另一端。如 果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执 行的最开始总要先等待套接字的发送缓冲中的数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回SOCKET_ERROR)
注意:在Unix系统下,如果send在等待协议传送数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
通过测试发现,异步socket的send函数在网络刚刚断开时还能发送返回相应的字节数,同时使用select检测也是可写的,但是过几秒钟之后,再send就会出错了,返回-1。select也不能检测出可写了。
recv函数
int recv( SOCKET s, char FAR *buf, int len, int flags );
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。
该函数的第一个参数指定接收端套接字描述符;
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;
第三个参数指明buf的长度;
第四个参数一般置0。
这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲 中的数据被协议传送完毕,如果协议在传送s的发送缓冲中的数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数 据或者数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,只到 协议把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。
注意:在Unix系统下,如果recv函数在等待协议接收数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
/*******************************************************************************/
int send (
SOCKET s,
const char FAR * buf,
int len,
int flags
);
请问send函数每次最多可以发送多少数据?是int的最大值吗?
【sms88】:
不是int的最大值
【sms88】:
理论上视乎是一个窗口大小减去协议头的大小
【xxyyboy】:
不是, send 有不指定长度的,有指定长度的。
int send (
SOCKET s, //连路
const char FAR * buf, //buffer 内容
int len, //buffer len
int flags //不知道
);
函数 int 只是表示发送成功失败的返回值,不表示 多长
像你这个就是 有buffer 就发送
recv到的数据流可能是断断续续的,你要把他们放在一起然后解码。
【dodoboy】:
那也就是说如果buffer中的数据过大,我也只需要调用一次send函数,而低层到底是一次传输成功还是陆续传输我不用管了吗?
【mudunke775】:
recv到的数据流可能是断断续续的,你要把他们放在一起然后解码。
//////////////////////////////////////////////////////////////////
解码和合并包是低层做的,不用你自己解码吧?
【unsigned】:
Send分为阻塞和非阻塞,阻塞模式下,如果正常的话,会直到把你所需要发送的数据发完再返回;非阻塞,会根据你的socket在底层的可用缓冲区的大小,来将你 的缓冲区当中的数据拷贝过去,有多大缓冲区就拷贝多少,缓冲区满了就立即返回,这个时候的返回值,只表示拷贝到缓冲区多少数据,但是并不代表发送多少数 据,同时剩下的部分需要你再次调用send才会再一次拷贝到底层缓冲区。
【iambic】:
和write差不多。
【Arthur_】:
You can use *setsockopt* to enlarge the buffer.
作为一个套接字,它拥有两个缓冲,接收数据缓冲和发送数据缓冲(此缓冲不同与你自己定义的缓冲),当有数据到达时,首先进入的就是接收数据缓冲,然 后用户从这个缓冲中将数据读出来,这就是套接字接受的过程,这个缓冲的大小可以自己用SetSocketOpt()设定,同时操作系统对它有一个默认大 小,如果对方在很短时间内发送大量数据到达这个套接子时,可能它没来得及接收完,因此接收缓冲处于满的状态,再有数据来的时候就进不去了,因此对方的SEND可能就返回错误,在对方发送的数据量很小时不会出现这种情况,当数据量很大时,情况就很明显了,很容易造成收不到的情况。
同样,发送方的发送缓冲也有相对应的问题。
解决这个问题的办法有几种:
1:利用SetSocketOpt()函数将接收方套接子接收缓冲设为足够大小;
2:在发送方进行数据发送时判断发送是否成功,如果不成功重发;
3:要求接收方收到数据后给发送方回应,发送方只在收到回应后才发送下一条数据。
版权声明:转载时请以超链接形式标明文章原始出处和作者信息及本声明
http://huu427.blogbus.com/logs/63084689.html
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送 数据。
客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
第四个参数一般置0。
这里只描述同步Socket的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仅仅是把buf中的 数据copy到s的发送缓冲区的剩余空间里)。如果send函数copy 数据成功,就返回实际copy的字节数,如果send在copy 数据时出现错误,那么send就返回SOCKET_ERROR;如果send在等待协议传送 数据时网络断开的话,那么send函数也返回SOCKET_ERROR。
要注意send函数把buf中的 数据成功copy到s的发送缓冲的剩余空间里后它就返回了,但是此时这些 数据并不一定马上被传到连接的另一端。如 果协议在后续的传送过程中出现网络错误的话,那么下一个Socket函数就会返回SOCKET_ERROR。(每一个除send外的Socket函数在执 行的最开始总要先等待套接字的发送缓冲中的 数据被协议传送完毕才能继续,如果在等待时出现网络错误,那么该Socket函数就返回 SOCKET_ERROR)
注意:在Unix系统下,如果send在等待协议传送 数据时网络断开的话,调用send的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收 数据。
第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的 数据;
第四个参数一般置0。
这里只描述同步Socket的recv函数的执行流程。当应用程序调用recv函数时,recv先等待s的发送缓冲中的 数据被协议传送完毕,如果协议在传送s的发送缓冲中的 数据时出现网络错误,那么recv函数返回SOCKET_ERROR,如果s的发送缓冲中没有数 据或者 数据被协议成功发送完毕后,recv先检查套接字s的接收缓冲区,如果s接收缓冲区中没有 数据或者协议正在接收 数据,那么recv就一直等待,只到 协议把 数据接收完毕。当协议把 数据接收完毕,recv函数就把s的接收缓冲中的 数据copy到buf中(注意协议接收到的 数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的 数据copy完。recv函数仅仅是copy 数据,真正的接收 数据是协议来完成的),recv函数返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收 数据时网络中断了,那么它返回0。
注意:在Unix系统下,如果recv函数在等待协议接收 数据时网络断开了,那么调用recv的进程会接收到一个SIGPIPE信号,进程对该信号的默认处理是进程终止。
cp协议本身是可靠的,并不等于应用程序用tcp发送 数据就一定是可靠的.不管是否阻塞,send发送的大小,并不代表对端recv到多少的 数据.
在阻塞模式下, send函数的过程是将应用程序请求发送的 数据拷贝到发送缓存中发送并得到确认后再返回.但由于发送缓存的存在,表现为:如果发送缓存大小比请求发送的大小要大,那么send函数立即返回,同时向网络中发送 数据;否则,send向网络发送缓存中不能容纳的那部分 数据,并等待对端确认后再返回(接收端只要将 数据收到接收缓存中,就会确认,并不一定要等待应用程序调用recv);
在非阻塞模式下,send函数的过程仅仅是将 数据拷贝到协议栈的缓存区而已,如果缓存区可用空间不够,则尽能力的拷贝,返回成功拷贝的大小;如缓存区可用空间为0,则返回-1,同时设置errno为EAGAIN.
linux下可用sysctl -a | grep net.ipv4.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:%d/n", sendbuflen); sendbuflen = 10240; setsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, len); getsockopt(clientSocket, SOL_SOCKET, SO_SNDBUF, (void*)&sendbuflen, &len); printf("now,sendbuf:%d/n", sendbuflen);
需要注意的是,虽然将发送缓存设置成了10k,但实际上,协议栈会将其扩大1倍,设为20k.
在实际应用中,如果发送端是非阻塞发送,由于网络的阻塞或者接收端处理过慢,通常出现的情况是,发送应用程序看起来发送了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
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马上返回了,而且返回的字节长度是等于发送的字节长度的,情况和阻塞模式是向相同的
第一次send,返回16384字节(16k),也就是填满了缓冲区
第二次send,在这之前sleep了1000毫秒,这段时间可能已经有5000字节从缓冲区发出,到达另外一端了,于是缓冲区空了5000字节出来,相应的,这次返回的是5000,表示新放入了5000字节到缓冲区
第三次send ,和第二次相同,又放了6000字节
最后一次send,放入了剩下的字节数,这个时候缓冲还是有 数据的。