java網路程式設計(二)複用Socket連線以及使用多執行緒完成多個客戶端的連線
在前面的示例中,客戶端中建立了一次連線,只發送一次資料就關閉了,這就相當於撥打電話時,電話打通了只對話一次就關閉了,其實更加常用的應該是撥通一次電話以後多次對話,這就是複用客戶端連線。
那 麼如何實現建立一次連線,進行多次資料交換呢?其實很簡單,建立連線以後,將資料交換的邏輯寫到一個迴圈中就可以了。這樣只要迴圈不結束則連線就不會被關 閉。按照這種思路,可以改造一下上面的程式碼,讓該程式可以在建立連線一次以後,傳送三次資料,當然這裡的次數也可以是多次,示例程式碼如下:
public class SocketClient {
public static void main(String[] args) {
Socket socket = null ;
InputStream inputStream = null;
OutputStream outputStream = null;
String data[] = { "hello world1", "hello world2", "hello world3" };
try {
// 建立Socket例項
socket = new Socket("hadoop", 10000);
// 獲取輸出流,向伺服器發生資料
outputStream = socket.getOutputStream();
/// 獲取輸入流,獲取伺服器的響應
inputStream = socket.getInputStream();
byte[] buff = new byte[1024];
// 迴圈傳送
for (int i = 0; i < data.length; i++) {
// 傳送資料
outputStream.write(data[i].getBytes());
int len = inputStream.read(buff);
// 列印服務端的相應
System.out.println(new String(buff, 0, len));
}
} catch (Exception e) {
e.printStackTrace();
} finally {
// 釋放資源
try {
inputStream.close();
outputStream.close();
socket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
現在客戶端可以向伺服器發生三次請求且獲取三次相應,而我們伺服器端是對話一次資料以後就關閉了連線,如果伺服器端程式關閉了,客戶端繼續傳送資料肯定會出現異常
按照客戶端實現的邏輯,也可以複用伺服器端的連線,實現的原理也是將伺服器端的資料交換邏輯寫在迴圈中即可,按照該種思路改造以後的伺服器端程式碼為:
public class SocketService {
public static void main(String[] args) {
InputStream inputStream =null;
OutputStream outputStream =null;
Socket socket = null;
ServerSocket serviceSocket =null;
try {
//伺服器端接收訊息的類。定製埠號為8888
serviceSocket = new ServerSocket(10000);
//獲取socket。這個方法是阻塞式的
socket = serviceSocket.accept();
inputStream = socket.getInputStream();
byte buf[] = new byte[1024];
//向客戶端生成響應
outputStream = socket.getOutputStream();
for(int i=0;i<3;i++){
int len =inputStream.read(buf);
//列印客戶端的訊息
System.out.println(new String(buf,0,len));
outputStream.write("收到".getBytes());
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//釋放資源
try {
outputStream.close();
inputStream.close();
socket.close();
serviceSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
在該示例程式碼中,也將資料傳送和接收的邏輯寫在了一個for迴圈內部,只是在實現時硬性的將迴圈次數規定成了3次,這樣程式碼雖然比較簡單,但是通用性比較差,客戶端和伺服器端的次數控制還不夠靈活,如果客戶端的次數不固定怎麼辦呢?不著急,這個問題會一一進行解答。
現在我們學習另外一個網路程式設計的突出問題。
如何使伺服器端支援多個客戶端同時工作?
前面介紹的伺服器端程式,只是實現了概念上的伺服器端,離實際的伺服器端程式結構距離還很遙遠,如果需要讓伺服器端能夠實際使用,那麼最需要解決的問題就是——如何支援多個客戶端同時工作。
一個伺服器端一般都需要同時為多個客戶端提供通訊,如果需要同時支援多個客戶端,則必須使用執行緒的來解決。簡單來說,也就是當伺服器端接收到一個連線時,啟動一個專門的執行緒處理和該客戶端的通訊。
按照這個思路改寫的服務端示例程式將由兩個部分組成,SocketService類實現伺服器端控制,實現接收客戶端連線,然後開啟專門的邏輯執行緒處理該連線,LogicThread類實現對於一個客戶端連線的邏輯處理,將處理的邏輯放置在該類的run方法中。該服務端示例的程式碼實現為:
public class SocketService {
public static void main(String[] args) {
Socket socket = null;
ServerSocket serviceSocket =null;
try {
//伺服器端接收訊息的類。定製埠號為8888
serviceSocket = new ServerSocket(10000);
System.out.println("伺服器已經啟動");
while(true){
//獲取socket。這個方法是阻塞式的
socket = serviceSocket.accept();
//一但獲取連線後就開子執行緒來處理
new LogicThread(socket);
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}finally {
//釋放資源
try {
serviceSocket.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class LogicThread extends Thread {
Socket socket;
InputStream inputStream;
OutputStream outputStream;
public LogicThread(Socket socket) {
this.socket = socket;
start(); // 啟動執行緒
}
public void run() {
byte[] buf = new byte[1024];
try {
// 初始化流
outputStream = socket.getOutputStream();
inputStream = socket.getInputStream();
for (int i = 0; i < 3; i++) {
// 讀取資料
int len = inputStream.read(buf);
System.out.println(new String(buf,0,len));
// 反饋資料
outputStream.write("收到".getBytes());
}
} catch (Exception e) {
e.printStackTrace();
} finally {
close();
}
}
/**
*
* 關閉流和連線
*
*/
private void close() {
try {
// 關閉流和連線
outputStream.close();
inputStream.close();
socket.close();
} catch (Exception e) {
}
}
}
每次使用一個連線物件構造該執行緒,該連線物件就是該執行緒需要處理的連線,線上程構造完成以後,該執行緒就被啟動起來了,然後在run方法內部對客戶端連線進行處理,資料交換的邏輯和前面的示例程式碼一致,都是IO流處理。
這裡的示例還只是基礎的伺服器端實現,在實際的伺服器端實現中,由於硬體和埠數的限制,所以不能無限制的建立執行緒物件,而且頻繁的建立執行緒物件效率也比較低,所以程式中都實現了執行緒池來提高程式的執行效率。