Socket通信

網絡基礎

計算機網絡

計算機網絡是指將地理位置不同的具有獨立功能的多臺計算機及其外部設備,通過通信線路連接起來,在網絡操作系統,網絡管理軟件及網絡通信協議的管理和協調下,實現資源共享和信息傳遞的計算機系統。
網絡編程就是用來實現網絡互連的不同計算機上運行的程序間可以進行數據交換。

網絡通信

兩臺計算機通過網絡進行通信,需要具備以下條件
IP地址:計算機唯一標識
協議:通訊語言/規則
端口:程序入口,不同進程的標識

TCP/IP協議

TCP/IP是目前世界上應用最為廣泛的協議
是以TCP和IP為基礎的不同層次上多個協議的集合
也稱:TCP/IP協議族或TCP/IP協議棧
TCP:Transmission Control Protocol 傳輸控制協議
IP:Internet Protocol 互聯網協議

TCP/IP模型
相關概念

IP地址:實現網絡中不同計算機之間的通信,每臺機器都必須有一個唯一的標識
端口:用于區分不同應用程序,端口號范圍0-65535,其中0-1023是系統保留的端口號
IP地址和端口號組成了所謂的SocketSocket是網絡上運行的程序之間雙向通信鏈路的終結點,是TCP和UDP的基礎

java中的網絡支持

針對網絡通信的不同層次,Java提供的網絡功能有四大類:在 java.net包下
InetAddress: 用于標識網絡上的硬件資源
URL: 統一資源定位符通過URL可以直接讀取或寫入網絡上的數據
Socket: 使用TCP協議實現網絡通信的Socket相關的類
Datagram: 使用UDP協議,將數據保存在數據報中,通過網絡進行通信

InetAddress類

此類表示互聯網協議(IP)地址

常用方法
示例
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;

public class InetAddressDemo {

    public static void main(String[] args) throws UnknownHostException {
        // 獲取本機的IntetAddress實例
        InetAddress address = InetAddress.getLocalHost();
        System.out.println("計算機名:" + address.getHostName());
        System.out.println("IP地址:" + address.getHostAddress());
        byte[] bytes = address.getAddress();// 獲取字節數組形式的IP地址
        System.out.println("字節數組:" + Arrays.toString(bytes));
        System.out.println(address);// 直接輸出InetAddress對象

        // InetAddress address2 = InetAddress.getByName("DESKTOP-JQI6O11");
        InetAddress address2 = InetAddress.getByName("192.168.84.1");
        System.out.println("計算機名:" + address.getHostName());
        System.out.println("IP地址:" + address.getHostAddress());
        /*
         * 輸出結果 
         * 計算機名:DESKTOP-JQI6O11 
         * IP地址:192.168.84.1 
         * 字節數組:[-64, -88, 84, 1]
         * DESKTOP-JQI6O11/192.168.84.1 
         * 計算機名:DESKTOP-JQI6O11 
         * IP地址:192.168.84.1
         */
    }
}

URL類

URL(Uniform Resource Locator) 統一資源定位符,表示Internet上某一資源的地址
URL主要由兩部分組成:協議名稱和資源名稱,中間用冒號隔開

構造方法
常用方法
示例
import java.net.MalformedURLException;
import java.net.URL;

public class URLDemo {

    public static void main(String[] args) {
        try {
            URL silly = new URL("http://www.silly.com");
            //?后面代表參數,#后面表示錨點
            URL url = new URL(silly, "/index.html?username=tom#test");
            System.out.println("協議:"+url.getProtocol());
            System.out.println("主機:"+url.getHost());
            //如果未指定端口號,則使用默認http協議的端口號80進行訪問
            //對于缺省端口號getPort()方法統一返回-1
            System.out.println("端口:"+url.getPort());
            System.out.println("文件路徑:"+url.getPath());
            System.out.println("文件名:"+url.getFile());
            System.out.println("相對路徑:"+url.getRef());
            System.out.println("查詢字符串:"+url.getQuery());
        } catch (MalformedURLException e) {
            e.printStackTrace();
        }
        /*
        協議:http
        主機:www.silly.com
        端口:-1
        文件路徑:/index.html
        文件名:/index.html?username=tom
        相對路徑:test
        查詢字符串:username=tom
        */
    }
}
使用URL讀取網頁內容

