【nginx】网络通讯实战一

C/S, TCP/IP协议妙趣横生、惟妙惟肖谈

一、客户端与服务器

浏览器就是一个可执行程序(客户端),淘宝网nginx服务器返回数据包,来回很多次才完全发完,最后发一个特殊的包结束。

【客户端服务器角色规律总结】

a)数据通讯总在两端进行,其中一端叫客户端,另一端叫服务器端;

b)总有一方先泛起第一个数据包,这发起第一个数据包的这一端,就叫客户端【浏览器】;被动收到第一个数据包这端,叫服务器端【淘宝服务器】;

c)连接建立起来,数据双向流动(双工)

d)既然服务器端是被动接收连接,那么客户端必须得能够找到服务器在哪里;

浏览器要访问淘宝网,需要知道淘宝服务器的地址(IP地址),以及淘宝服务器的姓名(端口号,一个0-65535的无符号数字)

淘宝网服务器(nginx服务器)会调用listen()函数来监听80端口。

在编写网络通讯程序时,只需要指定淘宝服务器的ip地址和淘宝服务器的端口号,就能够跟淘宝服务器进行通讯。

扫描二维码关注公众号,回复: 6131147 查看本文章

e)epoll:单台服务器高并发

二、网络模型

1、OSI七层网络模型

物【物理层】 链【数据链路层】 网【网络层】 传【传输层】 会【会话层】 表【表示层】 应【应用层】

把一个要发送出去的数据包从里到外裹了7层发送到网络上去。

2、TCP/IP协议四层模型

tcp/ip实际是一组协议的代名词,而不仅仅是一个协议;每一层都对应着一些协议。

3、TCP/IP协议的解释和比喻

上层数据先加TCP头,再加IP头,再加LLC头,再加MAC头。

三、最简单的客户端和服务器程序实现代码

1、套接字socket概念

套接字(socket):就是个数字,通过调用socket()函数来生成;这个数字具有唯一性;一直用直到调用close()函数把这个数字关闭;一切皆文件,咱们就把socket也看成是文件描述符,我们可以用socket来收发数据;send(),recv()。

2、一个简单的服务器端通讯程序范例

3、IP地址

写服务器程序,不用考虑ipv4、ipv6的问题,遵照ipv4规则写就行;

写客户端程序,只演示ipv4版本的客户端范例。

4、一个简单的客户端通讯程序范例

c/s建立连接时双方彼此都要有ip地址/端口号;连接一旦建立起来,那么双方的通讯【双工收发】就只需要用双方彼此对应的套接字即可。

5、客户端服务器程序综合演示和调用流程图

服务器端程序要先运行

四、TCP和UDP的区别

TCP(Transfer Control Protocol):传输控制协议

UDP(User Datagram Protocol):用户数据报协议

TCP协议:可靠的面向连接的协议,数据包丢失的话操作系统底层会感知并且帮助你重新发送数据包;

UDP协议:不可靠的,无连接的协议。

【优缺点】

a)tcp:可靠协议,耗费更多的系统资源确保数据传输的可靠;得到好处就是只要不断线,传输给对方的数据,一定正确的,不丢失,不重复,按顺序到达对端;

b)udp:不可靠协议,发送速度特别快;但无法确保数据可靠性。

【各自的用途】

a)tcp:文件传输,收发邮件需要准确率高,但效率可以相对差;一般TCP比UDP用的范围和场合更广。

b)udp:qq聊天信息,DNS解析等,估计随着网络的发展,网络性能更好,丢包率更低,那么udp应用范围更广。

TCP三次握手详析、telnet,wireshark示范

一、TCP连接的三次握手

只有TCP有三次握手(UDP没有)

1、最大传输单元MTU

MTU:每个数据包包含的数据最多可以有多少个字节,1.5K左右;

要发送100K,操作系统内部会把这100K数据拆分成若干个数据包(分片),每个数据包大概1.5K之内(大概拆解成68个),对端重组;我们只需要知道有拆包,组包这68个包各自传送的路径可能不同,每一个包可能因为路由器,交换机原因可能被再次分片;最终TCP/IP协议保证了我们收发数据的顺序性和可靠性。

2、TCP包头结构(往左90度)

a)源端口,目标端口

b)关注syn位和ack位(开/关)

c)一个tcp数据包,是可能没有包体,此时,总会设置一些标志位来达到传输控制信息的目的,起控制作用

3、TCP数据包收发之前的准备工作

TCP数据的收发是双工的:每端既可以收数据,又可以发数据。

【TCP数据包的收发三大步骤】

a)建立TCP连接[connect:客户端],三次握手

b)多次反复的数据收发[read/wirte]

c)关闭TCP连接[close]

UDP不存在三次握手来建立连接的问题。UDP数据包是直接发送出去,不用建立所谓的连接。

