网络编程之Netty

什么是TCP协议?

TCP是一种面向连接的可靠的基于字节流的传输层通信协议。

三次握手,建立TCP连接

所谓三次握手即建立TCP连接,就是指建立一个TCP连接时,需要客户端和服务端总共发送3个包以确认连接的建立。在socket编程中,这一过程由客户端执行connect来触发,整个流程如下图所示:
TCP三次握手
tcp三次握手的最主要目的是保证连接是双工的,可靠更多的是通过重传机制来保证的:

  1. 第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SEND状态,等待服务器确认;
  2. 第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器 进入SYN_RECV状态
  3. 第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。

Socket API

TCP socket的API调用流程:
TCP socket的API调用流程

  1. socket函数创建一个套接字;
  2. bind绑定套接字的本地 IP地址和端口号;
  3. listen将服务端TCP套接字设置为监听模式,并设置队列大小;
  4. accept 接收一个连接请求,创建新的套接字,如果没有连接则会一直阻塞直到有连接进来。得到客户端的fd(套接字)之后,就可以调用read, write函数和客户端通讯,读写方式和其他I/O类似;
  5. read 从fd读数据,socket默认是阻塞模式的,如果对方没有写数据,read会一直阻塞着;
  6. write 写fd写数据,socket默认是阻塞模式的,如果对方没有写数据,write会一直阻塞着;
  7. connect 客户端向服务器发起连接;

Socket应用之BIO

同步阻塞IO,阻塞整个步骤,如果连接少,他的延迟是最低的,因为一个线程只处理一个连接,适用于少连接且延迟低的场景,比如说数据库连接。
在这里插入图片描述

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class BIOServer {
	public static void start(int port) throws IOException {
		// 1. 创建服务器端实例,接受客户端请求,转发给Socket对象 	socket
		ServerSocket serverSocket = new ServerSocket();
		// 2. 指定服务器的监听端口,阻塞  bind & listen  
		serverSocket.bind(new InetSocketAddress(port), 2);//backlog, accept queue was created in listen()
		
		while(true) {
			// 3. 创建Socket进行请求转发, blocking(阻塞操作) 因为没有请求过来的话一直处于等待状态  accept
			final Socket clientSocket = serverSocket.accept();//block!
			System.out.println("accept!");
			new Thread(()-> {// or user thread pool
					try {
						// 4. 获取客户端意图
						BufferedReader in = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
						// 5. 给出客户端响应
						PrintWriter out = new PrintWriter(clientSocket.getOutputStream(), true);
						String line = in.readLine();//block
						while(line  != null) {
							out.println(line);
							out.flush();
							line = in.readLine();//block
						}
						// 6. 关闭资源
						clientSocket.close();
					} catch (IOException e) {
						e.printStackTrace();
						try {
							clientSocket.close();
						} catch (IOException ee) {
							e.printStackTrace();
						}
					}
			}).start();
		}
	}
	
    public static void main( String[] args ) throws InterruptedException, IOException{
   	 	start(8084);
    }
}

thread pre connection 模型
在这里插入图片描述
BIO同步阻塞多线程时的缺点:

  • 线程的创建和销毁成本很高;
  • 线程本身占用较大内存;
  • 线程的上下文切换成本很高;
  • 容易造成锯齿状的系统负载(因为系统负载是用活动线程数或CPU核心数,一旦线程数量高但外部网络环境不是很稳定,就很容易造成大量请求的结果同时返回,激活大量阻塞线程从而使系统负载压力过大)。

NIO

同步非阻塞IO,阻塞业务处理但不阻塞数据接收,适用于高并发且处理简单的场景,比如聊天软件。

