先上一个最基本的socket io的例子,首先是server端:
ServerSocket serverSocket = new ServerSocket(PORT);
while (true) { Socket client = serverSocket.accept(); new HandlerThread(client); }DataInputStream input = new DataInputStream(socket.getInputStream());
DataOutputStream out = new DataOutputStream(socket.getOutputStream()); while(true){ String clientInputStr = input.readUTF(); }首先new一个ServerSocket,之后循环监听,得到一个新的连接之后,交给一个HandlerThread来处理。
而在HandlerThread当中,也是需要阻塞的方式来读取输入,读不着了就等着。
然后是client端:
socket = new Socket(IP_ADDR, PORT);
DataInputStream input = new DataInputStream(socket.getInputStream());
DataOutputStream out = new DataOutputStream(socket.getOutputStream()); while(true){ String str = new BufferedReader(new InputStreamReader(System.in)).readLine(); out.writeUTF(str); String ret = input.readUTF(); }而在client当中,大同小异,new一个socket对象,连接到server上,之后向server发信息,然后等着server的响应。
上面3处标红的地方,server监听接入,server和client的读取input stream,都是通过阻塞的方式来完成。如果说,一个server介入了若干个连接,就需要若干个线程来完成和客户端的通信工作。
然后我们再上一个java nio的例子,首先是server端:
ServerSocketChannel serverChannel = ServerSocketChannel.open();
serverChannel.configureBlocking(false); serverChannel.socket().bind(new InetSocketAddress(port)); this.selector = Selector.open();serverChannel.register(selector, SelectionKey.OP_ACCEPT);
public void listen() throws Exception {
while (true) { selector.select(); Iterator ite = this.selector.selectedKeys().iterator(); while (ite.hasNext()) { SelectionKey key = (SelectionKey) ite.next(); ite.remove(); if (key.isAcceptable()) { ServerSocketChannel server = (ServerSocketChannel) key.channel(); SocketChannel channel = server.accept(); channel.configureBlocking(false); channel.write(ByteBuffer.wrap(new String("hello client").getBytes())); channel.register(this.selector, SelectionKey.OP_READ);} else if (key.isReadable()) {
read(key); }}
} }private void read(SelectionKey key) throws Exception {
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("server receive from client: " + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer); }基本流程解释一下:
首先是准备阶段,生成一个server channel的对象,然后绑定端口,再打开一个selector,注册accept事件。
然后是监听,监听到一个连接,accept,并返回一个socket channel,把这个客户端channel的read事件注册到selector,注意,这里的selector跟之前监听server channel的是同一个,也就是说,目前这个selector要听2件事,一个是监听室针对server channel,另一个是监听client channel有没有数据进来。
有数据进来的话,读数据,然后写个返回回去。
然后是client端:
SocketChannel channel = SocketChannel.open();
channel.configureBlocking(false); this.selector = Selector.open(); channel.connect(new InetSocketAddress(ip, port)); channel.register(selector, SelectionKey.OP_CONNECT);public void listen() throws Exception { // 轮询访问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("hello server!").getBytes())); channel.register(this.selector, SelectionKey.OP_READ); // 获得了可读的事件 } else if (key.isReadable()) { read(key); } } } }private void read(SelectionKey key) throws Exception {
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("client receive msg from server:" + msg); ByteBuffer outBuffer = ByteBuffer.wrap(msg.getBytes()); channel.write(outBuffer); }这里也需要简单解释一下流程:
准备阶段,创建一个SocketChannel对象,连接到服务器上,然后打开一个selector,注册connect事件到selector上。
然后同样是监听,监听到服务器已经响应了自己的连接,需要调用一个finishConnect方法,这就算是完成连接了,把read事件注册到selector上。
读到服务器响应,读取数据,返回响应。
到此为止,对于io和nio的实现方式,我们都已经有了大体的了解,我们现在来比较一下2种方式的不同点:
1、多路选择器(selector)的使用,使得一个线程可以针对多个channel进行监听,大大的减少线程的数量,提高了系统运行的效率。
2、io是针对流的,而nio针对的是缓冲区,而在缓冲区当中指针可以任意进行移动,对某些复杂的业务场景,可以极大地简化编程的复杂度。