什麼是socket通信

傳輸層協議TCP、UDP,但它們畢竟只是協議,看不見摸不着,那我們怎們通過TCP、和UDP進行實際傳輸呢?不用着急,等看完這篇文章你一定會明白的

Socket概述

Socket中文意思爲插座的意思,專業術語稱之爲套接字,它把TCP/IP封裝成了調用接口供開發者調用,也就是說開發者可以通過調用Socket相關API來實現網絡通訊。在Java中也存在Socket相關API,主要分爲兩個,分別是基於UDP傳輸協議的Socket和基於TCP傳輸協議的Socket,本篇文章會對基於這兩種傳輸協議的Socket進行詳細描述。

UDP Socket

通過上節的內容我們知道UDP是無連接的,只要提供對方的IP地址和端口號就能進行數據的傳輸,其中IP負責定位主機端口負責定位應用。知道了目標IP和目標端口號通過Java中的UDP Socket就能進行IO傳輸,我們來看一下具體的代碼體現

/**
* 發送方UDP
*/
public class UDPSocketSend {
   public static void main(String[] args) throws IOException {

       System.out.println("Sender Start...");

       //1.創建socket服務
       DatagramSocket ds = new DatagramSocket();

       //2.封裝數據
       String str = "Did you recite words today";
       byte[] bytes = str.getBytes();
       //地址
       InetAddress address =InetAddress.getByName("192.168.31.137");
       //參數:數據、長度、地址、端口
       DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,6666);

       //3.發送數據包
       ds.send(dp);

       //4.關閉socket服務
       ds.close();
   }

/**
* 接收方UDP
*/
public class UDPSocketReceive{
   public static void main(String[] args) throws IOException {

       System.out.println("Receiver Start...");

       //1.創建udp的socket服務,並聲明端口號
       DatagramSocket ds = new DatagramSocket(6666);

       //2.創建接收數據的數據包
       byte[] bytes = new byte[1024];
       DatagramPacket dp = new DatagramPacket(bytes,bytes.length);

       //3.將數據接收到數據包中,爲阻塞式方法
       ds.receive(dp);

       //4.解析數據
       InetAddress address = dp.getAddress();//發送方IP
       int port = dp.getPort();//發送方端口
       String content = new String(dp.getData(),0,dp.getLength());
       System.out.println("address:"+address+"---port:"+port+"---content:"+content);

       //關閉服務
       ds.close();
   }
}

分別啓動發送方和接收方,我們來看一下打印結果

發送方:

Sender Start...

接收方:

Receiver Start...
address:/192.168.31.137---port:65037---content:Did you recite words today

成功接收到消息,並打印出發送方IP和端口,下面我來個大家擼一遍步驟

發送方:
首先創建udp的socket服務
將需要發送的數據放在數據包DatagramSocket中,DatagramSocket會根據UDP協議對數據包、IP、端口號進行封裝
通過udp的socket服務將數據包發送
最後將udp服務關閉
接收方:
創建udp的socket服務,並且明確自己的端口號
創建DatagramSocket用來解析數據接收到的數據包
將數據接收到數據包DatagramSocket中
通過DatagramSocket解析數據
關閉服務

整個UDP發送數據的流程就是這樣

注意點:
爲UDP是無連接的不可靠傳輸,所以接收方需要在發送方發送數據之前就啓動,否則會接收不到數據,也就是說必須先運行UDPSocketReceive再運行UDPSocketSend。
在這裏插入圖片描述

聊天實例

把上面的例子進行一些小改動就可以實現聊天功能

