网络编程

网络编程

三次握手

TCP包和握手规则
位码即tcp标志位,有6种标示:SYN(synchronous建立联机) 、ACK(acknowledgement 确认)、 PSH(push传送)、 FIN(finish结束)、RST(reset重置)、URG(urgent紧急)、Sequence number(顺序号码) 、Acknowledge number(确认号码)。

三次握手

B ----> 【SYN】----> A
B需要跟A建立连接时,首先向A发送一个SYN (Synchronize) 标记的包,告诉A请求建立连接。
注意:一个 SYN包就是仅SYN标记设为1的TCP包(参见TCP包头Resources).认识到这点很重要,只有当A受到B发来的SYN包,才可建立连接,除此之外别无他法。
A ---->【SYN/ACK】----> B
A收到后会发一个对SYN包的确认包(SYN/ACK)回去,表示对第一个SYN包的确认,并继续握手操作。
注意:SYN/ACK包是仅SYN 和 ACK 标记为1的包。
B ----> 【ACK】----> A
B收到SYN/ACK 包,B发一个确认包(ACK),通知A连接已建立.至此三次握手完成,建立连接。
注意:ACK包就是仅ACK 标记设为1的TCP包。需要注意的是当三此握手完成、连接建立以后,TCP连接的每个包都会设置ACK位。
四次挥手

B ----> 【ACK/FIN】----> A
A ----> 【ACK】----> B
A ----> 【ACK/FIN】----> B
B ----> 【ACK】----> A
注意:由于TCP连接是双向连接, 因此关闭连接需要在两个方向上做。ACK/FIN 包(ACK 和FIN标记设为1)通常被认为是FIN(终结)包.然而, 由于连接还没有关闭, FIN包总是打上ACK标记.没有ACK标记而仅有FIN标记的包不是合法的包,并且通常被认为是恶意的。

RST包

四次挥手不是关闭TCP连接的唯一方法。有时,如果主机需要尽快关闭连接(或连接超时,端口或主机不可达),RST(Reset)包将被发送。 注意在,由于RST包不是TCP连接中的必须部分,可以只发送RST包(即不带ACK标记)。但在正常的TCP连接中RST包可以带ACK确认标记。
PSH包

当设置为1时,要求把数据尽快的交给应用层,不做处理。
URG包

设置为1时,首部中的紧急指针有效;为0时,紧急指针没有意义。

通常的数据中都会带有PSH但URG只在紧急数据的时设置,也称“带外数据”,解释如下:
  紧急数据:URG标志设置为1时,紧急指针才有效,紧急方式是向对方发送紧急数据的一种方式,表示数据要优先处理。他是一个正的偏移量,与TCP收不中序号字段的值相加表示紧急数据后面的字节,即紧急指针是指向紧急数据最后一个字节的下一个字节。这是协议编写上的错误,RFC1122中对此给出了更正说明,紧急指针是数据最后一个字节,不是最后字节的下一位置,TCP首部中只有紧急指针指出紧急数据的位置,他所指的字节为紧急数据,但没有办法指定紧急数据的长度。

URG=1,表示紧急指针指向包内数据段的某个字节(数据从第一字节到指针所指向字节就是紧急数据)不进入缓冲区(一般不都是待发送的数据要先进入发送缓存吗?就直接交个上层进程,余下的数据都是要进入接收缓冲的;一般来说TCP是要等到整个缓存都填满了后在向上交付,但是如果PSH=1的话,就不用等到整个缓存都填满,直接交付,但是这里的交付仍然是从缓冲区交付的,URG是不要经过缓冲区的

网络

相对于SOCKET开发者,TCP创建过程和链接折除过程是由TCP/IP协议栈自动创建的.因此开发者并不需要控制这个过程.但是对于理解TCP底层运作机制,相当有帮助.

而且对于有网络协议工程师之类笔试,几乎是必考的内容.企业对这个问题热情之高,出乎我的意料:-)。有时上午面试前强调这个问题,并重复讲一次,下午几乎每一个人都被问到这个问题。

因此在这里详细解释一下这两个过程。

TCP三次握手

所谓三次握手(Three-way Handshake),是指建立一个TCP连接时,需要客户端和服务器总共发送3个包。

