java-socket的bio与nio示例

socket-bio

传统的阻塞IO(blocking_io)的缺点:
1.服务器以阻塞方式处理客户端连接,所以服务器需要创建大量线程来处理客户端连接;线程的创建与销毁影响服务器性能
2.服务器需要维护已连接的客户端信息,遍历过程会消耗服务器性能
3.服务器以阻塞方式读取客户端信息,服务端会因客户端响应不及时而无法释放连接影响性

示例代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
private Map<String, Socket> clientSocketMap = new ConcurrentHashMap<>();
/**
* 使用bio处理多个客户端连接<br/>
* TODO 客户端直接关闭的话,不会及时通知服务端释放资源,需要服务端额外通过心跳检测客户端状态才行
* @throws Exception
*/
@Test
public void doBlockingServer() throws Exception {
ServerSocket serverSocket = new ServerSocket(9999);

System.out.println("启动服务器");
while (true){
//没有客户端连接时,此处会阻塞
//所以服务器在处理 传统的blocking_socket时,需要使用单独线程(线程池)来处理socket连接,放在阻塞主线程(只能处理一个客户端连接)
Socket socket = serverSocket.accept();
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyyMMddHHmmss");
String clientId = simpleDateFormat.format(new Date());
clientSocketMap.put(clientId, socket);
System.out.println("客户端"+clientId+"连接到服务器,服务器访问数:"+clientSocketMap.size());
new BioSocketHandler(clientId).start();
}

}

/**
* socket处理器
* 使用子线程处理客户端socket连接,防止主线程阻塞
*/
class BioSocketHandler extends Thread {

private String clientId;

public BioSocketHandler(String clientId){
this.clientId = clientId;
}

@Override
public void run() {
Socket socket = clientSocketMap.get(clientId);
//使用循环处理客户端连接
// 客户端端口连接或者客户端发出结束信号(bye) 时,跳出循环并结束处理线程及关闭客户端连接
while (true){
InputStream inputStream = null;
try {
//客户端先连接后关闭的情况下,会报异常,这种情况不需要服务端处理
inputStream = socket.getInputStream();
}catch (IOException e){

}
if(inputStream==null) break;

byte[] bytes = new byte[1024];
int i = -1;
try {
i = inputStream.read(bytes);//客户端连接上,没发送数据时,此处会阻塞
} catch (IOException e) {

}
if(i==-1) continue; //-1表示没接收到客户端数据,继续保持连接

System.out.println("读取到客户端数据 clientId:"+clientId);
String str = new String(bytes);
if(str!=null && str.startsWith("bye")){
try {
inputStream.close();
} catch (IOException e) {

}
break;
}else {
System.out.println(str);
}
}

try {
System.out.println("客户端退出或关闭:"+clientId);
socket.close();
clientSocketMap.remove(clientId);
} catch (IOException e) {
//e.printStackTrace();
}

System.out.println("服务端连接数据:"+clientSocketMap.size());
}
}

nio

使用nio API处理客户端请求示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
package com.lixl.demo;

import org.junit.Test;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
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.Set;

/**
* 通过nio api处理客户端连接和请求
*/
public class NioSocketServerDemo {

@Test
public void doBlockingServer() throws Exception {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()
.bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(true);//默认为true,不能设置为false java.nio.channels.IllegalBlockingModeException

ServerSocket serverSocket = serverSocketChannel.socket();

Socket socket = serverSocket.accept();// block=true,此处会阻塞方式等待客户端连接
byte[] bytes = new byte[1024];
int i = socket.getInputStream().read(bytes);// 与blocking_io一样,阻塞在
if (i != -1) {
System.out.println(new String(bytes));
}
socket.close();
serverSocket.close();
serverSocketChannel.close();

}


@Test
public void doServer() throws Exception {
// 1.创建ServerSocketChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open()
.bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);//设置为非阻塞

// 2.创建选择器Selector
Selector selector = Selector.open();

// 3.将ServerSocketChannel注册到Selector(默认监听客户端的连接请求)
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);//block=true的话此处会报错

while (true) {
selector.select();//阻塞在此处并等待客户端连接及请求

// 4.通过Selector获取需要处理的客户端连接
Set<SelectionKey> selectionKeys = selector.selectedKeys();
for (SelectionKey selectionKey : selectionKeys) {
// 5.循环处理接收到的客户端请求
if (selectionKey.isAcceptable()) {//处理客户端的连接请求
ServerSocketChannel server = (ServerSocketChannel) selectionKey.channel();
//获取到真实的客户端
try {
SocketChannel client = server.accept();
client.configureBlocking(false);
//客户端连接注册到selector
client.register(selector, SelectionKey.OP_READ);
} catch (IOException e) {
//e.printStackTrace();
}

} else if (selectionKey.isReadable()) {//处理客户端的访问请求
try {
SocketChannel client = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//服务端读数据到Buffer
int count = client.read(byteBuffer);
if (count > 0) {
//读写转换
byteBuffer.flip();
System.out.println(new String(byteBuffer.array()));
}
} catch (IOException e) {
//e.printStackTrace();
}
}
}

selectionKeys.clear();//循环处理完客户端请求后,需要清空Selector中已处理过的事件索引,防止下次重复处理
}

}


}