public class UDPSocket1 {
   public static void main(String[] args) {
       try {
           new Thread(new Runnable() {
               @Override
               public void run() {
                   try {
                       receive();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
           }).start();
           send();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

   //接收消息方法
   private static void receive() throws IOException {
       System.out.println("UDOSocket1 Receiver Start...");

       //1.創建udp的socket服務,並聲明端口號
       DatagramSocket ds = new DatagramSocket(6666);
       //無限循環,一直處於接收狀態
       while (true) {
           //2.創建接收數據的數據包
           byte[] bytes = new byte[1024];
           DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

           //3.將數據接收到數據包中
           ds.receive(dp);

           //4.解析數據
           String content = new String(dp.getData(), 0, dp.getLength());
           System.out.println("UDPSocket1 Receive:" + content);
       }
   }

   private static void send() throws IOException {
       //1.創建socket服務
       DatagramSocket ds = new DatagramSocket();

       //將鍵盤輸入的信息轉換成輸入流再放入到緩衝區
       BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
       String line = null;
       while ((line=br.readLine())!=null){
           //2.封裝數據
           byte[] bytes = line.getBytes();
           //地址
           InetAddress address =InetAddress.getByName("192.168.31.137");
           //參數:數據、長度、地址、端口
           DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,7777);

           //3.發送數據包
           ds.send(dp);
       }

       //4.關閉socket服務
       ds.close();
   }
}

public class UDPSocket2 {
   public static void main(String[] args){
       try {
           new Thread(new Runnable() {
               @Override
               public void run() {
                   try {
                       receive();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
           }).start();
           send();
       } catch (IOException e) {
           e.printStackTrace();
       }
   }

   //接收消息方法
   private static void receive() throws IOException {
       System.out.println("UDOSocket2 Receiver Start...");

       //1.創建udp的socket服務,並聲明端口號
       DatagramSocket ds = new DatagramSocket(7777);
       //無限循環,一直處於接收狀態
       while (true) {
           //2.創建接收數據的數據包
           byte[] bytes = new byte[1024];
           DatagramPacket dp = new DatagramPacket(bytes, bytes.length);

           //3.將數據接收到數據包中
           ds.receive(dp);

           //4.解析數據
           String content = new String(dp.getData(), 0, dp.getLength());
           System.out.println("UDPSocket2 Receive:" + content);
       }
       //關閉服務
//        ds.close();
   }

   private static void send() throws IOException {
       //1.創建socket服務
       DatagramSocket ds = new DatagramSocket();

       //將鍵盤輸入的信息轉換成輸入流再放入到緩衝區
       BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
       String line = null;
       while ((line=br.readLine())!=null){
           //2.封裝數據
           byte[] bytes = line.getBytes();
           //地址
           InetAddress address =InetAddress.getByName("192.168.31.137");
           //參數:數據、長度、地址、端口
           DatagramPacket dp = new DatagramPacket(bytes,bytes.length,address,6666);

           //3.發送數據包
           ds.send(dp);
       }

       //4.關閉socket服務
       ds.close();
   }
}

在接收方方法寫一個無限循環讓其處於持續接收狀態,發送發通過對鍵盤錄入加回車進行消息的發送,並且兩個端點都具備發送和接收功能。需要注意的是receive()會執行一個無限循環,所以receive()和send()必須位於不同線程,否則會導致無法發送消息從而也接收不到消息。

來看打印結果:
UDPSocket1

UDPSocket Receiver Start...
UDPSocket Receive:hello udp1
heelo udp2

UDPSocket2

UDPSocket2 Receiver Start...
hello udp1
UDPSocket2 Receive:hello udp2

我在UDPSocket1 的控制檯中輸入了:“hello udp2”、在UDPSocket2 的控制檯中輸入了:“hello udp1”,接收內容和發送內容完全一致,一個簡單的聊天功能就實現了,希望不熟悉的朋友也可以敲一遍代碼練一遍。

TCP Socket

TCP基於client-server是面向連接的可靠傳輸,上篇文章我們也講解了TCP安全傳輸機制,可謂是相當複雜,如果需要個人對TCP協議進行封裝顯然大多數開發者是很難實現的,所以Java也爲開發者提供了基於TCP的Socket,不同於UDP,TCP Socket分爲Socket和ServerSocket對應着client和server,下面我來用代碼實現一個簡單的TCP通訊功能:
客戶端:

//客戶端
public class TCPClient {
   public static void main(String[] args) throws IOException {

       //1.創建TCP客戶端Socket服務
       Socket client = new Socket();
       //2.與服務端進行連接
       InetSocketAddress address = new InetSocketAddress("192.168.31.137",10000);
       client.connect(address);
       //3.連接成功後獲取客戶端Socket輸出流
       OutputStream outputStream = client.getOutputStream();
       //4.通過輸出流往服務端寫入數據
       outputStream.write("hello server".getBytes());
       //5.關閉流
       client.close();
   }
}

首先創建一個Socket和InetSocketAddress ,然後通過Socket的connect()方法進行連接,連接成功後可以獲取到輸出流,通過該輸出流就可以向服務端傳輸數據。
服務端:

public class TCPServer {
   public static void main(String[] args) throws IOException {
       //1.創建服務端Socket並明確端口號
       ServerSocket serverSocket = new ServerSocket(10000);
       //2.獲取到客戶端的Socket
       Socket socket = serverSocket.accept();
       //3.通過客戶端的Socket獲取到輸入流
       InputStream inputStream = socket.getInputStream();
       //4.通過輸入流獲取到客戶端傳遞的數據
       BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
       String line = null;
       while ((line = bufferedReader.readLine())!=null){
           System.out.println(line);
       }

       //5.關閉流
       socket.close();
       serverSocket.close();
   }

首先創建一個服務端Socket並明確端口號,通過accept()方法獲取到鏈接過來的客戶端Socket,從客戶端Socket中獲取輸入流,最後由輸入流讀取客戶端傳輸來的數據。

我們來看看服務端的打印結果:

hello server

成功接收到客戶端發來的數據

注意點:
一個服務端是可以同時和多個客戶端進行通信的,那麼它是如何區分不同客戶端呢?從上面代碼我們可以看到,服務端首先通過accept()獲取到客戶端Socket,然後通過客戶端的Socket獲取的流進行通訊,這也讓服務端得以區分每個客戶端。
在這裏插入圖片描述

文件傳輸實例

流程:客戶端上傳一個文件到服務端,服務端收到文件後進行保存,保存成功後給客戶端一個響應。
客戶端代碼

public class TCPUploadClient {

   public static void main(String[] args) throws IOException {

       //1.創建TCP客戶端Socket服務
       Socket client = new Socket();

       //2.與服務端進行連接
       InetSocketAddress address = new InetSocketAddress("192.168.31.137",10001);
       client.connect(address);

       //3.讀取客戶端文件
       FileInputStream fis = new FileInputStream("E://girl.jpg");

       //4.獲取輸出流
       OutputStream outputStream = client.getOutputStream();

       //5.將文件寫入到服務端
       byte[] bytes = new byte[1024];
       int len = 0;
       while ((len = fis.read(bytes))!=-1){
           outputStream.write(bytes,0,len);
       }

       //6.通知服務器數據寫入完畢
       client.shutdownOutput();

       //7.讀取服務端響應的數據
       InputStream inputStream = client.getInputStream();
       BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
       String line = br.readLine();
       System.out.println(line);

       //8.關閉流
       inputStream.close();
       fis.close();
       client.close();
   }
}

創建Socket並連接,讀取本地文件輸入流,將文件寫入到服務端,寫入成功後讀取服務端返回的數據。

服務端代碼

public class TCPUploadServer {
   public static void main(String[] args) throws IOException {

       //1.創建客戶端Socket
       ServerSocket serverSocket = new ServerSocket(10001);
       
       //2.獲取到客戶端Socket
       Socket socket = serverSocket.accept();
       
       //3.通過客戶端Socket獲取到輸入流
       InputStream is = socket.getInputStream();
       
       //4.將流以文件的形式寫入到磁盤
       File dir = new File("F://tcp");
       //如果文件夾不存在就創建文件夾
       if(!dir.exists()){
           dir.mkdirs();
       }
       File file = new File(dir,"girl.jpg");
       FileOutputStream fos = new FileOutputStream(file);
       byte[] bytes = new byte[1024];
       int len = 0;
       while ((len = is.read(bytes))!=-1){
           fos.write(bytes,0,len);
       }
       
       //5.通知客戶端文件保存完畢
       OutputStream os = socket.getOutputStream();
       os.write("success".getBytes());
       
       //6.關閉流
       fos.close();
       os.close();
       socket.close();
   }
}

創建Socket並獲取到客戶端Socket和輸入流,在F盤的tcp目錄下創建一個文件,將從客戶端讀取到的數據輸出到文件中,保存成功後給客戶端返回’‘success’’

這樣我們就實現了一哥客戶端上傳文件的功能,還是比較簡單的,希望大家也能夠跟着代碼敲一遍。

TCP Socket多線程應用

細心的同學可能已經發現,上面我們的實例中一個服務端只能接收一個客戶端的一次請求,這顯然是不符合實際的,那麼如何使一個服務端同時服務多個客戶端呢?接着擼代碼

//客戶端1
public class TCPClient1 {
   public static void main(String[] args) throws IOException {
       System.out.println("TCPClient1 Start...");
       //1.創建TCP客戶端Socket服務
       Socket client = new Socket();

       //2.與服務端進行連接
       InetSocketAddress address = new InetSocketAddress("192.168.31.137",10004);
       client.connect(address);

       //3.連接成功後獲取客戶端Socket輸出流
       OutputStream outputStream = client.getOutputStream();

       //4.通過輸出流往服務端寫入數據
       outputStream.write("Hello my name is Client1".getBytes());

       //5.告訴服務端發送完畢
       client.shutdownOutput();

       //6.讀取服務端返回數據
       InputStream is = client.getInputStream();
       byte[] bytes = new byte[1024];
       int len = is.read(bytes);
       System.out.println(new String(bytes,0,len));

       //7.關閉流
       client.close();
   }
}

//客戶端2
public class TCPClient2 {

   public static void main(String[] args) throws IOException {
       System.out.println("TCPClient2 Start...");

       //1.創建TCP客戶端Socket服務
       Socket client = new Socket();

       //2.與服務端進行連接
       InetSocketAddress address = new InetSocketAddress("192.168.31.137",10004);
       client.connect(address);

       //3.連接成功後獲取客戶端Socket輸出流
       OutputStream outputStream = client.getOutputStream();

       //4.通過輸出流往服務端寫入數據
       outputStream.write("Hello my name is Client2".getBytes());

       //5.告訴服務端發送完畢
       client.shutdownOutput();

       //6.讀取服務端返回數據
       InputStream is = client.getInputStream();
       byte[] bytes = new byte[1024];
       int len = is.read(bytes);
       System.out.println(new String(bytes,0,len));

       //7.關閉流
       client.close();
   }
}
//服務端
public class TCPServer {
   public static void main(String[] args) throws IOException {
       receive();
   }

   private static void receive() throws IOException {
       System.out.println("Server Start...");
       //創建服務端Socket並明確端口號
       ServerSocket serverSocket = new ServerSocket(10004);

       while (true){
           //獲取到客戶端的Socket
           Socket socket = serverSocket.accept();
           //通過客戶端的Socket獲取到輸入流
           InputStream is = socket.getInputStream();

           //通過輸入流獲取到客戶端傳遞的數據
           byte[] bytes = new byte[1024];
           int len = is.read(bytes);
           System.out.println(new String(bytes,0,len));

           //將客戶端發來的數據原封不動返回
           OutputStream os = socket.getOutputStream();
           os.write(new String(bytes,0,len).getBytes());
           //關閉連接
           socket.close();
       }
   }
}

客戶端1控制檯

TCPClient1 Start...
Hello my name is Client1

客戶端2控制檯

TCPClient2 Start...
Hello my name is Client2

這樣就可以實現一個服務端服務多個客戶端

細心的同學可能又發現了,上面的寫法是存在問題的,由於服務端始終都在主線程中處理請求,所以客戶端的請求需要被服務端排隊處理,舉個例子:Client1對服務端進行了一次請求,服務端在響應Client1之前是不會接受其他請求的,顯然這是不符合邏輯的,真正的服務器是要具備併發處理的。而多線程恰好能解決這個問題,我們來看修改之後的服務端代碼

public class TCPServer {
   public static void main(String[] args) throws IOException {
       receive();
   }

   private static void receive() throws IOException {
       System.out.println("Server Start...");

       //創建服務端Socket並明確端口號
       ServerSocket serverSocket = new ServerSocket(10004);
       while (true){
           //獲取到客戶端的Socket
           final Socket socket = serverSocket.accept();
           //通過線程執行客戶端請求
           new Thread(new Runnable() {
               @Override
               public void run() {
                   try {
                       //通過客戶端的Socket獲取到輸入流
                       InputStream is = socket.getInputStream();

                       //通過輸入流獲取到客戶端傳遞的數據
                       byte[] bytes = new byte[1024];
                       int len = is.read(bytes);
                       System.out.println(new String(bytes,0,len));

                       //將客戶端發來的數據原封不動返回
                       OutputStream os = socket.getOutputStream();
                       os.write(new String(bytes,0,len).getBytes());
                       //關閉連接
                       socket.close();
                   } catch (IOException e) {
                       e.printStackTrace();
                   }
               }
           }).start();

       }
   }
}

運行效果適合加線程之前是一樣的,但這種方式效率更高。

本篇文章敘述了基於UDP和TCP的Socket,UDP是無連接的,所以UDP Socket在發送數據的時候只需要目標IP和端口即可發送。TCP是面向連接的並且是基於client-server模式,在傳輸數據前需要進行連接,可以通過多線程技術實現併發處理客戶端請求。本篇文章內容還是比較簡單的,希望大家能把文章中代碼自己敲一遍,掌握Socket的同時也能讓你自己UDP和TCP的理解更加深刻。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章