詳解Java Socket的工做機制

Socket的前因後果

下面的分析主要是參閱了計算機網絡(謝希仁第7版)進行總結的,從系統調用--->應用編程接口API--->套接字接口來分析Socket的前因後果,固然維基百科上也有對Socket的詳細解釋html

1. 系統調用

大多數操做系統使用系統調用的機制在應用程序和操做系統之間傳遞控制權。對程序員來講,系統調用和通常程序設計中的函數調用很是類似 java

系統調用

2. 應用編程接口API

當某個應用進程啓動系統調用時,控制權就從應用進程傳遞給了系統調用接口,此接口再將控制權傳遞給計算機的操做系統。操做系統將此調用轉給某個內部過程,並執行所請求的操做。內部過程一旦執行完畢,控制權就又經過系統調用接口返回給應用進程。程序員

系統調用接口實際上就是應用進程的控制權和操做系統的控制權進行轉換的一個接口,即應用編程接口APIsql

3. 套接字

因爲TCP/IP協議族被設計成可以運行在多種操做系統的環境下,TCP/IP標準容許系統設計者可以選擇有關API的具體實現細節。編程

目前,可供應用程序使用TCP/IP的應用編程接口API的最著名的是套接字接口bash

而套接字不是物理實體,而是一種抽象,套接字是提供應用程序建立和使用的數據結構服務器

套接字

4. 套接字描述符

當應用進程(客戶或者服務器)須要使用網絡進行通訊時,必須首先發出socket系統調用,請求操做系統爲其建立一個"套接字"。這個調用的實際效果是請求操做系統把網絡通訊所須要的一些 系統資源(存儲器空間,CPU時間,網絡帶寬等) 分配給該應用進程。網絡

操做系統爲這些資源的總和用一個叫作套接字描述符(socket descriptor)的號碼(小的整數) 來表示,而後把這個套接字描述符返回給應用進程。數據結構

socket 描述符

Socket通訊圖示

socket

由圖能夠看出Socket通訊與TCP/IP協議是分不開的,要使主機A和主機B可以通訊,必須創建Socket鏈接,創建Socket鏈接必須經過底層TCP/IP協議來創建TCP鏈接。多線程

Socket通訊協議分析

上面就提到,Socket通訊與TCP/IP協議是緊密相關的,關於Socket編程通訊咱們有兩種協議能夠選擇,那就是常見的TCP協議和UDP協議

UDP協議

UDP協議是一種無鏈接的協議,也稱爲數據報協議。每次發送數據報時,須要同時發送本機的socket描述符(就是上面所說的套接字描述符)和接收端的socket描述符。因此,每次通訊都要發送額外的數據。

TCP協議

TCP協議是一種有鏈接的協議,使用應用程序以前,必須先創建TCP鏈接。因此每次在進行通訊以前那,咱們須要先創建Socket鏈接,一個socket做爲服務端監聽請求,一個socket做爲客戶端進行鏈接請求。只有雙方創建鏈接好之後,雙方纔能夠通訊。

兩種協議區別及選擇

簡單分析二者的區別:

  • 在UDP中,每次發送數據報,須要附上本機的socket描述符和接收端的socket描述符.而TCP是基於鏈接的協議,在通訊的socket之間須要在通訊以前創建鏈接,即TCP的三次握手,,所以創建鏈接會有必定耗時
  • 在UDP中,數據報數據在大小有64KB的限制。而TCP不存在這樣的限制,一旦TCP通訊的socket對創建鏈接,他們通訊相似IO流。
  • UDP是不可靠的協議,發送的數據報不必定會按照其發送順序被接收端的socket接收。而TCP是一種可靠的協議。接收端收到的包的順序和包在發送端的順序大致一致(這裏不討論丟包的狀況)

說到這,至於選擇哪一種協議,仍是取決於你的使用場景,固然目前見得比較多就是基於TCP協議的Socket通訊。固然一些實時性較高的一些服務,局域網的一些服務用UDP的多一些。

基於TCP協議的Java Socket編程實例

socket2

socket編程總的來講分爲3步:創建鏈接,數據傳送,鏈接釋放。固然服務端程序和客戶端程序具體步驟有些區別,如上圖所示。

這裏咱們用java.net包下的ServerSocket類(主要用於服務端)和Socket類(用於創建鏈接)來實現一個Socket通訊實例

服務端編寫

package com.pjmike.Socket;

import java.io.BufferedInputStream;
import java.io.DataInputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.sql.SQLOutput;

/** * 服務端 * * @author pjmike * @create 2018-08-12 17:43 */
public class Server {
    private ServerSocket serverSocket = null;
    private Socket socket = null;
    private DataInputStream input = null;

    public Server(int port) {
        try {
            //綁定端口
            System.out.println("bind port ...");
            serverSocket = new ServerSocket(port);
            System.out.println("Server started and waiting a client ..");
            //調用accept()方法,提取鏈接請求
            socket = serverSocket.accept();
            //通常都是以字節傳輸
            input = new DataInputStream(new BufferedInputStream(socket.getInputStream()));
            String line = "";
            while (!line.equals("exit")) {
                try {
                    //readUTF()方法須要讀取writeUTF()寫過來的數據
                    line = input.readUTF();
                    System.out.println("recd: " + line);
                } catch (IOException o) {
                    o.printStackTrace();
                }
            }
            //關閉鏈接
            System.out.println("connection closed ...");
            input.close();
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    public static void main(String[] args) {
        Server server = new Server(5000);
    }
}

複製代碼

客戶端程序

package com.pjmike.Socket;

import java.io.*;
import java.net.Socket;
import java.nio.Buffer;

/** * 客戶端 * * @author pjmike * @create 2018-08-12 17:52 */
public class Client {
    private Socket socket = null;
    private DataOutputStream output = null;
    private BufferedReader input = null;

    public Client(String address, int port) {
        try {
            //創建鏈接
            socket = new Socket(address, port);
            System.out.println("Connected ...");
            //從控制檯輸入信息
            input = new BufferedReader(new InputStreamReader(System.in));
            output = new DataOutputStream(socket.getOutputStream());

        } catch (IOException e) {
            e.printStackTrace();
        }
        String line = "";
        while (!line.equals("exit")) {
            try {
                line = input.readLine();
                System.out.println("客戶端輸入的是: "+line);
                output.writeUTF(line);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        try {
            input.close();
            socket.close();
            output.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Client client = new Client("localhost", 5000);
    }
}

複製代碼

測試

客戶端

Connected ...
hello world
客戶端輸入的是: hello world
nihao
客戶端輸入的是: nihao
exit
客戶端輸入的是: exit
複製代碼

服務端

bind port ...
Server started and waiting a client ..
recd: hello world
recd: nihao
recd: exit
connection closed ...

複製代碼

固然有時咱們也須要多個客戶端鏈接到同一個服務端,這也不是什麼難事,採用多線程的方式,讓服務器循環調用accept()方法,每收到一個客戶端請求就開啓一個線程來進行處理。

小結

實際上,Socket就是一種進程間的通訊機制,在Java Socket編程中,也是分三步走:創建通訊鏈路,數據傳輸,鏈路關閉。而網絡編程也是和Java I/O操做緊密結合在一塊兒的,熟悉I/O操做也是必不可少的。

參考資料

相關文章
相關標籤/搜索