4、TCP三次握手建立连接的过程([syn] [syn/ack] [ack]

1)客户端给服务器发送 了一个SYN标志位置位的无包体TCP数据包,SYN被置位,就表示发起TCP链接;

2)服务器收到了这个SYN标志位置位的数据包,服务器给客户端返回一个SYN和ACK标志位都被置位的无包体TCP数据包;

3)客户端收到服务器发送回来的数据包之后,再次发送ACK置位的数据包,服务器端收到这个数据包之后,客户端和服务器端的TCP链接就正式建立。

5、为什么TCP握手是三次握手而不是二次

三次握手很大程度上是为了防止恶意的人坑害别人而引入的一种TCP连接验证机制。

为了确保数据稳定可靠的收发 ,尽量减少伪造数据包对服务器的攻击。

源ip,源端口 ---------------- 目的ip,目的端口

syn------------->

<--------syn/ack(验证源ip和源端口真实存在)

ack-------------->

二、telnet工具使用介绍

telnet是一款命令行方式运行的客户端TCP通讯工具,可以连接到服务器端,往服务器端发送数据,也可以接收从服务器端发送过来的信息;该工具能够方便的测试服务器端的某个TCP端口是否通,是否能够正常收发数据,所以是一个非常实用,重要,常用的工具。

telnet ip地址 端口号

telnet在windows下输入一个字符就往server发,在linux下输入字符后要回车才往server发。

三、wireshark监控数据包

wireshark是个软件,分析网络数据包,规则:host 192.168.1.126 and port 9000。

【TCP断开的四次挥手】

a)FIN,ACK   服务器->客户端

b)ACK            客户端->服务器

c)FIN,ACK   客户端->服务器

d)ACK            服务器->客户端

TCP状态转换,TIME_WAIT详解,SO_REUSEADDR

一、TCP状态转换

同一个IP(INADDR_ANY),同一个端口SERV_PORT,只能被成功的bind()一次,若再次bind()就会失败,并且会显示:Address already in use。

介绍命令netstat:显示网络相关信息

-a:显示所有选项

-n:能显示成数字的内容全部显示成数字

-p:显示段落这对应程序名

netstat -anp | grep -E 'State|9000'

【测试】用两个客户端连接到服务器,服务器给每个客户端发送一串字符"I sent sth to client!\n",并关闭客户端;我们用netstat观察,原来那个监听端口一直在监听,但是当来了两个连接之后(连接到服务器的9000端口),虽然这两个连接被close掉了,但是产生了两条TIME_WAIT状态的信息。

客户端连接到服务器,并且服务器把客户端关闭,服务器端就会产生一条针对9000监听端口的状态为 TIME_WAIT 的连接;只要用netstat看到 TIME_WAIT状态的连接,那么此时杀掉服务器程序再重新启动,就会启动失败,bind()函数返回失败。

TCP状态转换图(11种状态)是针对一个TCP连接来说的。

客户端: CLOSED ->SYN_SENT->ESTABLISHED【连接建立,可以进行数据收发】

服务端: CLOSED ->LISTEN->【客户端来握手】SYN_RCVD->ESTABLISHED【连接建立,可以进行数据收发】

谁主动close连接,谁就会给对方发送一个FIN标志置位的一个数据包给对方,假设是服务器端先关闭:

服务器主动关闭连接:ESTABLISHED->FIN_WAIT1->FIN_WAIT2->TIME_WAIT(等一段时间回到close状态)

客户端被动关闭:ESTABLISHED->CLOSE_WAIT->LAST_ACK

二、TIME_WAIT状态

【何时产生】四次挥手主动关闭的一方就会产生!

具有TIME_WAIT状态的TCP连接,就好像一种残留的信息一样,服务器程序退出并重新执行会失败;连接处于TIME_WAIT状态是有时间限制的(1-4分钟之间) = 2 MSL(最长数据包生命周期)。

【为什么要引入TIME_WAIT】:

(1)可靠的实现TCP全双工的终止

如果服务器最后发送的ACK包因为某种原因丢失了,那么客户端一定会重新发送FIN,这样因为服务器端有TIME_WAIT的存在,服务器会重新发送ACK包给客户端,但是如果没有TIME_WAIT这个状态,服务器都已经关闭连接了,此时客户端重新发送FIN,服务器给回的就不是ACK包,而是RST(连接复位)包,从而使客户端没有完成正常的4次挥手,可能出现错误

RST标志:当我们close一个TCP连接时,如果发送缓冲区有数据,那么操作系统会很优雅的把发送缓冲区里的数据发送完毕,然后再发fin包表示连接关闭;出现这个RST标志的包一般都表示异常关闭,如果发生了异常,一般都会导致丢失一些发送缓冲区的数据包,主动关闭一方也不会进入TIME_WAIT。

(2)允许老的重复的TCP数据包在网络中消逝

