网络学习内容
网络的分层:
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层
应用层:OSI的最高层,是直接为应用程序提供服务,作用是实现一系列的业务处理需要服务
表示层:关注的是传输数据信息的格式定义,为应用成提供的信息服务、提供的数据格式、控制信息的格式、数据加密的统一表示
会话层:主要功能是负责应用程序的建立和释放、维护通信的稳定,提供单工、半双工、全双工通信方式、使系统服务通信更加有序
传输层:保证源端点和目的端点之间(应用间可靠传输)的可靠传输,建立连接时三次握手、断开连接时四次挥手。
网络层:保证源主机节点和目的主机的节点的可靠传输、包括路由选择、网络寻址、流量控制
数据链路层:计算机网络相邻节点的可靠传输
物理层:通过通信信道传输原始比特流 ,为数据端设备提供传输数据的通道
TCP/IP协议族
TCP/IP即Transmission Control Protocol/Internet Protocol 的缩写,即是传输控制协议/因特网互联协议
应用层:提供网络应用的服务
传输层:保证源程序到目的程序的端到端的可靠通信(端口)
网络层:源主机到目的主机间的可靠通信(IP)
网络接口层:对应的是OSI中的数据链路层和物理层
各层协议简介
应用层:
协议: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地址
数据包
每个分层中,都会对发送的数据封装一个首部,首部包含必要的信息,地址信息和协议信息
数据包首部
网络中传输的数据包由两部分组成:一部分是协议所要用到的首部,另一部分是上一层传过来的数据。首部的结构由协议的具体规范详细定义。在数据包的首部,明确标明了协议应该如何读取数据。反过来说,看到首部,也就能够了解该协议必要的信息以及所要处理的数据。包首部就像协议的脸。
数据处理过程
假如a向b发送消息为例:
数据处理流程
① 应用程序处理
首先应用程序会进行编码处理,这些编码相当于 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报文头部
源端口
源端口占用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的紧急方式发送一条数据给接收端
三次握手
三次握手过程:
第一次握手:
客户端发送请求报文大服务端,并进入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)
四次挥手
四次挥手过程:
第一次挥手:
客户端向发送端发送断开连接报文段,客户端进入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次挥手
存在三次挥手的情况:客户端和服务端同时发起挥手请求
3、为什么第四次挥手之后需等待2MSL时间呢?
1、为了保证发送的最后一个ACK到达服务端
2、防止已经失效的连接请求报文段出现在本次连接中
TCP如何发送数据的
TCP的层的包发送和接收存在两个缓冲区,一个是发送缓冲区,一个是接收缓冲区
发送缓冲区:TCP传输层数据包有大小限制,如果包过大的话,拆分成多个TCP包,发送的数据包过小,等待多个应用层消息打包成一个TCP的数据包进行发送 TCP报文头至少是20个字节
接收缓冲区:数据是没有明确的边界,接收数据是没办法指定一个或者多个消息一起读,只能选择一次读取多大的数据流,而这个数据流中包含着某个消息包的一部分数据
名词:半包,粘包,拆包
滑动窗口协议
用于网络数据传输时的流量控制,以免发生拥塞
移动过程:
拥塞控制
防止过多的数据包注入到网络中。通过拥塞控制使网络中的路由器或者链路不可过载
慢启动,拥塞控制
快重传,快回复
快重传机制
TCP如何保证数据可靠性
1、校验和:报文头的检验和用来保证当前传输包的完整性
2、序列号:报文头中的序列号是用来对TCP包进行编号,接收端通过编号可以对数据进行去重和排序
3、确认应答机制(ACK)
4、超时重传/快重传
5、拥塞控制
UDP协议
UDP(User Datagram Protocol)用户数据包协议,是TCP/IP协议族中无连接的传输层协议
UDP协议特点
无连接
尽最大努力交付
面向报文
无拥塞控制
支持一对一,一对多,多对一的交互通信
首部开销小
UDP的报文头
UDP报文头信息:
源端口;目的端口:都占用是16bit,分别表示源端口和目的端口
长度:UDP数据包的长度,包含头部和数据,最小值为8字节(仅包含头部)
校验和:用校验UDP包的完整性,有错包就丢失
应用场景
适用实时性要求比较高的场景,比如视频,语言、直播....
Socket通信
Socket即套接字,两个主机之间的逻辑连接的断点
Socket编程是一种C/S模式的编程,即客户端和服务端
TCP编程
服务端:
客户端:
注意:
1、客户端连接的端口和服务端绑定的端口必须一致才能连接成功
否则会抛出以下异常
2、C/S模型中一定先启动服务端(服务端需要绑定端口处于Listen状态),客户端才能启动连接
否则抛出以下异常
UDP编程
服务端:
客户端:
通过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地址
一个域名有多个层次组成,包含顶级域名,二级域名,三级域名以及四级域名
域名服务器分类:
域名服务器主要分为四类
根域名服务器:解析顶级域名
顶级域名服务器:解析二级域名
权限域名服务器:解析区内的域名
本地域名服务器:默认域名服务器,可以在其中配置缓存
域名解析过程
域名通过域名服务器解析使用递归和迭代两种方式:
迭代方式:本地域名服务器向一个域名服务器发送域名解析请求之后,结果返回到本地域名服务器,然后本地域名服务器继续向其他的域名服务器请求解析
递归方式:请求的结果不是直接返回去,而是继续向前请求解析,最后的结果才会返回
经典面试题
从输入网址到获取页面的网络请求的过程?
- 域名解析
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)
阻塞和非阻塞是进程在访问数据的时候,数据是否准备就绪的一种处理方式
阻塞:需要等待缓冲区的数据准备好后才能处理其他的事情,否则一致等待在这里
非阻塞:当进程访问数据缓冲区时,如果数据没有准备好则直接返回,不会等待。如果数据已经准备好了,也直接返回
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操作,主要包含两个阶段:
- 等待数据准备就绪
- 将数据从内核拷贝到进程中
例如:读函数:分为等待系统可读和真正读
写函数:分为等待网卡可以写和真正写
- 阻塞IO
- 非阻塞IO
- I/O复用
- 信号驱动IO
- 异步IO
示例说明:
活动:演唱会
角色1:举办方,售票业务员
角色2:黄牛
角色3:小明
角色4:送票快递员
阻塞IO
举例:小明从家里先到演唱会现场购票问售票员买票,但是票还没有出来,还需要两天才出来,小明就直接在售票厅打地铺,一致等到票出来,然后买票
非阻塞IO
通过进程反复调用IO函数,采用轮训方式,占用CPU
举例:小明从家里先到演唱会现场购票问售票员买票,但是票还没有出来,然后小明走了,办理其他事情,然后过两个小时,又去售票现场买票,如果票还没有出来,小明又去办其他的事情,重复上面的操作,知道买到票结束
IO复用
主要使用IO复用器:selectpollepoll,将关注的时间都注册到复用器上,由OS来关注事件是否就绪
一个IO请求,需要两次调用,两次返回。
能够实现对多个IO端口进行监听,多个连接共用一个等待机制
举例:小明想买票看演唱会,直接给黄牛(select等IO复用器)打电话,帮我留意买个票,票买了通知我,我自己去取,那么票还没出来之前,小明完全可以做自己的事情
信号驱动IO
举例:小明想买票看演唱会,给举办方业务员说,给你一个电话,有票了给我打个电话通知一下(需要系统支持,Linux系统支持,window没有这种机制),我自己再来买票(小明完全可以干自己的时时期,但是票还是需要小明自己去取)
异步IO
举例:小明想买票看演唱会,给举办方的售票员打电话说,给你留个地址,有票了请你通知快递员,由快递员将票送到给定的地址,当小明听到敲门声,看见快递员了,就知道票已经好了并且已经送到他手上
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放在主线程执行
子线程负责和客户端交互,每一个客户端连接都分配一个子线程
多线程+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(); } }
Channel
ChannelFuture
EventLoop
ChannelHandler
ChannelPipeline
Channel
在 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 在它的生命周期内只能与一个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 cf = serverBootStrap.bind(port).sync(); System.out.println("服务端启动了"); //关闭 cf.channel().closeFuture().sync();
//从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 { }
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()); }