通信协议的分层规定
把用户应用层作为最高层,把物理通信线路作为最底层,期间的协议处理分为若干层,规定每层处理的任务,也规定每层的接口标准。
目前分层国际的标准有两种:OSI参考模型和TCP/IP参考模型
一台机器想把一句话送出去的流程
需要从应用层一步步的把数据封装传递到最底层直到物理层的底层转换为二进制0100101010然后在送出去
送出去之后 对方最底层接收到0101010101然后一步步的翻译到应用层 这是数据在网络之间传输的一个过程。
底层对我们透明 看似是应用层之间通信,并不是。
HTTPS加密
在使用HTTPS时,所有的HTTP请求和响应数据在发送到网络之前,都要进行加密。
HTTPS在HTTP下面提供了一个传输级的密码安全层SSL/TSL(Transport Layer Security)。即在应用层和传输层之间加了一个安全层。
对称秘钥的加密技术:编/解码时使用相同秘钥的算法。
在对称加密技术中,发送端和接收端要共享相同的秘钥K才能进行通信。发送端用共享的秘钥加密报文,并将得到的密文发送给接收端。
接收端接收到密文,并对其应用解密函数和相同的共享秘钥,恢复出原始的明文。
对称秘钥加密技术的缺点之一就是发送者和接受者在互相对话之前,一定要有一个共享的保密秘钥。
流行的对称秘钥加密算法包括: DES Triple-DES RC2和RC4。64为的秘钥应该是大多数公司所采用的。
不对称秘钥加密系统:编/解码使用不同秘钥的算法。
非对称加密使用两个秘钥,一个用来对主机报文编码,另一个用来对主机报文解码。
编码秘钥是众所周知的,只有主机才知道私有的解密秘钥。
通过公开秘钥的加密技术,全球所有的计算机用户就都可以使用安全协议了,其中RSA算法是一个比较流行的公开秘钥的加密算法。
用证书对服务器进行认证。
通过HTTPS建立了一个安全的Web事物之后,浏览器都会自动获取所连接服务器的数字证书。
浏览器收到证书时,会对签名颁发机构进行检查。如果这个机构是个很有权威的公共签名机构,浏览器可以知道其公开秘钥了(浏览器会预装很多签名颁发机构的证书)。
参考文章 https://blog.csdn.net/zmx729618/article/details/78485665
数字签名
数字签名是附加在报文上特殊加密校验码,使用数字签名有以下两个好处
签名可以证明是作者(服务器)编写了这条报文
签名可以防止报文被篡改。如果有恶意攻击者在报文传输过程中对其进行了修改,校验和就不匹配了。
数字签名技术将摘要信息用发送者(服务器)的私钥加密,与原文一起传送给接收者。
数字签名是个加密的过程,数字签名验证是个解密的过程。私钥加密,公钥解密。
公钥加密 私钥解密 和私钥加密 公钥解密一起使用,既保证了安全性也保证了唯一性。
Socket实现网络通信(阻塞式IO)
- 在使用IO和Socket构造网络服务时 接收连接:accept(),接收请求数据,发送响应数据都可能引起阻塞的操作。(Handler必须使用多线程异步操作,不然别的连接进不来)
- 线程从Socket输入流读数据时,如果没有足够的数据就会进入阻塞状态,直到读够了足够的数据,或者达到输入流的末尾,或者出现了异常,才能从输入流的read()方法返回或异常中断。
public class Server { public static void main(String[] args) throws IOException { ServerSocket ss = new ServerSocket(); ss.bind(new InetSocketAddress("127.0.0.1", 8888)); while(true) { Socket s = ss.accept(); //阻塞方法 new Thread(() -> { handle(s); }).start(); } } static void handle(Socket s) { try { byte[] bytes = new byte[1024]; int len = s.getInputStream().read(bytes); System.out.println(new String(bytes, 0, len)); s.getOutputStream().write(bytes, 0, len); s.getOutputStream().flush(); } catch (IOException e) { e.printStackTrace(); } } }
NIO对网络通信改进
网络通信在阻塞模式下,
- read()方法会争取读到n个字节,如果输入流中不足n个字节,就进入阻塞状态,直到读取了n个字节,或者读到了输入流末尾,或者出现了I/O异常。
- socket.accept()方法如果没有接收到连接,也会一直等待
大量线程连接进来的时候,效率比较低(所有线程都阻塞在那里)
网络通信在非阻塞模式下(NIO对BIO的改进)
- read()方法奉行能读到多少数据就读到多少数据的原则。read()方法读取当前通道中的可读数据,有可能不足n个字节,或者为0个字节,read()方法总会立刻返回。而不会等到读取了n个字节才返回,read()方法返回实际上读入的字节数。SocketChannel extends AbstractSelectableChannel 类的中 int read(ByteBuffer dst)方法是非阻塞式的。
- ServerSocketChannel或SockeChannel通过register()方法向Selector注册事件时,register()方法会创建一个SelectionKey对象,这个SelectionKey对象是跟踪注册事件的句柄。在SelectionKey对象有效期,Selector会一直监控与SelectorKey对象相关的事件,如果事件发生,就会把SelectionKey对象加入到Selector-keys集合中。
1)NIO-Single(单线程模型)
大管家selector
除了管理客户端的连接之外
连接通道建立后:还盯着有没有需要读写的数据(一个大管家 领着一帮工人)
使用Selector创建一个非阻塞的服务器。
public static void main(String[] args) throws IOException { ServerSocketChannel ssc = ServerSocketChannel.open(); ssc.socket().bind(new InetSocketAddress("127.0.0.1", 8888)); ssc.configureBlocking(false); System.out.println("server started, listening on :" + ssc.getLocalAddress()); Selector selector = Selector.open(); ssc.register(selector, SelectionKey.OP_ACCEPT); while(true) { selector.select(); Set<SelectionKey> keys = selector.selectedKeys(); Iterator<SelectionKey> it = keys.iterator(); while(it.hasNext()) { SelectionKey key = it.next(); it.remove(); handle(key); } } }
handler(key)没有异步的去处理,这是单线程模型的NIO
NIO的读写都是用ByteBuffer 一块一块的读 相比BIO的一个byte的读,提高了很多效率 但是特别难用:推荐NIO类库介绍
当你忘记flip复位的操作,你可以把消息读成一半,这也是Netty受欢迎的原因。
2)NIO-Reactor(多线程模型)
Selector任务是BOSS 不干别的事,就负责客户端的连接
要不要写 交给Worker工人来做,工人是一个池子(线程池)
也就是NIO单线程模型中 Handler用线程池调用
AIO对网络通信改进
AIO是类似观察者模式的事件回调,而不在需要轮循
当客户端需要连接的时候 交给操作系统去链接
操作系统一旦连接上了客户端,会给大管家Selector有人要连上来了
大管家只负责连接的功能 而不需要轮循环,连好的通过交给工人worker处理通道里面的信息
public class Server { public static void main(String[] args) throws Exception { final AsynchronousServerSocketChannel serverChannel = AsynchronousServerSocketChannel.open() .bind(new InetSocketAddress(8888)); serverChannel.accept(null, new CompletionHandler<AsynchronousSocketChannel, Object>() { @Override public void completed(AsynchronousSocketChannel client, Object attachment) { serverChannel.accept(null, this); try { System.out.println(client.getRemoteAddress()); ByteBuffer buffer = ByteBuffer.allocate(1024); client.read(buffer, buffer, new CompletionHandler<Integer, ByteBuffer>() { @Override public void completed(Integer result, ByteBuffer attachment) { attachment.flip(); System.out.println(new String(attachment.array(), 0, result)); client.write(ByteBuffer.wrap("HelloClient".getBytes())); } @Override public void failed(Throwable exc, ByteBuffer attachment) { exc.printStackTrace(); } }); } catch (IOException e) { e.printStackTrace(); } } @Override public void failed(Throwable exc, Object attachment) { exc.printStackTrace(); } }); while (true) { Thread.sleep(1000); } } }
serverChannel.accept的时候 就可以走了,回调函数 观察者设计模式,把这个CompletionHandler方法交给操作系统去执行
下面之所以写while(true)是为了防止程序结束,如果想写的严谨一些,可以用countDownLatch
Netty
Netty是对NIO进行了封装,封装的API更像AIO
netty的写法和AIO差不多
netty把NIO中难用的byteBuffer封装的特别好
疑问点:有了AIO为什么还需要NIO
因为AIO和NIO在linux底层都是用的epoll模型实现的,epoll本身就是轮循模型
所以你上层在怎么封装,下层还是轮循(Netty就是用的NIO而不是AIO),在Linux中AIO的效率未必比NIO高
而Windows的AIO是自己单独实现的,不是轮训模型而是事件模型(Windows的Server比较少 Netty未做重点)
package com.example.nio.io.netty; import io.netty.bootstrap.ServerBootstrap; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; import io.netty.channel.*; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.SocketChannel; import io.netty.channel.socket.nio.NioServerSocketChannel; import io.netty.util.CharsetUtil; public class HelloNetty { public static void main(String[] args) { new NettyServer(8888).serverStart(); } } class NettyServer { int port = 8888; public NettyServer(int port) { this.port = port; } public void serverStart() { /**定义两个线程池**/ EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap(); /**把这两个group传给Server启动的封装类**/ b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class)/**指定Server启动之后 客户端连接上来的通道类型**/ .childHandler(new ChannelInitializer<SocketChannel>() {/**每一个客户端连上来之后 监听器处理**/ @Override protected void initChannel(SocketChannel ch) throws Exception { /**通道一旦init 在这个通道上就添加对这个通道的处理器**/ ch.pipeline().addLast(new Handler()); } }); try { ChannelFuture f = b.bind(port).sync(); f.channel().closeFuture().sync(); } catch (InterruptedException e) { e.printStackTrace(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } } class Handler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //super.channelRead(ctx, msg); System.out.println("server: channel read"); ByteBuf buf = (ByteBuf)msg; System.out.println(buf.toString(CharsetUtil.UTF_8)); ctx.writeAndFlush(msg); ctx.close(); //buf.release(); } /**发生异常的回调方法**/ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { //super.exceptionCaught(ctx, cause); cause.printStackTrace(); ctx.close(); } }