java socket 長連線事例
阿新 • • 發佈:2019-02-03
一 .我們知道java的socket是基於TCP的連線,而ServerSocket 的accept()方法是阻塞的,直到有客戶端連線到伺服器端,我們常用多執行緒的方式來實現伺服器端響應多個客戶端,以下是程式碼:
public class server { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(9999); System.out.println("伺服器端--開始監聽"); while(true){ Socket socket = serverSocket.accept(); ServerHandel hm = new ServerHandel(socket); Thread t = new Thread(hm); t.start(); } //1.socket 的輸入輸出流任意一個關閉,則socket都不可再用了,所以要關閉就一起關閉了。 //2.socket 流的讀是阻塞的,A不要輸入流關閉前時,要考慮B端的輸出流是否還需要寫。否者,B端一直等待A端接收,而A端卻接受不了,B一直阻塞,在長連線中尤其要注意,注意流的結束標誌 //3.io 流最後一定要關閉,不然會一直佔用記憶體,可能程式會崩潰。檔案輸出也可能沒有任何資訊 //4.字元輸出推薦使用printWriter 流,它也可以直接對檔案操作,它有一個引數,設定為true 可以自動重新整理,強制從緩衝中寫出資料 //5.緩衝流 都有 bufw.newLine();方法,新增換行 //6.輸入流 是指從什麼地方讀取/輸出流是指輸出到什麼地方 //7.OutputStreamWriter和InputStreamReader是轉換流,把位元組流轉換成字元流 //8.Scanner 取得輸入的依據是空格符,包括空格鍵,Tab鍵和Enter鍵,任意一個按下就會返回下一個輸入,如果需要包括空格之類的則用bufferreader來獲取輸入 } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
二 這裡有2個概念介紹下:
1.短連線: 當輸出流傳送完畢後,立即關閉流,下次傳送,需要新建通訊. 會頻繁的新建通訊,丟棄通訊,效率低
2.長連線: 客戶端和伺服器端都不關閉流, 可以多次傳送資料
三 長連線的使用需要注意一些問題:
1.客戶端和伺服器端,不能關閉任一socket的輸入流或者輸出流,否則socket通訊會關閉
2.由於都不關閉連線,而read方法又是阻塞的,會一直讀取資料,不知道何時讀取結束,所以要約定通訊協議,如特定字元,或者使用包頭+包體的方式,傳遞資料,包頭固定長度,裡面儲存包體長度等資訊,這樣服務端就知道讀取到何時結束了.(本文使用此種方式)以下是程式碼:
客戶端:
public class client { public static void main(String[] args) { Socket socket = null; try { socket = new Socket("127.0.0.1",9999); System.out.println("客戶端開始連線"); //一直讀取控制檯 BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); while (true){ //包體 byte [] content = br.readLine().getBytes(); //包頭,固定4個位元組,包含包體長度資訊 byte [] head = Tool.intToByteArray1(content.length); BufferedOutputStream bis = new BufferedOutputStream(socket.getOutputStream()); bis.write(head); bis.flush(); bis.write(content); bis.flush(); } } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); }finally { try { socket.close(); }catch (Exception e){ e.printStackTrace(); } } } }
伺服器端執行緒類:
public class ServerHandel implements Runnable {
public static int count = 0;
Socket socket = null;
public ServerHandel(Socket socket){
count++;
this.socket = socket;
System.out.println("使用者"+count+"接入");
}
@Override
public void run() {
BufferedInputStream bis = null;
try {
bis = new BufferedInputStream(socket.getInputStream());
while (true){
byte [] head = new byte[4];
bis.read(head);
byte [] data = new byte[Tool.byteArrayToInt(head)];
bis.read(data);
System.out.println(new String(data).trim());
}
} catch (Exception e) {
e.printStackTrace();
}finally {
try {
bis.close();
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
四 由於以位元組陣列傳遞包頭,就涉及到位元組陣列和int型別轉換的問題,java中int型別是4個位元組,所以用byte[4]來裝,
//int 轉位元組陣列
public static byte[] intToByteArray1(int i) {
byte[] result = new byte[4];
result[0] = (byte)((i >> 24) & 0xFF);
result[1] = (byte)((i >> 16) & 0xFF);
result[2] = (byte)((i >> 8) & 0xFF);
result[3] = (byte)(i & 0xFF);
return result;
}
public static byte[] intToByteArray2(int i) throws Exception {
ByteArrayOutputStream buf = new ByteArrayOutputStream();
DataOutputStream out = new DataOutputStream(buf);
out.writeInt(i);
byte[] b = buf.toByteArray();
out.close();
buf.close();
return b;
}
//位元組陣列轉int
public static int byteArrayToInt(byte[] b) {
int intValue=0;
for(int i=0;i<b.length;i++){
intValue +=(b[i] & 0xFF)<<(8*(3-i));
}
return intValue;
}
五 對於網路IO有一些基本的處理規則如下:
1.減少互動的次數。比如增加快取,合併請求。
2.減少傳輸資料大小。比如壓縮後傳輸、約定合理的資料協議。
3.減少編碼。比如提前將字元轉化為位元組再傳輸。
4.根據應用場景選擇合適的互動方式,同步阻塞,同步非阻塞,非同步阻塞,非同步非阻塞。
六 在jdk1.4種引入了 java.nio.* 包, 大大提高了io的效能,並加入了非阻塞io模型,引入了通道和基礎資料型別的buffer型別,各位看官可以去看看,這裡就不說了,有空再總結.