解决了BIO上下文切换耗内存的问题(使用较少的线程便可以处理许多连接,因此也减少了内存管理和上下文切换所带来开销)。
在这里插入图片描述

  • ServerSocketChannel:监听新进来的TCP连接,像Web服务器那样。对每一个新进来的连接都会创建一个SocketChannel;
  • SocketChannel :能通过TCP读写网络中的数据;
  • Buffer:Buffer用于和通道进行交互。数据是从通道读入缓冲区,从缓冲区写入到通道中的;
  • Selector:,是阻塞的,能够检测一到多个NIO通道,并能够知晓通道是否为诸如读写事件做好准备的组件。这样,一个单独的线程可以管理多个channel,从而管理多个网络连接。(ON_ACCEPT、ON_CONNECT、ON_READ、ON_WRITE)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class NIOServer {
	public static void start(int port) throws IOException {
		 // 1. 创建ServerSocketChannel,绑定监听端口
		 ServerSocketChannel serverChannel = ServerSocketChannel.open();
		 // 2. 设置Channel为非阻塞模式
		 serverChannel.configureBlocking(false);//nonblocking
		 // 3. 为Channel通道绑定监听端口bind & listen
		 serverChannel.bind(new InetSocketAddress(port));
		 // 4. 创建Selector
		 Selector selector = Selector.open();	
		 // 5. 将Channel通道注册到Selector上,监听连接事件
		 serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		 // 循环等待新接入的连接
		 while(true) {
		 	// 获取可用Channel数量
			 selector.select();//scan 
			 // 6. 获取可用Channel的集合
			 Set<SelectionKey> readyKeys = selector.selectedKeys(); 
			 Iterator<SelectionKey> it = readyKeys.iterator();
			 while (it.hasNext()) {
                 SelectionKey key = it.next();
                 /*
                 * 根据就绪状态,调用对应方法处理业务逻辑
                 */
                 // 7. 如果是链接事件 处理。accept
                 if (key.isAcceptable()) {
                 	 // 第一步:从SelectedKey中获取已经就绪的Channel
                	 ServerSocketChannel server = (ServerSocketChannel)key.channel();
                	 // 第二步:创建ServerSocketChannel 
                	 SocketChannel socket = server.accept();
                	 System.out.println("Accept !");
                	 // 第三步:将SocketChannel设置为非阻塞工作模式
                	 socket.configureBlocking(false);
                	 // 第四步:将Channel注册到Selector上,监听可读事件
             		 socket.register(selector, SelectionKey.OP_READ );//tricky
                 }
                 // 8. 如果是读写事件 处理
                 if (key.isReadable()) {
                 	 // 第一步:从SelectedKey中获取已经就绪的Channel
                	 SocketChannel socket = (SocketChannel) key.channel();
                	 // 第二步:创建buffer
                	 final ByteBuffer buffer = ByteBuffer.allocate(64);
                	 // 第三步:读取客户端请求信息,非阻塞
                	 final int bytesRead = socket.read(buffer);//also nonblock
                     if (bytesRead > 0) {
                     	 // 第四步:切换buffer为读模式
                         buffer.flip();
                         // 第五步:将信息发送到客户端
                         int ret = socket.write(buffer);
                         if (ret <=0) {
                        	 // 第六步:将Channel再次注册到Selector上,监听它的可读事件 register op_write
                        	 socket.register(selector, SelectionKey.OP_WRITE);
                         }
                         buffer.clear();
                     } else if (bytesRead < 0) {//means connection closed
                         key.cancel();
                         socket.close();
                         System.out.println("Client close");
                     }
                 }
//                 if (key.isWritable()) {
//                	 SocketChannel socket = (SocketChannel) key.channel();
//                	 final ByteBuffer buffer = ByteBuffer.allocate(64);
//                	 socket.write(buffer);
//                	 //remove 
//                 }
				 // 第七步:remove
                 it.remove();// 处理完了,需要remove掉。
             }
		 }	 
	}
	
	 public static void main( String[] args ) throws InterruptedException, IOException{
	   	 	start(8084);
	  }
}	

Selector模型
缺点:

  • 存在空轮询BUG,会让CPU飙到100%。
NIO的Reactor模式(多路复用)
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;

public class Reactor {
	
	interface ChannelHandler{
		public void onRead(SocketChannel channel) throws Exception;
		public void onAccept();
	}
	private static ChannelHandler echo = new ChannelHandler() {
		public void onRead(SocketChannel socket) throws IOException {
			 final ByteBuffer buffer = ByteBuffer.allocate(256);
        	 
        	 final int bytesRead = socket.read(buffer);
             if (bytesRead > 0) {
                 buffer.flip();
                 socket.write(buffer);
                 buffer.clear();
             } else if (bytesRead < 0) {
                 
                 socket.close();
                 System.out.println("Client close");
             }
		}
		public void onAccept() {
		}
	};
	
