1、服务端代码
- import java.io.IOException;
- import java.net.InetSocketAddress;
- import java.nio.ByteBuffer;
- import java.nio.channels.ClosedChannelException;
- import java.nio.channels.SelectionKey;
- import java.nio.channels.Selector;
- import java.nio.channels.ServerSocketChannel;
- import java.nio.channels.SocketChannel;
- import java.util.Iterator;
- public class Server {
- // 多路复用器(管理所有的通道)
- private Selector selector;
- // 用于初始化Server配置
- private void init(int port) {
- ServerSocketChannel servSocketChannel;
- try {
- // 打开服务器通道
- servSocketChannel = ServerSocketChannel.open();
- // 设置非阻塞模式
- servSocketChannel.configureBlocking(false);
- // 绑定端口
- servSocketChannel.socket().bind(new InetSocketAddress(port));
- // 打开路复用器
- selector = Selector.open();
- // 把服务器通道注册到多路复用器上,并且监听阻塞事件
- servSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
- // 输出,服务端开启信息
- System.out.println("Server start, port :" + port);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- // 用于一直轮询选择器中,管道的状态
- public void listen() {
- // 用于一直轮询选择器中,管道的状态
- while (true) {
- try {
- // 1 必须要让多路复用器开始监听
- this.selector.select();
- // 2 返回多路复用器已经选择的结果集
- Iterator<SelectionKey> keys = this.selector.selectedKeys()
- .iterator();
- // 3 进行遍历
- while (keys.hasNext()) {
- // 4 获取一个选择的元素
- SelectionKey key = keys.next();
- // 5 直接从容器中移除就可以了
- keys.remove();
- // 6 如果是有效的,对Key相应的事件进行处理
- if (key.isValid()) {
- handleKey(key);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
- private void handleKey(SelectionKey key) throws IOException,
- ClosedChannelException {
- if (key.isAcceptable()) {
- accept(key);
- } else if (key.isReadable()) {
- read(key);
- }
- }
- private void read(SelectionKey key) {
- SocketChannel channel = null;
- try {
- // 0 用于服务端接受客户端数据的缓冲区
- ByteBuffer readBuf = ByteBuffer.allocate(2048);
- // 1 清空缓冲区旧的数据
- readBuf.clear();
- // 2 获取之前注册的socket通道对象
- channel = (SocketChannel) key.channel();
- // 3 读取数据
- int count = channel.read(readBuf);
- // 4 如果有数据,就进行处理
- if (count > 0) {
- // 5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
- // 进入读模式
- readBuf.flip();
- // 6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
- byte[] bytes = new byte[readBuf.remaining()];
- // 7 接收缓冲区数据
- readBuf.get(bytes);
- // 8 打印结果
- String requestInfo = new String(bytes).trim();
- System.out.println("Server : " + requestInfo);
- // 9 写回给客户端数据
- // 根据客户端的请求数据,获取相应数据,并回写给客户端
- String response = getAnswer(requestInfo);
- // 通过String的response,构造ByteBuffer,返回给客户端
- ByteBuffer bufferResponse = ByteBuffer
- .wrap(response.getBytes());
- channel.write(bufferResponse);
- }
- } catch (IOException e) {
- // 3 读取数据发生异常的时候,关闭连接,释放资源
- // int count = channel.read(readBuf);
- try {
- key.channel().close();
- key.cancel();
- System.out.println("客户端退出");
- } catch (IOException e1) {
- e1.printStackTrace();
- }
- }
- }
- private void accept(SelectionKey key) {
- try {
- // 1 获取服务通道
- ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
- // 2 执行阻塞方法
- SocketChannel sc = ssc.accept();
- // 3 设置阻塞模式
- sc.configureBlocking(false);
- // 4 注册到多路复用器上,并设置读取标识
- sc.register(this.selector, SelectionKey.OP_READ);
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- // 根据用户的请求,获取返回信息
- // 实际的开发,可以到数据库获取数据
- private String getAnswer(String question) {
- String answer = "请输入 who, 或者what, 或者where";
- if ("who".equals(question)) {
- answer = "我是莉莉";
- } else if ("what".equals(question)) {
- answer = "我是来帮你解闷的";
- } else if ("where".equals(question)) {
- answer = "我来自外太空";
- } else {
- answer = "请输入 who, 或者what, 或者where";
- }
- // 实际开发,可以根据请求信息
- // 到数据库查询数据,并且返回给客户端
- System.out.println(answer);
- return answer;
- }
- public static void main(String[] args) {
- Server server = new Server();
- server.init(8889);
- server.listen();
- }
- }
2、客户端代码
- 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.SocketChannel;
- import java.util.Iterator;
- import java.util.Scanner;
- public class Client {
- private SocketChannel channel;
- private Selector selector;
- public static void main(String[] args) {
- Client c = new Client();
- c.connect("localhost", 8889);
- }
- public void connect(String ipaddress, int port) {
- try {
- channel = SocketChannel.open();
- channel.configureBlocking(false);
- // 请求连接
- channel.connect(new InetSocketAddress(ipaddress, port));
- selector = Selector.open();
- channel.register(selector, SelectionKey.OP_CONNECT);
- boolean isOver = false;
- while (!isOver) {
- selector.select();
- Iterator<SelectionKey> ite = selector.selectedKeys().iterator();
- while (ite.hasNext()) {
- SelectionKey key = (SelectionKey) ite.next();
- // 处理,并移除这个Key
- ite.remove();
- // 进行Key的处理
- handle(key);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- } finally {
- CloseUtils.closeCloseable(channel, selector);
- }
- }
- private void handle(SelectionKey key) {
- try {
- // 处理前,先判断,这个Key是有效的
- if (key.isValid()) {
- if (key.isConnectable()) {
- if (channel.isConnectionPending()) {
- if (channel.finishConnect()) {
- // 只有当连接成功后才能注册OP_READ事件
- key.interestOps(SelectionKey.OP_READ);
- // 客户端连接成功,给服务端发送请求
- String request = "Hi I am client ...";
- ByteBuffer buffer = ByteBuffer.wrap(request
- .getBytes());
- channel.write(buffer);
- } else {
- key.cancel();
- }
- }
- } else if (key.isReadable()) {
- read(key);
- }
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- // 用于读取,客户端的请求数据
- // 并根据,客户端的请求数据。将相应的数据返回给客户端
- private void read(SelectionKey key) {
- try {
- // 2 用于服务端接受客户端数据的缓冲区
- ByteBuffer readBuf = ByteBuffer.allocate(2048);
- // 2 获取之前注册的socket通道对象
- SocketChannel channel = (SocketChannel) key.channel();
- // 3 读取数据
- int count = channel.read(readBuf);
- // 有数据,就读取;没数据,就不读取
- if (count > 0) {
- // 5 有数据则进行读取 读取之前需要进行复位方法(把position 和limit进行复位)
- // 进入读模式
- readBuf.flip();
- // 6 根据缓冲区的数据长度创建相应大小的byte数组,接收缓冲区的数据
- byte[] bytes = new byte[readBuf.remaining()];
- // 7 接收缓冲区数据
- readBuf.get(bytes);
- // 8 打印结果
- String question = new String(bytes).trim();
- System.out.println("Server : " + question);
- // 9.再次向服务端,发送数据
- Scanner scanner = new Scanner(System.in);
- String strData = scanner.nextLine();
- ByteBuffer buffer = ByteBuffer.wrap(strData.getBytes());
- channel.write(buffer);
- } else {
- // 3 读取数据,发生异常
- // 关闭连接,释放资源
- // int count = sc.read(readBuf);
- channel.close();
- key.cancel();
- return;
- }
- } catch (IOException e) {
- e.printStackTrace();
- }
- }
- }
3、客户端关闭后,服务端抛出异常信息,java.io.IOException: 远程主机强迫关闭了一个现有的连接
3.0 异常的详细信息
- java.io.IOException: 远程主机强迫关闭了一个现有的连接。
- at sun.nio.ch.SocketDispatcher.read0(Native Method)
- at sun.nio.ch.SocketDispatcher.read(SocketDispatcher.java:43)
- at sun.nio.ch.IOUtil.readIntoNativeBuffer(IOUtil.java:223)
- at sun.nio.ch.IOUtil.read(IOUtil.java:197)
- at sun.nio.ch.SocketChannelImpl.read(SocketChannelImpl.java:384)
- at bhz.nio2.Server.read(Server.java:89)
- at bhz.nio2.Server.handleKey(Server.java:74)
- at bhz.nio2.Server.listen(Server.java:59)
- at bhz.nio2.Server.main(Server.java:165)
3.1 异常产生的原因
在服务端的private void read(SelectionKey key)方法里面的, 读取数据int count = channel.read(readBuf);在客户端,关闭后会发生异常。
3.2 解决的方法
捕获异常,并进行处理。关闭连接,释放资源。
3.2 异常的原因,以及解决方法