zoukankan      html  css  js  c++  java
  • 网络

    网络学习内容

    网络的分层:

    OSI:网络划分为7层:物理层、数据链路层、网络层、传输层、会话层、表示层、应用层

    TCP/IP:网络接口层、网络层、传输层、应用层

    各层协议:

    应用层:HTTP、FTP、SMTP、Telnet

    传输层:TCP、UDP

    网络层:IP

    网络接口层:ARP、RARP

    TCP:

    TCP报文头、三次握手、四次挥手、滑动窗口、重试机制、拥塞控制...TCP编程

    UDP:UDP报文头、UDP的编程

    UDP和TCP的区别

    网路层:IP协议、IP的协议头,IP协议的划分、 A、B、C、D、E类的网络划分、子网掩码

    IP路由规则

    socket编程步骤

    同步、异步、阻塞、非阻塞

    BIO(同步阻塞IO)

    NIO(同步非阻塞IO)

    AIO(异步非阻塞IO)

    网络通信框架:NettyMina -》dubbo

    参考书籍

    《TCP/IP协议 卷一》 第3、9、11、16-21章

     

    网络概括

    计算机网络:通过传输介质、通信设备和网络通信的协议、把分散在不同地点的计算机设备互联起来、实现资源共享和数据传输的系统

     

    网络协议:TCP、UDP、IP...

    网络编程:编写程序使网络中的两个或多个设备(计算机)直接进行通信

     

    OSI网络模型

    国际标准化组织提出的网络的开发互联参考模型OSI(Open System InterConnection),将网络划分成7层

    图片.png

    应用层:OSI的最高层,是直接为应用程序提供服务,作用是实现一系列的业务处理需要服务

    表示层:关注的是传输数据信息的格式定义,为应用成提供的信息服务、提供的数据格式、控制信息的格式、数据加密的统一表示

    会话层:主要功能是负责应用程序的建立和释放、维护通信的稳定,提供单工、半双工、全双工通信方式、使系统服务通信更加有序

    传输层:保证源端点和目的端点之间(应用间可靠传输)的可靠传输,建立连接时三次握手、断开连接时四次挥手。

    网络层:保证源主机节点和目的主机的节点的可靠传输、包括路由选择、网络寻址、流量控制

    数据链路层:计算机网络相邻节点的可靠传输

    物理层:通过通信信道传输原始比特流 ,为数据端设备提供传输数据的通道

     

    TCP/IP协议族

    TCP/IP即Transmission Control Protocol/Internet Protocol 的缩写,即是传输控制协议/因特网互联协议

    图片.png

    应用层:提供网络应用的服务

    传输层:保证源程序到目的程序的端到端的可靠通信(端口)

    网络层:源主机到目的主机间的可靠通信(IP)

    网络接口层:对应的是OSI中的数据链路层和物理层

    各层协议简介

    图片.png

    应用层:

    协议:FTP、SMTP、HTTP

    FTP(File Transfer Protocol):文件传输协议<端口号 21> 进行文件传输,减少或消除不同操作系统下处理文件的不兼容性

    HTTP(Hypertext Transfer Protocol)超文本传输协议<端口号 80> 面向浏览器的事务处理协议

    SMTP(Simple Mail Transfer Protocol)简单邮件传输协议<端口号25> 用来发送邮件

    传输层:

    TCP(Transmission Control Protocol)传输控制协议提供的可靠的面向连接的服务

    传输数据前需要建立通信,结束后需要释放,全双工,可靠,有序,无丢失,不重复

    UDP(User Datagram Protocol)用户数据包协议发送数据是不可靠的,不需要连接,不能保证数据可靠交互交付

    网络层:

    IP(IPv4、IPv6)(Internet Protocol)网络互连互通协议

    ICMP(Internet Control Message Protocol)Internet控制报文协议

    数据链路层:

    ARP(Address Resolution Protocol)地址解析协议,实现通过IP地址得到物理地址(Mac 地址)

    RARP( Reverse Address Resolution Protocol)反向地址解析协议 ,将MAC地址解析成IP地址

     

    数据包

    每个分层中,都会对发送的数据封装一个首部,首部包含必要的信息,地址信息和协议信息

    图片.png

    数据包首部

    网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上一层传过来的数据。首部的结构由协议的具体规范详细定义。在数据包的首部,明确标明了协议应该如何读取数据。反过来说,看到首部,也就能够了解该协议必要的信息以及所要处理的数据。包首部就像协议的脸。

     

    数据处理过程

    假如a向b发送消息为例:

    图片.png

    数据处理流程

    ① 应用程序处理

    首先应用程序会进行编码处理,这些编码相当于 OSI 的表示层功能;

    编码转化后,邮件不一定马上被发送出去,这种何时建立通信连接何时发送数据的管理功能,相当于 OSI 的会话层功能。

    ② TCP 模块的处理

    TCP 根据应用的指示,负责建立连接、发送数据以及断开连接。TCP 提供将应用层发来的数据顺利发送至对端的可靠传输。为了实现这一功能,需要在应用层数据的前端附加一个 TCP 首部。

    ③ IP 模块的处理

    IP 将 TCP 传过来的 TCP 首部和 TCP 数据合起来当做自己的数据,并在 TCP 首部的前端加上自己的 IP 首部。IP 包生成后,参考路由控制表决定接受此 IP 包的路由或主机。

    ④ 网络接口(以太网驱动)的处理

    从 IP 传过来的 IP 包对于以太网来说就是数据。给这些数据附加上以太网首部并进行发送处理,生成的以太网数据包将通过物理层传输给接收端。

    ⑤ 网络接口(以太网驱动)的处理

    主机收到以太网包后,首先从以太网包首部找到 MAC 地址判断是否为发送给自己的包,若不是则丢弃数据。

    如果是发送给自己的包,则从以太网包首部中的类型确定数据类型,再传给相应的模块,如 IP、ARP 等。这里的例子则是 IP 。

    ⑥ IP 模块的处理

    IP 模块接收到 数据后也做类似的处理。从包首部中判断此 IP 地址是否与自己的 IP 地址匹配,如果匹配则根据首部的协议类型将数据发送给对应的模块,如 TCP、UDP。这里的例子则是 TCP。

    另外吗,对于有路由器的情况,接收端地址往往不是自己的地址,此时,需要借助路由控制表,在调查应该送往的主机或路由器之后再进行转发数据。

    ⑦ TCP 模块的处理

    在 TCP 模块中,首先会计算一下校验和,判断数据是否被破坏。然后检查是否在按照序号接收数据。最后检查端口号,确定具体的应用程序。数据被完整地接收以后,会传给由端口号识别的应用程序。

    ⑧ 应用程序的处理

    接收端应用程序会直接接收发送端发送的数据。通过解析数据,展示相应的内容。

    传输层:TCP和UDP协议

    TCP协议

    TCP(Transmission Control Protocol)传输控制协议,面向连接的传输协议,在传输层。

    TCP协议特点

    • 面向连接:通信之前必须建立连接,通信后断开连接
    • 每一个TCP连接只能是点对点的(一对一)
    • 提供的可靠的交付服务:通过TCP连接传输的数据,无差错,不丢失,不重复
    • 提供全双工通信
    • 面向字节流:TCP中的传输数据是以流的形式传输
    • TCP的首部占20字节

    TCP报文头部

    图片.png

    源端口

    源端口占用16bit位,表示发送方主机进程占用的端口号,通过端口可以表时主机上的某一个应用

    目的端口

    目的端口占用16bit位,表示的是目的主机的端口号,网络通信连接接收方需要用IP+端口,IP在网络层,端口就在传输层记录, 端口号的个数 2^16 =65536

    序号

    序号占用32位,对发送的数据进行编号,接收方返回的确认序号就是下一个发送包的编号

    确认号

    确认号32位,确认号接收端发送给发送端的确认编号,表示该编号之前的数据都成功接收,接收端接收到该消息之后就可以继续发送后续的报文

    报头长度(数据偏移)

    占用了4个bit位,以32位(4字节)即字长为单位,报头的长度是可变的,最大是60个字节(占用4个bit位,2^4=15, 15 *4(字节) =60字节)

    保留位

    保留位占6位,必须全为0

    标志位

    标志位占6bit,存在6个标志,每个占用一个bit,如果有效则置为1,依次URG、ACK、PSH、RST、SYN、FIN

    URG:该位为1表示TCP包的紧急指针有效,督促上层应用尽快的处理紧急处理

    ACK:确认号有效

    PSH:push操作,该标志位为1接收方应尽快将报文交给应用层

    RST:(rest)连接复位,在通信过程找那个存在主机奔溃或其他原因早成连接错误,用该标志位来拒绝连接

    SYN:是一个同步序号,通常是与ACK合用来建立连接,三次握手

    FIN:需要端口连接时用到该标志位

    窗口大小

    占用16bit位,TCP中用来进行流量控制,通过窗口可以告知发送方窗口的大小,通过动态的控制发送窗口大小来控制发送到网络中包的大小,网络通畅时发送大的数据包,网络发送拥塞时发送小的数据包

    检验和

    占用16bit,通过对首部和数据进行校验来判断当前包是否正确

    紧急指针

    只有当URG标志置为1时才有效,紧急指针是一个正的偏移,和序号字段中的是相加表示紧急指针的最后的序号,TCP的紧急方式发送一条数据给接收端

    三次握手

    图片.png

    三次握手过程:

    第一次握手:

    客户端发送请求报文大服务端,并进入SYN_SENT状态,等待服务的确认(SYN1,seq=x)

    第二次握手:

    服务端接收到连接请求,如果同意建立连接,向客户端发回确认报文段,服务端进入到SYN_RECV状态,

    (SYN=1,ACK=1,ack=x+1,seq=y)

    第三次握手:

    客户端接收到服务端的确认报文后,向服务端给出确认报文段,连接完成,客户端和服务端都进入了ESTAB-LISHED状态,即可以双向通信(ACK=1,ack=y+1,seq=x+1)

     

    四次挥手

    图片.png

    四次挥手过程:

    第一次挥手:

    客户端向发送端发送断开连接报文段,客户端进入FIN-WAIT1状态(FIN1,seq=u)

    第二次挥手:

    服务端接收到客户端的请求报文后,确认客户端的消息,由服务端回复客户端一个ACK报文,服务端进入到

    CLOSE-WAIt状态(ACK=1,seq=v, ack=u+1),客户端接收到第二次挥手消息后计入到FIN-WAIT2状态,这事客户端不能给服务端发送消息,但服务端可以继续给客户端发送消息(单通道通信)

    第三次挥手:

    服务端向客户端发起断开连接报文,服务端进入到LASK-ACK状态(FIN=1,ACK=1,seq=w, ack=u+1)

    第四次挥手:

    客户端接收到服务端发送的请求报文后,向服务端发送一个确认消息,客户端进入到TIME-WAIT状态。在等待2MSL时间后才进入到CLose状态,服务端接收到客户端的消息后进入到CLOSE状态(ACK=1,seq=u+1,ack =w+1)

     

     


    1、三次握手可以改成2次握手嘛?为什么

    不可以,TCP的三次握手是为了防止已经过期的连接再次连接上

    2、TCP可以设置成3次挥手?为什么?

    不可以,假如是3次挥手

    图片.png

    存在三次挥手的情况:客户端和服务端同时发起挥手请求

    图片.png

    3、为什么第四次挥手之后需等待2MSL时间呢?

    1、为了保证发送的最后一个ACK到达服务端

    2、防止已经失效的连接请求报文段出现在本次连接中

     

     

    TCP如何发送数据的

    TCP的层的包发送和接收存在两个缓冲区,一个是发送缓冲区,一个是接收缓冲区

    图片.png

    发送缓冲区:TCP传输层数据包有大小限制,如果包过大的话,拆分成多个TCP包,发送的数据包过小,等待多个应用层消息打包成一个TCP的数据包进行发送 TCP报文头至少是20个字节

    接收缓冲区:数据是没有明确的边界,接收数据是没办法指定一个或者多个消息一起读,只能选择一次读取多大的数据流,而这个数据流中包含着某个消息包的一部分数据

     

    名词:半包,粘包,拆包

    图片.png

    滑动窗口协议

    用于网络数据传输时的流量控制,以免发生拥塞

    图片.png

    移动过程:

     

    图片.png

    图片.png

    拥塞控制

    防止过多的数据包注入到网络中。通过拥塞控制使网络中的路由器或者链路不可过载

    慢启动,拥塞控制

    快重传,快回复

    图片.png

    快重传机制

    图片.png

    TCP如何保证数据可靠性

    1、校验和:报文头的检验和用来保证当前传输包的完整性

    图片.png

    2、序列号:报文头中的序列号是用来对TCP包进行编号,接收端通过编号可以对数据进行去重和排序

    3、确认应答机制(ACK)

    4、超时重传/快重传

    5、拥塞控制

    UDP协议

    UDP(User Datagram Protocol)用户数据包协议,是TCP/IP协议族中无连接的传输层协议

    UDP协议特点

    无连接

    尽最大努力交付

    面向报文

    无拥塞控制

    支持一对一,一对多,多对一的交互通信

    首部开销小

    UDP的报文头

    图片.png

    UDP报文头信息:

    源端口;目的端口:都占用是16bit,分别表示源端口和目的端口

    长度:UDP数据包的长度,包含头部和数据,最小值为8字节(仅包含头部)

    校验和:用校验UDP包的完整性,有错包就丢失

    应用场景

    适用实时性要求比较高的场景,比如视频,语言、直播....

     

     

    Socket通信

    Socket即套接字,两个主机之间的逻辑连接的断点

    Socket编程是一种C/S模式的编程,即客户端和服务端

    图片.png

    TCP编程

    服务端:

    图片.png

    客户端:

    图片.png

    注意:

    1、客户端连接的端口和服务端绑定的端口必须一致才能连接成功

    否则会抛出以下异常

    图片.png

    2、C/S模型中一定先启动服务端(服务端需要绑定端口处于Listen状态),客户端才能启动连接

    否则抛出以下异常

    图片.png

     

    UDP编程

    服务端:

    图片.png

    客户端:

     

    通过UDP编程:发送的数据不关注接收端是否在线,接收端在线就可以接受到数据,接收端不在线数据也可以正常发送

     

    C/S模式 B/S模式

    C/S模式(Client/Server )客户端和服务端

    C/S模式中服务端中主要进行业务交互,数据存储,考虑并发,高可用等,客户端主要是页面展示,将服务端返回去的内容展示在客户端,将客户端提交的信息做一处理

     

    B/S(Browser/Server) 浏览器和服务端

    B/S模式中服务端用来进行业务交互,数据存储等,浏览器主要用来展示和基础的交互

     

    C/S和B/S的区别:

    B/S模型浏览器具有通用性,对于不同的系统中安装浏览器都可以进行交互,不用考虑系统间的差异问题

    B/S模型中浏览器侧重内容展示,页面渲染,不处理业务逻辑,业务逻辑都放在服务端做处理,交互上比较多,需要网络带宽大

    B/S模型对网络要求比较高,网络条件差的情况下用户体验较差

    B/S模型中更新内容,只需要对服务端进行升级,客户端请求就能直接看到最新的结果

     

    C/S模型中客户端也能实现一些业务逻辑,能够减轻服务端的业务交互提高服务端的响应速度和提高并发量

    C/S模型中客户端是需要适配系统,不同的系统要考虑适配,开发客户端成本是比较高

    C/S模型中客户端考虑页面展示,服务端提供数据交给客户端,客户端和服务端仅仅交互数据,网络带宽消耗小

    即使网络不佳,C/S的模型响应是比较快

    C/S模式中需要更新内容,就需要对客户端和服务端同时进行升级

     

    TCP和UDP的区别

    TCP:面向连接、可靠的、流式服务

    UDP:无连接,不可靠的、数据包服务

     

    如何保证UDP的数据不丢失?

    借鉴TCP的ACK机制:UDP发送出的数据包,接收方接收到之后回复一个确认包,发送端若未接收到确认包可以再次返送

     

    客户端需要IP+端口的形式,而服务端直接绑定端口没有IP?

    三次握手(客户端connection)客户端主动连接服务端(连接的服务端的IP和服务端的端口),服务端等待连接(端口)

     

    网络层:IP协议

    IP协议属于ICP/IP协议族中的网络层,网络层主要的作用“实现终端点对点的通信”

     

    IP报头格式

    计算机中,为了识别通信的对端,必须要一个类似地址的识别码进行识别。在数据链路中是MAC地址表示不同的计算机,在网络层,使用的IP地址

     

     

    IP首部至少20个字节

    • 版本

    占用4bit,有4(IPV4)和6(IPV6)两个值

    • 首部长度

    占用4位,最大值为15,值为1表示的是一个32位的长度,也就是4字节,首部占用最大字节是:4*15=60字节,固定占用字节是20字节,首部的长度必须是4字节的整数倍,如果不足,在尾部填充部分进行填充

    • 区分服务

    占8位,一般情况下不使用

    • 总长度

    占用16位,包括首部长度和数据部分长度

    • 标识

    占用16位,在数据报长度过长发生分片的情况下,相同的数据报的不同分片具有相同的标识符

    • 标志

    占用3位,第一位保留位,第二位表示该IP报文是否分片(1表禁止分片 0表示分片)第三位表示该IP报文是非最后一位

    • 片偏移

    和标识一起,用于发生分片的情况下,片偏移的单位是8字节

    • 生存时间

    TTL:存在是为了方式无法交付的数据报一致存在互联网中,以路由器跳数为单位,当TTL为0时就丢弃数据包

    • 协议

    指出携带的数据应该上交给哪一个协议处理,例如ICMP、TCP、UDP等

    • 首部校验和

    首部校验和是用来检测数据包在网络传输中是否完整

    • 源地址、目的地址

    各占用32位,分别用来存放发送方IP和接收端IP

     

    IP地址表示形式

    IP地址(IPv4地址)由32位正整数表示,IP地址在计算内部是以二进制的形式处理,将32位的IP地址以每8位为一组,分成4组,每组以“.”隔开,再将每一组转化为

    点分十进制:数据分4组,每一组的范围在[0,255],如:数据表示为255.255.255.255

    二进制表示:每一组8位,如:11111111 11111111 11111111 11111111

    IP地址划分

    IP地址是由网络标识和主机标识组成

    网络标识在数据链路中每个端配置的不同的值,网络标识必须保证相互连接的每个段的地址不能重复,而相同端内相连的主机必须是相同的网络标识

    主机标识则不允许在同一个网段内(同一个网络标识段内)不允许出现重复

     

    IP地址的分级

    IP地址分为4个级别:分别是A类、B类、C类、D类,根据IP地址从高第一个到第4位的比特位对其网络标识和主机标识进行划分

     

    • A类地址:

    首位以“0”开头的地址, 高8位为网络标识,用0.0.0.0~127.0.0.0 ,低24位为主机标识,一个网段中能够容纳的主机地址上线2^24-2

    • B类地址

    高两位是“10”开头的地址,高16位为网络标识,128.0.0.0~191.255.0.0,低16位置为主机标识,一个网段能够容纳的主机标识是2^16=65536-2

    • C类地址:

    高3位是“110”开头的地址,高24位作为网络标识,192.0.0.0~223.255.255.0,低8位是主机标识,一个网段中主机标识上线为254个

    • D类地址:

    高4位为“1110”开头的地址,32位全部作为网络标识,224.0.0.0~239.255.255.255

     

    DNS:域名系统

    DNS:将域名解析为IP地址

    一个域名有多个层次组成,包含顶级域名,二级域名,三级域名以及四级域名

     

    图片.png

    图片.png

    域名服务器分类:

    域名服务器主要分为四类

    根域名服务器:解析顶级域名

    顶级域名服务器:解析二级域名

    权限域名服务器:解析区内的域名

    本地域名服务器:默认域名服务器,可以在其中配置缓存

     

    域名解析过程

    域名通过域名服务器解析使用递归和迭代两种方式:

    迭代方式:本地域名服务器向一个域名服务器发送域名解析请求之后,结果返回到本地域名服务器,然后本地域名服务器继续向其他的域名服务器请求解析

    递归方式:请求的结果不是直接返回去,而是继续向前请求解析,最后的结果才会返回

    图片.png

    经典面试题

    从输入网址到获取页面的网络请求的过程?

    • 域名解析

     1、先查找浏览器中缓存是否存在对应IP,浏览器会缓存DNS一段时间

     2、如果没找到,在从Hosts文件查找是否有域名和对应IP的信息

     3、如果没找,再从路由器中查找

     4、如果没找到,再从DNS缓存查找

     5、如果还是没找到,浏览器域名服务器会想根域名服务器查找域名对应的IP

    • 网络传输通信

    1、拿到IP之后进行通信,先和对方建立连接(三次握手...),连接完成后建立了可靠的通道,就可以通过http请求数据打包发送给服务端,

    2、服务端接收请求报文,处理报文信息,处理完成后将数据打包发送给客户端

    3、通信完成后,通过4次挥手,断开TCP的连接

    4、客户端拿到服务端返回的数据

    • 页面渲染

    页面的解析渲染(js、CSS、Html)

     

    Java的网络编程

    IO相关的操作:

    操作字节的:InputStream和OutputStream

    操作字符:Reader和Writer

    操作文件:File

    操作网络:Socket

    网络IO编程是:BIO、NIO、AIO

     

    IO模型术语

    IO模型之前需要明白同步/异步、阻塞/非阻塞

    同步(Synchronization)/异步(Asynchronization)

    同步和异步是基于应用程序与操作系统处理IO所采用的方式

    同步:是应用程序直接参与IO读写的操作

    异步:所有的IO读写操作都交给操作系统处理,应用程序只需要等待通知

     

    阻塞(Block)/非阻塞(NonBlock)

    阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式

    阻塞:需要等待缓冲区的数据准备好后才能处理其他的事情,否则一致等待在这里

    非阻塞:当进程访问数据缓冲区时,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好了,也直接返回

     

    图片.png

     

    IO模型可以分为:

    同步非阻塞/同步阻塞/异步阻塞/异步非阻塞

    同步阻塞IO:在这种IO模型下,用户进程发起一个IO操作以后,必须等待IO操作完成,只有当真正完成IO操作之后,用户程序才能继续运行。Java中的传统的IO模型就是同步阻塞模型,BIO

    同步非阻塞IO:在这种IO模型下,用户进行发起一个IO操作后可立即返回做其他事情,但是用户进行需要时不时询问IO操作是否完成。这个要求用户进行不断的进行询问,从而引起不必要的CPU资源的浪费,NIO

    异步阻塞IO:在这种IO模型下,应用发起一个IO操作,不等待内核IO操作完成,等内核完成IO操作之后会通知应用程序。

    异步非阻塞IO:在这种IO模型下,用户进程发起一个IO操作然后立即返回,等IO操作真正完成之后,应用程序会得到IO操作完成的通知,此时用户进程只需要对数据进行处理,不需要进行实际的IO读写操作。AIO

     

    常见的IO模型

    对于IO操作,主要包含两个阶段:

    1. 等待数据准备就绪
    2. 将数据从内核拷贝到进程中

    例如:读函数:分为等待系统可读和真正读

    写函数:分为等待网卡可以写和真正写

     

    • 阻塞IO
    • 非阻塞IO
    • I/O复用
    • 信号驱动IO
    • 异步IO

     

    示例说明:

    活动:演唱会

    角色1:举办方,售票业务员

    角色2:黄牛

    角色3:小明

    角色4:送票快递员

    阻塞IO

    图片.png

    举例:小明从家里先到演唱会现场购票问售票员买票,但是票还没有出来,还需要两天才出来,小明就直接在售票厅打地铺,一致等到票出来,然后买票

    非阻塞IO

    通过进程反复调用IO函数,采用轮训方式,占用CPU

    图片.png

    举例:小明从家里先到演唱会现场购票问售票员买票,但是票还没有出来,然后小明走了,办理其他事情,然后过两个小时,又去售票现场买票,如果票还没有出来,小明又去办其他的事情,重复上面的操作,知道买到票结束

     

    IO复用

    图片.png

    主要使用IO复用器:selectpollepoll,将关注的时间都注册到复用器上,由OS来关注事件是否就绪

    一个IO请求,需要两次调用,两次返回。

    能够实现对多个IO端口进行监听,多个连接共用一个等待机制

     

    举例:小明想买票看演唱会,直接给黄牛(select等IO复用器)打电话,帮我留意买个票,票买了通知我,我自己去取,那么票还没出来之前,小明完全可以做自己的事情

    信号驱动IO

    图片.png

    举例:小明想买票看演唱会,给举办方业务员说,给你一个电话,有票了给我打个电话通知一下(需要系统支持,Linux系统支持,window没有这种机制),我自己再来买票(小明完全可以干自己的时时期,但是票还是需要小明自己去取)

     

    异步IO

    图片.png

     

    举例:小明想买票看演唱会,给举办方的售票员打电话说,给你留个地址,有票了请你通知快递员,由快递员将票送到给定的地址,当小明听到敲门声,看见快递员了,就知道票已经好了并且已经送到他手上

     

    Java中IO模型

    BIO(Blocking IO)同步阻塞模型 JDK1.4之前使用该模型

    NIO  同步非阻塞模型,JDK1.5开始提供

    AIO 异步非阻塞模

    BIO的编程流程

    服务端:

    1、创建ServerSocket实例

    2、绑定端口

    3、通过accept来监听客户端的连接,有客户端连接会返回socket实例

    4、进行读写操作

    5、关闭资源

     

    客户端:

    1、创建socket实例

    2、通过connect并指定服务端的IP+端口连接服务端

    3、进行读写操作

    4、关闭资源

    服务端接收客户端的多次请求

    服务端:

     

     客户端:

    BIO是同步阻塞模型,BIO编程中阻塞体现在哪里?

    accept方法:阻塞接收客户端的连接

    read方法/write方法

    connect方法:和服务端建立连接,连接的过程中connect会阻塞

    服务端可以处理很多的客户端的连接?

    怎么设计考虑呢??

    在accept能进行循环接收客户端连接,借助于多线程处理

    主线程负责接收客户端的连接:accept放在主线程执行

    子线程负责和客户端交互,每一个客户端连接都分配一个子线程

    图片.png

     

    多线程+BIO完成多用户请求处理

    服务端:通过bind函数new InetSocketAdress类连接

    package BIO;
    import java.io.*;
    import java.net.InetSocketAddress;
    import java.net.ServerSocket;
    import java.net.Socket;
    
    public class BIOServerThreads {
        //当配置服务器时,服务器会将本机当作默认ip地址,所以不需要我们去配置
        //负责TCP通信的类
        ServerSocket serverSocket;
        public BIOServerThreads() {
            try {
                //初始化时只需要端口号即可
                //绑定端口
                serverSocket=new ServerSocket();
                serverSocket.bind(new InetSocketAddress(9999));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void startServer() {
            System.out.println("开始等待用户建立连接");
            while(true){
                try {
                    //等待客户端连接  accept()方法就是监听有哪些客户端在向他发送请求也就是socket,,如果没有连接,那么就会阻塞,一直等待
                    Socket clientSocket = serverSocket.accept();
                    // 如果有请求套接字就直接接收并创建一个socket,否则阻塞
                    //创建子线程,因为不是一个客户端去访问服务器,服务器要为多个客户端服务,而我们为了处理客户端,我们只需要知道客户端的socket就好
                    new Thread(new Link(clientSocket)).start();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        private class Link extends Thread{
            private Socket clientSocket;
            Link(Socket socket){
                clientSocket = socket;
            }
            @Override
            public void run() {
                try {
                    //当客户端和服务器建立连接完毕之后 会有一个返回值
                    //返回值作用:clientSocket和建立连接的客户端一一对应
                    //我们使用clientSocket完成和客户端的通信
                    //accept(); 是阻塞的方法  如果没有用户建立连接代码会阻塞住
                    System.out.println("开始通信");
                    InputStream inputStream = null;
                    BufferedReader reader = null;
                    OutputStream outputStream = null;
                    BufferedWriter writer = null;
                    while (true) {
                        inputStream = clientSocket.getInputStream();
                        //inputStream 节点流
                        reader = new BufferedReader(new InputStreamReader(inputStream));
                        String s = reader.readLine();
                        System.out.println("收到客户端的消息为: " + s);
                        if (s.equals("exit")) {
                            break;
                        }
                        outputStream = clientSocket.getOutputStream();
                        //获取到发送消息的字节流
                        writer =
                                new BufferedWriter(new OutputStreamWriter(outputStream));
                        String str = "i receive msg
    ";//'/n'刷新网络的缓冲区
                        writer.write(str);
                        writer.flush();
                    }
                    clientSocket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            new BIOServerThreads().startServer();
        }
    }

    客户端:通过connect函数连接

    package BIO;
    import java.io.*;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.util.Scanner;
    
    public class BIOClientThreads{
        //套接字来存储ip和port
        private Socket socket;
        private Scanner scanner;
    
        public BIOClientThreads() {
            //建立连接
            try {
                scanner = new Scanner(System.in);
                //连接服务端
                socket=new Socket();
                socket.connect(new InetSocketAddress("127.0.0.1",9999));
                //如果socket创建成功说明连接建立成功了
                startClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }finally {
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public void startClient(Socket socket) {
            //因为发消息不是一条消息就发完了,而是要发很多条才结束
            while (true) {
                String s = scanner.nextLine();
                try {
                    OutputStream outputStream = socket.getOutputStream();
                    //使用转换流流
                    BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(outputStream));
                    //将数据流输出
                    writer.write(s);
                    //强制将缓存中的数据直接发送
                    writer.write("
    ");
                    writer.flush();
                    //如果当前字符是exit,那么就说明结束发送消息,跳出循环。
                    if (s.equals("exit")) {
                        break;
                    }
                    //发送消息之后服务器一定会给我们返回消息,所以我们就要在网络里读取流
                    InputStream inputStream = socket.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream));
                    String s1 = reader.readLine();
                    System.out.println("收到服务器回复为:" + s1);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    
        public static void main(String[] args) {
            //启动客户端
            new BIOClientThreads();
        }
    
    }

    方法2:通过构造函数

    服务端:ServerSocket类传递port参数

    package BIO;
    import java.io.*;
    import java.net.ServerSocket;
    import java.net.Socket;
    //用构造函数的方法连接服务端和客户端
    public class BIOSerCon {
        ServerSocket sSocket;
        Socket socket;
        //端口号
        private int port = 1111;
    
        public BIOSerCon() {
            //创建ServerSocket实例
            try {
    
                sSocket = new ServerSocket(port);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
        public void start() {
            while (true) {
                try {
                    //进行监听,等待客户端连接,返回的是客户端的socket实例
                    socket = sSocket.accept();
                    System.out.println("客户端:" + socket.getRemoteSocketAddress() + "连接上了");//获取客户端的ip和port
    
                    //当有客户端连接,就为其创建一个子线程
                    new Thread(new BB(socket)).start();
                } catch (IOException e) {
                    e.printStackTrace();
                } finally {
                    if (sSocket != null) {
                        try {
                            sSocket.close();
                        } catch (IOException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        }
    }
     class BB extends Thread{
        private Socket clientSocket;
        BB(Socket socket){
            clientSocket = socket;
        }
        @Override
        public void run() {
            try {
    
                System.out.println("开始通信");
                InputStream inputStream = null;
                BufferedReader reader = null;
                OutputStream outputStream = null;
                BufferedWriter writer = null;
                while (true) {
                    inputStream = clientSocket.getInputStream();
    
                    reader = new BufferedReader(new InputStreamReader(inputStream));
                    String s = reader.readLine();
                    System.out.println("收到客户端的消息为: " + s);
                    if (s.equals("exit")) {
                        break;
                    }
                    outputStream = clientSocket.getOutputStream();
    
                    writer = new BufferedWriter(new OutputStreamWriter(outputStream));
                    String str = "get it
    ";//'/n'刷新网络的缓冲区
                    writer.write(str);
                    writer.flush();
                }
                clientSocket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
         public static void main(String[] args) {
             new BIOSerCon().start();
         }
    }

    客户端:Socket类传递ip,port

    package BIO;
    import java.io.BufferedReader;
    import java.io.IOException;
    import java.io.InputStreamReader;
    import java.io.OutputStream;
    import java.net.InetSocketAddress;
    import java.net.Socket;
    import java.util.Scanner;
    
    //用构造函数的方法连接服务端和客户端
    public class BIOCliCon {
        //服务器和客户端的端口号要一致,就好比,QQ不能给微信发消息一个道理。
        private int port = 1111;
        //127.0.0.1是特殊地址,也就是本机地址,用来测试
        private String ip = "127.0.0.1";
        private Socket socket;
        private Scanner scanner;
        public BIOCliCon(){
            try {
                socket = new Socket(ip,port);
                start(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    
    
        public void start(Socket socket) {
            try {
              socket.connect(new InetSocketAddress("127.0.0.1", 9999));
              System.out.println("客户端连接服务端成功");
              OutputStream outputStream = socket.getOutputStream();
    
              scanner = new Scanner(System.in);
              scanner.useDelimiter("
    ");//分隔符
            //读服务端返回的数据
              BufferedReader reader = null;
              while (scanner.hasNext()) {
                String msg = scanner.nextLine();
                outputStream.write((msg + "
    ").getBytes());
                reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String line = reader.readLine();
                System.out.println(line);
                if ("exit".equals(msg)) break;
            }
    
            outputStream.flush();
            //关闭资源
            reader.close();
            outputStream.close();
            socket.close();
    
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
        public static void main(String[] args) {
            new BIOCliCon();
        }
    
    }
    Netty核心组件
    为了后期更好地理解和进一步深入 Netty,有必要总体认识一下 Netty 所用到的核心组件以及他们在整个 Netty 架构中是如何协调工作的。Nettty 有如下几个核心组件:

    Channel
    ChannelFuture
    EventLoop
    ChannelHandler
    ChannelPipeline
     

    Channel
    Channel 是 Netty 网络操作抽象类,它除了包括基本的 I/O 操作,如 bind、connect、read、write 之外,还包括了 Netty 框架相关的一些功能,如获取该 Channe l的 EventLoop。
    在传统的网络编程中,作为核心类的 Socket ,它对程序员来说并不是那么友好,直接使用其成本还是稍微高了点。而Netty 的 Channel 则提供的一系列的 API ,它大大降低了直接与 Socket 进行操作的复杂性。而相对于原生 NIO 的 Channel,Netty 的 Channel 具有如下优势(摘自《Netty权威指南(第二版)》):

    在 Channel 接口层,采用 Facade 模式进行统一封装,将网络 I/O 操作、网络 I/O 相关联的其他操作封装起来,统一对外提供。
    Channel 接口的定义尽量大而全,为 SocketChannel 和 ServerSocketChannel 提供统一的视图,由不同子类实现不同的功能,公共功能在抽象父类中实现,最大程度地实现功能和接口的重用。
    具体实现采用聚合而非包含的方式,将相关的功能类聚合在 Channel 中,有 Channel 统一负责和调度,功能实现更加灵活。
     
              //客户端          
              Channel channel = bootStrap.connect(ip, port).sync().channel(); System.out.println("客户端启动了"); //通过channel和服务端交互 SendService sendService = new SendService(channel); sendService.send(); //关闭通信 channel.closeFuture().sync();

     

    EventLoop
    Netty 基于事件驱动模型,使用不同的事件来通知我们状态的改变或者操作状态的改变。它定义了在整个连接的生命周期里当有事件发生的时候处理的核心抽象。
    Channel 为Netty 网络操作抽象类,EventLoop 主要是为Channel 处理 I/O 操作,两者配合参与 I/O 操作。
    下图是Channel、EventLoop、Thread、EventLoopGroup之间的关系(摘自《Netty In Action》):
     
    一个 EventLoopGroup 包含一个或多个 EventLoop。
    一个 EventLoop 在它的生命周期内只能与一个Thread绑定。
    所有有 EnventLoop 处理的 I/O 事件都将在它专有的 Thread 上被处理。
    一个 Channel 在它的生命周期内只能注册与一个 EventLoop。
    一个 EventLoop 可被分配至一个或多个 Channel 。

    当一个连接到达时,Netty 就会注册一个 Channel,然后从 EventLoopGroup 中分配一个 EventLoop 绑定到这个Channel上,在该Channel的整个生命周期中都是有这个绑定的 EventLoop 来服务的。
           //服务端
            //创建两个事件循环组,boss worker
            NioEventLoopGroup boss=new NioEventLoopGroup();
            NioEventLoopGroup worker=new NioEventLoopGroup(5);
            try {
                //创建启动辅助类
                ServerBootstrap serverBootStrap = new ServerBootstrap();
                serverBootStrap.group(boss,worker)        
    
           //客户端
            NioEventLoopGroup loopGroup = new NioEventLoopGroup();
            //创建启动辅助类
            Bootstrap bootStrap = new Bootstrap();
            bootStrap.group(loopGroup)
    ChannelFuture
    Netty 为异步非阻塞,即所有的 I/O 操作都为异步的,因此,我们不能立刻得知消息是否已经被处理了。Netty 提供了 ChannelFuture 接口,通过该接口的 addListener() 方法注册一个 ChannelFutureListener,当操作执行成功或者失败时,监听就会自动触发返回结果。
              //同步启动服务端
                ChannelFuture cf = serverBootStrap.bind(port).sync();
                System.out.println("服务端启动了");
    
                //关闭
                cf.channel().closeFuture().sync();               
    ChannelHandler
    ChannelHandler 为 Netty 中最核心的组件,它充当了所有处理入站和出站数据的应用程序逻辑的容器。ChannelHandler 主要用来处理各种事件,这里的事件很广泛,比如可以是连接、数据接收、异常、数据转换等。
    ChannelHandler 有两个核心子类 ChannelInboundHandler 和 ChannelOutboundHandler,其中 ChannelInboundHandler 用于接收、处理入站数据和事件,而 ChannelOutboundHandler 则相反。
     
    //从SendService类里取出
    public class ClientHandler extends SimpleChannelInboundHandler<String> {
        //定义同步阻塞队列(同步:每次只能放一个线程,放完立马取),用户主线程和工作线程通信,将子线程获取的服务端返回给主线程
        public static SynchronousQueue<Integer> queue = new SynchronousQueue<Integer>();
    
        @Override
        protected void channelRead0(ChannelHandlerContext channelHandlerContext, String s) throws Exception {
    
        }
    
        //接收服务端返回的消息
        @Override
        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        }
    ChannelPipeline
    ChannelPipeline 为 ChannelHandler 链提供了一个容器并定义了用于沿着链传播入站和出站事件流的 API。一个数据或者事件可能会被多个 Handler 处理,在这个过程中,数据或者事件经流 ChannelPipeline,由 ChannelHandler 处理。在这个处理过程中,一个 ChannelHandler 接收数据后处理完成后交给下一个 ChannelHandler,或者什么都不做直接交给下一个 ChannelHandler。
     
    当一个数据流进入 ChannlePipeline 时,它会从 ChannelPipeline 头部开始传给第一个 ChannelInboundHandler ,当第一个处理完后再传给下一个,一直传递到管道的尾部。与之相对应的是,当数据被写出时,它会从管道的尾部开始,先经过管道尾部的 “最后” 一个ChannelOutboundHandler,当它处理完成后会传递给前一个 ChannelOutboundHandler 。
    当 ChannelHandler 被添加到 ChannelPipeline 时,它将会被分配一个 ChannelHandlerContext,它代表了 ChannelHandler和ChannelPipeline 之间的绑定。其中 ChannelHandler 添加到 ChannelPipeline 过程如下:
    1. 一个 ChannelInitializer 的实现被注册到了 ServerBootStrap中
    2. 当 ChannelInitializer.initChannel() 方法被调用时,ChannelInitializer 将在 ChannelPipeline 中安装一组自定义的 ChannelHandler
    3. ChannelInitializer 将它自己从 ChannelPipeline 中移除
                  protected void initChannel(NioSocketChannel ch) throws Exception {
                                //获取容器
                                ChannelPipeline pipeline = ch.pipeline();
                                pipeline.addLast(new StringDecoder());
                                pipeline.addLast(new StringEncoder());
                                pipeline.addLast(new ClientHandler());
                            }
  • 相关阅读:
    Android TTS
    观察者模式(C++实现)
    android 8未知来源app安装
    NotificationChannel
    java底层知识
    Java14
    数据库分区、分库分表
    二叉搜索树的第k大节点
    从上到下按层打印二叉树
    springcloud面试知识点
  • 原文地址:https://www.cnblogs.com/laurarararararara/p/12772702.html
Copyright © 2011-2022 走看看