	public static void start(int port) throws Exception {
		 final ServerSocketChannel serverChannel = ServerSocketChannel.open();
		 serverChannel.configureBlocking(false);
		 InetSocketAddress address = new InetSocketAddress(port);
		 //bind & listen
		 serverChannel.bind(address);
		 
		 final Selector selector = Selector.open();		 
		 SelectionKey sk  = serverChannel.register(selector, SelectionKey.OP_ACCEPT);
		 
		 sk.attach(new ChannelHandler() {//Acceptor
			public void onRead(SocketChannel channel) {}

			public void onAccept() {
				try {
					SocketChannel socket = serverChannel.accept();
					System.out.println("Accept !");
					socket.configureBlocking(false);
					SelectionKey sk = socket.register(selector, 0);//register op_read here is also ok in single thread
					sk.attach(echo);
					sk.interestOps(SelectionKey.OP_READ);
				} catch (IOException e) {
					// TODO Auto-generated catch block
					e.printStackTrace();
				}
			}
		 });

		while (true) {
			selector.select();
			Set<SelectionKey> readyKeys = selector.selectedKeys();
			Iterator<SelectionKey> it = readyKeys.iterator();
			while (it.hasNext()) {
				SelectionKey key = it.next();
				ChannelHandler handler = (ChannelHandler) key.attachment();
				//accept
                if (key.isAcceptable()) {
                	handler.onAccept();
                }
                if (key.isReadable()) {
                	handler.onRead((SocketChannel) key.channel());
                }
				it.remove();
			}
		}
	}
	
	 public static void main( String[] args ) throws Exception{
	   	 	start(8084);
	  }
}

缺点:

  • 处理op_write事件:写忙时需要注册op_write,不忙时注销op_write
  • 处理byteBuffer: byteBuffer API 非常难用
  • 处理粘包:需要记录接收到的字节状态
  • 处理多个selector
  • 提供优雅的业务编程模型
  • 根据环境运行参数

总结

异步和非阻塞的区别?

Netty

Netty 是一个基于NIO(非阻塞IO)的客户端、服务器端编程框架,Netty提供异步的,事件驱动的网络应用程序框架和工具,可以快速开发高可用的客户端和服务器。

优点:

  • 并发高
  • 传输快
  • 封装好
Netty核心概念

Channel:表示一个连接,可以理解为每一个请求,就是一个Channel。相当于SocketChannel。
EventLoop:负责处理注册到Channel上的处理 I/O 操作。 相当于Selector。
ChannelPipeline:用于保存处理过程需要用到的ChannelHandler和ChannelHandlerContext。
ChannelHandler:核心处理业务就在这里,用于处理业务请求。
Bootstrap:启动器main。
ByteBuf:相当于ByteBuffer。

public class Server {

    private static void start(int port) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup);
            b.channel(NioServerSocketChannel.class);
            b.childHandler(new ChannelInitializer() {
                @Override
                protected void initChannel(Channel ch) throws Exception {
                	// 自定义ChannelInboundHandler 
                    ch.pipeline().addLast(new PrintInboundHandler("id1"));
                }
            });

            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        start(8084);
    }
}
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandler;

public class PrintInboundHandler implements ChannelInboundHandler {
    private final String id;

    public PrintInboundHandler(String id) {
        this.id = id;
    }

    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerAdded " + id);

    }

    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("handlerRemoved " + id);

    }

    public void channelRegistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelRegistered " + id);
        ctx.fireChannelRegistered();

    }

    public void channelUnregistered(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelUnregistered " + id);
        ctx.fireChannelUnregistered();

    }

    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelActive " + id);
        ctx.fireChannelActive();

    }

    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelInactive " + id);
        ctx.fireChannelInactive();
    }

    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("channelRead " + id);
        ctx.fireChannelRead(msg);
        //ctx.channel().pipeline().fireChannelRead(msg);
    }

    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelReadComplete " + id);
        ctx.fireChannelReadComplete();
    }

    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        System.out.println("userEventTriggered " + id);

    }

    public void channelWritabilityChanged(ChannelHandlerContext ctx) throws Exception {
        System.out.println("channelWritabilityChanged " + id);

    }

    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        System.out.println("exceptionCaught " + id);

    }
}
EventLoop

