Java網(wǎng)絡(luò)編程之BIO
由于工作需要搭建一個TCP服務(wù),之前忽略了這塊尖奔,最近撿起來,順便記錄一下穷当。
1. 什么是BIO提茁?
在JDK1.4
之前,基于Java的所有Socket通信都使用的是同步阻塞I/O(Blocking I/O
)馁菜,這也被稱為傳統(tǒng)阻塞型I/O茴扁,還有稱之為舊的阻塞I/O(Old I/O
,即OIO
)汪疮。
OIO是相對于NIO來說的峭火,因為NIO也被稱為新的IO,即New I/O智嚷。NIO會在下篇文章中介紹卖丸。
BIO
的服務(wù)器實現(xiàn)模式為一個連接一個線程
,即客戶端有連接請求時盏道,服務(wù)器就需要啟動一個線程進行處理稍浆,如果這個連接不做任何事情,就會造成不必要的線程開銷(可以通過線程池改善)。示意圖如下:
2. BIO的特點和使用場景
特點:
- 同步并阻塞
- 一個連接對應(yīng)一個線程
- 線程開銷大
- 對服務(wù)器資源要求較高
- 程序簡單易理解
使用場景:
emmm~粹湃,我覺得現(xiàn)在應(yīng)該沒有人會用這個了恐仑,除了在學(xué)習過程中。如果有的話为鳄,那它的使用場景必是:連接數(shù)目小且架構(gòu)穩(wěn)定裳仆。
在JDK1.4
以前,BIO是唯一的選擇孤钦。
3. 代碼實現(xiàn)
服務(wù)端代碼:
public class SocketServer {
public static void main(String[] args) throws Exception {
// 創(chuàng)建一個ServerSocket對象歧斟,指定服務(wù)端端口、地址
ServerSocket serverSocket = new ServerSocket(7072, 50, InetAddress.getByName("localhost"));
System.out.println("服務(wù)器啟動:" + serverSocket);
while (true) {
System.out.println("等待連接...");
// 等待客戶端連接
Socket activeSocket = serverSocket.accept();
System.out.println("接收到一個連接偏形,來自:" + activeSocket);
// 接收到一個連接静袖,就開啟一個線程處理
Runnable runnable = new Runnable() {
@Override
public void run() {
handleClientRequest(activeSocket);
}
};
new Thread(runnable).start();
}
}
private static void handleClientRequest(Socket socket) {
BufferedReader socketReader = null;
BufferedWriter socketWrite = null;
try {
// 通過socket獲取字節(jié)流,然后包裝成字符緩沖流
socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
socketWrite = new BufferedWriter(new OutputStreamWriter(new BufferedOutputStream(socket.getOutputStream())));
String inMsg = null;
// 獲取客戶端傳輸?shù)椒?wù)端的消息
while ((inMsg = socketReader.readLine()) != null) {
System.out.println("接收到客戶端的消息:" + inMsg);
String outMsg = "喵喵喵";
// 向客戶端響應(yīng)消息
socketWrite.write(outMsg);
socketWrite.write("\n");
socketWrite.flush();
}
} catch (IOException e) {
e.printStackTrace();
} finally {
// 手動關(guān)閉資源
if (socketReader != null) {
try {
socketReader.close();
} catch (IOException e) {
e.printStackTrace();
}
}
if (socketWrite != null) {
try {
socketWrite.close();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
客戶端可以不用編寫代碼俊扭,使用Windows
自帶的telnet
功能队橙,向服務(wù)端發(fā)送消息,由于Windows
的命令行是GBK
的編碼萨惑,與服務(wù)器不同捐康,發(fā)送內(nèi)容或響應(yīng)內(nèi)容如果有中文字符,就會出現(xiàn)亂碼庸蔼,涉及字符集轉(zhuǎn)換解总,這里就不演示了。想用telnet
功能調(diào)試的話姐仅,可以找一下telnet
命令使用花枫。
下面給出客戶端代碼。
客戶端代碼:
public class SocketClient {
public static void main(String[] args) throws IOException {
// 創(chuàng)建socket掏膏,并指定服務(wù)器的ip(host) 和 端口
Socket socket = new Socket("localhost", 7072);
// 獲取socket所綁定的本地地址
System.out.println("啟動客戶端:" + socket.getLocalAddress());
// 通過socket獲取字節(jié)流劳翰,并包裝成字符緩沖流
BufferedReader socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter socketWrite = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
// 通過控制臺輸入發(fā)送給服務(wù)端的消息,并把字節(jié)流包裝成字符緩沖流
BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
String promptMsg = "請輸入消息(輸入Bye退出):";
String outMsg = null;
// 提示語
System.out.println(promptMsg);
// 獲取控制臺輸入內(nèi)容壤追,每次一行
while ((outMsg = consoleReader.readLine()) != null) {
if (outMsg.equalsIgnoreCase("bye")) {
break;
}
// 向服務(wù)器發(fā)送一行消息磕道,因為服務(wù)器每次讀取一行
socketWrite.write(outMsg);
socketWrite.write("\n");
socketWrite.flush();
// 讀取并顯示來自服務(wù)器的消息
String inMsg = socketReader.readLine();
System.out.println("來自服務(wù)器的消息:" + inMsg);
System.out.println(); // 輸出一個空白行
System.out.println(promptMsg);
}
// 關(guān)閉資源,socket關(guān)閉時行冰,其對應(yīng)的流也會關(guān)閉溺蕉,為了防止內(nèi)存泄漏,
// 可以手動關(guān)閉其他流對象悼做,這里偷個懶
socket.close();
}
}
操作演示
分別運行服務(wù)端和客戶端(注意這里需要先運行服務(wù)端疯特,后運行客戶端),就會看到如下信息:
服務(wù)端:
客戶端:
客戶端啟動后肛走,服務(wù)端就能接收到客戶端的連接漓雅,如下所示:
由于我們服務(wù)端使用的是死循環(huán)并且每次接收到新的連接都會創(chuàng)建一個線程進行處理,所以只要服務(wù)端資源允許,就可以一直接收客戶端的請求邻吞。
現(xiàn)在向服務(wù)端發(fā)送消息:
服務(wù)端接收到的消息: