Socket通信,基本方法介紹


Socket是什么呢?

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。

在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。

TCP/IP(Transmission Control Protocol/Internet Protocol)即傳輸控制協議/網間協議,是一個工業標准的協議集,它是為廣域網(WANs)設計的。
 UDP(User Data Protocol,用戶數據報協議)是與TCP相對應的協議。它是屬於TCP/IP協議族中的一種。

Socket 原理

Socket 連接,至少需要一對套接字,分為 clientSocket,serverSocket 連接分為3個步驟:

(1) 服務器監聽:服務器並不定位具體客戶端的套接字,而是時刻處於監聽狀態;

(2) 客戶端請求:客戶端的套接字要描述它要連接的服務器的套接字,提供地址和端口號,然后向服務器套接字提出連接請求;

(3) 連接確認:當服務器套接字收到客戶端套接字發來的請求后,就響應客戶端套接字的請求,並建立一個新的線程,把服務器端的套接字的描述發給客戶端。一旦客戶端確認了此描述,就正式建立連接。而服務器套接字繼續處於監聽狀態,繼續接收其他客戶端套接字的連接請求.

過程圖解:

先從服務器端說起。服務器端先初始化Socket,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。

在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。

客戶端發送數據請求,服務器端接收請求並處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束。

實例展示:

服務器端:

package com.socket.test;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer {
  public static void main(String[] args) throws Exception {
    // 監聽指定的端口
    int port = 55533;
    ServerSocket server = new ServerSocket(port);
    
    // server將一直等待連接的到來
    System.out.println("server將一直等待連接的到來");
    Socket socket = server.accept();
    // 建立好連接后,從socket中獲取輸入流,並建立緩沖區進行讀取
    InputStream inputStream = socket.getInputStream();
    byte[] bytes = new byte[1024];
    int len;
    StringBuilder sb = new StringBuilder();
    //只有當客戶端關閉它的輸出流的時候,服務端才能取得結尾的-1
    while ((len = inputStream.read(bytes)) != -1) {
      // 注意指定編碼格式,發送方和接收方一定要統一,建議使用UTF-8
      sb.append(new String(bytes, 0, len, "UTF-8"));
    }
    System.out.println("get message from client: " + sb);
  // 發送信息給客戶端
    OutputStream outputStream = socket.getOutputStream();
    outputStream.write("Hello Client,I get the message.".getBytes("UTF-8"));

    inputStream.close();
    outputStream.close();
    socket.close();
    server.close();
  }
}

當讀取完客戶端的消息后,打開輸出流,將指定消息發送回客戶端

客戶端:

package com.socket.test

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class SocketClient {
  public static void main(String args[]) throws Exception {
    // 要連接的服務端IP地址和端口
    String host = "127.0.0.1";
    int port = 55533;
    // 與服務端建立連接
    Socket socket = new Socket(host, port);
    // 建立連接后獲得輸出流
    OutputStream outputStream = socket.getOutputStream();
    String message = "你好  fuwuqiduan";
    socket.getOutputStream().write(message.getBytes("UTF-8"));
    //通過shutdownOutput高速服務器已經發送完數據,后續只能接受數據
    socket.shutdownOutput();
    
    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"));
    }
    System.out.println("get message from server: " + sb);
    
    inputStream.close();
    outputStream.close();
    socket.close();
  }
}

客戶端也有相應的變化,在發送完消息時,調用關閉輸出流方法,然后打開輸出流,等候服務端的消息。

服務器段優化

在上面的例子中,服務端僅僅只是接受了一個Socket請求,並處理了它,然后就結束了,但是在實際開發中,一個Socket服務往往需要服務大量的Socket請求,那么就不能再服務完一個Socket的時候就關閉了,這時候可以采用循環接受請求並處理的邏輯:

package com.socket.test
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;

public class SocketServer {
  public static void main(String args[]) throws IOException {
    // 監聽指定的端口
    int port = 55533;
    ServerSocket server = new ServerSocket(port);
    // server將一直等待連接的到來
    System.out.println("server將一直等待連接的到來");
    
    while(true){
      Socket socket = server.accept();
      // 建立好連接后,從socket中獲取輸入流,並建立緩沖區進行讀取
      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"));
      }
      System.out.println("get message from client: " + sb);
      inputStream.close();
      socket.close();
    }
    
  }
}

這種一般也是新手寫法,但是能夠循環處理多個Socket請求,不過當一個請求的處理比較耗時的時候,后面的請求將被阻塞,所以一般都是用多線程的方式來處理Socket,即每有一個Socket請求的時候,就創建一個線程來處理它。

  不過在實際生產中,創建的線程會交給線程池來處理,為了:

  • 線程復用,創建線程耗時,回收線程慢
  • 防止短時間內高並發,指定線程池大小,超過數量將等待,方式短時間創建大量線程導致資源耗盡,服務掛掉
package com.socket.test

import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class SocketServer {
  public static void main(String args[]) throws Exception {
    // 監聽指定的端口
    int port = 55533;
    ServerSocket server = new ServerSocket(port);
    // server將一直等待連接的到來
    System.out.println("server將一直等待連接的到來");

    //如果使用多線程,那就需要線程池,防止並發過高時創建過多線程耗盡資源
    ExecutorService threadPool = Executors.newFixedThreadPool(100);
    
    while (true) {
      Socket socket = server.accept();
      
      Runnable runnable=()->{
        try {
          // 建立好連接后,從socket中獲取輸入流,並建立緩沖區進行讀取
          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"));
          }
          System.out.println("get message from client: " + sb);
          inputStream.close();
          socket.close();
        } catch (Exception e) {
          e.printStackTrace();
        }
      };
      threadPool.submit(runnable);
    }

  }
}

借鑒網址:https://www.cnblogs.com/yiwangzhibujian/p/7107785.html


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM