socket网络编程

1、TCP

  TCP 提供一种面向连接的、可靠的字节流服务;在一个 TCP 连接中,仅有两方进行彼此通信。广播和多播不能用于 TCP;TCP 使用校验和,确认和重传机制来保证可靠传输;TCP 给数据分节进行排序,并使用累积确认保证数据的顺序不变和非重复;TCP 使用滑动窗口机制来实现流量控制,通过动态改变窗口的大小进行拥塞控制

  1、确认位ACK :只有ACK=1时有效,连接建立后所有发送的报文的ACK必须为1。

  2、同步位SYN:SYN=1表示这是一个连接请求(ACK=0,无效不写)或连接接收(ACK=1)报文(携带MSS:告知对端本端在此次连接中每个TCP分节中愿意接收的最大数据量:窗口规模选项:可用最大窗口大小是65535,套接字接收缓冲区中可用空间大小限制此值;时间戳选项:告诉网络32位序号很快会循环,此值防止失而复得分组与下一轮同序号分节混淆)。

  3、终止位FIN:FIN=1表明此报文段的发送发数据已发送完毕,并要求释放传输连接。

  4、序号:TCP面向字节流,传送数据流中每个字节都编上序号,值为本报文段所发送数据的第一个字节的序号,每次建立连接都随机生成,而后在上一个+1。

1.1 三次握手建立连接

  建立一个 TCP 连接时,需要客户端和服务器总共发送3个包。

  1、客户机TCP向服务器TCP发送连接请求报文段, SYN=1,seq=x(随机选择)(不含应用层数据,但消耗序号);

  2、服务器TCP收到连接请求报文段后,若同意建立连接,向客户机发送确认,并为该TCP连接分配TCP缓存和变量,SYN=1,ACK=1,ack=x+1,seq=y(随机)(不含数据,但消耗序号,此时服务器是半连接)(把响应客户端的请求ACK和请求客户端的确认SYN放在一起发送给客户端了);

  3、当客户机收到确认报文段后,向服务器给出确认,并为连接分配缓存和变量。ACK=1,ack=y+1,seq=x+1(可携带数据,若不携带,则不消耗序号seq)(若该ACK丢失,TCP不会为没有数据的ACK超时重传,而服务器端会重传第二步的SYN+ACK包)。

1.1.1 对三次握手的SYN洪泛攻击

  服务器资源在第二次握手分配,易受到SYN洪泛攻击:攻击客户端在短时间内伪造大量不存在的IP地址,向服务器不断地发送SYN包,服务器回复确认包,并等待客户的确认。由于源地址是不存在的,服务器需要不断的重发直至超时,这些伪造的SYN包将长时间占用未连接队列,正常的SYN请求被丢弃,导致目标系统运行缓慢,严重者会引起网络堵塞甚至系统瘫痪。

  检测:大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击。在 Linux/Unix 上可以使用系统自带的 netstats 命令来检测 SYN 攻击。

  防御:缩短超时(SYN Timeout)时间;增加最大半连接数。

1.1.2 为什么不是两次握手

  防止已失效的连接请求报文段突然又传送到了服务端,因而产生错误。client发出的第一个连接请求报文段并没有丢失,而是在某个网络结点长时间的滞留了,以致延误到连接释放以后的某个时间才到达server。本来这是一个早已失效的报文段。但server收到此失效的连接请求报文段后,就误认为是client再次发出的一个新的连接请求。于是就向client发出确认报文段,同意建立连接。假设不采用“三次握手”,那么只要server发出确认,新的连接就建立了。由于现在client并没有发出建立连接的请求,因此不会理睬server的确认,也不会向server发送数据。但server却以为新的运输连接已经建立,并一直等待client发来数据。这样,server的很多资源就白白浪费掉了。采用“三次握手”的办法可以防止上述现象发生。例如刚才那种情况,client不会向server的确认发出确认。server由于收不到确认,就知道client并没有要求建立连接。

1.1.3 如果客户端发送的最后ACK包丢失会怎样

  服务器收到SYN包后发出SYN+ACK数据包,服务器进入SYN_RECV状态。而这个时候客户端发送ACK给服务器失败了,服务器没办法进入ESTABLISH状态,肯定不能传输数据的,不论客户端主动发送数据与否,服务器都会有定时器发送第二步SYN+ACK数据包,如果客户端再次发送ACK成功,建立连接。如果一直不成功,服务器有超时设置,超时之后会给客户端发RST报文,进入CLOSED状态,防止SYN洪泛攻击,这个时候客户端应该也会关闭连接。

1.1.4 为什么不是四次握手

  (不能确保服务器受到了客户机最后发送的确认)因为完全可靠的通信协议是根本不存在的,而三次握手后,至少可以确认之前的通信情况,但无法确认之后的情况。 在这个道理上说,无论是四次还是五次或是更多次都是徒劳的。

