Java网络编程之(三): TCP协议使用NIO实现非阻塞Soket通信
前面介绍的网络通信程序是基于阻塞式API的————即当程序执行输入、输出操作后,在这些操作返回之前会一直阻塞该线程,所以服务器必须为每个客户端都提供一条独立线程进行处理,当服务器需要同时处理大量客户端时,这种做法会导致恨不能下降。
使用NIO方式刚可以让服务器使用一个或几个有限几个线程来同时处理连接到服务器上的所有客户端。
一. TcpServer_3.java
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; public class TcpServer_3 { //用于检测所有Channel状态的selector private Selector selector = null; //定义编码格式 private Charset charset = Charset.forName("UTF-8"); public void init()throws IOException { selector = Selector.open(); //通过open方法来打开一个未绑定的ServerSocketChannel实例 ServerSocketChannel server = ServerSocketChannel.open(); InetSocketAddress isa = new InetSocketAddress("192.168.31.168", 30002); //将该ServerSocketChannel绑定到指定IP地址 server.socket().bind(isa); //设置ServerSocket以非阻塞方式工作 server.configureBlocking(false); //将Server注册到指定Selector对象 server.register(selector, SelectionKey.OP_ACCEPT); while(selector.select() > 0) { //依次处理Selector上的每个已选择的SelectionKey for(SelectionKey sk : selector.selectedKeys()) { //从selector上的已选择Key集中删除正在处理的SelectionKey selector.selectedKeys().remove(sk); //如果sk对应的通道包含客户端的连接请求 if(sk.isAcceptable()) { //调用accept方法接受连接,产生服务器端对应的SocketChannel SocketChannel sc = server.accept(); //设置非阻塞方式工作 sc.configureBlocking(false); //将该SocketChnnel也注册到selector sc.register(selector, SelectionKey.OP_READ); //将sk对应的Channel设置成准备接受其它请求 sk.interestOps(SelectionKey.OP_ACCEPT); } //如果sk对应的通道有数据需要读取 if(sk.isReadable()) { //获取该SelectionKey对应的Channel,该Channnel中有可读的数据 SocketChannel sc = (SocketChannel)sk.channel(); //定义准备执行读取数据的ByteBuffer ByteBuffer buff = ByteBuffer.allocate(1024); String content = ""; //开始读数据 try { while(sc.read(buff) > 0) { buff.flip(); content += charset.decode(buff); } //打印从该sk对应的Channel里读到的数据 System.out.println("====" + content); //将sk对应的Channel设置成准备下一次读取 sk.interestOps(SelectionKey.OP_READ); } //如果捕捉到该sk对应的Channel出现了异常,即表明该Channel对应的Client出现了问题 //所以从Selector中取消sk的注册 catch(IOException ex) { //从Selector中删除指定的SelectionKey sk.cancel(); if(sk.channel() != null) { sk.channel().close(); } } //如果content的长度大于0,即聊天信息不为空 if(content.length() > 0) { //遍历该selector里注册的所有selectionKey for(SelectionKey key : selector.keys()) { //获取该channel是SocketChannel对象 Channel targetChannel = key.channel(); if(targetChannel instanceof SocketChannel) { //将读到的内容写入该Channel中 SocketChannel dest = (SocketChannel)targetChannel; dest.write(charset.encode(content)); } } } } } } } public static void main(String[] args)throws IOException { new TcpServer_3().init(); } }
二. Tcpclient_3.java
import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.InetSocketAddress; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.channels.Channel; import java.nio.channels.SelectionKey; import java.nio.channels.Selector; import java.nio.channels.ServerSocketChannel; import java.nio.channels.SocketChannel; import java.nio.charset.Charset; import java.util.Scanner; public class TcpClient_3 { //定义检测SocketChannel的Selector对象 private Selector selector = null; //定义处理编码和解码的字符集 private Charset charset = Charset.forName("UTF-8"); //客户端SocketChannel private SocketChannel sc = null; public void init()throws IOException { selector = Selector.open(); InetSocketAddress isa = new InetSocketAddress("192.168.31.168", 30002); //调用open静态方法创建连接到指定主机的SocketChannel sc = SocketChannel.open(isa); //设置该sc以非阻塞方式工作 sc.configureBlocking(false); //将SocketChannel对象注册到指定Selector sc.register(selector, SelectionKey.OP_READ); //启动读取服务器端数据的线程 new ClientThread().start(); //创建键盘输入流 Scanner scan = new Scanner(System.in); while(scan.hasNextLine()) { //读取键盘输入 String line = scan.nextLine(); //将键盘输入的内容输出到SocketChannel中 sc.write(charset.encode(line)); } } //定义读取服务器数据的线程 private class ClientThread extends Thread { public void run() { try { while(selector.select() > 0) { //遍历每个有可用IO操作Channel对应的SelectionKey for(SelectionKey sk : selector.selectedKeys()) { //删除正在处理的SelectionKey selector.selectedKeys().remove(sk); //如果该SelectionKey对应的Channel中有可读的数据 if(sk.isReadable()) { //使用NIO读取Channel中的数据 SocketChannel sc = (SocketChannel)sk.channel(); ByteBuffer buff = ByteBuffer.allocate(1024); String content = ""; while(sc.read(buff) > 0) { sc.read(buff); buff.flip(); content += charset.decode(buff); } System.out.println("message: " + content); //为下一次读取作准备 sk.interestOps(SelectionKey.OP_READ); } } } } catch(IOException ex) { ex.printStackTrace(); } } } public static void main(String[] args)throws IOException { new TcpClient_3().init(); } }
三. 运行
电脑A先运行TcpServer_3,然后电脑A和电脑B分别运行TcpClient_3;
任意一个Client发送消息,服务端和另外一个客户端都能接受到消息。