1. 程式人生 > >java socket 長連線事例

java socket 長連線事例

一 .我們知道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型別,各位看官可以去看看,這裡就不說了,有空再總結.