1.2 四次握手释放连接

  全双工通信发送FIN的一端不能再发送数据,关闭了一条数据通路,还有一条,所以需要4个包。

  1、客户机发送连接释放报文段,并停止发送数据,主动关闭TCP连接。FIN=1,seq=u(前面发送数据的最后一个字节序号+1)(不携带数据,但消耗一个序号);

  2、服务器收到连接释放报文段后发出确认,ACK=1,ack=u+1,seq=v(前面发送数据的最后一个字节序号+1)。客户机到服务器方向的连接释放,TCP连接处于半关闭状态。服务器发送数据,客户机仍要接收,因该方向没关闭;

  3、若服务器没有要向客户机发送的数据,就通知TCP释放连接,发送FIN=1,ACK=1,ack=u+1,seq=w;

  4、客户机收到连接释放报文段后,必须发出确认,ACK=1,seq=u+1,ack=w+1。此时TCP连接还没有释放,而是进入TIME_WAIT状态,需要经过时间等待计时器设置的时间2MSL后,客户机才进入连接关闭状态

  注意:在服务器发送了FIN-ACK之后,会立即启动超时重传计时器。客户端在发送最后一个ACK之后会立即启动时间等待计时器(本该是服务器的超时重传+FIN的传输时间,为保证可靠,设为2MSL(这是TCP对报文段生存时间的限制=任何一个IP数据报能存活的最大时间))

1.2.1 客户端等待2MSL作用

  可靠地实现TCP全双工连接终止:为了保证客户端发送的最后一个ACK报文段能够到达服务器。因为这个ACK有可能丢失,从而导致处在LAST-ACK状态的服务器收不到对FIN-ACK的确认报文。服务器会超时重传这个FIN-ACK,接着客户端再重传一次确认,重新启动时间等待计时器。最后客户端和服务器都能正常的关闭。假设客户端不等待2MSL,而是在发送完ACK之后直接释放关闭,一但这个ACK丢失的话,服务器就无法正常的进入关闭连接状态。

  允许老的重复分节在网络中消失:因为如果客户端不发送最后一次的确认,而是直接关闭,当有一个新的连接继续使用了这个端口,而属于上一次连接的某些数据滞留在网络中又刚好到达,就会导致TCP以为误以为这是属于新一次连接的数据。因此需要等待2MSL,确认服务器的关闭,才能确保本次连接的所有数据都从网络中消失。 

2、SOCKET网络编程、与三次握手关系

  流程:当客户端调用connect时,触发了连接请求,向服务器发送了SYN J包,这时connect进入阻塞状态;服务器监听到连接请求,即收到SYN J包,调用accept函数接收请求向客户端发送SYN K ,ACK J+1,这时accept进入阻塞状态;客户端收到服务器的SYN K ,ACK J+1之后,这时connect返回,并对SYN K进行确认;服务器收到ACK K+1时,accept返回,至此三次握手完毕,连接建立。

2.1 函数

  socket新建监听套接字。

  bind将一个本地协议地址赋予一个套接字。

  listenTCP服务器调用,当socket创建套接字时默认是主动套接字,即会通过connect发起连接的客户套接字,通过listen将未连接的套接字转换为被动套接字,backlog参数还规定了套接字排队的最大连接数(内核为每个socket维护两个队列,队列和不能超过backlog参数:未完成连接队列处于SYN_RCVD状态(未完成三路握手,刚接到客户端connect发过来,在未完成队列中建立条目),已完成连接队列处于ESTABLISTED状态(已完成三路握手,该条目从未完成队列中转移至此,服务器accept返回已连接套接字))。注意:未完成连接队列中任何一项在其中的存留时间是一个RTT(往返时间)。当一个客户SYN到达时,队列是满的,则忽略该分节,而不是发送RST(复位位,重新建立连接),因为如果发送RST,则客户端的connect就会返回错误,需要其处理,而应当让TCP的正确重传机制处理。

  acceptTCP服务器调用,从已完成队列返回下一个已完成连接,若为空,则睡眠。返回一个已连接套接字代表与所返回客户的TCP连接【项目中用于监听的父进程在fork完子进程后会关闭已连接套接字(防止耗尽可用描述符,并且不和客户端通信),子进程中会关闭监听套接字】。

  close关闭监听/已连接套接字,将该套接字标记为关闭并立即返回到调用进程(其实是减少该已连接套接字的引用计数,只有计数为0才会发送FIN开始终止连接,强制发送FIN可以用shutdown),进程不可再使用该套接字,而TCP将尝试发送已排队等待发送到对端的数据,发送完毕后就是正常TCP四次握手终止连接。

  connectTCP客户端操作,套接字连接操作,connect操作之后代表对应的套接字已连接。

  recv(send类似)返回其实际copy的字节数。如果recv在copy时出错,那么它返回SOCKET_ERROR;如果recv函数在等待协议接收数据时网络中断了,那么它返回0。先检查套接字s的接收缓冲区,如果s接收缓冲区中没有数据或者协议正在接收数据,那么recv就一直等待,直到协议有数据且把数据接收完毕。当协议把数据接收完毕,recv函数就把s的接收缓冲中的数据copy到buf中(注意协议接收到的数据可能大于buf的长度,所以 在这种情况下要调用几次recv函数才能把s的接收缓冲中的数据copy完。recv函数仅仅是copy数据,真正的接收数据是协议来完成的)【项目中宿主机客户端recv阻塞。用户态程序中父进程作为服务器端通过fcntl(connect_fd,F_SETFL,O_NONBLOCK)设置recv不阻塞,但他需要while阻塞等待宿主机客户端发送检测命令,而子进程中是不需要阻塞的,而是每次发送完视图,检查一下是否接收到了stop未处理,否则阻塞就不能继续检测和发送视图了,这里也就使得每次都会把完整数据发送出去,不会因为接收到stop就立刻停止,即停止是有延迟的】。

参考:《UNIX网络编程 卷1:套接字联网API》 

原文地址:https://www.cnblogs.com/beixiaobei/p/10498121.html