在这里插入图片描述

ChannelHandler & ChannelPipeline

在这里插入图片描述

粘包&拆包

在这里插入图片描述
参数说明:

  • lengthFieldOffset
    Length Field在帧的什么位置
  • lengthFieldLength
    Length Field 自身长度多少
  • lengthAdjustment
    Length Field中的长度值具体指哪部分
  • initialBytesToStripe
    Frame Decode后希望获得的内容(想要丢弃的帧部分)

服务端:

import com.tim.nettttty.handler.IProtocalHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
import io.netty.util.concurrent.DefaultEventExecutorGroup;

public class Server {

    private static void start(int port) throws InterruptedException {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup);
            b.channel(NioServerSocketChannel.class);
            b.childHandler(new ChannelInitializer() {
                @Override
                protected void initChannel(Channel ch) throws Exception {
                	/*
                	* 参数说明:
                	* 第一个参数:字节大小
                	* 第二个参数:lengthFieldOffset 
                	* 第三个参数:lengthFieldLength
                	* 第四个参数:lengthAdjustment
                	* 第五个参数:initialBytesToStripe
                	*/
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 2, 2, -2, 0));
                    ch.pipeline().addLast(new DefaultEventExecutorGroup(16), new IProtocalHandler());
                    ch.pipeline().addLast(new StringEncoder(CharsetUtil.UTF_8));
                }
            });

            ChannelFuture f = b.bind(port).sync();
            f.channel().closeFuture().sync();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        start(8084);
    }
}
/**
* 自定义业务处理
*/
import java.util.Random;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;

public class IProtocalHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, final Object msg) throws Exception {
        int sleep = 500 * new Random().nextInt(5);
        System.out.println("sleep:" + sleep);
        Thread.sleep(sleep);

        final ByteBuf buf = (ByteBuf) msg;
        char c1 = (char) buf.readByte();
        char c2 = (char) buf.readByte();

        if (c1 != 'C' || c2 != 'D') {
            ctx.fireExceptionCaught(new Exception("magic error"));
            return;
        }
        buf.readShort();//skip length
        String outputStr = buf.toString(CharsetUtil.UTF_8);
        System.out.println(outputStr);
        ctx.channel().writeAndFlush(outputStr + "\n");
    }

}

客户端:

import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class OIOClient {

    public static final String[] commands = new String[]{
            "nihao",
            "wo shi ke hu duan",
            "hello world",
            "java and netty"
    };

    public static void main(String[] args) throws IOException {
        int concurrent = 1;
        Runnable task = () -> {
            try {
                Socket socket = new Socket("127.0.0.1", 8084);
                DataOutputStream out = new DataOutputStream(socket.getOutputStream());
                /**
                 *  HEADER(2)|LENGTH(2)|BODY
                 *  LENGTH = (self(2) + BODY),  not include header
                 */
                for (String str : commands) {
                    out.writeByte('C');
                    out.writeByte('D');
                    int length = str.length();
                    out.writeShort(length * 2 + 2);//why *2 here?
                    out.writeChars(str);
                }
                out.flush();

                BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                String line = null;
                while (!((line = br.readLine()) == null)) {
                    System.out.println(line);
                }
                socket.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        };

        for (int i = 0; i < concurrent; i++) {
            new Thread(task).start();
        }
    }
}
Netty和Tomcat有什么区别?

Netty和Tomcat最大的区别就在于通信协议,Tomcat是基于Http协议的,他的实质是一个基于http协议的web容器,但是Netty不一样,他能通过编程自定义各种协议,因为netty能够通过codec自己来编码/解码字节流,完成类似redis访问的功能,这就是netty和tomcat最大的不同。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

抽抽了

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值