NIO的引入
使用BIO進行socket通信時,accept連接 和 read讀 均會阻塞。
這樣就會出現以下問題:
1. 第一個客戶端建立連接後,但是不發送數據,server會釋放cpu資源,並且等待客戶端發送數據
2. 第二個客戶端無法建立連接
編寫一個BIOServer
public class BIOServer {
public static void main(String[] args) throws IOException {
byte[] bytes = new byte[1024];
ServerSocket serverSocket = new ServerSocket();
// 端口號+ip,ip默認本機
serverSocket.bind(new InetSocketAddress(9876));
// 阻塞--程序釋放cpu資源,程序不會向下執行
// accept,專門負責通信
while (true) {
System.out.println("--等待連接--");
Socket accept = serverSocket.accept();
System.out.println("--連接成功--");
// 解阻塞,向下執行,read也會阻塞
System.out.println("--開始讀數據--");
int read = accept.getInputStream().read(bytes);
System.out.println("--數據讀取完成:" + read + "--");
// 字節數組轉string
String content = new String(bytes);
System.out.println(content);
// 回覆,寫,略
}
}
}
再創建一個客戶端
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket();
socket.connect(new InetSocketAddress("127.0.0.1", 9876));
System.out.println("--請輸入內容--");
Scanner scanner = new Scanner(System.in);
while (true) {
String next = scanner.next();
socket.getOutputStream().write(next.getBytes());
}
}
}
運行BIOServer 和client,便會出現以下結果:
總結:
1. 如果採用單線程,無法處理併發
2. 解決? 引入多線程
多線程實現socket通信
client不變,新建一個BioServerBatch類,子線程自己阻塞
public class BioServerBatch {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(9876));
while (true) {
System.out.println("--等待連接--");
Socket socket = serverSocket.accept();
System.out.println("--連接成功--");
Thread thread = new Thread(new ExecuteSocket(socket));
thread.start();
}
}
static class ExecuteSocket implements Runnable {
private Socket socket;
private byte[] bytes = new byte[1024];
// 處理每個客戶端連接 -- 讀寫
ExecuteSocket(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
System.out.println("--開始讀數據--");
socket.getInputStream().read(bytes);
String content = new String(bytes);
System.out.println("--數據讀取完成:" + content + "--");
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
這樣就可以實現併發通信,但是存在一個問題,比如開100個線程,真正發生讀寫的線程只有20%有效,80%的線程存在浪費現象,對服務器損耗很大。
解決?單線程實現高併發
單線程解決高併發 NIO
實現機制:
不讓程序阻塞
-
等待連接和讀數據不阻塞
-
有人來連,設置爲非阻塞讀,將當前socket放入到list,遍歷list,查看是否有人發數據
-
沒人來連,將當前socket放入到list,遍歷list,查看是否有人發數據
Java爲我們提供了API來實現該機制,代碼如下:
public class NIOServer {
public static void main(String[] args) throws IOException, InterruptedException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
List<SocketChannel> list = new ArrayList<>();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9876));
// 設置爲非阻塞
serverSocketChannel.configureBlocking(false);
while (true) {
Selector selector = Selector.open();
SocketChannel socketChannel = serverSocketChannel.accept();
if (socketChannel == null) {
System.out.println("--沒人連接--");
Thread.sleep(1000);
for (SocketChannel channel : list) {
int k = channel.read(byteBuffer);
System.out.println(k);
if (k != 0) {
// 有人發數據
byteBuffer.flip();
System.out.println("========no conn=======》" + new String(byteBuffer.array()));
}
}
} else {
System.out.println("--有人連接--");
socketChannel.configureBlocking(false);
list.add(socketChannel);
// 得到套接字,循環所以的套接字,通過套接字獲取數據
for (SocketChannel channel : list) {
int k = channel.read(byteBuffer);
if (k != 0) {
byteBuffer.flip();
System.out.println("========conn=======》" + new String(byteBuffer.array()));
}
}
}
}
}
}
但該程序依然存在以下問題:
每次循環的時候,都會遍歷list列表,如果列表很大,且只有20%有讀寫,性能還是較低
解決?IO多路複用
IO多路複用
將list交給操作系統來處理
具體的方法有三個:select、poll和epoll,有興趣的可以去了解一下
應用:redis、nginx和java nio在linux上的實現