java Netty框架学习
JAVA IO模型
理解java io基本知识
1、同步和异步的主要区别:重点在于多个任务和事件发生时,一个事件的发生或者执行是否会导致整个流程的暂停。
2、阻塞和非阻塞:(阻塞)client发出请求,不满足就一直等待,直至条件满足;(非阻塞)发送一个请求,不满足就告知,不会一直等待。
3、阻塞io和非阻塞io:io有两个阶段,查看数据是否就绪;进行数据拷贝。阻塞和非阻塞的区别就是在第一阶段是否会一直等待,还是会发送消息
4、同步io和异步io:同步IO和异步IO的关键区别反映在数据拷贝阶段是由用户线程完成还是内核完成。所以说异步IO必须要有操作系统的底层支持。
5、理解多路复用IO模型
在多路复用IO模型中,Selector不断去轮询多个socket的状态,只有当socket真正有读写事件时,才真正调用实际的IO读写操作
NIO编程
- 声明并得到通道管理器
- 获得ServerSocket通道,并进行初始化
- 将该通道设置为非阻塞
- 将ip和port端口绑定到此通道
- 将通道管理器和该通道绑定,并可以监听
SelectionKey.OP_CONNECT;//当客户端的尝试连接到服务器
SelectionKey.OP_ACCEPT;//当服务器接受来自客户端的请求
SelectionKey.OP_READ;//当服务器可以从channel中读取数据
SelectionKey.OP_WRITE;//当服务器可以向channel中写入数据
- 轮询监听selector上是否有需要处理的事件,如果有则处理。
- 返回有该键的相关操作集(应该是可读可写操作吧)所标识的一个操作准备就绪的集合
处理信息
NioServer
package com.hoolai.osi;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketAddress;
public class TcpServer {
public static void main(String[] args) {
serverRun();
}
public static void serverRun() {
int port = 4700;
ServerSocket serverSocket = null;
int recvMsgSize = 0;
//接收缓冲数组
byte[] recvBuf = new byte[32];
try {
serverSocket = new ServerSocket(port);
System.out.println("服务器启动,请求建立连接");
while(true) {
//阻塞io一直等待客户端连接
Socket clientSocket = serverSocket.accept();
SocketAddress clientIp = clientSocket.getRemoteSocketAddress();
System.out.println("客户端的ip为 :"+clientIp);
InputStream in = clientSocket.getInputStream();
OutputStream out = clientSocket.getOutputStream();
while ((recvMsgSize = in.read(recvBuf))!=-1) {
String recvData = new String(recvBuf);
System.out.println(recvData);
out.write(recvBuf, 0, recvMsgSize);
}
clientSocket.close();
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
NioClient
package com.hoolai.osi;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class TcpClient {
public static void main(String[] args) {
clientRun();
}
public static void clientRun() {
int port = 4700;
String host = "localhost";
Socket socket = null;
String msg = "begin game";
try {
socket = new Socket(host, port);
System.out.println("连接服务器并发送数据");
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream();
byte[] data = msg.getBytes();
out.write(data);
//接收数据
//目前收到的总字节长度
int totalRecvByte = 0;
//最后一次读取的字节长度
int lastRecvByte;
while(totalRecvByte < data.length) {
lastRecvByte = in.read(data, totalRecvByte, data.length-totalRecvByte);
if (lastRecvByte == -1) {
System.out.println("连接已断开");
}
totalRecvByte += lastRecvByte;
System.out.println("接收的数据"+new String(data));
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
if (socket != null) {
socket.close();
}
} catch (Exception e2) {
e2.printStackTrace();
}
}
}
}
我对这个过程的理解:服务器得到serversocketchannel,绑定ip和端口。并注册到Selector中。客户端得到socketChannel,绑定ip和端口,发送connect指令,并注册进Selector(连接)事件,取出有消息的通道,判断是否连接(可读),如果连接成功,则发送消息给服务器,服务器端的Selector.select()检测到请求过来,返回值,并根据检测request(即每个request会封装一个channel,将所有的channel注册在一个Selector(接收连接)上,然后selector开始不断的轮询每个request的可读状态。)得到一个类似于key-serverchannel的集合,然后通过操作key得到ServerSocketChannel,并将此ServerSocketChannel与Socket,channel连接
理解有错误的地方,欢迎指出,因为自己也是个初学者。
Java Netty
Netty的零拷贝:
Netty 的接收和发送 ByteBuffer 采用 DIRECT BUFFERS,使用堆外直接内存进行 Socket 读写,不需要进行字节缓冲区的二次拷贝。如果使用传统的堆内存(HEAP BUFFERS)进行 Socket 读写,JVM 会将堆内存 Buffer 拷贝一份到直接内存中,然后才写入 Socket 中。相比于堆外直接内存,消息在发送过程中多了一次缓冲区的内存拷贝。
- 创建client连接服务器处理线程组和io处理线程组
- 定义Bootstrap,服务工具类,服务器通道的一系列配置参数
- bootstrap绑定两个线程组,和选择通道模式。
- 通过serverhandler中的channelReader方法读取object 和ChannelHandlerContext
- 响应消息给客户端并断开channel连接
ServerNetty
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class ServerNetty {
private int port;
public ServerNetty(int port) {
this.port = port;
}
public static void main(String[] args) {
new ServerNetty(4700)
.run();
}
public void run(){
EventLoopGroup bossGrop = new NioEventLoopGroup();//用于处理服务器接收客户端连接
EventLoopGroup workerGrop = new NioEventLoopGroup();//用于进行网络通信(读写)
try {
ServerBootstrap bootstrap = new ServerBootstrap();//服务工具类,服务器通道的一系列参数配置
bootstrap.group(bossGrop, workerGrop)//绑定两个线程池
.channel(NioServerSocketChannel.class)//制定NIO的模式
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {//配置数据具体的处理方式
ch.pipeline().addLast(new ServerlHandler());
}
})
/**
*
* SO_BACKLOG的理解,服务器端有两个队列,分别为A,B
* 当客户端发出SYN连接,服务器返回ACK时,TCP内核把客户端加入A队列
* 当客户端对服务器端回复ACK时,TCP内核把客户端移动到B队列,
* accept()方法返回,完成3次握手
* A队列和B队列的和就是ChannelOption.SO_BACKLOG,当SO_BACKLOG小了
* A和B的和就小了,可能会导致新的客户端无法连接。
*/
.option(ChannelOption.SO_BACKLOG, 128)//设置tcp的缓冲区大小
.option(ChannelOption.SO_RCVBUF, 32*1024)//设置发送缓冲区大小
.option(ChannelOption.SO_SNDBUF,32*1024)//设置接收缓冲区大小
.childOption(ChannelOption.SO_KEEPALIVE, true);//保持连接
ChannelFuture future = bootstrap.bind(port).sync();//打开通道直到完成
future.channel().closeFuture().sync();
}catch (Exception e) {
e.printStackTrace();
}finally {
workerGrop.shutdownGracefully();
bossGrop.shutdownGracefully();
}
}
}
ServerHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
/**
* 读取通道channel中的channelHandlerContext和object
* 通过channelRead()方法解析get和post的数据,
* 并发送给protomessage交给MsgHandler处理.
* 注意:netty5之后的版本channelRead方法在ChannelHandlerAdapter中
* @author lenovo
*
*/
public class ServerlHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg) throws Exception{
ByteBuf buf = (ByteBuf)msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String req = new String(data,"utf-8");
System.out.println("Server: "+req);
//发送给客户端
String resp = "发给你,客户端";
ctx.writeAndFlush(Unpooled.copiedBuffer((resp+"6666666666666666666666666666666").getBytes()));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
ClientNetty
import io.netty.bootstrap.Bootstrap;
import io.netty.buffer.Unpooled;
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.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
/**
* netty客户端
* @author lenovo
*
*/
public class ClientNetty {
public static void main(String[] args) throws Exception{
EventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new ClientHandler());
}
});
/**
* ChannelFuture Netty中的所有I / O操作都是异步的。
* 这意味着任何I / O调用都将立即返回,不保证在呼叫结束时所请求的I / O操作已完成。
* 相反,您将返回一个ChannelFuture实例,该实例向您提供有关I / O操作结果或状态的信息。
*/
ChannelFuture future = bootstrap.connect("localhost",8000).sync();
future.channel().writeAndFlush(Unpooled.copiedBuffer("66666666".getBytes()));
future.channel().closeFuture().sync();
workerGroup.shutdownGracefully();
}
}
ClientHandler
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.ReferenceCountUtil;
public class ClientHandler extends ChannelInboundHandlerAdapter{
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf buf = (ByteBuf) msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
String info = new String(data,"utf-8");
System.out.println("Client:" + info);
} finally {
ReferenceCountUtil.release(msg);
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
目前博主正在学习,上面只是学习过程中写的一点笔记。如有错误,欢迎指出