转自于http://blog.csdn.net/phunxm/article/details/5085869(有全文)
8.I/O通信
从I/O的角度来看,套接字也是文件,它提供了同文件读写(fread()/fwrite())对应的收发数据操作接口:send()/recv()。
8.1 发送数据
8.1.1 send
// The send function sends data on a connected socket.
int send(
SOCKETs,
// [in] Descriptor identifying a connected socket.
const char FAR *buf,
// [in] Buffer containing the data to be transmitted.
int len,
// [in] Length of the data in buf.
int flags// [in] Indicator specifying the way in which the call is made.
);
send()函数在一个已连接的套接字s上执行数据发送操作。对于客户机而言,发送的目标地址即connect()调用时所指定的地址;对于服务器而言,发送的目标地址即accept()调用所返回的地址。发送的内容为保存在缓冲区buf中,发送的内容长度为len。最后一个参数flags,通常情况下填0。
send()函数只是将欲发送的内容从用户缓冲区拷贝到系统缓冲区(TCP Send Socket Buffer),系统的默认socket
发送缓冲区(SO_SNDBUF)的大小为8K,我们可以调用setsockopt()将其更改,理论上最大为64K(The
maximum congestion window is related to the amount of buffer space that the kernel allocates for each socket)。
只要系统缓冲区足够大,send()执行完拷贝立即返回实际拷贝的字节数。如果系统缓冲区不够大,例如在网络拥塞或带宽下降的情况下,用户大量地投递send()操作导致TCP
Send Socket Buffer迅速充满,此时再调用send()操作,可能返回的值(即实际拷贝字节数)要小于我们传入的期待发送数量(len),在超时不得受理的情况下,返回SOCKET_ERROR,WSAGetLastError()=WSAETIMEDOUT。故大块的数据可能不能一次性“发送”完毕,通常需要检测send()返回值,多次调用send()直到“发送”完毕,可参考CSocket::Send()实现。关于发送超时限制(send
timeout),可以调用setsockopt()在SOL_SOCKET级别设置SO_SNDTIMEO选项值,以毫秒为单位。建议最多两分钟,因为TCP的MSL(Maximum
Segment Lifetime)即为两分钟。
需要注意的是,用户可能短时间内需要发送多个小数据包,在TCP/IP中,Nagle算法要求主机等待数据积累到一定数量后或超过预定时间才发送。默认情况下实施Nagle算法,通信方会在向对方发送确认(ACK)信息之前,花费一定的时间来等待要传入的数据,这样,主机的就不必发送一个只有确认信息的数据报。发送小的数据包不仅没有多少意义,而且徒增错误检查和确认的开销。如果不想是使用Nagle算法,以“保留发送边界”,用户可调用setsockopt()函数在IPPROTO_TCP选项级别设置TCP_NODELAY为TRUE。例如一次独立的HTTP
GET请求往往希望“保留发送边界”,服务器的HTTP Response Header往往希望“保留发送边界”以区分后续的HTTP
Response Content。体现在TCP层,即开启“PSH”选项。
具体的发送工作交由系统的传输层驱动程序完成。因为TCP提供可靠有序的传输机制,故我们总是很放心地认为它会将我们的数据发送到目的端。至于TCP分多少次将数据发送至对方,由协商的MSS(Max
Segment Size)和接收方的TCP Window决定。
8.1.2 sendto
// The sendto function sends data to a specific destination.
int sendto(
SOCKETs,
const char FAR *buf,
int len,
int flags,
const struct sockaddr FAR *to,
// [in] Optional pointer to the address of the target socket.
int tolen// [in] Size of the address in to.
);
sendto()函数只是比send()函数多出了一个目的地址信息参数,主要用于面向无连接的UDP通信。TCP套接字在建立连接(connect-accept)时,便知晓对方地址信息,而UDP套接字通信之前不建立连接,需要通信时,调用sendto()将消息发送给目的地址(to)。无论对方是否在指定端口“监听”,sendto总是把数据发出去,要知道UDP是没有回应确认的。
注释中,sendto()函数的目标地址是“optional”,当我们忽略最后两个参数时,完全可以替换send()函数使用。实际上,这很方便我们在编程接口上提供统一。例如live555的writeSocket接口针对TCP和UDP套接字统一使用sendto()。
由于UDP协议基本上只是在IP协议上做了简单的封装(Source Port+Destination
Port+Length+Checksum),其没有做可靠性传输保障,故对UDP套接字一次sendto()的数据量不宜过大,最好以MTU为基准。使用UDP套接字往发送大数据块,往往因为IP分片等原因丢包,考虑异构网络及设备的MTU不同,一般一次发送512字节左右比较合适。
我们在一个UDP套接字上执行connect()操作,并未真正建立连接,而是执行一种目的地址“绑定”,事后我们可以使用send()函数替换sendto()函数。要取消UDP套接字与目的地址的关联,唯一的办法是在这个套接字上以INADDR_ANY为目标地址调用connect()。
8.2 接收数据
8.2.1 recv
// The recv function receives data from a connected or bound socket.
int recv(
SOCKETs,
// [in] Descriptor identifying a connected socket.
char FAR *buf,
// [out] Buffer for the incoming data.
int len,
// [in] Length of buf.
int flags// [in] Flag specifying the way in which the call is made.
);
recv()函数在一个已连接的套接字s上执行数据接收操作。对于客户机而言,数据的源地址即connect()调用时所指定的地址;对于服务器而言,数据的源地址即accept()调用所返回的地址。接收的内容为保存至长度为len的缓冲区buf,最后一个参数flags,通常情况下填0。
recv()函数只是将TCP层当前接收到的数据流从系统缓冲区(TCP Receive
Socket Buffer)拷贝到用户缓冲区,系统的默认socket
接收缓冲区(SO_RCVBUF)的大小为8K,我们可以调用setsockopt()将其更改,理论上最大为64K(The
maximum congestion window is related to the amount of buffer space that the kernel allocates for each socket)。
recv()函数返回实际接收到的数据,可能小于缓冲区的长度len,可能当前到达的有效数据大于len,但最大返回len。在超时仍无数据到来的情况下,返回SOCKET_ERROR,WSAGetLastError()=WSAETIMEDOUT。关于接收超时限制(receive
timeout),可以调用setsockopt()在SOL_SOCKET级别设置SO_RCVTIMEO选项值,以毫秒为单位。建议最多两分钟,因为TCP的MSL(Maximum
Segment Lifetime)即为两分钟。
如果对方不停发送数据,而本机过于繁忙疲于应付,则可能导致数据大量累积,一旦TCP Receive Socket Buffer或TCP Window充满,则可能产生数据溢出。TCP滑动窗口机制,由接收方建议性的控制发送量,即每一次确认回应(ACK)时都告知对方自己当前的接收能力(TCP窗口的大小),发送方据此有效地控制自己的发送行为,协调双方的通信步伐。
由于基于流的TCP协议,未保留消息边界(boundary)的概念,发送者发送的数据很快就会聚集在系统接收缓冲区(TCP堆栈)中。假设这样一种情景,客户端连接流媒体服务器(如IP摄像头)后,发送请求码流的请求,这以后服务器总是将连续不断地推送数据过来(如IP摄像头实时监控码流)。若客户端不执行recv()拷贝操作而又尚未关闭连接,则服务器不断推送数据到客户端的TCP
Stack,直至TCP window size=0。
不管消息边界是否存在,接收端都会尽量地读取当前的有效数据。执行拷贝后,数据将立即从系统缓冲区删除,以释放部分TCP Window。因为流的无边界性,故用户投递了三个send(),可能接收端只需一次或两次recv()即接收完成。若客户三次send()的是结构化的数据,而接收端收到的是粘连在一起的一大坨数据或两块随机边界数据,这种情况即通常所说的TCP粘包问题。
具体的接收工作交由系统的传输层驱动程序完成。因为TCP提供可靠有序的传输机制,故我们总是很放心地认为它会将对方发送过来的数据正确的提交给我们。这里面的“正确”是指应用层面的报文结构及格式,即使TCP层面发生了偶然的丢包重传(retransmit
out of order),但我们得到的仍然是对方提交的完整的报文。应用层协议就需要我们自己解析了。
粘包问题需要我们联合发送方,采取有效边界措施在应用层重组出正确的报文。例如,发送方往往在一个数据包的头4个字节告知对方接下来的数据有多少,这样接收方就能有效的执行接收,以保留边界和结构性。假设接收方得知发送方将发送32KB的数据过来,便投递一个32KB的缓冲区调用recv试图一次性接收完毕,这将以失败告终。实际上,发送方的TCP层将按MSS尺寸将TCP报文分解成很多个段(Segment)分多次发送给接收方。当然,它们往往具有相同的确认号(ack),以表示这些段是一个回应报文。这样,客户端才能识别出TCP
segment of a reassembled PDU,以正确重组报文。可参考CSocket::Receive()实现。
8.2.2 recvfrom
// The recvfrom function receives a datagram and stores the source address.
int recvfrom(
SOCKETs,
char FAR*
buf,
int len,
int flags,
struct sockaddr FAR *from,
// [out] Optional pointer to a buffer that will hold the source address upon return.
int FAR *fromlen// [in, out] Optional pointer to the size of the from buffer.
);
recvfrom/recv与sendto/send在行为学上同功,因为事先不知发送方为谁,故只要进来的通信,都将对方的地址保存在参数from中。值得注意的是,尽管UDP中没有TCP监听、连接等概念,但是作为接收方往往需要在本地某个端口上等待,这个端口必须是专用,约定用户预知的。故通常在调用recvfrom之前,必须显式调用bind()函数将UDP套接字关联到本地某个指定端口,进行“监听”。
UDP通信是基于离散消息(message)的,故要么收到对方发送的消息包,要么整包丢失,接收方不得而知。如果整包丢失了,由于接收方不得而知,故没有反馈信息,也不会重发。这就是UDP通信的不可靠处。
live555中的readSocket接口针对TCP和UDP套接字统一使用recvfrom。
分享到:
相关推荐
《UNIX网络编程 第2卷 TCP协议》深入探讨了TCP(传输控制协议)这一核心网络协议,它是面向连接的,提供可靠的数据流传输服务,与无连接的UDP协议形成鲜明对比。在本书中,作者详细分析了TCP的实现,涵盖了比UDP更...
在游戏开发中,特别是网络编程方面,TCP因其可靠性而成为常用的选择之一。但使用TCP进行数据传输时会遇到一些问题,比如“粘包”和“拆包”,这些问题可能会对游戏的用户体验产生负面影响。 #### 粘包与拆包问题...
最后,书中还会讨论网络编程接口,如套接字API,它是开发网络应用的标准接口,让程序员可以利用TCP/IP协议进行通信。 总之,《TCP/IP详解卷二:实现》是一本全面介绍TCP/IP协议实现的书籍,它深入浅出地剖析了网络...
8. **TCP性能优化**:分析如何通过调整TCP内核参数来提高网络性能,例如最大段大小(TCP_MSS)、TCP缓冲区大小等。 9. **TCP连接的内存管理**:讨论内核如何分配和管理内存以支持TCP连接,包括套接字结构、缓冲区和...
综上所述,TCP协议的详解涵盖了连接建立、数据传输、可靠性保障、流量和拥塞控制等多个方面,深入理解TCP有助于提升网络编程和系统设计的能力。通过阅读《TCP详解.pdf》这样的文档,可以进一步了解TCP的细节和实践...
总之,《TCP卷2:实现源码 4.4BSD-Lite》是TCP/IP研究者的宝典,它揭示了TCP协议在实际操作系统中的实现细节,对于网络协议的深入理解和系统级编程具有极大的教育意义。通过阅读和分析这些源代码,我们可以提高网络...
第三卷的内容细节覆盖了当今TCP/IP编程人员和网络管理员必须熟练掌握的四个基本方面: T/TCP (TCP事务协议),这是对TCP的扩展,使客户--服务器间的事务传输更快更有效和更可靠; HTTP (超文本传送协议),这是飞速...
1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...
TCP/IP详解 卷1:协议 译者序 前言 第1章 概述 1 1.1 引言 1 1.2 分层 1 1.3 TCP/IP的分层 4 1.4 互联网的地址 5 1.5 域名系统 6 1.6 封装 6 1.7 分用 8 1.8 客户-服务器模型 8 1.9 端口号 9 1.10 标准化过程 10 ...
1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...
【TCP模块详解】 TCP(Transmission Control Protocol)是互联网协议栈中的关键组件,为应用程序提供可靠的、面向连接的、基于字节流的传输服务。...理解TCP的工作原理对于网络编程和网络故障排查至关重要。
总的来说,这个“TCP/IP半成品代码”项目可以帮助我们了解TCP协议的实现细节,学习网络编程和嵌入式系统的知识,对于开发者来说是一个有价值的参考资源。不过,由于这是一个半成品,可能需要开发者自行完善和测试,...
TCP/IP协议是互联网通信的基础,它是网络层与传输层之间的一组通信协议,负责将数据分包并发送到目标地址。...通过阅读和理解这个源码,开发者可以深入学习TCP/IP协议的实现细节以及如何在实际项目中应用这些知识。
### TCP/IP详解卷3知识点梳理 #### 一、TCP事务协议概述 ...通过阅读本书,读者可以全面了解T/TCP与HTTP的工作原理及其在网络编程中的应用。对于希望深入学习TCP/IP协议栈的人来说,这是一本不可或缺的好书。
文件中讨论了计算机网络的知识点,如以太网协议(Ethernet)、TCP、UDP和IP协议,以及TCP MSS(Maximum Segment Size)、RTT(Round-Trip Time)和网络带宽(例如100Mbps)等参数。 4. 操作系统原理 文档提到的概念...
1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...
1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...
1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...
1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...
1.15 应用编程接口 12 1.16 测试网络 13 1.17 小结 13 第2章 链路层 15 2.1 引言 15 2.2 以太网和IEEE 802封装 15 2.3 尾部封装 17 2.4 SLIP:串行线路IP 17 2.5 压缩的SLIP 18 2.6 PPP:点对点协议 18 2.7 环回接口...