如果最后一个ack还没到客户端,不用TIME_WAIT来阻止新server启动,这时启动一个server,再来一个老数据包,就不知道这个包是给老的还是给新的了,容易引起混乱。所以维持TIME_WAIT一段时间是为了在这段时间内彻底丢弃老的数据包,等待最后一个ack到达客户端,再全部结束。

三、SO_REUSEADDR选项

setsockopt(SO_REUSEADDR)用在服务器端,socket()创建之后,bind()之前。

所有TCP服务器都应该指定本套接字选项,以防止当套接字处于TIME_WAIT时bind()失败的情形出现。

【设置后测试】两个进程,绑定同一个IP和端口:bind()失败;TIME_WAIT状态时的bind绑定:bind()成功。

SO_REUSEADDR:主要解决TIME_WAIT状态导致bind()失败的问题。

listen()队列剖析、阻塞非阻塞、同步异步

一、listen()队列剖析

listen():监听端口,用在TCP连接中的服务器端角色。

listen()函数调用格式:int listen(int sockfd, int backlog);

要理解好backlog这个参数,要了解 “监听套接字队列”!

1、监听套接字的队列

对于一个调用listen()进行监听的套接字,操作系统会给这个套接字维护两个队列:

a)未完成连接队列(保存连接用的)

当客户端发送tcp连接三次握手的第一次(syn包)给服务器的时候,服务器就会在未完成队列中创建一个跟这个syn包对应的一项,我们可以把这项看成是一个半连接(因为连接还没建立),这个半连接的状态会从LISTEN变成SYN_RCVD状态,同时给客户端返回第二次握手包(syn,ack包),这个时候,其实服务器是在等待完成第三次握手。

b)已完成连接队列(保存连接用的)

当第三次握手完成了,这个连接就变成了ESTABLISHED状态,每个已经完成三次握手的客户端都放在这个队列中作为一项。

backlog曾经的含义:已完成队列和未完成队列里边条目之和不能超过backlog。

【总结】

(1)客户端这个connect()什么时候返回?收到三次握手的第二次握手包(也就是收到服务器发回来的syn/ack)之后返回。

(2)RTT是未完成队列中任意一项在未完成队列中留存的时间,这个时间取决于客户端和服务器。

对于客户端,这个RTT时间是第一次和第二次握手加起来的时间;对于服务器,这个RTT时间实际上是第二次和第三次握手加起来的时间。如果这三次握手包传递速度特别快的话,大概187毫秒能够建立起来这个连接,这个时间不快,所以建立TCP连接的成本很高。

(3)如果一个恶意客户,迟迟不发送三次握手的第三个包。那么这个连接就建立不起来,那么这个处于SYN_RCVD的这一项,就会一致停留在服务器的未完成队列中,这个停留时间大概是75秒,如果超过这个时间,这一项会被操作系统干掉。

2、accept()函数

accept()函数:就是用来从已完成连接队列中的队首位置取出来一项(每一项都是一个已经完成三路握手的TCP连接)返回给进程。如果已完成连接队列是空,那么accept()一般会一直卡在这里(休眠)等待,一直到已完成队列中有一项时才会被唤醒。因为客户端connet返回就可以收发数据了,所以服务器端要尽快地用accept()把已完成队列中的数据(TCP连接)取走。

accept()返回的是个套接字,这个套接字就代表那个已经用三次握手建立起来的那个tcp连接,因为accept()是从已完成队列中取的数据,所以一定要区别连个服务器端的两个套接字:

a)监听9000端口这个套接字——“监听套接字”(listenfd),只要服务器程序在运行,这个套接字就应该一直存在;

b)当客户端连接进来,操作系统会为每个成功建立三次握手的客端再创建一个套接字(connfd),accept()返回的就是这种套接字,也就是从已完成连接队列中取得的一项。随后,服务器是使用这个accept()返回的套接字和客户端通信的。

【思考题】

(1)如果两个队列之和(已完成连接队列和未完成连接队列)达到了listen()所指定的第二参数,也就是说队列满了,此时,再有一个客户发送syn请求,服务器怎么反应?

实际上服务器会忽略这个syn,不给回应;客户端这边,发现syn没回应,过一会会重发syn包。

(2)从连接被扔到已经完成队列中去,到accept()从已完成队列中把这个连接取出之间是有个时间差的,如果还没等accept()从已完成队列中把这个连接取走的时候,客户端如果发送来数据,这个数据就会被保存在已经连接的套接字的接收缓冲区里,这个缓冲区有多大,最大就能接收多少数据量。

3、syn攻击(syn flood):典型的利用TCP/IP协议涉及弱点进行坑爹的一种行为。

拒绝服务攻击(DOS/DDOS):不停发第一次握手的syn包,服务器未完成队列中就越来越多,总会超过backlog;

