自顶向下:计算机网络基础

本系列总结:只梳理思路,不重复造轮子
谢谢,软文中出现的大佬的笔记,PPT
如有侵权,私信更换

传输层:TCP

TCP三次握手和四次挥手

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

TCP的端口时为了区分什么?

在这里插入图片描述

由此可见,两个计算机的进程要互相通信,不仅必须知道对方的IP地址(为了找到对方的计算机),而且还要知道对方的端口号(为了找到对方计算机中的应用进程)

应用层:HTTP

服务器返回一个 HTTP 响应

Spring:@Controller

服务器收到了我们的请求,也处理我们的请求,到这一步,它会把它的处理结果返回,也就是返回一个HTPP响应。
HTTP响应与HTTP请求相似,HTTP响应也由3个部分构成,分别是:
• 状态行
• 响应头(Response Header)
• 响应正文

HTTP/1.1 200 OK
Date: Sat, 31 Dec 2005 23:59:59 GMT
Content-Type: text/html;charset=ISO-8859-1
Content-Length: 122

<html>
<head>
<title>http</title>
</head>
<body>
<!-- body goes here -->
</body>
</html>

状态行:
状态行由协议版本、数字形式的状态代码、及相应的状态描述,各元素之间以空格分隔。
格式: HTTP-Version Status-Code Reason-Phrase CRLF
例如: HTTP/1.1 200 OK \r\n
| -协议版本:是用http1.0还是其他版本
| -状态描述:状态描述给出了关于状态代码的简短的文字描述。比如状态代码为200时的描述为 ok
| -状态代码:状态代码由三位数字组成,第一个数字定义了响应的类别,且有五种可能取值。如下(实在太长,极度不适)

常见的HTTP状态码

1xx:信息性状态码,表示服务器已接收了客户端请求,客户端可继续发送请求。
2xx:成功状态码,表示服务器已成功接收到请求并进行处理。
3xx: 重定向状态码,表示服务器要求客户端重定向。
4xx:客户端错误状态码,表示客户端的请求有非法内容。
5xx:服务器错误状态码,表示服务器未能正常处理客户端的请求而出现意外错误。

HTTP请求报文的组成

TCP连接如图所示:
  建立了TCP连接之后,发起一个http请求。一个典型的 http request header 一般需要包括请求的方法,例如 GET 或者 POST 等,不常用的还有 PUT 和 DELETE 、HEAD、OPTION以及 TRACE 方法,一般的浏览器只能发起 GET 或者 POST 请求。
  客户端向服务器发起http请求的时候,会有一些请求信息,请求信息包含三个部分:
• 请求方法URI协议/版本
• 请求头(Request Header)
• 请求正文:
下面是一个完整的HTTP请求例子:

GET/sample.jspHTTP/1.1
Accept:image/gif.image/jpeg,*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible;MSIE5.01;Window NT5.0)
Accept-Encoding:gzip,deflate

username=jinqiao&password=1234

注意:最后一个请求头之后是一个空行,发送回车符和换行符,通知服务器以下不再有请求头。
(1)请求的第一行是“方法URL议/版本”:GET/sample.jsp HTTP/1.1
(2)请求头(Request Header)
   请求头包含许多有关的客户端环境和请求正文的有用信息。例如,请求头可以声明浏览器所用的语言,请求正文的长度等。

