JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信

阻塞IO与非阻塞IO

通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程。

从JAVA1.4开始引入了NIO API, NIO可以实现非阻塞IO, 这样就可以使用一个线程处理所有的客户请求。

基于NIO的非阻塞Socket通信

服务器将用来监听客户端请求的channel注册到selector上,启动一个线程,使用selector的select()获取求情的客户端的channel数量,

当监听到有客户端请求时,就通过SelectionKey返回对应的客户端channel进行通信。

下面是一个非常简单的例子,

服务器端

  1 package niochat;
  2 
  3 import java.io.IOException;
  4 import java.net.InetSocketAddress;
  5 import java.nio.ByteBuffer;
  6 import java.nio.channels.Channel;
  7 import java.nio.channels.SelectionKey;
  8 import java.nio.channels.Selector;
  9 import java.nio.channels.ServerSocketChannel;
 10 import java.nio.channels.SocketChannel;
 11 import java.nio.charset.Charset;
 12 
 13 public class NServer {
 14     //用于检查所有channel状态的selector
 15     private Selector selector = null;
 16     static final int PORT = 3001;
 17     private Charset charset = Charset.forName("utf-8");
 18     public void init() throws IOException, InterruptedException {
 19         selector = Selector.open();
 20         //通过open方法打开一个未绑定的ServerSocketChannel实例
 21         ServerSocketChannel server = ServerSocketChannel.open();
 22         InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);
 23         server.bind(isa);
 24         //设置非阻塞
 25         server.configureBlocking(false);
 26         //注册ServerSocketChannel到selector
 27         server.register(selector, SelectionKey.OP_ACCEPT);
 28         
 29         //服务器端需要轮询selector看是否有channel需要通信
 30         while ( true ) {
 31             //select()将会一直阻塞,直到选择了至少一个channel(进行通信),此时sector就会调用wakeup(),select()方法才能返回
 32             int num = selector.select();
 33             Thread.sleep(1000);
 34             //select()方法返回后,用selectedKeys()返回对应channel的SelectionKey集合,通过key.channel()方法可以返回对应的channel实例
 35             //被选中的key集合selectedKeys表示需要进行IO处理的channel集合,一个key代表一个channel
 36             System.out.println("SelectionKey.size = "+selector.selectedKeys().size());
 37             for (SelectionKey sk : selector.selectedKeys()) {
 38                 //从selector上的已选择的key集合中删除正在处理的key
 39                 System.out.println("SelectionKey.value = "+sk);
 40                 selector.selectedKeys().remove(sk);
 41                 //如果sk对应channel包含客户端连接请求
 42                 if (sk.isAcceptable()) {
 43                     //调用accept方法接受请求,产生一个服务器端的SocketChannel
 44                     //在非阻塞模式下,如果没有连接则直接返回null
 45                     SocketChannel sc = server.accept();
 46                     //设置非阻塞模式
 47                     sc.configureBlocking(false);
 48                     //将SocketChannel也注册到selector
 49                     sc.register(selector, SelectionKey.OP_READ);
 50                     //再将sk对应的channel设置为请求准备接受其他请求
 51                     sk.interestOps(SelectionKey.OP_ACCEPT);
 52                 }
 53 
 54                 //如果sk对应的channel有数据需要读取
 55                 if (sk.isReadable()) {
 56                     //获取sk对应的channel
 57                     SocketChannel sc = (SocketChannel)sk.channel();
 58                     //channel中的数据必须先写入buffer中,然后才能写入进content中
 59                     ByteBuffer buff = ByteBuffer.allocate(1024);
 60                     String content = "";
 61                     try {
 62                         while(sc.read(buff) > 0) {
 63                             //buffer 复位
 64                             buff.flip();
 65                             content += charset.decode(buff);
 66                         }
 67                         System.out.println("读取的数据:"+ content);
 68                         //sk复位
 69                         sk.interestOps(SelectionKey.OP_READ);
 70                     } //遇到channel有异常说明客户端有异常,取消注册此sk
 71                     catch (IOException ex) {
 72                         //从已选择key集合中取消sk,下一次select()时此channel将自动被删除 
 73                         ex.printStackTrace();
 74                         
 75                         sk.cancel();
 76                         if(sk.channel() != null) {
 77                             sk.channel().close();
 78                         }
 79                         
 80                     }
 81                     
 82                     if (content.length() > 0) {
 83                         //广播
 84                         for(SelectionKey key : selector.keys()) {
 85                             Channel targetChannel = key.channel();
 86                             if (targetChannel instanceof SocketChannel) {
 87                                 SocketChannel dest = (SocketChannel)targetChannel;
 88                                 dest.write(charset.encode(content));
 89                             }
 90                         }
 91                     }
 92                 } 
 93             
 94             }
 95         }
 96     }
 97     
 98     public static void main(String[] args) throws IOException, InterruptedException {
 99         new NServer().init();
100     }
101 }

 

客户端

 1 package niochat;
 2 
 3 import java.io.IOException;
 4 import java.net.InetSocketAddress;
 5 import java.nio.ByteBuffer;
 6 import java.nio.channels.Channel;
 7 import java.nio.channels.SelectionKey;
 8 import java.nio.channels.Selector;
 9 import java.nio.channels.SocketChannel;
10 import java.nio.charset.Charset;
11 import java.util.Scanner;
12 
13 public class NClient {
14     //定义检测SocketChannel的selector
15     private Selector selector = null;
16     private static final int PORT = 3001;
17     private Charset charset = Charset.forName("utf-8");
18     //客户端的SocketChannel
19     private SocketChannel sc = null;
20     public void init() throws IOException {
21          selector = Selector.open();
22          InetSocketAddress isa = new InetSocketAddress("127.0.0.1", PORT);
23          //调用open静态方法创建连接到指定主机的SocketChannel
24          sc = SocketChannel.open(isa);
25          sc.configureBlocking(false);
26          sc.register(selector, SelectionKey.OP_READ);
27          //创建子线程读取服务器返回的数据
28          new ClientThread().start();
29          //键盘输入流
30          Scanner scan = new Scanner(System.in);
31          while (scan.hasNextLine())    {
32             String line = scan.nextLine();
33             //放进SocketChannel
34             sc.write(charset.encode(line));
35          }
36     }
37     
38     private class ClientThread extends Thread {
39         public void run() {
40             try {
41                 while (selector.select() > 0) {
42                     //被选中的key集合selectedKeys表示需要进行IO处理的channel集合
43                     for (SelectionKey sk : selector.selectedKeys()) {
44                         //删除正在处理的key
45                         selector.selectedKeys().remove(sk);
46                         if (sk.isReadable()) {    
47                             SocketChannel sc = (SocketChannel)sk.channel();
48                             ByteBuffer buff = ByteBuffer.allocate(1024);
49                             String content = "";
50                             while (sc.read(buff) > 0) {
51                                 sc.read(buff);
52                                 buff.flip();
53                                 content += charset.decode(buff);
54                             }
55                             System.out.println("聊天信息:"+content);
56                             sk.interestOps(SelectionKey.OP_READ);
57                         }
58                     }
59                 }
60             } catch (IOException e) {
61                 e.printStackTrace();
62             }
63         }
64     }
65     
66     public static void main(String[] args) throws IOException {
67         new NClient().init();
68     }
69 }

执行结果,启动了一个服务器端,然后启动了两个客户端,

 

posted @ 2016-11-21 21:53  fysola  阅读(565)  评论(0编辑  收藏  举报