通過URL對象的openStream()方法可以得到指定資源的輸入流
通過輸入流可以讀取、訪問網絡上的數據

import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.URL;

public class URLDemo2 {
    
    public static void main(String[] args) {
        
        //創建url實例
        try {
            URL url = new URL("http://www.baidu.com");
            //通過url的openStream方法獲取url對象所表示的資源的字節輸入流
            InputStream is = url.openStream();
            //將字節輸入流轉化為字符輸入流
            InputStreamReader isr = new InputStreamReader(is,"utf-8");
            //為字符輸入流添加緩沖
            BufferedReader br = new BufferedReader(isr);
            String data = br.readLine(); //讀取數據
            while(data != null){ //循環讀取數據
                System.out.println(data);//輸出數據
                data = br.readLine();
            }
            br.close();
            is.close();
            isr.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
    /*
     打印的內容就是百度首頁的html文檔
    */
}

Socket通信簡介

TCP協議是面向連接、可靠的、有序的,以字節流的方式發送數據
基于TCP協議實現網絡通信的類
— 客戶端的Socket
— 服務器端的ServerSocket

通訊模型
Socket通信實現步驟
  1. 創建ServerSocket和Socket
  2. 打開連接到Socket的輸入/輸出流
  3. 按照協議對Socket進行讀/寫操作
  4. 關閉輸入輸出流、關閉Socket

ServerSocket類

此類實現服務器套接字。服務器套接字等待請求通過網絡傳入并進行相關操作

構造方法

ServerSocket(int port):創建綁定到特定端口的服務器套接字

常用方法

Socket類

此類實現客戶端套接字,套接字是兩臺機器間通信的端點

構造方法

Socket(String host, int port):創建一個套接字并將其連接到指定主機上的指定端口號

常用方法

DatagramPacket類

此類表示數據報包,數據報包用來實現無連接包投遞服務

構造方法
常用方法

DatagramSocket類

此類表示用來發送和接收數據報包的套接字,數據報套接字是包投遞服務的發送或接收點

構造方法
常用方法

基于TCP的socket通信

服務端
  1. 創建ServerSocket對象,綁定監聽端口
  2. 通過accept()方法監聽客戶端請求
  3. 連接建立后,通過輸入流讀取客戶端發送的請求信息
  4. 通過輸出流向客戶端發送響應信息
  5. 關閉相關資源
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 基于tcp協議的socket通信,實現用戶登錄
 * 服務器端
 */
public class Server {

    public static void main(String[] args) {
        
        try {
            //1.創建服務器端Socket,指定綁定的端口
            ServerSocket server = new ServerSocket(8888);
            //2.調用accept()方法開始監聽,等待客戶端的連接
            System.out.println("***服務器啟動,等待客戶端連接***");
            Socket socket = server.accept();
            //3.獲取輸入流,并讀取客戶端信息
            InputStream in = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));//轉換為緩沖字符流
            String info = null;
            while((info = br.readLine()) != null){//循環讀取客戶端信息
                System.out.println("我是服務器,客戶端說:"+info);
            }
            //此處必須關閉輸入流
            socket.shutdownInput();
            //4.獲取輸出流,響應客戶端的請求
            OutputStream os = socket.getOutputStream();
            PrintWriter pw = new PrintWriter(os);//包裝為打印流
            pw.write("歡迎您!");
            pw.flush(); //將緩沖輸出
            socket.shutdownOutput();
            //5.關閉資源
            socket.close();
            server.close();
        } catch (IOException e) {
            e.printStackTrace();
        }   
    }
}
客戶端
  1. 創建Socket對象,指明需要連接的服務器的地址和端口號
  2. 連接建立后,通過輸出流向服務器端發送請求信息
  3. 通過輸入流獲取服務器響應的信息
  4. 關閉相關資源
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
 * 基于TCP的socket通信,實現用戶登錄
 * 客戶端
 */
public class Client {