Accept:image/gif.image/jpeg.*/*
Accept-Language:zh-cn
Connection:Keep-Alive
Host:localhost
User-Agent:Mozila/4.0(compatible:MSIE5.01:Windows NT5.0)
Accept-Encoding:gzip,deflate.

(3)请求正文
请求头和请求正文之间是一个空行,这个行非常重要,它表示请求头已经结束,接下来的是请求正文。请求正文中可以包含客户提交的查询字符串信息:
username=jinqiao&password=1234

HTTP请求报文包含哪些方法, GET和POST的区别?

什么是cookie和session,区别是什么?

输入URL跳转网页的过程

1、输入地址

当我们开始在浏览器中输入网址的时候,浏览器其实就已经在智能的匹配可能得url了,他会从历史记录,书签等地方,找到已经输入的字符串可能对应的 url,然后给出智能提示,让你可以补全url地址。对于google的chrome的浏览器,他甚至会直接从缓存中把网页展示出来,就是说,你还没有按下 enter,页面就出来了。

2、浏览器查找域名的 IP 地址

1、请求一旦发起,浏览器首先要做的事情就是解析这个域名,一般来说,浏览器会首先查看本地硬盘的 hosts 文件,看看其中有没有和这个域名对应的规则,如果有的话就直接使用 hosts 文件里面的 ip 地址。
     2、如果在本地的 hosts 文件没有能够找到对应的 ip 地址,==浏览器会发出一个 DNS请求到本地DNS服务器 ==。本地DNS服务器一般都是你的网络接入服务器商提供,比如中国电信,中国移动。
     3、查询你输入的网址的DNS请求到达本地DNS服务器之后,本地DNS服务器会首先查询它的缓存记录,如果缓存中有此条记录,就可以直接返回结果,此过程是递归的方式进行查询。如果没有,本地DNS服务器还要向DNS根服务器进行查询
    4、根DNS服务器没有记录具体的域名和IP地址的对应关系,而是告诉本地DNS服务器,你可以到域服务器上去继续查询,并给出域服务器的地址。这种过程是迭代的过程。
    5、本地DNS服务器继续向域服务器发出请求,在这个例子中,请求的对象是.com域服务器。.com域服务器收到请求之后,也不会直接返回域名和IP地址的对应关系,而是告诉本地DNS服务器,你的域名的解析服务器的地址。
    6、最后,本地DNS服务器向域名的解析服务器发出请求,这时就能收到一个域名和IP地址对应关系,本地DNS服务器不仅要把IP地址返回给用户电脑,还要把这个对应关系保存在缓存中,以备下次别的用户查询时,可以直接返回结果,加快网络访问。
在这里插入图片描述

3、浏览器向 web 服务器发送一个 HTTP 请求

  1. 拿到域名对应的IP地址之后,浏览器会以一个随机端口(1024<端口<65535)向服务器的WEB程序(常用的有httpd,nginx等)80端口发起TCP的连接请求。
    关于TCP端口号:

  2. 打包:这个连接请求到达服务器端后(这中间通过各种路由设备,局域网内除外),进入到网卡(物理层),

  3. ==拆包:==然后是进入到内核的TCP/IP协议栈(用于识别该连接请求,解封包,一层一层的剥开),还有可能要经过Netfilter防火墙(属于内核的模块)的过滤,最终到达WEB程序,最终建立了TCP/IP的连接。

  4. IO多路复用:IO多路复用,很有意思

计算机网络四层协议,五层协议,七层协议

在这里插入图片描述

  1. 低3层:相对底层
  2. 传输层:承上启下,屏蔽底三层通信的复杂性,保证进程跨机器实现数据的传输,如tcp协议和UDP协议,就是具体的实现
  3. 高三层:应用层:我们写的java应用程序,tomcat,nginx服务器,涵盖了高三层的实现
  4. 典型实例:http服务器,http是应用层的协议,用来基于tcp开发web服务的一种协议

在这里插入图片描述

在这里插入图片描述

IO多路复用(涉及操作系统内核)

建议先看:多线程

BIO缺点:阻塞接口

  1. B:blocking的意思,“阻塞”。作为服务端开发,我们使用ServerSocket绑定完端口号之后,我们会监听该端口,等待accept事件,accept会阻塞当前主线程;当我们收到accept事件后,程序就会拿到一个客户端于当前服务端连接的socket。针对这个socket我们可以进行读写,但是,这个socket读写方法都是会阻塞当前线程的。
  2. 一般,我们使用多线程的方式进行c/s交互,但是很难做到C10K。比如说1W客户端就需要服务端1W个线程去支持,这样的话, cpu肯定爆炸。线程上下文切换,就会把机器负载给拉飞。
    在这里插入图片描述

NIO:非阻塞接口

在这里插入图片描述

Non-Blocking IO
1. NIO靠什么解决的C10K,靠什么解决多客户端的问题?
Java角度:NIO包给我们提供了一套非阻塞的接口;这样就不需要我们为每一个c/s长连接,保留一个单独的处理线程。
阻塞IO之所以需要给每个socket长连接,指定一个线程,就是因为它阻塞。
现在NIO API具备非阻塞特性,就可以用1个线程去检查n个socket。
2. Java代码层面:nio包,给我们提供了一个选择器selector。
我们需要把检查的socket注册到这个selector中,
然后主线程,阻塞在selector#select方法里头。当选择器发现某个socket就绪,就唤醒主线程,然后,咱们可以通过selector获取到就绪状态的socket,进行相应的处理。
3. IO站在java层聊没意义,因为java最终还是要映射到内核去完成。
Selector其实底层是java包装的native api,再底层的实现,是jvm虚拟机它去使用的系统调用SystemCall kernel去实现的。
JVM:本地方法栈

Kernel操作系统提供的select函数?实现原理?

操作系统概述:中断/系统调用
在这里插入图片描述

  1. 每次调用kernel#select函数,它都会涉及到用户态/内核态的转换。
  2. 还需要检查的socket集合,其实就是需要检查的fd(文件描述符fd)。因为程序都运行再linux或者unix操作系统上。
    操作系统:文件系统
  3. 一切皆文件,socket也不例外。这里传递的fd其实就是文件系统中对应的socket生成的文件描述符。
  4. Select函数被调用以后,(第一件事)首先会按照:fd集合,去检查内存中的socket套接字状态。这个复杂度是O(N)的,然后检查完一遍后,如果有就绪状态的socket,那么直接返回,不会阻塞当前调用线程。否则,就说明指定fd集合对应的socket没有就绪状态的。就需要要阻塞当前调用线程,知道某个socket有数据后,才唤醒线程。
    操作系统:进程的五种状态:socket既没有占用cpu也没有占用其他资源

5.因为select函数他检查到就绪状态的socket后,它做了两件事:
第一件事,跑到就绪状态的socket对应得到filedescriptor(fd文件)中设置一个标记mask,表示当前fd对应的socket就绪了。
第二件事,就是返回select函数,对应的,唤醒java线程。站在java层面,它会收到一个int结果值,表示有几个socket处于就绪状态。但是,具体是哪个socket就绪,java程序目前不清楚。
接下来,又是一个O(N)系统调用(不同于第一次查找,此时就绪态的socket带mask标记),检查fd_set集合中每一个socket的就绪状态,其实就是检查,文件系统中指定socket的文件描述符状态。涉及到 用户态-内核态的来回切换。如果bitmap更多,更恶心。更多的系统调用。系统调用涉及到参数的数据拷贝,如果数据太大,也不利于系统调用速度。
操作系统调度和操作系统中断:

所谓中断,其实就是让cpu正在执行的进程先保留程序上下文,然后必然出cpu,给中断程序让道;中断程序会拿到cpu执行权限,进行相应代码执行,比如说:键盘的中断程序。

i. 调度:cpu同一时刻,它只能运行一个进程,操作系统最主要的任务就是系统调用,就是有n个进程,然后让n个进程在cpu上切换执行。
ii. 未挂起的进程都在工作队列内,都有机会获取到cpu执行权;
iii. 挂起的进程,就会从这个工作队列内移除出去,反射到java层面就是线程阻塞。

假设select()第一遍O(N)去检查时未发现就绪状态的socket,过一会,有某一个socket就绪了,select函数怎么发现?难道这个select函数它在底层kernel内是一直占着cpu去轮询去检查这些socket?

在这里插入图片描述

Socket结构,他有三块核心区域,读缓存,写缓存,还有等待队列;

处理机调度:三个层次:作业调度,内存调度,进程调度

操作系统:处理机调度

第一次轮询:
i. Select函数,它第一遍轮询,他没有发现就绪状态的socket,它就把当前进程,保留给需要检查的socket的等待队列中;
ii. Socket结构,他有三块核心区域,读缓存,写缓存,还有等待队列;
Select函数,他把当前进程保留到每个需要检查的socket#等待队列之后,就会把当前进程从工作队列移除。移除之后,就是挂起当前进程,select函数就不会再运行。
iii. 假设客户端往当前服务器发送数据,数据通过网线到网卡,网卡再到DMA硬件的这种方式直接将数据写到内存里。

整个过程,CPU是不参与的。当数据完成传输以后,它就会触发网络数据传输完毕的中断程序(操作系统中断)。这个程序会把CPU正在执行的程序顶掉。然后CPU就会执行咱们这个中断程序的逻辑。
根据内存中他有的数据包,分析出来数据包是哪个socket的数据;tcp/ip协议,它又保证传输的时候是有端口号的,数据包是有端口号的,然后根据端口号找到对应的socket实例,找到后,就把数据导入到socket的读缓冲区里。

iv.导入后,开始检查socket的等待队列,是不是有等待者?有的话,就把等待者移动到工作队列(就绪队列/就绪态),中断程序这步就执行完了。
咱们的进程又回到工作队列,又有机会获得到cpu时间片了。
当前进程执行select函数再检查,就会发现有就绪的socket了,会给就绪的socket的fd文件描述符打标记,然后select函数就执行完了,返回到java层面。
涉及到内核态-用户态的转换。

内存角度:叫就绪队列
进程状态:叫就绪态

内存角度:叫挂起队列
进程状态:叫阻塞态

Poll()函数 是select()函数的加强版,说下他俩的区别?

最大区别:传参不一样,select它使用的是bitmap,他表示需要检查的socket集合。
Poll使用的是数组结构,表示需要检查的socket集合。主要为了解决select bitmap长度1024这个问题。Poll使用数组就没有这个限制,他就可以让咱们线程监听超过1024个socket限制。

为什么有epoll?

Epoll为了解决select和poll函数的缺陷。

  1. 第一个缺陷(参数):
    1.就是select和poll函数,这两个系统函数,每次调用都需要我们提供给它所有的需要监听的socket文件描述符集合。
    2.而且咱们的程序主线程是死循环调用select/poll函数的,涉及到:用户空间数据到内核空间拷贝的过程。比较耗费性能。 总结:咱们需要监听socket集合,数据变化量非常小,可能它每次就1~2个socket_fd需要更改,但是没有办法,因为select和poll函数只是一个很单纯的函数,他在kernel层面,他不会保留任何数据信息,所以说只能每次调用,都进行数据拷贝,这是第一个缺陷。
  2. 第二个缺陷(返回值):select和poll函数它的返回值是int整型值,只能代表有几个socket就绪或者有错误。它没办法表示出是哪个具体的socket就绪。导致程序被唤醒以后,还需要新的一轮系统调用去检查哪个socket是就绪状态。然后,再进行socket数据处理逻辑。
    我们都清楚,系统调用需要涉及用户态和内核态的来回切换,这个缺陷更严重了。

Epoll函数如何设计的?

为了提升效率,必须解决这两个问题。第一个问题:函数调用参数拷贝问题。第二个问题:系统调用返回后不知道哪些socket就绪的问题。
解决这两个问题:就需要epoll函数再内核空间内,它有一个对应的数据结构,去存储一些数据。这个数据结构就是eventpoll对象。Eventpoll对象,它是可以通过一个系统函数epoll_create去创建。创建完成之后,系统函数返回一个eventpoll对象的id(其实是epfd文件号)。相当于我们在内核开辟一小块空间,并且我们也知道空间的位置。

a) EvenPoll结构:主要是两块重要的区域:一块是存放需要监听的socket_fd描述符列表;另一块区域就是就绪列表,存放就绪状态的socket信息。
另外,还提供两个函数,一个是epoll_ctl函数,另一个是epoll_wait函数。
b) epoll_ctl函数:它可以根据event_id去增删改查内核空间上的event_poll对象的socket_fd描述符列表(即关注的socket信息),去增加或者修改,需要检查的socket文件描述符。
c) epoll_wait(…):主要参数是eventpoll_id。表示此次系统调用需要监听的socket_fd集合。是eventpoll中已经指定好的那些socket信息。Epoll_wait函数,默认情况下,会阻塞调用线程,直到eventpoll中关联的某个或某些个socket就绪以后,epoll_wait(…)它才会返回。
在这里插入图片描述

socket就绪列表(就绪态socket信息列表),怎么维护的?

  1. 参考:select函数调用的一个流程
    i. Socket对象,他有三块区域,读缓存,写缓存还有等待队列。
    ii. Select函数调用时会把当前调用进程从工作队列里面拿出,然后把进程引用追加到 当前进程关注的每一个socket对象的等待队列中。
    iii. 然后,当socket连接客户端发送完数据之后,数据还是通过硬件DMA的方式把数据写入到内存,然后相应的硬件,就会往cpu发送中断信号,cpu就会让出位置,执行网络数据就绪的中断程序。
    iv. 中断程序,它会把内存中的网络数据写入到对应的socket的读缓存里,把这个socket等待队列中的进程全部移动到工作队列,再然后select函数返回了。
    v. 这是select函数调用的一个流程
  2. Epoll工作流程和这个非常相似。当我们调用系统函数epoll_ctl时,比如说,我们新添加一个需要关注的socket。其实内核程序它会把当前eventpoll对象,追加到这个socket#等待队列里。当socket连接客户端发送完数据之后,
    i. 还是通过网线进入到服务器,
    ii. 还是通过硬件DMA的方式把数据写入到内存。
    iii. 还是触发中断程序,cpu将当前进程让出位置,去执行中断程序。
    iv. 中断程序还是将网络数据转移到对应的socket的读缓冲区里面
    v. 再去检查这个socket的等待队列。
    vi. 然后,他发现这个socket#等待队列,内等待的不是进程,是一个eventpoll对象引用。他就根据eventpoll引用,将当前socket的引用,追加到eventpoll的socket就绪链表的末尾。
  3. 补充:eventpoll还有一块空间是eventpoll#等待队列,等待队列保存的就是调用epoll_wait()的进程。
    i. 然后,当中断程序,把socket的引用追加到就绪列表以后,就继续检查eventpoll对象的等待队列,如果有进程,就会把进程转移到工作队列内。
    ii. 转移完毕后,进程就又会获取到cpu执行时间片,然后就调用epoll_wait()函数,这个进程返回到java层面
  4. Eventpoll对象等待队列里面,他有进程,这个进程就是咱们调用epoll_wait(…)函数进去的进程。这个进程,从eventpoll等待队列里面,迁移到工作队列里面。
    a) Epoll_wait(…)函数返回值,是Int类型,返回0表示没有就绪socket,返回大于0表示有几个就绪的socket,-1表示异常。那有没有表示出来哪个socket是就绪的?获取就绪的socket是怎么实现的?
    i. Epoll_wait函数,他调用的时候,会传入一个epoll_event事件数组指针,epoll_wait函数正常返回之前,会把就绪的socket事件 信息,拷贝到这个数组指针里面 ( 指针表示在数组里) ,返回到上层程序,这样就可以通过这个数组拿到就绪列表。
    b) Epoll_wait(…)函数可不可以设置称非阻塞的?
    ii. 可以的,默认epoll_wait(…)是阻塞的,有一个参数,表示阻塞时间的长度,如果这个参数设置为0,就表示这个epoll_wait是非阻塞调用,每次调用她都会去检查就绪列表。
    c) Eventpoll中它需要存放需要监视的这个socket集合信息吗,存放socket集合信息,采用什么数据结构?
    iii. 采用红黑树,因为这个socket集合信息,经常会有增删改查的需求,红黑树合适,保持一种相对稳定的查找效率,复杂度是O(Log(N))。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值