從零構建netty--socket基礎

socket基礎

對於java網絡編程來講,咱們能接觸到的最底層即是socket了。我相信大部分閱讀此篇文章的同仁都用過socket。可是,我這篇教程的理念之一就是事無鉅細,或者說,囉裏囉嗦。由於原本就是一個思惟的過程,因此,看官老爺們,就當作意識流風格來看吧(也是由於我文筆較差,所以文章的觀賞性可能不是太好,給本身找個理由)。java

我在剛學編程的時候,實際上是一臉茫然的,雖然也會爲屏幕輸出「hello world",感到興奮,可是興奮之餘會以爲,嗯~?,這東西跟我想象的不同啊,就一個黑屏上輸出幾個字符,能幹啥?這玩意跟網站啥的不沾邊啊。包括學習了一些算法、數據結構後,仍是以爲,網站啥的,跟我學的不沾邊。算法

後來在學習參加了一個項目,涉及到網絡編程,項目簡單點說就是樹莓派跟pc經過路由器進行數據交換。從那時候起開始使用socket,固然那個時候不求甚解,根本跟學過的網絡模型也對不上號。什麼三次握手,四次揮手,沒用到啊。先無論,幹就完了。shell

秉着這個徐循漸進的學習及思考過程,我就開始了socket編程。編程

連接

來一段服務端socket示例:服務器

代碼 1-1網絡

public class OioServer {
    private ServerSocket serverSocket;
    private void openServer(int port) throws IOException {
        // 1 建立ServerSocket
        serverSocket = new ServerSocket();
        // 2 綁定端口
        SocketAddress socketAddress = new InetSocketAddress(port);
        serverSocket.bind(socketAddress);
    }

    @Test
    public void testOpenServer() throws IOException {
        OioServer oioServer = new OioServer();
        oioServer.openServer(8081);
        // block
        Scanner scan = new Scanner(System.in);
        scan.next();
    }
}

運行test後,服務器開始監聽port端口了。而後咱們驗證下是否是真的在監聽,我再window上測試的,因此這裏以window爲例。首先要打開cmd窗口,而後輸入命令數據結構

代碼 1-2app

netstat -aon|findstr "8081"

圖1-1
image-20210317210840754.pngsocket

能夠看到真的在監聽了, 如今雖然開始監聽了,可是,真的有客戶端從8081進來該怎麼辦?咱們還須要處理鏈接,這個鏈接就是咱們的主角socket.學習

修改1-1代碼以下:

代碼1-2

public class OioServer {
    private ServerSocket serverSocket;
    private void openServer(int port) throws IOException {
        // 1 建立ServerSocket
        serverSocket = new ServerSocket();
        // 2 綁定端口
        SocketAddress socketAddress = new InetSocketAddress(port);
        serverSocket.bind(socketAddress);
    }

    @Test
    public void testOpenServer() throws IOException {
        OioServer oioServer = new OioServer();
        oioServer.openServer(8081);
        Socket socket = oioServer.listenAccept();
    }
}

此時,服務端就運行有客戶端進來鏈接了。而後咱們再寫一個客戶端。

代碼 1-3

public class OioClient{
   
    public Socket connect(String host, int port) throws IOException {
        Socket socket = new Socket();
        socket.connect(new InetSocketAddress(host, port));
        return socket
    }

    @Test
    public void testClient() throws IOException, InterruptedException {
        OioClient oioServer = new OioClient();
        oioServer.connect("127.0.0.1", 8081);
         // block
         Scanner scan = new Scanner(System.in);
         scan.next();
    }
}

運行test,爲例不讓test退出,咱們用標準出入block住test線程。記住,如今咱們服務端還在監聽,我服務端和客戶端都跑在同一臺pc上。效果如圖

圖1-2
image-20210319171212314.png

能夠看到第二行本地地址是127.0.0.1:6283遠程地址是127.0.0.1:8081,那咱們是否是能夠猜到,這個TCP鏈接是客戶端的,客戶端的IP是6283,咱們雖然沒指定,可是系統會給咱們隨機分配一個可用的端口,不信能夠試一下,下次可能就不同了。