所以backlog被进一步明确和规定了:已完成连接队列中最大条目数。虽然这样即使未完成连接队列被填满后还是无法处理新的连接,但是系统会自动处理未完成连接队列的内容,75s后删除。

所以在写代码时尽快用accept()把已完成队列里边的连接取走,尽快留出空闲位置给后续的已完成三次握手的条目用,这个已完成队列就一般不会满,一般这个backlog值给300左右。

二、阻塞与非阻塞I/O

阻塞和非阻塞主要是指调用某个系统函数时,这个函数是否会导致我们的进程进入sleep()(卡住休眠)状态而言的。

1、阻塞I/O

调用一个函数,这个函数卡在这里等待一个事情发生,只有这个事情发生了,这个函数才会往下走,如阻塞函数accept()。

这种阻塞并不好,效率很低,所以一般不会用阻塞方式来写服务器程序。

2、非阻塞I/O:不会卡住,充分利用时间片,执行效率更高。

【非阻塞模式的两个鲜明特点】

(1)不断地调用accept()、recvfrom()函数来检查有没有数据到来,如果没有,函数会返回一个特殊的错误标记来标识,这种标记可能是EWULDBLOCK,也可能是EAGAIN;如果数据没到来,那么这里有机会执行其他函数,但是也得不停的再次调用accept(),recvfrom()来检查数据是否到来,非常累;

(2)如果数据到来,那么就得卡在这里把数据从内核缓冲区复制到用户缓冲区,所以复制这个阶段是卡着完成的

三、同步与异步I/O:这两个概念容易和阻塞/非阻塞混淆

1、异步I/O:

调用一个异步I/O函数时,要给这个函数指定一个接收缓冲区,我还要给定一个回调函数

调用完一个异步I/O函数后,该函数会立即返回,其余判断交给操作系统,操作系统会判断数据是否到来,如果数据到来了,操作系统会把数据拷贝到你所提供的缓冲区里,然后调用你所指定的这个回调函数来通知你。

【非阻塞和异步I/O区别】

a)非阻塞I/O要不停的调用I/O函数来检查数据是否来,如果数据来了,就得卡在I/O函数这里把数据从内核缓冲区复制到用户缓冲区,然后这个函数才能返回;

b)异步I/O根本不需要不停的调用I/O函数来检查数据是否到来,只需要调用一次,然后就可以干别的事情去了;内核判断数据到来,拷贝数据到你提供的缓冲区,调用你的回调函数来通知你,并没有被卡在那里的情况。

2、同步I/O:select/poll和epoll

步骤一:调用select()判断有没有数据,有数据,走下来,没数据卡在那里;

步骤二:select()返回之后,用recvfrom()去取数据,当然取数据的时候(内核到用户)也会卡那么一下。

同步I/O感觉更麻烦,要调用两个函数才能把数据拿到手;但是同步I/O和阻塞式I/O比,有 I/O复用(2个函数的)能力。所谓I/O复用,就是多个socket(多个TCP连接)可以弄成一捆,我可以用select等数据,因为select()的能力是等多条TCP连接上的任意一条有数据来都可以感知到,然后哪条TCP有数据来,我再用具体的比如recvfrom()去收。

所以,这种调用一个函数能够判断一堆TCP连接是否来数据的这种能力,叫I/O复用,英文I/O multiplexing。

很多资料把阻塞I/O,非阻塞I/O,同步I/O归结为一类 ,因为他们多多少少的都有阻塞的行为发生;也可以把阻塞I/O,非阻塞I/O 都归结为同步I/O模型,而把异步I/O单独归结为一类,因为异步I/O是真正的没有阻塞行为发生的。

【通俗理解】参考:https://www.cnblogs.com/wangzhaobo/articles/9596623.html

阻塞与非阻塞关注的是在等待数据来的时候是不是在干别的事(有没有一直傻等),而同步与异步关注的是如何获取到数据来了这件事,是自己看到的还是别人告诉的,所以阻塞和非阻塞都算有一点同步。现实中非阻塞异步是最符合我们常理的,好比我们去买煎饼,我跟摊煎饼的阿姨说,你摊好了给我打电话(异步),然后我就去聊天、玩手机(非阻塞),然后摊好了通知我(回调函数),我取走煎饼。最蠢的其实就是阻塞异步地做事,就好比你跟阿姨说数据说你来了通知我一下,然后你还在哪里一直等着数据来而不干别的事(阻塞),那你让阿姨通知你干嘛?同理,阻塞同步就是一直站在那里等煎饼出来,然后看到煎饼就拿走,因为是自己看而不是等通知所以是同步。非阻塞同步就是去聊天玩手机,然后每过一会儿就过来看一下煎饼摊好没有,摊好了就拿走。

猜你喜欢

转载自blog.csdn.net/u012836896/article/details/89107767