文章导航
- 前言
- 正文
- 概念介绍
- 什么是JAVA BIO?
- 什么是JAVA NIO?
- 什么是JAVA AIO?
- 代码编写
- 测试
- 踩坑点
- 总结
前言
最近有个对接渠道需求,对方提供文档中要求使用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案例。