    public static void main(String[] args) {
        try {
            // 1.創建客戶端Socket,指定服務器地址和端口
            Socket socket = new Socket("127.0.0.1", 8888);
            // 2.獲取輸出流,向服務器端發送信息
            OutputStream os = socket.getOutputStream();
            PrintWriter pw = new PrintWriter(os);// 將輸出流包裝為打印流
            pw.write("用戶名:alice;密碼:123456");
            pw.flush();
            // 此處必須關閉輸出流
            //注意不能使用os.close()或者pw.close()方法關閉流,會導致socket也被關閉
            socket.shutdownOutput();
            // 3.獲取輸入流,并讀取服務器端的響應信息
            InputStream in = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));
            String info = null;
            while ((info = br.readLine()) != null) {
                System.out.println("我是客戶端,服務器說:" + info);
            }
            socket.shutdownInput();
            // 4.關閉資源
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
運行結果

首先啟動服務端,然后啟動客戶端

# 服務端打印內容
***服務器啟動,等待客戶端連接***
我是服務器,客戶端說:用戶名:alice;密碼:123456
# 客戶端打印內容
我是客戶端,服務器說:歡迎您!
多線程服務器

應用多線程來實現服務器與多客戶端之間的通信基本步驟

  1. 服務器端創建ServerSocket,循環調用accept() 等待客戶端連接
  2. 客戶端創建一個socket并請求和服務器端連接
  3. 服務器端接受客戶端請求,創建socket與該客戶建立專線連接
  4. 建立連接的兩個socket在一個單獨的線程上對話
  5. 服務器端繼續等待新的連接
創建服務端線程處理類
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.Socket;
/**
 * 服務器線程處理類
 */
public class ServerThread implements Runnable{
    
    // 和本線程相關的Socket
    private Socket socket;
    
    public ServerThread(Socket socket) {
        this.socket = socket;
    }
    //線程執行的操作,響應客戶端的請求
    public void run() {
        try {
            //獲取輸入流,并讀取客戶端信息
            InputStream in = socket.getInputStream();
            BufferedReader br = new BufferedReader(new InputStreamReader(in));//轉換為緩沖字符流
            String info = null;
            while((info = br.readLine()) != null){//循環讀取客戶端信息
                System.out.println("我是服務器,客戶端說:"+info);
            }
            //此處必須關閉輸入流
            socket.shutdownInput();
            //獲取輸出流,響應客戶端的請求
            OutputStream os = socket.getOutputStream();
            PrintWriter pw = new PrintWriter(os);//包裝為打印流
            pw.write("歡迎您!");
            pw.flush();//將緩沖輸出
            socket.shutdownOutput();
            //關閉資源
            socket.close();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(socket!=null)
                try {
                    socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
        }
    }
}
服務端改寫
import java.io.IOException;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
/**
 * 
 * 基于tcp協議的socket通信,實現用戶登錄
 * 服務器端多線程實現
 */
public class Server {

