UNIX网络套接字相关总结
文章目录
- 网络协议
- RFC 相关文档
- 网络 ip 层
- ip 头部
- 传输层
- tcp
- 相关概念
- MSL是Maximum Segment Lifetime,“报文最大生存时间”
- MSS:Maximum Segment Size
- RTT:Round Trip Time
- RTO:Retransmission TimeOut
- /proc/sys/net/ipv4/
- tcp 头部
- tcp 状态转换图
- tcp 定时器
- 流量控制和滑动窗口
- 拥塞控制和拥塞窗口
- tcp 延时 ACK
- Time-wait状态(2MSL)
- 1. 为什么需要TIME_WAIT状态
- linux网络编程
- 相关概念
- 套接字地址结构
- struct in_addr
- struct sockaddr和struct sockaddr_in
- 字节排序函数
- 地址转换函数:
- 1. in_addr_t inet_addr(const char *cp)函数转换标准的ASCII以点分十进制的地址值返回为网络字节序二进制值
- 2. int inet_aton(const char *__cp, in_addr *__inp)转换标准的ASCII以点分十进制的地址值返回网络字节序二进制值
- 3. char *inet_ntoa (struct in_addr __in)函数转换网络字节序二进制值返回标准的ASCII以点分十进制的地址值
- 4. 代码示例:
- 1. int inet_pton(int domain, const char *str, void *addr)将标准的ASCII以点分十进制的地址值转化为网络传输的二进制数值格式
- 2. const char *inet_ntop(int domain, const void *addr, char *str, socklen_t size)将网络传输的二进制数值转化标准的ASCII以点分十进制的地址值格式
- 3. 代码示例:
- 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通信相关函数
- 1. recvfrom()函数
- 2. sendto()函数
- 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 Format0 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 DefinitionsEnd 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 DiagramFigure 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 2unsigned short int sin_port; // Port number 2struct in_addr sin_addr; //Internet address 4unsigned 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 2char 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,内核将会自动进行选择,可以默认填0IPPROTO_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的返回值,进入步15. 返回参数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 */elsereturn(-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的返回值,进入步骤15. 返回参数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 */elsereturn(-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 howto1.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 延迟确认问题
UNIX网络套接字相关总结相关推荐
- 【Android 应用开发】Android 网络编程 API笔记 - java.net 包 权限 地址 套接字 相关类 简介
Android 网络编程相关的包 : 9 包, 20 接口, 103 类, 6 枚举, 14异常; -- Java包 : java.net 包 (6接口, 34类, 2枚举, 12异常); -- An ...
- Linux网络编程——Unix本地套接字
概述 今天给大家讲解网络编程中的一个内容--Unix 本地套接字. 发现很多人不知道或者不太了解 Unix 本地套接字这个概念,这也难怪,socket API 原本就是为多台主机之间网络通信设计的,并 ...
- linux篇【12】:网络套接字<前序>—网络基础+udp套接字
目录 一.网络基础 1.认识 "协议" 举例: 2.协议分层 (1)软件分层 (2)协议分层 3.OSI七层模型 4.TCP/IP五层(或四层)模型 5.网络和操作系统之间的关系 ...
- 网络套接字编程之IO模型详解
网络套接字编程之IO模型详解 本文主要参考自<UNIX网络编程>(第1卷)(套接口API第3版) Unix下可用的五种I/O模型有: 阻塞式I/O 非阻塞式I/O I/O复用(select ...
- 【Linux】网络基础+UDP网络套接字编程
只做自己喜欢做的事情,不被社会和时代裹挟着前进,是一件很奢侈的事. 文章目录 一. 网络基础 1.局域网和广域网 2.协议初识和网络协议分层(TCP/IP四层模型) 3.MAC地址和IP地址(子网掩码 ...
- 什么是网络套接字(Socket)?
什么是网络套接字(Socket)?一时还真不好回答,而且网络上也有各种解释,莫衷一是.下文将以本人所查阅到的资料来说明一下什么是Socket. Socket定义 Socket在维基百科的定义: A n ...
- 网络套接字(Network socket)
网络套接字(英语:Network socket:又译网络套接字.网络接口.网络插槽)在计算机科学中是电脑网络中进程间资料流的端点.使用以网际协议(Internet Protocol)为通信基础的网络套 ...
- 【Linux】网络套接字编程
前言 在掌握一定的网络基础,我们便可以先从代码入手,利用UDP协议/TCP协议进行编写套接字程序,明白网络中服务器端与客户端之间如何进行连接并且通信的. 目录 一.了解源目的IP.端口.网络字节序.套 ...
- Linux套接字编程之sockaddr与sockaddr_in网络套接字,sockaddr_un进程间通信本地套接字
sockaddr struct sockaddr { unsigned short sa_family; /* address family, AF_xxx */ char sa_dat ...
最新文章
- 姚班代有才人出:清华本科生用“最简单的形式”,大幅提高少样本学习性能...
- Kafka使用遇到的坑
- 【天池赛事】零基础入门语义分割-地表建筑物识别 Task1:赛题理解与 baseline
- 《LoadRunner 12七天速成宝典》—第2章2.1节开始
- WiresShark之抓http包如何倒出图片格式的对象
- Windows 8 JavaScript Metro应用程序--入门(上)
- 【项目管理】CMM能力成熟度模型
- iphonex如何关机_历时一个月,跨越一千里,我找回了在澳门被偷的iphoneX
- 11dayC语言指针-指针变量
- 互联网搜索的哪些环节 机器学习_什么是机器学习?有哪些分类?到底有什么用?终于有人讲明白了...
- .net中的设计模式---单例模式
- 服务器本地打开asp文件路径,服务器本地打开asp文件
- 用python分析拼多多_python:拼多多订单接口api
- 计算机学院实验室安全管理办法,计算机科学学院实验室安全管理制度
- kettle使用命令行来运行ktr和kjb
- 案例教程:一步步教你ps制作二寸照片
- view-ui中select全选实现
- 视频教程-区块链基础:共识算法-区块链
- Qt编写地图综合应用49-地图类型(街道图、卫星图)
- 阿里、京东、乐语们纷纷下注商超,到底为什么?
热门文章
- Java 8中Collectors.groupingBy方法空指针异常源码分析
- 跟班学习JavaScript第一天——运算符、数据类型、ECMAScript
- rtos中的喂狗思路——freertos
- verilog always语法_Verilog 最全经验总结(建议收藏)
- Groovy on Grails(Java笨狗)系列---前言(二)
- 动图制作工具---LICEcap
- 详解生物地理学优化(BBO)算法(一)
- 今天一天学习的MYSQL语句,立此存照
- python 图像清晰度_图像清晰度评价指标(Python)
- jws webservice 跳过https认证_【大连学为贵5周年庆典】多邻国考试不能认证是怎么回事?这些雷区不要踩!...