1. 程式人生 > >網路通訊之Socket小結

網路通訊之Socket小結

最近在寫一個關於資料通訊系列的文章,所以Socket是少不了的,今天就和大家來簡單分享下Socket的使用方式,以及關於Socket的幾個比較重要,容易被小夥伴們忽略且常用的方法,
好了,進入今天的正題。
之前有在面試時候問到http請求底層是基於什麼實現的,沒錯http請求底層也是基於socket的。 另外Socket也就是我們通常說的TCP的封裝形式。

說起TCP大家都知道“三次握手”這個協議。我們的客戶端Socket和服務端ServerSocket建立了聯絡之後,雙方都確定了對方的身份,證明大家都是認識的是朋友,才可以進行“悄悄話的交流”。下面我們來分析Socket與服務端ServerSocket建立聯絡的三次握手過程:

(1)第一次握手:建立連線時,客戶端Socket向服務端ServerSocket傳送SYN包,並進入SYN_SEND狀態,等待伺服器B確認。這個過程,就好比我向你打電話,我的手機號通過訊號到了你的手機上並顯示 156XXXXXXXX來電,此時要等你確認這個手機號。

(2)第二次握手:此時服務端收到客戶端的SYN包,與客戶端的SYN進行確認,確認後,服務端向客戶端傳送個SYN包,即SYN+ACK 包。服務端進入SYN_RECV狀態。這個過程就好比,當收到某人的來電後,我確認手機號是我的朋友,此時我要接通他的電話。

(3)第三次握手:客戶端收到服務端傳送過來的SYN + ACK包,此時客戶端像服務端再發送一個ACK確認包。此時傳送完畢後,客戶端與服務端就進入了ESTABLISHED狀態,完成通訊前的三次握手。這個過程就好比,當我接通了電話後,我問對方是XXX嗎?對方說是的!(媽的,終於可以交流了。。。)

接下來看看Sokcet的簡單使用。

(1)建立客戶端Socket:

try {
            Socket socket = new Socket("192.168.191.1", 8888);
            socket.setSoTimeout(3000);
            socket.setKeepAlive(true);
            socket.setTcpNoDelay(true);
            socket.setSoLinger(true, 5);
            socket.setReceiveBufferSize(1024
); socket.setReuseAddress(true); OutputStream oup = socket.getOutputStream(); oup.write("nihao I'm client".getBytes()); byte[] readbyte = new byte[40]; InputStream ins = socket.getInputStream(); ins.read(readbyte); System.out.println(new String(readbyte)); } catch(SocketException e){ e.printStackTrace(); }catch (UnknownHostException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } }

在上述程式碼中我們可以看到,需要得到一個socket的例項,然後對其進行相關引數設定,通過socket得到InputStream或者OutputStream 。以上的七個引數的作用分別為:
(1)setSoTimeOut:設定客戶端Socket讀取資料的超時時長。即當與服務端建立聯絡後,此時要接收服務端返回的資料,設定該方法可以限定客戶端等待服務端傳送資料的時間,如果超過了該時長,系統會丟擲一個InterruptedIOException異常。在丟擲異常後,輸入流並未關閉,你可以繼續通過read方法讀取資料。 如果將timeout設為0,就意味著read將會無限等待下去,直到服務端程式關閉這個Socket.這也是timeout的預設值。

(2)setKeepAlive:該方法指定檢測與伺服器的連結狀態。如果設定引數為true,即開啟該設定,此時,客戶端會利用空閒的連線每隔兩個小時向服務端傳送一次資料包進行連線驗證伺服器是否仍處於活動狀態。如果此時服務端未響應,客戶端會在第11分鐘後繼續傳送一個驗證包,如果在12分鐘內服務端還未響應,此時客戶端就會執行關閉連線的操作。如果將該設定項關閉,客戶端Socket在伺服器無效的情況下可能會長時間不會關閉,佔據資源。預設情況下是關閉的。

(3)setTcpNoDelay:該方法很有聰明,如果開啟該設定,當客戶端傳送資料給服務端時,傳送的過程中,會檢測該資料的大小,如果該資料較小,此時不會發送給服務端,而是將較小的包和較大的資料包合,然後一起傳送給服務端。在傳送下一個資料包時,系統會等待伺服器對前一個數據包的響應,當收到伺服器的響應後,再發送下一個資料包,這就是所謂的Nagle演算法;優點很明顯,節省了通訊的開銷,有效地改善網路傳輸的效率。在預設情況下是開啟的。

(4)setSoLinger:其實該方法和Socket的關閉方法( close() )是有聯絡的。該方法有兩個引數,第一個如果設定為true,即開啟該設定。第二個引數指定一個時間值(秒:0 ~ 65535,不可為負值)。該方法的作用是當你呼叫了Socket的close方法時,系統會去檢測是否還有未傳送完畢的資料,此時如果存在未傳送完畢的資料,系統就會在我們指定的時間內 “努力”傳送這些資料到伺服器,如果在我們指定的時間內還未傳送完畢,那麼此時Socket將會執行關閉。如果底層的Socket實現不支援SO_LINGER都會丟擲SocketException,可以通過getSoLinger方法來獲取延遲關閉的時間,如果返回 -1,則表明SO_LINGER是關閉的。

(5)setReceiveBufferSize:該方法比較簡單,設定輸入流的緩衝大小。預設情況下,輸入流的接收緩衝區是8096個位元組(8K)。這個值是Java所建議的輸入緩衝區的大小。緩衝值儘量不要設定的太小,否則會導致傳輸資料過於頻繁,從而降低網路傳輸的效率。如果底層不支援,系統將會丟擲IllegalArgumentException異常。

(6)setSendBufferSize:此方法和上面的對稱,設定輸出流的緩衝大小。其他特點和上面相同,不再贅述。

(7)setReuseAddress:如果你的服務程式停止後想立即重啟,不等60秒,而新套接字依舊 使用同一埠,此時 SO_REUSEADDR 選項非常有用。

一般情況下,幾個設定項我們都選擇開啟狀態,某些情況下,還需要具體情況具體分析。

接下來看服務端的建立:

ServerSocket ssSocket=null;
        Socket socket=null;
        try {
            //1.註冊ip和埠,註冊了一個伺服器程式
            ssSocket=new ServerSocket(8888);
            //2.等待接收連線請求,並且連線成功後建立Socket
            socket=ssSocket.accept();
            System.out.println("connect ok");
            //3.獲得連線物件的網路流,從而進行網路資料傳輸
            //獲得客戶端資料
            byte []readbyte=new byte[40];
            InputStream ins=socket.getInputStream();
            //DataInputStream datainput=new DataInputStream(ins);
            //String line=datainput.readUTF();
            ins.read(readbyte);
            System.out.println(new String(readbyte));

            //往客戶端發資料
            OutputStream oup=socket.getOutputStream();
            oup.write("nihao I'm server".getBytes());
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }

這是一個簡單的阻塞狀態socket通訊案例,服務端直到接收到客戶端傳送的資料才會繼續向下執行,接收到客戶端的Socket,繼續執行流的讀寫操作來讀取和寫回資料。關於sockect的更多用法可以參照這篇《基於Socket的Android與PC簡單聊天應用的實現》
好了,關於Socket的通訊我們就簡單分析到這裡。