    public static void main(String[] args) {
        
        try {
            //創建服務器端Socket,指定綁定的端口
            ServerSocket server = new ServerSocket(8888);
            Socket socket = null;
            //記錄計數連接過服務器的客戶端數量
            int count = 0;
            System.out.println("***服務器啟動,等待客戶端連接***");
            //循環監聽等待客戶端的連接
            while(true){
                //調用accept()方法開始監聽,等待客戶端的連接
                socket = server.accept();
                //創建一個新的線程
                ServerThread serverThread = new ServerThread(socket);
                Thread thread = new Thread(serverThread);
                thread.setPriority(4); //設置線程優先級,范圍為[1-10],默認值為5
                //啟動線程
                thread.start();
                count++;//統計數量
                System.out.println("連接過服務器端的客戶端的數量:"+count);
                InetAddress address = socket.getInetAddress();
                System.out.println("當前客戶端的IP:"+address.getHostAddress());
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}
運行結果

首先啟動服務端,然后啟動一個客戶端,修改客戶端寫入的內容,再次啟動一個客戶端

# 服務端打印內容
***服務器啟動,等待客戶端連接***
連接過服務器端的客戶端的數量:1
當前客戶端的IP:127.0.0.1
我是服務器,客戶端說:用戶名:alice;密碼:123456
連接過服務器端的客戶端的數量:2
當前客戶端的IP:127.0.0.1
我是服務器,客戶端說:用戶名:tom;密碼:555666

基于UDP的socket通信

UDP協議(用戶數據報協議)是無連接、不可靠的、無序的,UDP協議以數據報作為數據傳輸的載體
進行數據傳輸時,首先需要將要傳輸的數據定義成數據報(Datagram),在數據報中指明數據所要到達的Socket(主機地址和端口號),然后再將數據報發送出去

服務器端
  1. 創建DatagramSocket,指定端口號
  2. 創建DatagramPacket
  3. 接收客戶端發送的數據信息
  4. 讀取數據
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * 服務器端,實現基于UDP的用戶登陸
 */
public class UDPServer {
    
    public static void main(String[] args) throws IOException {
        
        /*********接收客戶端發送的數據*********/
        // 1.創建服務器端DatagramSocket,指定端口
        DatagramSocket socket = new DatagramSocket(8800);
        // 2.創建數據報,用于接收客戶端發送的數據
        byte[] data = new byte[1024];// 創建字節數組,指定接收的數據包的大小
        DatagramPacket packet = new DatagramPacket(data, data.length);
        // 3.接收客戶端發送的數據
        System.out.println("***服務器端已經啟動,等待客戶端發送數據***");
        socket.receive(packet);// 此方法在接收到數據報之前會一直阻塞
        // 4.讀取數據
        String info = new String(packet.getData(), 0, packet.getLength());
        System.out.println("我是服務器,客戶端說:" + info);
        
        /*********向客戶端響應數據*********/
        // 1.定義客戶端的地址、端口號、數據
        InetAddress address = packet.getAddress();
        int port = packet.getPort();
        byte[] data2 = "歡迎您!".getBytes();
        // 2.創建數據報,包含響應的數據信息
        DatagramPacket packet2 = new DatagramPacket(data2, data2.length, address, port);
        // 3.響應客戶端
        socket.send(packet2);
        // 4.關閉資源
        socket.close();
    }
}
客戶端
  1. 定義發送信息
  2. 創建DatagramPacket,包含將要發送的信息
  3. 創建DatagramSocket
  4. 發送數據
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

/**
 * 客戶端,實現基于UDP的用戶登陸
 */
public class UDPClient {
    
    public static void main(String[] args) throws IOException {
        
        /*********向服務器端發送數據*********/
        // 1.定義服務器的地址、端口號、數據
        InetAddress address = InetAddress.getByName("localhost");
        int port = 8800;
        byte[] data = "用戶名:admin;密碼:123".getBytes();
        // 2.創建數據報,包含發送的數據信息
        DatagramPacket packet = new DatagramPacket(data, data.length, address, port);
        // 3.創建DatagramSocket對象
        DatagramSocket socket = new DatagramSocket();
        // 4.向服務器端發送數據報
        socket.send(packet);

        /*********接收服務器端響應的數據*********/
        // 1.創建數據報,用于接收服務器端響應的數據
        byte[] data2 = new byte[1024];
        DatagramPacket packet2 = new DatagramPacket(data2, data2.length);
        // 2.接收服務器響應的數據
        socket.receive(packet2);
        // 3.讀取數據
        String reply = new String(data2, 0, packet2.getLength());
        System.out.println("我是客戶端,服務器說:" + reply);
        // 4.關閉資源
        socket.close();
    }
}
運行結果

首先啟動服務端,然后啟動客戶端

# 服務端打印內容
***服務器端已經啟動,等待客戶端發送數據***
我是服務器,客戶端說:用戶名:admin;密碼:123
# 客戶端打印內容
我是客戶端,服務器說:歡迎您!

Socket總結

多線程優先級

在使用多線程處理多客戶端通信時,未設置優先級時可能會導致運行時速度非常慢,可以降低優先級

輸入和輸出流的關閉
  1. 在同時使用輸入和輸出流時,socket只能接受一個流,要么輸入要么輸出。完成輸入或輸出流之后必須關閉(調用socket.shutdownInput()socket.shutdownInput())讓下一個流進來,此時socket仍然是連接狀態。
  2. 對于同一個socket,如果直接關閉輸入或者輸出流(調用close()方法),則與該輸入或者輸出流關聯的socket也會被關閉,所以一般不用關閉流,直接關閉socket即可。
使用TCP通信傳輸對象

真實的開發中都是以對象作為傳輸數據,可以使用ObjectOutputStreamObjectInputStream完成對象傳輸
1.定義User類,實現序列化接口

public class User implements Serializable{ 
    private String username;
    private String password;
    //省略getter和setter
    public User(String username, String password) {
        this.username = username;
        this.password = password;
    }
}

2.修改Client發送邏輯

3.修改ServerThread處理邏輯

Socket編程傳遞文件

這里簡單演示服務器向客戶端發送文件
1.修改ServerThread處理邏輯

2.修改Client邏輯

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。