BIO:同步阻塞式编程,同一个线程只能处理一个客户端请求。
代码实现如下(本地可以通过Telnet充当客户端+debug断点测试功能):
package io;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
/**
* 阻塞IO
*/
public class BioServerDemo {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(9000);
while(true){
System.out.println("等待连接。。");
// 阻塞方法
// telnet localhost 9000
// CTRL+]进入Telnet指令
Socket clientSocket = serverSocket.accept();
System.out.println("有客户端连接了。。");
// 虽然采用多线程可以支持多个线程同时访问,但是会引发C10K问题
// new Thread(new Runnable() {
// @Override
// public void run() {
// try {
handler(clientSocket);
// } catch (IOException e) {
// e.printStackTrace();
// }
// }
// }).start();
}
}
private static void handler(Socket clientSocket) throws IOException {
byte[] bytes = new byte[1024];
System.out.println("准备read。。");
// 接收客户端的数据,阻塞方法,没有数据可读时就阻塞
int read = clientSocket.getInputStream().read(bytes);
System.out.println("read完毕。。");
if (read !=-1){
System.out.println("接收客户端的数据:"+new String(bytes,0,read));
}
// clientSocket.getOutputStream().write("HelloClint".getBytes(StandardCharsets.UTF_8));
// clientSocket.getOutputStream().flush();
}
}
telnet指令(建议开两个看效果):telnet localhost 9000(连接后CTRL+]进入Telnet指令)
控制台输出:
NIO:同步非阻塞,同一个线程可以处理多个客户端请求。
代码实现:
package io;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
/**
*初版- NIO(非阻塞)编程
*/
public class NioServer {
static List<SocketChannel> channelList = new ArrayList<>();
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
serverSocketChannel.configureBlocking(false);
System.out.println("服务启动成功");
while (true){
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel!=null){
System.out.println("连接成功");
socketChannel.configureBlocking(false);
channelList.add(socketChannel);
}
// 问题点: 空循环时间耗时太久
Iterator<SocketChannel> iterator = channelList.iterator();
while (iterator.hasNext()){
SocketChannel sc = iterator.next();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
int len = sc.read(byteBuffer);
if (len>0){
System.out.println("接收到消息:"+new String(byteBuffer.array()));
}else if(len ==-1){
iterator.remove();
System.out.println("客户端断开连接");
}
}
}
}
}
package io;
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;
/**
*进阶版- NIO(非阻塞)编程
* 这是netty和Redis的雏形
*/
public class NioSelectorServer {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().bind(new InetSocketAddress(9000));
serverSocketChannel.configureBlocking(false);
// 启用epoll模型
Selector selector = Selector.open();
// 注册阻塞事件:创建连接
SelectionKey selectionKey = serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
System.out.println("服务启动成功");
while (true){
// 阻塞等待需要处理的事件发生,这个时候是等待连接
selector.select();
// 获取阻塞的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
// 对阻塞事件进行遍历
Iterator<SelectionKey> iterator = selectionKeys.iterator();
if(iterator.hasNext()){
SelectionKey key =iterator.next();
if(key.isAcceptable()){//连接已建立
ServerSocketChannel server = (ServerSocketChannel) key.channel();
SocketChannel socketChannel = server.accept();
socketChannel.configureBlocking(false);
// 注册阻塞事件:读取数据
SelectionKey selKey = socketChannel.register(selector,SelectionKey.OP_READ);
}else if(key.isReadable()){//这里只针对read事件,有需要可以针对write事件处理
SocketChannel socketChannel = (SocketChannel) key.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(128);
socketChannel.configureBlocking(false);
int len = socketChannel.read(byteBuffer);
if(len >0 ){
System.out.println("接收到消息:"+new String(byteBuffer.array()));
}else if(len ==-1 ){
// 关闭socket
socketChannel.close();
System.out.println("接收完成");
}
}
// 把处理完的阻塞事件移除
iterator.remove();
}
}
}
}
Telnet如图:
控制台输出:
netty:可以理解成在NIO的selector版本上进行了封装
代码实现:
package io.netty;
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;
/**
* netty 服务端
*/
public class NettyServer {
public static void main(String[] args) {
// 我们要创建两个EventLoopGroup,
// 一个是boss专门用来接收连接,可以理解为处理accept事件,
// 另一个是worker,可以关注除了accept之外的其它事件,处理子任务。
//上面注意,boss线程一般设置一个线程,设置多个也只会用到一个,而且多个目前没有应用场景,
// worker线程通常要根据服务器调优,如果不写默认就是cpu的两倍。
//boss注册accept事件
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// worker注册其他事件
EventLoopGroup workerGroup = new NioEventLoopGroup(8);
//服务端要启动,需要创建ServerBootStrap,
// 在这里面netty把nio的模板式的代码都给封装好了
ServerBootstrap bootstrap = new ServerBootstrap();
try {
//配置boss和worker线程
bootstrap.group(bossGroup,workerGroup)
//配置Server的通道,相当于NIO中的ServerSocketChannel
.channel(NioServerSocketChannel.class)
//初始化服务器连接队列大小,服务端处理客户端连接请求是顺利处理的,所以同一个事件只能处理一个客户端连接。
// 多个客户端同时来的时候,服务端将不能处理的客户端连接请求放在队列中等待处理。
.option(ChannelOption.SO_BACKLOG,1024)
//childHandler表示给worker那些线程配置了一个处理器,
// 配置初始化channel,也就是给worker线程配置对应的handler,当收到客户端的请求时,分配给指定的handler处理
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyServerHandler());//添加handler,也就是具体的IO事件处理器
}
});
System.out.println("netty server start ..");
//由于默认情况下是NIO异步非阻塞,所以绑定端口后,通过sync()方法阻塞直到连接建立
//绑定端口并同步等待客户端连接(sync方法会阻塞,直到整个启动过程完成)
ChannelFuture cf = bootstrap.bind(9000).sync();
//等待服务端监听端口关闭
cf.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
//释放线程资源
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
package io.netty;
import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
/**
* 服务端ChannelHandler处理类
*/
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端连接通道建立完成");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("收到客户端的消息:"+buf.toString(CharsetUtil.UTF_8));
}
}
package io.netty;
import io.netty.bootstrap.Bootstrap;
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;
/**
* @description:netty客户端
* @author: zhoufanshou
* @date: 2022/8/8 14:32
**/
public class NettyClient {
public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast(new NettyClientHandler());//增加处理类
}
});
System.out.println("netty client start..");
ChannelFuture cf = bootstrap.connect("127.0.0.1",9000);
cf.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}finally {
group.shutdownGracefully();
}
}
}
package io.netty;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.buffer.UnpooledDirectByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.util.CharsetUtil;
import java.nio.charset.StandardCharsets;
/**
* @description:客户端ChannelHandler处理类
* @author: zhoufanshou
* @date: 2022/8/8 14:29
**/
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
/**
* @description:
* @author: zhoufanshou
* @date: 2022/8/8 14:22
* @param: ctx
* @return:
**/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("helloworld".getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(buf);
}
/**
* @description:
* @author: zhoufanshou
* @date: 2022/8/8 14:22
* @param: ctx, msg
* @return:
**/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
System.out.println("客户端收到信息:"+buf.toString(CharsetUtil.UTF_8));
}
}
控制台输出:
首先设置idea客户端多实例运行:
输出结果(打开两个客户端的效果)如下: