参考资料
马士兵io模型和netty原理-视频讲解
马士兵io模型和netty原理-视频讲解-视频源码
socket的原理,http原理
bio
socket里面的
bind()–绑定ip,端口
listen()–接受客户端的连接请求
accept()–把建立好连接的socket从就绪队列里取出来返回而已,当然了如果就绪队列为空(没有就绪连接) 则会阻塞
ServerSocket ss = new ServerSocket();
ss.bind(new InetSocketAddress("127.0.0.1", 8888));
Socket s = ss.accept(); //阻塞方法[没有连接就会阻塞]
byte[] bytes = new byte[1024];
int len = s.getInputStream().read(bytes); //[io读取数据会阻塞住,比如客户端只是连进来了,但并没有发数据,或者发了很大的数据,就需要一直读]
System.out.println(new String(bytes, 0, len));
s.getOutputStream().write(bytes, 0, len); //【客户端不接受数据,就会写不出去数据,也会阻塞住】
缺点是不能处理大量的连接
nio(单线程模型)
selector一人干了监听accept,read,write的所有事情
ServerSocketChannel ssc = ServerSocketChannel.open();//它是对原来bio通道的一个封装,现在该通道可读可写,是双向的。bio需要拿到inputstream来读,outputstream来写,但现在对于nio来说ServerSocketChannel就可读可写了
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);//注册感兴趣的事件(有客户端连上来,即accept()发生了)
while(true) {
selector.select();//【这是阻塞的,当事件OP_ACCEPT发生了它才会往下走】
Set<SelectionKey> keys = selector.selectedKeys();//一个SelectionKey相当于一个想连接成功的客户端
Iterator<SelectionKey> it = keys.iterator();
while(it.hasNext()) {
SelectionKey key = it.next();
it.remove();
handle(key);
}}}
private static void handle(SelectionKey key) {
if(key.isAcceptable()) {
try {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();//给连接成功的客户端一个通道
sc.configureBlocking(false);
sc.register(key.selector(), SelectionKey.OP_READ );//注册一个想要读的事件
} catch (IOException e) {
e.printStackTrace();
} finally {
}
} else if (key.isReadable()) { //flip
SocketChannel sc = null;
try {
sc = (SocketChannel)key.channel();
ByteBuffer buffer = ByteBuffer.allocate(512);//先将字节收集到缓冲数组,再从数组中(bio是一个字节一个的读,效率低),ByteBuffer 的api很恶心,不好用,netty对此进行了优化,因此很受欢迎
buffer.clear();
int len = sc.read(buffer); //读的过程不小心阻塞,(单线程的)selector整体就会阻塞
if(len != -1) {
System.out.println(new String(buffer.array(), 0, len));
}
ByteBuffer bufferToWrite = ByteBuffer.wrap("HelloClient".getBytes());
sc.write(bufferToWrite); //写的过程不小心阻塞,(单线程的)selector整体就会阻塞
}
nio(响应式模型)
selector一人现在只干监听accept,将read,write的事情交给一个线程池去做。有一个accept连上来并发生了读事件,就将其丢到线程池里面去处理。
这里selector相当于boss,线程池相当于worker
aio
netty
netty使用的是nio模型
linux底层使用的是epoll模型(aio和nio都是epoll模型实现的),epoll本身是轮询模型,因此无论aio上层整么封装,底层实现仍是轮询(linux的aio效率未必比nio高),还不如直接nio(netty的实现的nio,但它封装的api更像是aio)
EventLoopGroup bossGroup = new NioEventLoopGroup();//线程组boss,处理连接,相当于selector,默认是1个,可指定多个线程new NioEventLoopGroup(5)
EventLoopGroup workerGroup = new NioEventLoopGroup();//线程组worker,处理读写
ServerBootstrap b = new ServerBootstrap();//对服务进行启动配置
//第一个线程组负责连接,第二线程组负责连接后的线程处理
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)//通道类型
//每个客户端连上来之后,给它一个监听器进行处理(过程如下:通道上加上处理器就是一个监听器)--整样处理
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
//一旦通道初始话了
protected void initChannel(SocketChannel ch) throws Exception {
//我就给通道添加处理。通道上有个监听器队列,在队列最后位置添加处理器
ch.pipeline().addLast(new Handler());
}
});
class Handler extends ChannelInboundHandlerAdapter {
@Override
//有数据上来,该方法就会被自动调用,且数据都已经读出来封装到了Object msg,都无需自己来处理数据的读
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();
}