java NIO 学习笔记

    项目组是做IM产品的,服务端当然用的是NIO技术做通信底层。但是一直都是对NIO有些理论的了解,没有实践,最近有空了,就实践了下NIO。

    NIO,新IO,也称之为非阻塞IO。非阻塞是它跟传统IO的最重要的区别之一。传统IO用Socket进行通信,NIO则用channel进行消息交互。channel必须注册到selector上,把它感兴趣的事件告诉selector。这是个观察者模式的实现。可以这样描述channel和selector的关系,channel是火车轨道,selector是火车调度室。多个channel可以注册到同一个selector上,在一个线程了对这些channel进行调度。然而传统IO需要为每一个socket创建一个线程。这也是NIO的优势之一,不需要太多的线程。

    NIO版本的服务端代码:

package cn.nio;

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;

public class NioServer {
	private Selector	serverSelector;

	public NioServer initServer(int port) throws IOException {
		ServerSocketChannel serverChannel = ServerSocketChannel.open();
		// 非阻塞模式
		serverChannel.configureBlocking(false);
		// 绑定端口号
		serverChannel.socket().bind(new InetSocketAddress(port));
		this.serverSelector = Selector.open();
		serverChannel.register(serverSelector, SelectionKey.OP_ACCEPT);
		return this;
	}

	public void startListening() throws IOException {
		System.out.println("服务端启动成功!");
		while (true) {
			// 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
			serverSelector.select();
			Iterator<SelectionKey> iterator = this.serverSelector.selectedKeys().iterator();
			while (iterator.hasNext()) {
				SelectionKey key = (SelectionKey) iterator.next();
				// 必须删除,否则下次遍历时还会遍历旧的key
				iterator.remove();	
				if (key.isAcceptable()) {
					accept(key);
				} else if (key.isReadable()) {
					read(key);
				}
			}
		}
	}
	
	// 接受客户端的连接请求
	private void accept(SelectionKey key) throws IOException {
		ServerSocketChannel server = (ServerSocketChannel) key.channel();
		// 获得和客户端连接的通道
		SocketChannel channel = server.accept();
		// 设置成非阻塞
		channel.configureBlocking(false);
		channel.write(ByteBuffer.wrap(new String("客户端连接成功\n").getBytes()));
		// 注册
		channel.register(this.serverSelector, SelectionKey.OP_READ);
	}

	private void read(SelectionKey key) {
		try {
			SocketChannel channel = (SocketChannel) key.channel();
			// 创建读取的缓冲区
			ByteBuffer buffer = ByteBuffer.allocate(1024);
			channel.read(buffer);
			String msg = new String(buffer.array()).trim();
			System.out.println("服务端:" + msg);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) throws IOException {
		new NioServer().initServer(8080).startListening();
	}
}
    NIO版本的客户端代码:

package cn.nio;

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.SocketChannel;
import java.util.Iterator;

public class NioClient {

	private Selector	selector;

	public NioClient initClient(String ip, int port) throws IOException {
		SocketChannel channel = SocketChannel.open();
		// 非阻塞
		channel.configureBlocking(false);
		this.selector = Selector.open();

		// connect方法的注释:此方法返回 false,并且必须在以后通过调用 finishConnect 方法来完成该连接操作。
		boolean result = channel.connect(new InetSocketAddress(ip, port));
		System.out.println(result); // 返回false
		// 注册
		channel.register(selector, SelectionKey.OP_CONNECT);
		return this;
	}

	public void startListening() throws IOException {
		while (true) {
			selector.select();
			Iterator<SelectionKey> iterator = this.selector.selectedKeys().iterator();
			while (iterator.hasNext()) {
				SelectionKey key = (SelectionKey) iterator.next();
				// 必须删除,否则下次遍历时还会遍历旧的key
				iterator.remove();
				if (key.isConnectable()) {
					connect(key);
				} else if (key.isReadable()) {
					read(key);
				}
			}
		}
	}

	private void connect(SelectionKey key) throws IOException, ClosedChannelException {
		SocketChannel channel = (SocketChannel) key.channel();
		// 如果正在连接,则完成连接
		if (channel.isConnectionPending()) {
			channel.finishConnect();
		}
		channel.configureBlocking(false);
		channel.write(ByteBuffer.wrap(new String("客户端连接成功").getBytes()));
		// 注册
		channel.register(this.selector, SelectionKey.OP_READ);
	}

	private void read(SelectionKey key) throws IOException {
		// 服务器可读取消息:得到事件发生的Socket通道
		SocketChannel channel = (SocketChannel) key.channel();
		// 创建读取的缓冲区
		ByteBuffer buffer = ByteBuffer.allocate(1024);
		channel.read(buffer);
		String msg = new String(buffer.array()).trim();
		System.out.println("客户端收到信息:" + msg);
	}

	/**
	 * 启动客户端测试
	 * 
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		new NioClient().initClient("localhost", 8080).startListening();
	}

}


    考虑到其他语言不一定有NIO技术,我们的客户端都是C++的。那么一个NIO服务端是否可以和一个非NIO的客户端进行通信呢?做了一个传统IO的客户端,做测试。可行!

package cn.nio;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;

public class NormalClient {

	Socket	clientSocket	= null;

	public NormalClient initClient(String ip, int port) {
		try {
			clientSocket = new Socket(ip, port);
		} catch (IOException e) {
			e.printStackTrace();
		}
		return this;
	}

	public void read() throws IOException {
		PrintWriter pw = new PrintWriter((clientSocket.getOutputStream()));
		BufferedReader br = new BufferedReader(new InputStreamReader(clientSocket.getInputStream()));
		String line = null;
		while (true) {
			pw.println("客户端发来的消息");
			pw.flush();
			line = br.readLine();
			System.out.println(line);
		}
	}

	public static void main(String[] args) throws IOException {
		new NormalClient().initClient("127.0.0.1", 8000).read();
	}
}
    

    写这个客户端的时候,客户端一直无法收到服务端的消息。请教资深同事,问题在readline()方法上。如果调用的是readline(),那么服务端给客户端发消息时,需要传换行符。


已标记关键词 清除标记
相关推荐
©️2020 CSDN 皮肤主题: 大白 设计师:CSDN官方博客 返回首页