一、BIO
1、机制
采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的链接,它接收到客户端的连接请求之后为每个客户端请求创建一个新的线程进行链路处理,处理完成之后通过输出流将响应返回给客户端,线程销毁,这就是典型的一请求一应答的通信模型。
2、分析
当客户端并发访问量增加后,服务端的线程个数和客户端并发访问按1:1的正比关系递增,线程膨胀之后,系统的性能会急剧下降,甚至会发生线程堆栈溢出、创建失败,最终发生宕机或将死的惨状。
当然我们可以在服务端使用线程池的方式,来保护我们的系统受到高并发的冲击,但是即使线程池在大毕竟也是有限的,这样会出现大量的请求等待线程池的资源,从而性能、时延、并发量还是会面临很糟糕的情况。
二、NIO
1、机制
概念组成就不说了,网上很多。
采用nio通信模型的服务端,通常由一个独立的线程selector(选择器)来管理一个或多个channel,当channel注册了selector之后,selector会监听channel的各种事件,如SelectionKey.OP_ACCEPT-接收事件,当注册的事件发生后,通过迭代器获取选中的事件-SelectionKey,如果SelectionKey为请求连接事件,则保存客户端的SocketChannel并设置非阻塞,再添加可读监听事件,这样在数据可读之前,selector可以做一些其他的事情;如果SelectionKey为可读事件,则可以通过线程池用SocketChannel获取数据,进行接下来的逻辑处理,最后将响应返回给客户端。
2、分析
由于selector可以判断数据的接收状态,所以可以节省掉等待io数据的时间,而监听状态的时间会很快,可以由单线程完成,这样也避免了线程的上下文切换。
三、代码
1、服务端
- public class NIOServer {
- //选择器
- private Selector selector;
- /**
- * 对该通道做一些初始化的工作
- */
- public void initServer(int port) throws IOException {
- // 获得ServerSocket通道
- ServerSocketChannel serverChannel = ServerSocketChannel.open();
- // 设置通道为非阻塞
- serverChannel.configureBlocking(false);
- // 绑定到port端口
- serverChannel.socket().bind(new InetSocketAddress(port));
- // 获得选择器
- this.selector = Selector.open();
- //为该通道注册SelectionKey.OP_ACCEPT事件
- serverChannel.register(selector, SelectionKey.OP_ACCEPT);
- }
- /**
- * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
- */
- @SuppressWarnings("unchecked")
- public void listen() throws IOException {
- // 轮询访问selector
- while (true) {
- //当注册的事件到达时,方法返回;否则,该方法会一直阻塞
- selector.select();
- // 获得selector中选中的项的迭代器,选中的项为注册的事件
- Iterator ite = this.selector.selectedKeys().iterator();
- while (ite.hasNext()) {
- SelectionKey key = (SelectionKey) ite.next();
- // 删除已选的key,以防重复处理
- ite.remove();
- // 客户端请求连接事件
- if (key.isAcceptable()) {
- ServerSocketChannel server = (ServerSocketChannel) key
- .channel();
- // 获得和客户端连接的通道
- SocketChannel channel = server.accept();
- // 设置成非阻塞
- channel.configureBlocking(false);
- //可以给客户端发送信息
- channel.write(ByteBuffer.wrap(new String("abc").getBytes()));
- //在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
- channel.register(this.selector, SelectionKey.OP_READ);
- } else if (key.isReadable()) {
- // 获得了可读的事件
- read(key);
- }
- }
- }
- }
- /**
- * 处理逻辑
- */
- public void read(SelectionKey key) throws IOException{
- // 服务器可读取消息:得到事件发生的Socket通道
- SocketChannel channel = (SocketChannel) key.channel();
- // 创建读取的缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(10);
- channel.read(buffer);
- byte[] data = buffer.array();
- String msg = new String(data).trim();
- System.out.println("服务端收到信息:"+msg);
- ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes());
- channel.write(outBuffer);// 将消息回送给客户端
- }
- /**
- * 启动服务端测试
- */
- public static void main(String[] args) throws IOException {
- NIOServer server = new NIOServer();
- server.initServer(8000);
- server.listen();
- }
- }
2、客户端
- public class NIOClient {
- //选择器
- private Selector selector;
- /**
- * 对该通道做一些初始化的工作
- */
- public void initClient(String ip,int port) throws IOException {
- // 获得一个Socket通道
- SocketChannel channel = SocketChannel.open();
- // 设置通道为非阻塞
- channel.configureBlocking(false);
- // 选择器
- this.selector = Selector.open();
- // 客户端连接服务器,其实方法执行并没有实现连接,需要在listen()方法中调
- //用channel.finishConnect();才能完成连接
- channel.connect(new InetSocketAddress(ip,port));
- //将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_CONNECT事件。
- channel.register(selector, SelectionKey.OP_CONNECT);
- }
- /**
- * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
- */
- @SuppressWarnings("unchecked")
- public void listen() throws IOException {
- // 轮询访问selector
- while (true) {
- selector.select();
- // 获得selector中选中的项的迭代器
- Iterator ite = this.selector.selectedKeys().iterator();
- while (ite.hasNext()) {
- SelectionKey key = (SelectionKey) ite.next();
- // 删除已选的key,以防重复处理
- ite.remove();
- // 连接事件发生
- if (key.isConnectable()) {
- SocketChannel channel = (SocketChannel) key
- .channel();
- // 如果正在连接,则完成连接
- if(channel.isConnectionPending()){
- channel.finishConnect();
- }
- // 设置成非阻塞
- channel.configureBlocking(false);
- //在这里可以给服务端发送信息哦
- channel.write(ByteBuffer.wrap(new String("abc").getBytes()));
- //在和服务端连接成功之后,为了可以接收到服务端的信息,需要给通道设置读的权限。
- channel.register(this.selector, SelectionKey.OP_READ);
- // 获得了可读的事件
- } else if (key.isReadable()) {
- read(key);
- }
- }
- }
- }
- /**
- * 处理逻辑
- */
- public void read(SelectionKey key) throws IOException{
- SocketChannel channel = (SocketChannel) key.channel();
- // 创建读取的缓冲区
- ByteBuffer buffer = ByteBuffer.allocate(10);
- channel.read(buffer);
- byte[] data = buffer.array();
- String msg = new String(data).trim();
- System.out.println("客户端收到信息:"+msg);
- }
- /**
- * 启动客户端测试
- */
- public static void main(String[] args) throws IOException {
- NIOClient client = new NIOClient();
- client.initClient("localhost",8000);
- client.listen();
- }
- }