本文旨在介绍传统Socket服务端与NIO服务端的差异.
以餐厅服务员简单举例,每个客人对应一个请求.
1 public class OioServer {
2
3 @SuppressWarnings("resource")
4 public static void main(String[] args) throws Exception {
5
6 ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
7 //创建socket服务,监听10101端口
8 ServerSocket server=new ServerSocket(10101);
9 System.out.println("服务器启动!");
10 while(true){
11 //获取一个套接字(阻塞)
12 final Socket socket = server.accept();
13 System.out.println("来个一个新客户端!");
14 newCachedThreadPool.execute(new Runnable() {
15
16 @Override
17 public void run() {
18 //业务处理
19 handler(socket);
20 }
21 });
22
23 }
24 }
25
26 /**
27 * 读取数据
28 * @param socket
29 * @throws Exception
30 */
31 public static void handler(Socket socket){
32 try {
33 byte[] bytes = new byte[1024];
34 InputStream inputStream = socket.getInputStream();
35
36 while(true){
37 //读取数据(阻塞)
38 int read = inputStream.read(bytes);
39 if(read != -1){
40 System.out.println(new String(bytes, 0, read));
41 }else{
42 break;
43 }
44 }
45 } catch (Exception e) {
46 e.printStackTrace();
47 }finally{
48 try {
49 System.out.println("socket关闭");
50 socket.close();
51 } catch (IOException e) {
52 e.printStackTrace();
53 }
54 }
55 }
56 }
缺点
单线程情况下只能有一个客户端
用线程池可以有多个客户端连接,但是非常消耗性能
类比图
1 public class NIOServer {
2 // 通道管理器
3 private Selector selector;
4
5 /**
6 * 获得一个ServerSocket通道,并对该通道做一些初始化的工作
7 *
8 * @param port
9 * 绑定的端口号
10 * @throws IOException
11 */
12 public void initServer(int port) throws IOException {
13 // 获得一个ServerSocket通道
14 ServerSocketChannel serverChannel = ServerSocketChannel.open();
15 // 设置通道为非阻塞
16 serverChannel.configureBlocking(false);
17 // 将该通道对应的ServerSocket绑定到port端口
18 serverChannel.socket().bind(new InetSocketAddress(port));
19 // 获得一个通道管理器
20 this.selector = Selector.open();
21 // 将通道管理器和该通道绑定,并为该通道注册SelectionKey.OP_ACCEPT事件,注册该事件后,
22 // 当该事件到达时,selector.select()会返回,如果该事件没到达selector.select()会一直阻塞。
23 serverChannel.register(selector, SelectionKey.OP_ACCEPT);
24 }
25
26 /**
27 * 采用轮询的方式监听selector上是否有需要处理的事件,如果有,则进行处理
28 *
29 * @throws IOException
30 */
31 public void listen() throws IOException {
32 System.out.println("服务端启动成功!");
33 // 轮询访问selector
34 while (true) {
35 // 当注册的事件到达时,方法返回;否则,该方法会一直阻塞
36 selector.select();
37 // 获得selector中选中的项的迭代器,选中的项为注册的事件
38 Iterator<?> ite = this.selector.selectedKeys().iterator();
39 while (ite.hasNext()) {
40 SelectionKey key = (SelectionKey) ite.next();
41 // 删除已选的key,以防重复处理
42 ite.remove();
43
44 handler(key);
45 }
46 }
47 }
48
49 /**
50 * 处理请求
51 *
52 * @param key
53 * @throws IOException
54 */
55 public void handler(SelectionKey key) throws IOException {
56
57 // 客户端请求连接事件
58 if (key.isAcceptable()) {
59 handlerAccept(key);
60 // 获得了可读的事件
61 } else if (key.isReadable()) {
62 handelerRead(key);
63 }
64 }
65
66 /**
67 * 处理连接请求
68 *
69 * @param key
70 * @throws IOException
71 */
72 public void handlerAccept(SelectionKey key) throws IOException {
73 ServerSocketChannel server = (ServerSocketChannel) key.channel();
74 // 获得和客户端连接的通道
75 SocketChannel channel = server.accept();
76 // 设置成非阻塞
77 channel.configureBlocking(false);
78
79 // 在这里可以给客户端发送信息哦
80 System.out.println("新的客户端连接");
81 // 在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读的权限。
82 channel.register(this.selector, SelectionKey.OP_READ);
83 }
84
85 /**
86 * 处理读的事件
87 *
88 * @param key
89 * @throws IOException
90 */
91 public void handelerRead(SelectionKey key) throws IOException {
92 // 服务器可读取消息:得到事件发生的Socket通道
93 SocketChannel channel = (SocketChannel) key.channel();
94 // 创建读取的缓冲区
95 ByteBuffer buffer = ByteBuffer.allocate(1024);
96 int read = channel.read(buffer);
97 if(read > 0){
98 byte[] data = buffer.array();
99 String msg = new String(data).trim();
100 System.out.println("服务端收到信息:" + msg);
101
102 //回写数据
103 ByteBuffer outBuffer = ByteBuffer.wrap("好的".getBytes());
104 channel.write(outBuffer);// 将消息回送给客户端
105 }else{
106 System.out.println("客户端关闭");
107 key.cancel();
108 }
109 }
110
111 /**
112 * 启动服务端测试
113 *
114 * @throws IOException
115 */
116 public static void main(String[] args) throws IOException {
117 NIOServer server = new NIOServer();
118 server.initServer(8000);
119 server.listen();
120 }
121
122 }
优点
利用Selector多路复用技术, 一个线程可以处理多个客户端.
类比图