三次握手的目的是连接服务器指定端口,建立TCP连接,并同步连接双方的序列号和确认号并交换 TCP 窗口大小信息.在socket编程中,客户端执行connect()时。将触发三次握手。

第一次握手:
客户端发送一个TCP的SYN标志位置1的包指明客户打算连接的服务器的端口,以及初始序号X,保存在包头的序列号(Sequence Number)字段里。

第二次握手:
服务器发回确认包(ACK)应答。即SYN标志位和ACK标志位均为1同时,将确认序号(Acknowledgement Number)设置为客户的I S N加1以.即X+1。

第三次握手.
客户端再次发送确认包(ACK) SYN标志位为0,ACK标志位为1.并且把服务器发来ACK的序号字段+1,放在确定字段中发送给对方.并且在数据段放写ISN的+1

SYN攻击

在三次握手过程中,服务器发送SYN-ACK之后,收到客户端的ACK之前的TCP连接称为半连接(half-open connect).此时服务器处于Syn_RECV状态.当收到ACK后,服务器转入ESTABLISHED状态.

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

Syn攻击是一个典型的DDOS攻击。检测SYN攻击非常的方便,当你在服务器上看到大量的半连接状态时,特别是源IP地址是随机的,基本上可以断定这是一次SYN攻击.在Linux下可以如下命令检测是否被Syn攻击

netstat -n -p TCP | grep SYN_RECV

一般较新的TCP/IP协议栈都对这一过程进行修正来防范Syn攻击,修改tcp协议实现。主要方法有SynAttackProtect保护机制、SYN cookies技术、增加最大半连接和缩短超时时间等.

但是不能完全防范syn攻击。

TCP 四次挥手

TCP的连接的拆除需要发送四个包,因此称为四次挥手(four-way handshake)。客户端或服务器均可主动发起挥手动作,在socket编程中,任何一方执行close()操作即可产生挥手操作。

参见wireshark抓包,实测的抓包结果并没有严格按挥手时序。我估计是时间间隔太短造成。

网络

TCP主动关闭连接
appl: close(), --> FIN FIN_WAIT_1 //主动关闭socket方,调用close关闭socket,发FIN
<-- ACK FIN_WAIT_2 //对方操作系统的TCP层,给ACK响应。然后给FIN
<-- FIN
–> ACK “TIME_WAIT” – 2MSL timeout -->CLOSED
//TIME_WAIT,防止ACK没有给到对方。
注意:close时,如果TCP发送队列中还有数据,那么将会发送RST包而不是FIN包。
参看:http://rdc.taobao.com/blog/cs/?p=1055
另外:对于Linux下来说,无论是FIN还是RST,应用层read将会返回0,可以认为对方请求关闭链接,调用close关闭fd即可。
TCP被动关闭连接
<-- FIN “CLOSE_WAIT” //被动方,收到对方的FIN,处于CLOSE_WAIT状态
–> ACK //被动方的TCP层,给ACK响应
appl: close(), --> FIN LAST_ACK
//被动方调用close,从CLOSE_WAIT转到LAST_ACK
不调close,将一直在CLOSE_WAIT状态。
<-- ACK --> CLOSED
tcp是全双工::
close()会关闭读写。
shutdown()可以选择性的关闭读、写或读写。

主动关系SOCKET链接的一方,会进入TIME_WAIT(作用是防止最后一个ACK包丢失)
TIME_WAIT的时间会非常长,因此server尽量减少主动关闭连接。

close和shutdown的区别:
int close(int sockfd);
close(fd)调用会将描述字的引用计数减1,只有当socket描述符的引用计数为0时,才关闭socket,即发送FIN包,因此,在fork()模式中,父进程在accept()返回后,fork()子进程,由子进程处理connfd,而父进程将close(connfd);由于connfd这个socket描述符的引用计数不为0,因此并不引发FIN,所以就没有关闭和客户端的连接。

