文章目录
- 网络协议
-
- linux网络编程
- 相关概念
- 套接字地址结构
-
- 字节排序函数
- 地址转换函数:
-
- TCP通信相关函数
- 1. int socket(family, type, protocol):创建套接字
- 2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen):绑定套接字
- 3. int listen(int fd,int backlog):设置同时通信的最大套接字数量
- 4. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)阻塞式监听客户端连接
- 5. int connect(int sockfd, const strcut sockaddr *addr, socklen_t addrlen)用来客户端和tcp服务器建立连接
- 5. ssize_t read(int fd,void *ptr,size_t nbytes)一次读取指定字节长度数据
- 6. ssize_t write(int fd,const void *ptr,size_t nbytes)一次写入指定字节长度数据
- 7. ssize_t Readn(int fd,void *vptr,size_t n);循环读取n个字节数据
- 8. ssize_t Writen(int fd,const void*vptr,size_t n)循环写入n个字节数据
- 9. ssize_t Readline(int fd, void *vptr, size_t maxlen) 读到'\n'或者读满缓冲区才返回
- 10. int close(int fd) 关闭套接字
- 11. shutdown()函数切断进程共享的套接字的所有连接
- 12. recv()和send()函数
- UDP通信相关函数
-
- UDP组播通信相关函数
- 常见的错误码
- 参考文献
网络协议
RFC 相关文档
- RFC官网
- RFC 791:INTERNET PROTOCOL
- RFC 793:Transmission Control Protocol
网络 ip 层
ip 头部
ip消息头可分为 20 个字节的固定头部和最多40字节可扩展头:
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
|Version| IHL |Type of Service| Total Length |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Identification |Flags| Fragment Offset |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Time to Live | Protocol | Header Checksum |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Destination Address |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
Example Internet Datagram Header
类型 | 长度 | 描述 |
---|
Version | 4bit | 值为4时代表IPV4;值为6时代表IPV6 | IHL | 4bit | ip消息头可分为20个字节的固定头部40字节可扩展头 | Type of Service | 8bit | 服务类型,只有在有QoS差分服务要求时这个字段才起作用 | Total Length | 16bit | 代表总长度,整个IP数据报的长度,包括首部和数据之和,单位为字节,最长65535,总长度必须不超过最大传输单元MTU | Identification | 16bit | 标识,主机每发一个报文值会加1,分片重组时会用到该字段 | Flags | 3bit | 分片重装时使用:第一位,为0,第二位,DF(Don’t Fragment),能否分片位,0表示可以分片,1表示不能分片;第三位MF(More Fragment),表示是否该报文为最后一片,0表示最后一片,1代表后面还有 | Fragment Offset | 13bit | 片偏移:分片重组时会用到该字段。表示较长的分组在分片后,某片在原分组中的相对位置 | Time to Live | 8bit | 生存时间可经过的最多路由数,即数据包在网络中可通过的路由器数的最大值 | Protocol | 8bit | 标识下一层协议 | Header Checksum | 16bit | 首部校验和,只检验数据包的首部,不检验数据部分 | Source Address | 32bi | 源IP地址 | Destination Address | 32bit | 目的IP地址。 | Options | 长度可变 | 选项字段,用来支持排错,测量以及安全等措施。 | Padding | 长度可变 | 填充字段,全为0 |
传输层
tcp
相关概念
MSL是Maximum Segment Lifetime,“报文最大生存时间”
它是任何报文在网络上存在的最长时间,超过这个时间报文将被丢弃。
因为TCP报文(segment)是IP数据报(datagram)的数据部分,而IP头中有一个TTL域,TTL是time to live的缩写,中文可以译为“生存时间”,这个生存时间是由源主机设置初始值但不是存的具体时间,而是存储了一个IP数据报可以经过的最大路由数,每经过一个处理他的路由器此值就减1,当此值为0则数据报将被丢弃,同时发送ICMP报文通知源主机。
RFC 793中规定MSL为2分钟,实际应用中常用的是30秒,1分钟和2分钟等 2MSL即两倍的MSL,TCP的TIME_WAIT状态也称为2MSL等待状态,当TCP的一端发起主动关闭,在发出最后一个ACK包后,即第3次握手完成后发送了第四次握手的ACK包后就进入了TIME_WAIT状态,必须在此状态上停留两倍的MSL时间。
等待2MSL时间主要目的是怕最后一个ACK包对方没收到,那么对方在超时后将重发第三次握手的FIN包,主动关闭端接到重发的FIN包后可以再发一个ACK应答包。 在TIME_WAIT状态时两端的端口不能使用,要等到2MSL时间结束才可继续使用。 当连接处于2MSL等待阶段时任何迟到的报文段都将被丢弃。不过在实际应用中可以通过设置SO_REUSEADDR选项达到不必等待2MSL时间结束再使用此端口。
MSS:Maximum Segment Size
对于IPv4,为了避免IP分片,主机一般默认MSS为536字节 (576IP最大字节数-20字节TCP协议头-20字节IP协议头=536字节)。同理,IPv6的主机默认MSS为1220字节(1280IP最大字节数-20字节TCP协议头-40字节IP协议头=1220字节)。
当发送方主机想要调整MSS时,应注意以下几点:
- MSS不包含TCP及IP的协议头长度。
- MSS选项只能在初始化连接请求(SYN=1)使用。
- 发送方与接收方的MSS不一定相等
最大报文段长度(MSS)与最大传输单元(Maximum Transmission Unit, MTU)均是协议用来定义最大长度的。不同的是,MTU应用于OSI模型的第二层数据链接层,并无具体针对的协议。MTU限制了数据链接层上可以传输的数据包的大小,也因此限制了上层(网络层)的数据包大小。例如,如果已知某局域网的MTU为1500字节,则在网络层的因特网协议(Internet Protocol, IP)里,最大的数据包大小为1500字节(包含IP协议头)。MSS针对的是OSI模型里第四层传输层的TCP协议。因为MSS应用的协议在数据链接层的上层,MSS会受到MTU的限制
RTT:Round Trip Time
发送一个数据包收到对应的ACK,所花费的时间
RTO:Retransmission TimeOut
重传时间间隔
/proc/sys/net/ipv4/
参考:/proc/sys/net/ipv4/下网络参数的理解以及sysctl命令修改内核参数
tcp 头部
TCP Header Format
0 1 2 3
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Source Port | Destination Port |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Sequence Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Acknowledgment Number |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Data | |U|A|P|R|S|F| |
| Offset| Reserved |R|C|S|S|Y|I| Window |
| | |G|K|H|T|N|N| |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Checksum | Urgent Pointer |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| Options | Padding |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
| data |
+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
TCP Header Format
- 端口号:用来标识同一台计算机的不同的应用进程。
1)源端口:源端口和IP地址的作用是标识报文的返回地址。 2)目的端口:端口指明接收方计算机上的应用程序接口。
TCP报头中的源端口号和目的端口号同IP数据报中的源IP与目的IP唯一确定一条TCP连接
-
序号和确认号:是TCP可靠传输的关键部分 1)序号是本报文段发送的数据组的第一个字节的序号。在TCP传送的流中,每一个字节一个序号。e.g.一个报文段的序号为300,此报文段数据部分共有100字节,则下一个报文段的序号为400。所以序号确保了TCP传输的有序性。 2)确认号,即ACK,指明下一个期待收到的字节序号,表明该序号之前的所有数据已经正确无误的收到。确认号只有当ACK标志为1时才有效。比如建立连接时,SYN报文的ACK标志位为0。 -
数据偏移/首部长度:4bits 由于首部可能含有可选项内容,因此TCP报头的长度是不确定的,报头不包含任何任选字段则长度为20字节,4位首部长度字段所能表示的最大值为1111,转化为10进制为15,15*32/8 = 60,故报头最大长度为60字节。首部长度也叫数据偏移,是因为首部长度实际上指示了数据区在报文段中的起始偏移值。 -
保留:为将来定义新的用途保留,现在一般置0。 -
控制位:URG ACK PSH RST SYN FIN,共6个,每一个标志位表示一个控制功能。 1)URG:紧急指针标志,为1时表示紧急指针有效,为0则忽略紧急指针。 2)ACK:确认序号标志,为1时表示确认号有效,为0表示报文中不含确认信息,忽略确认号字段。 3)PSH:push标志,为1表示是带有push标志的数据,指示接收方在接收到该报文段以后,应尽快将这个报文段交给应用程序,而不是在缓冲区排队。 4)RST:重置连接标志,用于重置由于主机崩溃或其他原因而出现错误的连接。或者用于拒绝非法的报文段和拒绝连接请求。 5)SYN:同步序号,用于建立连接过程,在连接请求中,SYN=1和ACK=0表示该数据段没有使用捎带的确认域,而连接应答捎带一个确认,即SYN=1和ACK=1。 6)FIN:finish标志,用于释放连接,为1时表示发送方已经没有数据发送了,即关闭本方数据流。 -
窗口:滑动窗口大小,用来告知发送端接受端的缓存大小,以此控制发送端发送数据的速率,从而达到流量控制。窗口大小时一个16bit字段,因而窗口大小最大为65535。 -
校验和:奇偶校验,此校验和是对整个的 TCP 报文段,包括 TCP 头部和 TCP 数据,以 16 位字进行计算所得。由发送端计算和存储,并由接收端进行验证。 -
紧急指针:只有当 URG标志置1时紧急指针才有效。紧急指针是一个正的偏移量,和顺序号字段中的值相加表示紧急数据最后一个字节的序号。TCP的紧急方式是发送端向另一端发送紧急数据的一种方式。传输层协议使用带外数据(out-of-band,OOB)来发送一些重要的数据,如果通信一方有重要的数据需要通知对方时,协议能够将这些数据快速地发送到对方.为了发送这些数据,协议一般不使用与普通数据相同的通道,而是使用另外的通道.linux系统的套接字机制支持低层协议发送和接受带外数据.但是TCP协议没有真正意义上的带外数据.为了发送重要协议,TCP提供了一种称为紧急模式(urgentmode)的机制.TCP协议在数据段中设置URG位,表示进入紧急模式.接收方可以对紧急模式采取特殊的处理.很容易看出来,这种方式数据不容易被阻塞,可以通过在我们的服务器端程序里面捕捉SIGURG信号来及时接受数据或者使用带OOB标志的recv函数来接受 -
选项和填充:最常见的可选字段是最长报文大小,又称为MSS(Maximum Segment Size),每个连接方通常都在通信的第一个报文段(为建立连接而设置SYN标志为1的那个段)中指明这个选项,它表示本端所能接受的最大报文段的长度。选项长度不一定是32位的整数倍,所以要加填充位,即在这个字段中加入额外的零,以保证TCP头是32的整数倍。 -
数据部分: TCP 报文段中的数据部分是可选的。在一个连接建立和一个连接终止时,双方交换的报文段仅有 TCP 首部。如果一方没有数据要发送,也使用没有任何数据的首部来确认收到的数据。在处理超时的许多情况中,也会发送不带任何数据的报文段
可选项:
- 选项的第一个字段kind说明选项的类型。有的TCP选项没有后面两个字段,仅包含1字节的kind字段
- 第二个字段length(如果有的话)指定该选项的总长度,该长度包括kind字段和length字段占据的2字节
- 第三个字段info(如果有的话)是选项的具体信息。常见的TCP选项有7种,
Currently defined options include (kind indicated in octal):
Kind Length Meaning
---- ------ -------
0 - End of option list.
1 - No-Operation.
2 4 Maximum Segment Size.
Specific Option Definitions
End of Option List
+--------+
|00000000|
+--------+
Kind=0
-
kind=0是选项表结束选项。 -
kind=1是空操作(nop)选项,没有特殊含义,一般用于将TCP选项的总长度填充为4字节的整数倍。 -
kind=2是最大报文段长度选项。TCP连接初始化时,通信双方使用该选项来协商最大报文段长度(Max Segment Size,MSS)。TCP模块通常将MSS设置为(MTU-40)字节(减掉的这40字节包括20字节的TCP头部和20字节的IP头部)。这样携带TCP报文段的IP数据报的长度就不会超过MTU(假设TCP头部和IP头部都不包含选项字段,并且这也是一般情况),从而避免本机发生IP分片。对以太网而言,MSS值是1460(1500-40)字节。 -
kind=3是窗口扩大因子选项。TCP连接初始化时,通信双方使用该选项来协商接收通告窗口的扩大因子。在TCP的头部中,接收通告窗口大小是用16位表示的,故最大为65535字节,但实际上TCP模块允许的接收通告窗口大小远不止这个数(为了提高TCP通信的吞吐量)。窗口扩大因子解决了这个问题。假设TCP头部中的接收通告窗口大小是N,窗口扩大因子(移位数)是M,那么TCP报文段的实际接收通告窗口大小是N乘2M,或者说N左移M位。注意,M的取值范围是0~14。我们可以通过修改/proc/sys/net/ipv4/tcp_window_scaling内核变量来启用或关闭窗口扩大因子选项。和MSS选项一样,窗口扩大因子选项只能出现在同步报文段中,否则将被忽略。但同步报文段本身不执行窗口扩大操作,即同步报文段头部的接收通告窗口大小就是该TCP报文段的实际接收通告窗口大小。当连接建立好之后,每个数据传输方向的窗口扩大因子就固定不变了。关于窗口扩大因子选项的细节,可参考标准文档RFC 1323。 -
kind=4是选择性确认(Selective Acknowledgment,SACK)选项。TCP通信时,如果某个TCP报文段丢失,则TCP模块会重传最后被确认的TCP报文段后续的所有报文段,这样原先已经正确传输的TCP报文段也可能重复发送,从而降低了TCP性能。SACK技术正是为改善这种情况而产生的,它使TCP模块只重新发送丢失的TCP报文段,不用发送所有未被确认的TCP报文段。选择性确认选项用在连接初始化时,表示是否支持SACK技术。我们可以通过修改/proc/sys/net/ipv4/tcp_sack内核变量来启用或关闭选择性确认选项。 -
kind=5是SACK实际工作的选项。该选项的参数告诉发送方本端已经收到并缓存的不连续的数据块,从而让发送端可以据此检查并重发丢失的数据块。每个块边沿(edge of block)参数包含一个4字节的序号。其中块左边沿表示不连续块的第一个数据的序号,而块右边沿则表示不连续块的最后一个数据的序号的下一个序号。这样一对参数(块左边沿和块右边沿)之间的数据是没有收到的。因为一个块信息占用8字节,所以TCP头部选项中实际上最多可以包含4个这样的不连续数据块(考虑选项类型和长度占用的2字节)。 -
kind=8是时间戳选项。该选项提供了较为准确的计算通信双方之间的回路时间(Round Trip Time,RTT)的方法,从而为TCP流量控制提供重要信息。我们可以通过修改/proc/sys/net/ipv4/tcp_timestamps内核变量来启用或关闭时间戳选项。
tcp 状态转换图
+---------+ ---------\ active OPEN
| CLOSED | \ -----------
+---------+<---------\ \ create TCB
| ^ \ \ snd SYN
passive OPEN | | CLOSE \ \
------------ | | ---------- \ \
create TCB | | delete TCB \ \
V | \ \
+---------+ CLOSE | \
| LISTEN | ---------- | |
+---------+ delete TCB | |
rcv SYN | | SEND | |
----------- | | ------- | V
+---------+ snd SYN,ACK / \ snd SYN +---------+
| |<----------------- ------------------>| |
| SYN | rcv SYN | SYN |
| RCVD |<-----------------------------------------------| SENT |
| | snd ACK | |
| |------------------ -------------------| |
+---------+ rcv ACK of SYN \ / rcv SYN,ACK +---------+
| -------------- | | -----------
| x | | snd ACK
| V V
| CLOSE +---------+
| ------- | ESTAB |
| snd FIN +---------+
| CLOSE | | rcv FIN
V ------- | | -------
+---------+ snd FIN / \ snd ACK +---------+
| FIN |<----------------- ------------------>| CLOSE |
| WAIT-1 |------------------ | WAIT |
+---------+ rcv FIN \ +---------+
| rcv ACK of FIN ------- | CLOSE |
| -------------- snd ACK | ------- |
V x V snd FIN V
+---------+ +---------+ +---------+
|FINWAIT-2| | CLOSING | | LAST-ACK|
+---------+ +---------+ +---------+
| rcv ACK of FIN | rcv ACK of FIN |
| rcv FIN -------------- | Timeout=2MSL -------------- |
| ------- x V ------------ x V
\ snd ACK +---------+delete TCB +---------+
------------------------>|TIME WAIT|------------------>| CLOSED |
+---------+ +---------+
TCP Connection State Diagram
Figure 6.
LISTEN:侦听来自远方的TCP端口的连接请求
SYN-SENT:再发送连接请求后等待匹配的连接请求(客户端)
SYN-RECEIVED:再收到和发送一个连接请求后等待对方对连接请求的确认(服务器)
ESTABLISHED:代表一个打开的连接
FIN-WAIT-1:等待远程TCP连接中断请求,或先前的连接中断请求的确认
FIN-WAIT-2:从远程TCP等待连接中断请求
CLOSE-WAIT:等待从本地用户发来的连接中断请求
CLOSING:等待远程TCP对连接中断的确认
LAST-ACK:等待原来的发向远程TCP的连接中断请求的确认
TIME-WAIT:等待足够的时间以确保远程TCP接收到连接中断请求的确认
CLOSED:没有任何连接状态
主动端可能出现的状态:FIN_WAIT1、FIN_WAIT2、CLOSING、TIME_WAIT?
被动端可能出现的状态:CLOSE_WAIT LAST_ACK
-
SYN_RCVD: 这个状态表示接收到了SYN报文,在正常情况下,这个状态是服务器端的SOCKET在建立TCP连接时的三次握手会话过程中的一个中间状态,很短暂,基本上用netstat你是很难看到这种状态的,除非你特意写了一个客户端测试程序,故意将三次TCP握手过程中最后一个ACK报文不予发送。因此这种状态时,当收到客户端的ACK报文后,它会进入到ESTABLISHED状态。如果收到一个RST信号,则返回到LISTEN状态 -
SYN_SENT: 这个状态与SYN_RCVD遥相呼应,当客户端SOCKET执行CONNECT连接时,它首先发送SYN报文,因此也随即它会进入到了SYN_SENT状态,并等待服务端的发送三次握手中的第2个报文。SYN_SENT状态表示客户端已发送SYN报文。 -
FIN_WAIT_1: 这个状态要好好解释一下,其实FIN_WAIT_1和FIN_WAIT_2状态的真正含义都是表示等待对方的FIN报文。而这两种状态的区别是:FIN_WAIT_1状态实际上是当SOCKET在ESTABLISHED状态时,它想主动关闭连接,向对方发送了FIN报文,此时该SOCKET即进入到FIN_WAIT_1状态。而当对方回应ACK报文后,则进入到FIN_WAIT_2状态,当然在实际的正常情况下,无论对方何种情况下,都应该马上回应ACK报文,所以FIN_WAIT_1状态一般是比较难见到的,而FIN_WAIT_2状态还有时常常可以用netstat看到。 -
FIN_WAIT_2:上面已经详细解释了这种状态,实际上FIN_WAIT_2状态下的SOCKET,表示半连接,也即有一方要求close连接,但另外还告诉对方,我暂时还有点数据需要传送给你,稍后再关闭连接。 -
TIME_WAIT: 表示收到了对方的FIN报文,并发送出了ACK报文,就等2MSL后即可回到CLOSED可用状态了。如果FIN_WAIT_1状态下,收到了对方同时带FIN标志和ACK标志的报文时,可以直接进入到TIME_WAIT状态,而无须经过FIN_WAIT_2状态。 -
CLOSING: 这种状态比较特殊,实际情况中应该是很少见,属于一种比较罕见的例外状态。正常情况下,当你发送FIN报文后,按理来说是应该先收到(或同时收到)对方的ACK报文,再收到对方的FIN报文。但是CLOSING状态表示你发送FIN报文后,并没有收到对方的ACK报文,反而却也收到了对方的FIN报文。什么情况下会出现此种情况呢?那就是如果双方几乎在同时close一个SOCKET的话,那么就出现了双方同时发送FIN报文的情况,也即会出现CLOSING状态,表示双方都正在关闭SOCKET连接。另外一种情况就是,ACK丢失了。 -
CLOSE_WAIT: 这种状态的含义其实是表示在等待关闭。怎么理解呢?当对方close一个SOCKET后发送FIN报文给自己,你系统毫无疑问地会回应一个ACK报文给对方,此时则进入到CLOSE_WAIT状态。接下来呢,实际上你真正需要考虑的事情是察看你是否还有数据发送给对方,如果没有的话,那么你也就可以close这个SOCKET,发送FIN报文给对方,也即关闭连接。所以你在CLOSE_WAIT状态下,需要完成的事情是等待你去关闭连接。 -
LAST_ACK: 这个状态还是比较容易好理解的,它是被动关闭一方在发送FIN报文后,最后等待对方的ACK报文。当收到ACK报文后,也即可以进入到CLOSED可用状态了
tcp 定时器
- 超时重传
- 坚持定时器:
- keepalive
- time_wait
流量控制和滑动窗口
滑动窗口协议是传输层进行流控的一种措施,接收方通过通告发送方自己的可以接受缓冲区大小(这个字段越大说明网络吞吐量越高),从而控制发送方的发送速度,不过如果接收端的缓冲区一旦面临数据溢出,窗口大小值也会随之被设置一个更小的值通知给发送端,从而控制数据发送量(发送端会根据接收端指示,进行流量控制)。
发送端:
- 已发送被确认
- 已发送未确认
- 允许发送未发送
- 暂不允许发送
接收端:
拥塞控制和拥塞窗口
发送方维持一个拥塞窗口 cwnd ( congestion window )的状态变量。拥塞窗口的大小取决于网络的拥塞程度,并且动态地在变化。发送方让自己的发送窗口等于拥塞。
发送方控制拥塞窗口的原则是:只要网络没有出现拥塞,拥塞窗口就再增大一些,以便把更多的分组发送出去。但只要网络出现拥塞,拥塞窗口就减小一些,以减少注入到网络中的分组数
- 慢开始( slow-start )
- 拥塞避免( congestion avoidance )
- 快重传( fast retransmit )
- 快恢复( fast recovery )
tcp 延时 ACK
参考:TCP/IP卷一:80—TCP数据流与窗口管理之(延时确认(延迟ACK)、Nagle算法
ACK延迟确认机制 接收方在收到数据后,并不会立即回复ACK,而是延迟一定时间。一般ACK延迟发送的时间低于500ms,但这个时间并非收到数据后需要延迟的时间。系统有一个固定的定时器会来检查是否需要发送ACK包。这样做有两个目的。
- 这样做的目的是ACK是可以合并的,也就是指如果连续收到两个TCP包,并不一定需要ACK两次,只要回复最终的ACK就可以了,可以降低网络流量。
- 如果接收方有数据要发送,那么就会在发送数据的TCP数据包里,带上ACK信息。这样做,可以避免大量的ACK以一个单独的TCP包发送,减少了网络流量。
不同操作系统对延迟确认的实现
- 采用延时ACK的方法会减少ACK传输数目,可以一定程度地减轻网络负载。对于批量数据传输通常为 2:1 的比例。基于不同的主机操作系统,延迟发送ACK的最大时延可以动态配置
- Linux使用了一种动态调节算法,可以在每个报文段返回一个ACK (称为“快速 确认”模式)与传统延时ACK模式间相互切换
- Mac OS X中,可以改变系统变量net.inet. tcp.delayed_ack值?来设置延时ACK。可选值如下:禁用延时(设为0),始终延时(设为1),每隔一个包回复一个ACK(设为2),自动检测确认时间(设为3)。默认值为3
- 最新的 Windows版本中,?注册表项中,每个接口的全局唯一标识(GUID)都不同(IG表示被引用的特定网络接口的GUID)。TcpAckFrequency值(需要被添加)可以设为0-255,默认为2。它?代表延时ACK计时器超时前在传的ACK数目?。将其设为1表明对每个收到的报文段都生成相应的ACK。ACK计时器值可以通过TcpDelAckTicks注册表项控制。该值可设为2 - 6,默认为2。它以百毫秒为单位,表明在发送延时ACK前要等待百毫秒数
//在c语言中可以通过设置socket来实现
int quickack = 1; /* 启用快速确认,如果赋值为0表示使用延迟确认 */
setsockopt(fd, SOL_TCP, TCP_QUICKACK, &quickack, sizeof(quickack));
Time-wait状态(2MSL)
1. 为什么需要TIME_WAIT状态
假设最后的ACK丢失,server将重发FIN,client必须维护TCP状态信息以便可以重发最后的ACK,否则将会发送RST,结果server认为发生错误。TCP实现必须可靠地终止连接的两个方向,所以client必须进入TIME_WAIT状态。
此外,考虑一种情况,TCP实现可能面临着先后两个相同的五元组。如果前一个连接处于TIME_WAIT状态,而允许另一个拥有相同五元组连接出现,可能处理TCP报文时,两个连接互相干扰。所以使用SO_REUSEADDR选项就需要考虑这种情况。
linux网络编程
相关概念
同步异步
同步和异步是针对应用程序和内核的交互而言的,同步指的是用户进程触发IO 操作并等待或者轮询的去查看IO 操作是否就绪,而异步是指用户进程触发IO 操作以后便开始做自己的事情,而当IO 操作已经完成的时候会得到IO 完成的通知。
阻塞非阻塞
阻塞和非阻塞是针对于进程在访问数据的时候,根据IO操作的就绪状态来采取的不同方式,说白了是一种读取或者写入操作方法的实现方式,阻塞方式下读取或者写入函数将一直等待,而非阻塞方式下,读取或者写入方法会立即返回一个状态值。
套接字地址结构
struct in_addr
字节顺序为网络顺序(network byte ordered),即该无符号整数采用大端字节序
//ipv4套接字地址结构,在<netinet/in.h>中声明
typedef uint32_t in_addr_t; //32位(unsigned int)的ip地址,
struct in_addr
{
in_addr_t s_addr;
};
struct sockaddr和struct sockaddr_in
struct sockaddr是通用的套接字地址,而struct sockaddr_in则是internet环境下套接字的地址形式, 二者长度一样,都是16个字节。二者是并列结构,指向sockaddr_in结构的指针也可以指向sockaddr。 一般情况下,需要把sockaddr_in结构强制转换成sockaddr结构再传入系统调用函数中
//sizeof(sockaddr_in)=16,定义在#include <netinet/in.h>
struct sockaddr_in
{
unsigned short int sin_family; //Address family 2
unsigned short int sin_port; // Port number 2
struct in_addr sin_addr; //Internet address 4
unsigned char sin_zero[8]; //未使用 8
};
//sizeof(sockaddr)=16,定义在#include <sys/socket.h>
struct sockaddr
{
sa_family_t sa_family; //sa_family_t为unsigned short int,地址家族, AF_INET 2
char sa_data[14]; // 14 bytes of protocol address
};
字节排序函数
首先解释一下字节序的概念,所谓字节序是指多字节数据的存储顺序,比如0x1234要放在0000H和0001H两存储单元,有两种存储方式:大端格式为[0000H]=12,[0001H]=34和小端格式为[0000H]=34,[0001H]=12。
-
大端格式:将高位字节数据存储在低地址,低位字节数据存储在高地址 -
小端格式:将高位字节数据存储在高地址,低位字节数据存储在低地址
#include <stdio.h>
int main(int argc, char *argv[])
{
union{
short temp;
char test[sizeof(short)];
}un_tmp;
un_tmp.temp = 0x1234;
if ((un_tmp.test[0] == 0x12) && (un_tmp.test[1] == 0x34))
{
printf("大端格式:高位字节数据存储在低地址,低位字节数据存储在高地址");
}
if ((un_tmp.test[0] == 0x34) && (un_tmp.test[1] == 0x12))
{
printf("小端格式:高位字节数据存储在高地址,低位字节数据存储在低地址");
}
return 0;
}
网际协议采取的是大端字节序,我们在编程的时候才需要考虑网络字节许和主机字节序之间的转换。下面是四个转换函数
#include <netinet/in.h>
uint16_t htons(uint16_t host16bitvalue);
uint32_t htonl(uint32_t host32bitvalue); //均返回网络字节序
uint16_t ntohs(uint16_t net16bitvalue);
uint32_t ntohl(uint32_t net32bitvalue); //均返回主机字节序
地址转换函数:
BSD网络软件中包含了inet_addr、inet_aton和inet_ntoa,用来在二进制地址格式和点分十进制字符串格式之间相互转换,但是这三个函数仅仅支持IPv4。(废弃,不建议使用)
1. in_addr_t inet_addr(const char *cp)函数转换标准的ASCII以点分十进制的地址值返回为网络字节序二进制值
如果参数 char *cp 无效则返回-1(INADDR_NONE),但这个函数有个缺点:在处理地址为255.255.255.255时也返回-1,虽然它是一个有效地址,但inet_addr()无法处理这个地址。
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp); //in_addr_t-->uint32_t
输入是点分的IP地址格式(如A.B.C.D)的字符串,从该字符串中提取出每一部分,转换为ULONG,假设得到4个ULONG型的A,B,C,D, ulAddress(ULONG型)是转换后的结果, ulAddress = D<<24 + C<<16 + B<<8 + A(网络字节序),即inet_addr(const char *)的返回结果 另外,我们也可以得到把该IP转换为主机序的结果,转换方法一样 A<<24 + B<<16 + C<<8 + D
2. int inet_aton(const char *__cp, in_addr *__inp)转换标准的ASCII以点分十进制的地址值返回网络字节序二进制值
如果这个函数成功,函数的返回值非零。如果输入地址不正确则会返回零。使用这个函数并没有错误码存放在errno中,所以他的值会被忽略。
#include <arpa/inet.h>
/**
* @brief inet_aton
* @param __cp 输入参数包含ASCII表示的IP地址
* @param __inp 输出参数将要用新的IP地址更新的结构
* @return
*/
extern int inet_aton (const char *__cp, struct in_addr *__inp);
3. char *inet_ntoa (struct in_addr __in)函数转换网络字节序二进制值返回标准的ASCII以点分十进制的地址值
该函数返回值指向保存点分十进制的字符串地址的指针,该字符串的空间为静态分配 的,所以在第二次调用这个函数时,意味着上一次调用并保存的结果将会被覆盖(重写)
#include <arpa/inet.h>
extern char *inet_ntoa (struct in_addr __in);
4. 代码示例:
#include <stdio.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
int main(int argc, char *argv[])
{
char ip1[] = "192.168.0.74";
char ip2[] = "211.100.21.179";
struct in_addr addr1, addr2;
long l1, l2;
l1 = inet_addr(ip1); //IP字符串——》网络字节
l2 = inet_addr(ip2);
printf("IP1: %s\nIP2: %s\n", ip1, ip2);
printf("Addr1: %ld\nAddr2: %ld\n", l1, l2);
memcpy(&addr1, &l1, 4); //复制4个字节大小
memcpy(&addr2, &l2, 4);
printf("%s <--> %s\n", inet_ntoa(addr1), inet_ntoa(addr2)); //注意:printf函数自右向左求值、覆盖
printf("%s\n", inet_ntoa(addr1)); //网络字节 ——》IP字符串
printf("%s\n", inet_ntoa(addr2));
return 0;
}
IP1: 192.168.0.74
IP2: 211.100.21.179
Addr1: 1241557184
Addr2: 3004523731
192.168.0.74 <--> 192.168.0.74
192.168.0.74
211.100.21.179
功能相似的两个函数同时支持IPv4和IPv6,p代表presentation表达,n代表numeric数值
1. int inet_pton(int domain, const char *str, void *addr)将标准的ASCII以点分十进制的地址值转化为网络传输的二进制数值格式
返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
#include <arpa/inet.h>
int inet_pton(int family, const char *strptr, void *addrptr)
{
//这两个函数的family参数既可以是AF_INET(ipv4)也可以是AF_INET6(ipv6)。
//如果,以不被支持的地址族作为family参数,这两个函数都返回一个错误,并将errno置为EAFNOSUPPORT.
if (family == AF_INET) {
struct in_addr in_val;
if (inet_aton(strptr, &in_val)) {
memcpy(addrptr, &in_val, sizeof(in_val));
return (1);
}
}
errno = EAFNOSUPPOPT;
return (-1);
}
2. const char *inet_ntop(int domain, const void *addr, char *str, socklen_t size)将网络传输的二进制数值转化标准的ASCII以点分十进制的地址值格式
inet_ntop函数的strptr参数不可以是一个空指针。调用者必须为目标存储单元分配内存并指定其大小,返回值:若成功则为指向结构的指针,若出错则为NULL
#include <arpa/inet.h>
const char *inet_ntop(int family, const void *addrptr, char *strptr, size_t len)
{
const u_char *p = (const u_char*)addrptr;
if (family == AF_INET) {
char temp[INET_ADDRSTRLEN];
snprintf(temp, sizeof(temp), "%d.%d.%d.%d", p[0], p[1], p[2], p[3]);
if (strlen(temp) >= len) {
errno = ENOSPC;
rturn (NULL);
}
strcpy(strptr, temp);
return (strptr);
}
errno = EAFNOSUPPOPT;
return (NULL);
}
3. 代码示例:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int main()
{
char ip[] = "192.168.0.74";
struct in_addr addr;
int ret = inet_pton(AF_INET, ip, (void *)&addr); //IP字符串 ——》网络字节流
if(0 == ret){
printf("inet_pton error, return 0\n");
return -1;
}else{
printf("inet_pton ip: %ld\n", addr.s_addr);
printf("inet_pton ip: 0x%x\n", addr.s_addr);
}
const char *pstr = inet_ntop(AF_INET, (void *)&addr, ip, 128); //网络字节流 ——》IP字符串
if(NULL == pstr){
printf("inet_ntop error, return NULL\n");
return -1;
}else{
printf("inet_ntop ip: %s\n", ip);
}
return 0;
}
inet_pton ip: 1241557184
inet_pton ip: 0x4a00a8c0
inet_ntop ip: 192.168.0.74
TCP通信相关函数
1. int socket(family, type, protocol):创建套接字
/**
* #include <sys/types.h>
* #include <sys/socket.h>
* @brief Socket 创建一个套接字用于通信
* @param family
* AF_INET IPv4地址协议
AF_INET6 IPv6地址协议
AF_LOCAL UNIX域协议
AF_ROUTE 路由套接字
AF_KEY 密钥套接字
* @param type 指定socket类型,
SOCK_STREAM 流式套接字
SOCKDGRAM 数据报套接字
SOCK_SEQPACKET 有序分组套接字
SOCKRAW 原始套接字,提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
* @param protocol 协议类型 If PROTOCOL 为0,内核将会自动进行选择,可以默认填0
IPPROTO_TCP TCP传输协议
IPPROTO_UDP UDP传输协议
IPPROTO_SCTP SCTP传输协议
* @return 成功返回非负整数套接字描述符;失败返回-1
*/
int socket(int family,int type,int protocol);
2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen):绑定套接字
#include <sys/types.h>
#include <sys/socket.h>
/**
* @brief bind
* @param fd 绑定套接子
* @param addr 要绑定的地址
* @param addrlen 地址长度
* @return 成功返回 0 失败返回 -1
*/
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
3. int listen(int fd,int backlog):设置同时通信的最大套接字数量
#include <sys/types.h>
#include <sys/sock.h>
/**
* @brief listen
* (1)一般来说,listen函数应该在调用socket和bind函数之后,调用accept函数之前调用
* (2)对于给定的监听套接字接口,内核要维护两个队列
* <1>已由客户发送并到达服务器,服务器正在等待完成对应的TCP三次握手过程
* <2>已经完成连接的队列
* @param fd socket函数返回的套接字
* @param backlog 规定内核为此套接字排队的最大的连接个数
* @return 成功返回 0 失败返回 -1
*/
int listen(int fd,int backlog);
4. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen)阻塞式监听客户端连接
#include <sys/types.h>
#include <sys/scoket.h>
/**
* @brief accept 从已经完成连接队列返回第一个连接,如果已经完成连接队列为空,则阻
* @param sockfd 服务器套接字
* @param addr 将返回对等待的套接字地址
* @param addrlen 返回对等方的套接字地址长度
* @return 成功返回非负整数:对应和客户点连接的新套接字 ,失败返回-1
*/
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
5. int connect(int sockfd, const strcut sockaddr *addr, socklen_t addrlen)用来客户端和tcp服务器建立连接
#include <sys/types.h>
#include <sys/socket.h>
/**
* @brief connect 用于建立与指定socket的连接
* @param sockfd 标识一个未连接的socket
* @param addr 指定要连接套接字的sockaddr结构体的指针
* @param addrlen sockaddr结构体的字节长度
* @return 0 on success, -1 for errors
*/
int connect(int sockfd, const strcut sockaddr *addr, socklen_t addrlen);
5. ssize_t read(int fd,void *ptr,size_t nbytes)一次读取指定字节长度数据
#include <unistd.h>
/**
* @brief read
* @param fd 将要读取数据的文件描述符
* @param ptr 所读取到的数据的内存缓冲
* @param nbytes 需要读取的数据量
* @return 成功执行时,返回所读取的数据量;
* 如果返回0, 表示已到达文件尾或是无可读取的数据
* 失败返回-1,errno被设为以下的某个值
EAGAIN:打开文件时设定了O_NONBLOCK标志,并且当前没有数据可读取
EBADF:文件描述词无效,或者文件不可读
EFAULT:参数buf指向的空间不可访问
EINTR:数据读取前,操作被信号中断
EINVAL:一个或者多个参数无效
EIO:读写出错
EISDIR:参数fd索引的时目录
*/
ssize_t read(int fd,void *ptr,size_t nbytes);
6. ssize_t write(int fd,const void *ptr,size_t nbytes)一次写入指定字节长度数据
/**
* @brief write
* @param fd 将要写入数据的文件描述符
* @param ptr 所写入到的数据的内存缓冲
* @param nbytes 需要写入的数据量
* @return 成功执行时,返回所写入的数据量。失败返回-1,错误代码存入errno中
*/
ssize_t write(int fd,const void *ptr,size_t nbytes);
7. ssize_t Readn(int fd,void *vptr,size_t n);循环读取n个字节数据
/**
* @brief Readn 从描述符fd中读取n个字节,存入vptr指针的位置
1. 当剩余长度大于0的时候就一直读啊读
2. 当read的返回值小于0的时候,做异常检测
3. 当read的返回值等于0的时候,退出循环
4. 当read的返回值大于0的时候,拿剩余长度减read的返回值,拿到新的剩余长度,读的入口指针加上read的返回值,进入步1
5. 返回参数n减去剩余长度,即实际读取的总长度
* @param fd
* @param vptr
* @param n
* @return
*/
/* Read "n" bytes from a descriptor. */
ssize_t Readn(int fd, void *vptr, size_t n)
{
size_t nleft;
ssize_t nread;
char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0)
{
if ( (nread = read(fd, ptr, nleft)) < 0)
{
if (errno == EINTR)
nread = 0; /* and call read() again */
else
return(-1);
} else if (nread == 0)
break; /* EOF */
nleft -= nread;
ptr += nread;
}
return(n - nleft); /* return >= 0 */
}
/* end readn */
8. ssize_t Writen(int fd,const void*vptr,size_t n)循环写入n个字节数据
/**
* @brief Writen 向描述符fd中写入n个字节,从vptr位置开始写
1. 当要写入的剩余长度大于0的时候就一直写啊写
2. 当write的返回值小于0的时候,做异常检测
3. 当write的返回值等于0的时候,出错退出程序
4. 当write的返回值大于0的时候,拿剩余长度减去write的返回值,拿到新的剩余长度,写的入口指针加上write的返回值,进入步骤1
5. 返回参数n的值,即期望写入的总长度
* @param fd
* @param vptr
* @param n
* @return
*/
/* Write "n" bytes to a descriptor. */
ssize_t Writen(int fd, const void *vptr, size_t n)
{
size_t nleft;
ssize_t nwritten;
const char *ptr;
ptr = vptr;
nleft = n;
while (nleft > 0)
{
if ( (nwritten = write(fd, ptr, nleft)) <= 0)
{
if (nwritten < 0 && errno == EINTR)
nwritten = 0; /* and call write() again */
else
return(-1); /* error */
}
nleft -= nwritten;
ptr += nwritten;
}
return(n);
}
/* end writen */
9. ssize_t Readline(int fd, void *vptr, size_t maxlen) 读到’\n’或者读满缓冲区才返回
static ssize_t readch(int fd, char *ptr)
{
static int read_cnt;
static char *read_ptr;
static char read_buf[100];
if(read_cnt <= 0)
{
again:
if((read_cnt = read(fd, read_buf, sizeof(read_buf))) < 0)
{
if(errno == EINTR)
{
goto again;
}
else
{
return -1;
}
}
else if(read_cnt == 0)
{
return 0;
}
read_ptr = read_buf;
}
read_cnt--;
*ptr = *read_ptr++;
return 1;
}
ssize_t Readline(int fd, void *vptr, size_t maxlen)
{
ssize_t n, rc;
char c, *ptr;
ptr = vptr;
for(n = 1; n < maxlen; n++)
{
if((rc = readch(fd, &c)) == 1)
{
*ptr++ = c;
if(c == '\n')
{
break;
}
}
else if(rc == 0)
{
*ptr = 0;
return n - 1;
}
else
{
return (n - 1);
}
}
*ptr = 0;
return n;
}
10. int close(int fd) 关闭套接字
close函数会关闭套接字ID,如果有其他的进程共享着这个套接字,那么它仍然是打开的,这个连接仍然可以用来读和写,并且有时候这是非常重要的 ,特别是对于多进程并发服务器来说
//一般不会立即关闭而经历TIME_WAIT的过程
#include<unistd.h>
int close(int sockfd); //返回成功为0,出错为-1
11. shutdown()函数切断进程共享的套接字的所有连接
#include<sys/socket.h>
/**
* @brief shutdown shutdown会切断进程共享的套接字的所有连接,不管这个套接字的引用计数是否为零,
* 那些试图读得进程将会接收到EOF标识,那些试图写的进程将会检测到SIGPIPE信号,
* 同时可利用shutdown的第二个参数选择断连的方式
* @param sockfd 文件描述符
* @param howto
1.SHUT_RD:值为0,关闭连接的读这一半。
2.SHUT_WR:值为1,关闭连接的写这一半。
3.SHUT_RDWR:值为2,连接的读和写都关闭。
* @return 成功为0,出错为-1.
*/
int shutdown(int sockfd,int howto);
12. recv()和send()函数
int recv(int sockfd,void *buf,int len,int flags);
int send(int sockfd,void *buf,int len,int flags);
flags | 含义 |
---|
0 | 相当于read和write函数 | MSG_DONTROUTE | 不查找表 | MSG_OOB | 接受或者发送带外数据 | MSG_PEEK | 查看数据,并不从系统缓冲区移走数据 | MSG_WAITALL | 等待所有数据 |
-
MSG_DONTROUTE:是send函数使用的标志。这个标志告诉IP,目的主机在本地网络上面,没有必要查找表。这个标志一般用网络诊断和路由程序里面。 -
MSG_OOB:表示可以接收和发送带外的数据。关于带外数据我们以后会解释的。 -
MSG_PEEK:是recv函数的使用标志。表示只是从系统缓冲区中读取内容,而不清除系统缓冲区的内容,这样下次读的时候仍然是一样的内容。一般在有多个进程读写数据时可以使用这个标志。 -
MSG_WAITALL:是recv函数的使用标志。表示等到所有的信息到达时才返回。使用这个标志的时候recv会一直阻塞,直到指定的条件满足或者是发生了错误。
1)当读到了指定的字节时,函数正常返回。返回值等于len 2)当读到了文件的结尾时,函数正常返回。返回值小于len 3)当操作发生错误时返回-1,且设置错误为相应的错误号(errno)
UDP通信相关函数
1. recvfrom()函数
/**
* @brief recvfrom
* @param sockfd 套接字
* @param buf UDP数据报缓存区(包含所接收的数据
* @param nbytes 缓冲区长度
* @param flags 调用操作方式(一般设置为0)
* @param from 指向发送数据的客户端地址信息的结构体(sockaddr_in需类型转换)
* @param fromlen 指针,指向from结构体长度值
* @return 成功则返回实际接收到的字符数,失败返回-1,错误原因会存于errno 中
*/
int recvfrom(int sockfd, const void *buf, size_t nbytes,int flags,
struct sockaddr *from, int *fromlen);
2. sendto()函数
sendto函数专用与UDP连接
/**
* @brief sendto
* @param sockfd 套接字
* @param buf 带发送数据存储缓冲区
* @param nbytes 要发送数据的字节数
* @param flags 可选标志
* @param destaddr (目标地址)数据接收方
* @param destlen 目标地址结构长度
* @return
*/
ssize_t sendto(int sockfd,const void * buf,size_t nbytes,int flags,
const struct sockaddr_in * destaddr,socklen_t destlen );
UDP组播通信相关函数
组播组可以是永久的也可以是临时的。组播组地址中,有一部分由官方分配的,称为永久组播组。永久组播组保持不变的是它的ip地址,组中的成员构成可以发生变化。永久组播组中成员的数量都可以是任意的,甚至可以为零。那些没有保留下来供永久组播组使用的ip组播地址,可以被临时组播组利用。
- 224.0.0.0~224.0.0.255为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
- 224.0.1.0~224.0.1.255是公用组播地址,可以用于Internet;
- 224.0.2.0~238.255.255.255为用户可用的组播地址(临时组地址),全网范围内有效;
- 239.0.0.0~239.255.255.255为本地管理组播地址,仅在特定的本地范围内有效。
getsockopt()/setsockopt()的选项 | 含义 |
---|
IP_MULTICAST_TTL | 设置多播组数据的TTL值 | IP_ADD_MEMBERSHIP | 在指定接口上加入组播组 | IP_DROP_MEMBERSHIP | 退出组播组 | IP_MULTICAST_IF | 获取默认接口或设置接口 | IP_MULTICAST_LOOP | 禁止组播数据回送 |
// IPv4 multicast request.
struct ip_mreq
{
// 多播组的IP地址 IP multicast address of group.
struct in_addr imr_multiaddr;
//加入的客户端主机IP地址 Local IP address of interface.
struct in_addr imr_interface;
};
//加入组播组
ip_mreq multiCast;
multiCast.imr_interface.s_addr=htonl(INADDR_ANY); //本地某一网络设备接口的IP地址。
multiCast.imr_multiaddr.s_addr=inet_addr("234.2.2.2"); //组播组的IP地址。
setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(char*)&multiCast,sizeof(multiCast));
常见的错误码
- 网络通信中 TCP 产生 RST 的三个条件分析
参考文献
- LInux Tcp 延迟确认问题
|