瓜熟蒂落,第三行就是咱們服務端的TCP鏈接。

那麼咱們是否是能夠有一個這樣的結論:socket其實就是一個TCP鏈接的封裝(固然也能夠是UDP,這裏不作討論)?或者socket的下層是TCP/IP層?

到如今咱們大概知道了socket跟TCP的聯繫,既跟網絡模型的聯繫。

有了TCP連接,咱們就能夠開始傳輸數據了。不少網上例子是,簡歷socket鏈接後,客戶端給服務器發送數據,而後服務器接收數據並回復。可是請不要誤解,過程不必定所有是這樣的。咱們客戶端和服務端創建連接後,兩邊都獲得一個socket,這兩個socket是等價的。若是你對TCP有了解的話,必定會知道TCP的四次揮手,網上關於四次揮手的圖大概也都是下圖這個樣子:

圖1-3

image-20210319172545627.png

可是呢,四次揮手一樣能夠由服務端發起。所以,就算是在TCP層,兩個socket的是等價的。

通信

咱們獲得活躍的socket後,就能夠對socket進行讀寫操做了。既然socket沒什麼區別,那咱們能夠無差別對待socket。咱們來寫socket讀寫取的方法:

代碼1-4

public class SocketUtils {
    /**
     * 從socket中讀數據
     */
    public static String read(Socket socket) {
        try {
            InputStream inputStream = socket.getInputStream();
            byte[] bytes = new byte[1024];
            int len;
            StringBuilder sb = new StringBuilder();
            while ((len = inputStream.read(bytes)) != -1) {
                //注意指定編碼格式,發送方和接收方必定要統一,建議使用UTF-8
                sb.append(new String(bytes, 0, len, "UTF-8"));
            }
            return sb.toString();
        } catch (IOException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * 往socket中寫數據
     */
    public static void write(Socket socket, String response) {
        try {
            OutputStream outputStream = socket.getOutputStream();
            outputStream.write(response.getBytes("UTF-8"));
            outputStream.flush();
            socket.shutdownOutput();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

上面代碼以入參的形勢將socket傳入,而後就行讀寫,注意,咱們這兩個並無調用方法socket.close,也就是說socket能夠重複讀寫數據,而網上大部分教程直接會吧socket給關閉,形成初學者覺得每次讀寫玩就得關閉socket的錯覺。

在方法read中,根據(len = inputStream.read(bytes)) != -1來判斷是否讀取完數據。而寫入數據端怎麼告訴接收端它完成了寫入呢?咱們能夠用socket.shutdownOutput()方法。

好,接下來咱們就來測試下發送與接收數據。

代碼1-4

public class OioServer {
    ...

    @Test
    public void testOpenServer() throws IOException {
         OioServer oioServer = new OioServer();
        oioServer.openServer(8081);
        Socket socket = oioServer.listenAccept();
        SocketUtils.write(socket, "Can you hear me?");
        String msg = SocketUtils.read(socket);
        System.out.println(msg);
        // block
        Scanner scan = new Scanner(System.in);
        scan.next();
    }
}

代碼1-5

public class OioClient {

   ...
    
    @Test
    public void testClient() throws IOException, InterruptedException {
        OioClient oioServer = new OioClient();
        Socket socket = oioServer.connect("127.0.0.1", 8081);
        String msg = SocketUtils.read(socket);
        System.out.println(msg);
        SocketUtils.write(socket, "Yes, I can hear you!");
        // block
        Scanner scan = new Scanner(System.in);
        scan.next();
    }
}

先執行testOpenServer()再執行testClient()。就會完成對話。固然咱們能夠重複調SocketUtils.read SocketUtils.write進行通信,只要兩邊socket不關閉。

小結

此篇文章總結了Socket的一些使用,包括服務器啓動,socket創建,並證實了socket的等價性

相關文章
相關標籤/搜索