int shutdown(int sockfd, int howto);
// howto: SHUT_RD, SHUT_WR, SHUT_RDWR
shutdown()则不管socket描述符的引用计数,而直接发生FIN,因此会直接关闭链接。
shutdown()可控制read/write两个方向的管道。
SHUT_RD shutdown(sockfd, SHUT_RD);后,来自对端的数据都被确认,然后悄然丢弃。
SHUT_WR half close状态。
close()引发的4次交互:(这里的close是client发起的)
client server
FIN_WAIT_1 ---- FIN M ------>
//(Server端操作系统的TCP层(网络协议栈)响应ACK包)
FIN_WAIT_2 <---- ACK M+1---- CLOSE_WAIT
//(这里必须调用close,才能从CLOSE_WAIT到LAST_ACK)
TIME_WAIT <------ FIN N ----- LAST_ACK
//(TIME_WAIT有一个重要的作用就是防止最后一个ACK丢失)
------- ACK N+1 ----> CLOSE

2020/2/27更新

最近又理解了一下,close是linux中关闭文件的命令,根据linux中任何皆为文件的特性,调用shutdown后还要调用close才能消除操作系统对这个socket的管理

关于shutdown和close的一些说明:

  1. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
  2. 在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信. 如果一个进程close(sfd)将不会影响到其它进程. 得自己理解引用计数的用法了. 有Kernel编程知识的更好理解了.
  3. 只要TCP栈的读缓冲里还有未读取(read)数据,则调用close时会直接向对端发送RST。
  4. shutdown与socket描述符没有关系,即使调用shutdown(fd, SHUT_RDWR)也不会关闭fd,最终还需close(fd)。
  5. 可以认为shutdown(fd, SHUT_RD)是空操作,因为shutdown后还可以继续从该socket读取数据,这点也许还需要进一步证实。

对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出

  1. 当有多个socket描述符指向同一socket对象时,调用close时首先会递减该对象的引用计数,计数为0时才会发送FIN包结束TCP连接。shutdown不同,只要以SHUT_WR/SHUT_RDWR方式调用即发送FIN包。
  2. SO_LINGER与close,当SO_LINGER选项开启但超时值为0时,调用close直接发送RST(这样可以避免进入TIME_WAIT状态,但破坏了TCP协议的正常工作方式),SO_LINGER对shutdown无影响。
  3. TCP连接上出现RST与随后可能的TIME_WAIT状态没有直接关系,主动发FIN包方必然会进入TIME_WAIT状态,除非不发送FIN而直接以发送RST结束连接。

SO_LINGER
这个参数对应大量短链接的服务器很有必要!

shutdown(fd, SHUT_RDWR);

struct linger linger;

linger.l_onoff = 1;

linger.l_linger = 0;

setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger));

close(fd);

        linger.l_linger = 0;
        setsockopt(fd, SOL_SOCKET, SO_LINGER, (char *) &linger, sizeof(linger));
        close(fd);

此选项指定函数close对面向连接的协议如何操作(如TCP)。内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。

SO_LINGER选项用来改变此缺省设置。使用如下结构:

struct linger {

 int l_onoff; /* 0 = off, nozero = on */

 int l_linger; /* linger time */

};

有下列三种情况:

1、设置 l_onoff为0,则该选项关闭,l_linger的值被忽略,等于内核缺省情况,close调用会立即返回给调用者,如果可能将会传输任何未发送的数据;

2、设置 l_onoff为非0,l_linger为0,则套接口关闭时TCP夭折连接,TCP将丢弃保留在套接口发送缓冲区中的任何数据并发送一个RST给对方,而不是通常的四分组终止序列,这避免了TIME_WAIT状态;

3、设置 l_onoff 为非0,l_linger为非0,当套接口关闭时内核将拖延一段时间(由l_linger决定)。如果套接口缓冲区中仍残留数据,进程将处于睡眠状态,直 到(a)所有数据发送完且被对方确认,之后进行正常的终止序列(描述字访问计数为0)或(b)延迟时间到。此种情况下,应用程序检查close的返回值是非常重要的,如果在数据发送完并被确认前时间到,close将返回EWOULDBLOCK错误且套接口发送缓冲区中的任何数据都丢失。close的成功返回仅告诉我们发送的数据(和FIN)已由对方TCP确认,它并不能告诉我们对方应用进程是否已读了数据。如果套接口设为非阻塞的,它将不等待close完成。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值