文章导航
前言
最近有个对接渠道需求,对方提供文档中要求使用Socket短链接的方式进行报文交互,所以这边采用NIO方式编写Socket。
正文
概念介绍
Java共支持3种网络编程模型/IO模式:BIO、NIO、AIO。
什么是JAVA BIO?
同步并阻塞(传统阻塞型),服务器实现模式为 一个连接对应一个线程,即客户端有连接请求时服务器端就需要启动一个线程进行处理,如果这个连接不做任何事情会造成不必要的线程开销。
什么是JAVA NIO?
同步非阻塞,服务器实现模式为 一个线程处理多个请求(连接),即客户端发送的连接请求都会注册到多路复用器上,多路复用器轮询到连接有I/O请求就进行处理。
什么是JAVA AIO?
异步非阻塞,AIO 引入 异步通道 的概念,采用了 Proactor 模式,简化了程序编写,有效的请求才启动线程,它的特点是:先由操作系统完成后才通知服务端程序启动线程去处理,一般适用于连接数较多且连接时间较长的应用。
代码编写
package nio; import java.net.InetSocketAddress; import java.net.Socket; 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; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SocketServer extends Thread { private int port = 9999; private String ip = "127.0.0.1"; private Boolean startListener = true; private int threadPoolSize = 10; @Override public void run() { Selector selector = null; ServerSocketChannel socketChannel = null; int nKeys = 0; try { selector = Selector.open(); socketChannel = ServerSocketChannel.open(); InetSocketAddress inetSocketAddress = new InetSocketAddress(ip, port); //信道绑定IP、端口 socketChannel.socket().bind(inetSocketAddress); //设置非阻塞 socketChannel.configureBlocking(false); //注册选择器 socketChannel.register(selector, SelectionKey.OP_ACCEPT); //开始监听 System.out.println("开启监听"); while (startListener) { //设置超时时间,多久返回一次选择器key nKeys = selector.select(100); if (nKeys > 0) { Set<SelectionKey> selectedKeys = selector.selectedKeys(); Iterator<SelectionKey> it = selectedKeys.iterator(); while (it.hasNext()) { SelectionKey key = it.next(); Socket socket = null; it.remove(); if (key.isAcceptable()) { //处理连接事件 SocketChannel channel = socketChannel.accept(); channel.configureBlocking(false); //设置为非阻塞 System.out.println("client:" + channel.getLocalAddress() + " is connect"); channel.register(selector, SelectionKey.OP_READ); //注册客户端读取事件到selector } else if (key.isReadable()) { //处理读取事件 ByteBuffer byteBuffer = ByteBuffer.allocate(1024); SocketChannel channel = (SocketChannel) key.channel(); channel.read(byteBuffer); //获取请求报文 String requestInfo = new String(byteBuffer.array(),"GBK"); System.out.println("请求报文:"+requestInfo+"--"+requestInfo.length()); try { if (requestInfo!=null&&!requestInfo.equals("")) { //响应报文 String newData="ZhuDaChang"; ByteBuffer buf = ByteBuffer.allocate(newData.getBytes().length); buf.clear(); buf.put(newData.getBytes()); buf.flip(); while (buf.hasRemaining()) { channel.write(buf); } buf.clear(); } }catch (Exception e){ e.printStackTrace(); } } } } } } catch (Exception e) { e.printStackTrace(); } finally { try { if (socketChannel != null) { socketChannel.close(); } } catch (Exception e) { e.printStackTrace(); System.out.println("关闭ServerSocketChannel异常" + e.getMessage()); } } } }
ByteBuffer.allocate()方法,指定一个大小的缓存空间,这个缓存空间需要我们去判断请求报文的大概长度范围,定义适量的空间大小,如果空间太小则会丢失报文,太大浪费空间。
测试
编写代码入口
package nio; public class SocketMain { public static void main(String[] args) { SocketServer socketServer=new SocketServer(); Thread thread=new Thread(socketServer); thread.start(); } }
telnet测试
连接命令:telnet 127.0.0.1 9999
返回响应信息:
踩坑点
客户端窗口按快捷键:ctrl + ] 进入窗口模式
c - close 关闭当前连接 d - display 显示操作参数 o - open hostname [port] 连接到主机(默认端口 23)。 q - quit 退出 telnet set - set 设置选项(键入 'set ?' 获得列表) sen - send 将字符串发送到服务器 st - status 打印状态信息 u - unset 解除设置选项(键入 'set ?' 获得列表) ?/h - help 打印帮助信息
客户端主动断开
我们按c关闭连接,发现后台报错了,这是因为当前读取内容读不到值了。
解决方法
查询是否读取到值,如果客户端可能端口了连接,此时会返回-1
总结
本篇文章介绍了常见IO的基本概念,及其编写NIO实现Socket案例。