Java Socket 通信(一)

1. 前言

基於傳輸層協議TCP的網絡通信是可靠的、有序的、差錯控制的。TCP是面向連接的、可靠的流服務協議。TCP協議中,只有實現連接的雙發纔可以進行通信,因此廣播和多播不是基於TCP的。下面首先介紹一些Socket和ServerSocket。
服務器、客戶端建立通信的過程如下:
在這裏插入圖片描述

  1. 服務器網絡接口的IP地址爲10.1.1.1。服務器端應用程序在80端口運行服務,建立ServerSocket,並監聽有無連接請求。
  2. 客戶端必須知道服務器的IP地址或主機名、服務開放端口,客戶端向服務器的服務Socket(10.1.1.1:80)發起請求。
  3. 服務器一旦接收該請求,就會在80端口上建立一個Socket,該Socket的本地地址爲10.1.1.1:80,外部地址爲客戶端建立的Socket(client端IP地址,client端port)。客戶端port通常由操作系統指定。
  4. 服務器每接收一個客戶端的請求,就會在服務端口上建立一個Socket,這些Socket的本地地址相同,但外部地址各不相同。每個外部地址即各個客戶端的本地Socket。這樣服務器既可以監聽服務請求,又可以和每個客戶端進行獨立的通信。
  5. 客戶端通過本地Socket與服務器進行通信、讀寫信息。就像打電話一樣,一個號碼呼叫另一個號碼,一旦被呼叫者接聽,通信鏈路就建立起來了,雙方就可以通話了。

2. Socket類

2.1 Socket的通信過程

在這裏插入圖片描述

2.2 控制Socket連接

  1. 設置客戶端的地址和端口
Socket socket = new Socket(InetAddress.getByName("10.1.1.1")),8888,InetAddress.getByName("10.1.1.100"),12345);
  1. 綁定本地地址
try{
	socket = new Socket();
	socket.bind(new InetSocketAddress("10.1.1.100", 12345));
	scoket.connect(new InetSocketAddress("10.1.1.1", 8888));
}catch(){
	
}
  1. 連接遠程服務器

見上一步,使用connect()方法去連接遠程服務器

  1. 設置Socket建立連接的超時時間
SocketAddress socketAddress = new InetSocketAddress("10.1.1.1", 8888);
//創建Socket
Socket socket = new Socket;
//連接超時時間設置爲10s
socket.connect(socketAddress, 10000);
  1. 獲取Socket的輸入流和輸出流
    服務端和客戶端之間通信的Socket一旦連接,就會通過一對輸入輸出流進行通信。如同打電話時,一旦被叫放接聽,雙方通話的業務信道建立,就可以交流了。
    Socket的getInputStream()方法和getOutputStream()方法分別實現獲得輸入流和輸出流。
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader br = new BufferedReader(new IntputStreamReader(socket.getInputStream()));
  1. 獲取Socket的地址端口信息
    如果需要獲得本地或者遠程Socket的地址和端口信息,可以使用以下方法:
getInetAddress() //獲得遠程服務器的IP地址
getPort() //獲得遠程服務器的端口
getLocalAddress() //獲得客戶端本地的IP地址
getLocalPort() //獲得客戶端本地的端口
  1. 關閉Socket
    當客戶端與服務器的通信結束時,應該關閉Socket,以釋放Socket佔用的包括InputStream和OutputStream在內的各種資源。
if(!s.isClosed()) s.close();
//判斷如果不是空的話則關閉
  1. Socket的狀態
    Socket類提供了3中方法(isBound 、isClosed 和 isConnected)來判斷Socket的狀態。
    isBound方法用來判斷Socket的綁定狀態,只要曾經綁定過,即使Socket已經關閉,仍然返回true。可以理解爲本地是否曾經建立過到遠程主機的Socket連接。
    isClosed方法用來判斷Socket是否已經關閉。
    isConnected方法用來判斷Socket的連接狀態。和isBound方法一樣,即使Socket已經關閉,仍然返回true,isConnected的狀態不清楚。可以理解爲到遠程主機的Socket是否曾經連接過。

2.3 設置Socket的選項

字段 含義
TCP_NODELAY 表示不要延遲和緩存
SO_LINGER 表示當執行Socket的close方法時,是否立即關閉底層的socket
SO_REUSEADDR 表示是否允許綁定Socket,即使之前的連接處於timeout狀態
SO_SNDBUF 表示輸出數據的緩衝區的大小
SO_RCVBUF 表示接收數據的緩衝區的大小
SO_KEEPALIVE 用來設置長時間處於空閒狀態的Socket
SO_OOBINLINE 表示是否支持發送一字節的TCP緊急數據
SO_TIMEOUT 表示Socket的輸入流等待數據的超時時間

3. ServerSocket類

3.1 構造ServerSocket

  • ServerSocket() throws IOException : 創建未綁定的服務器套接字
  • ServerSocket(int port) throws IOException:創建綁定到特定端口的服務器套接字
  • ServerSocket(int port, int backlog) throws IOException :創建綁定到特定端口的服務器套接字,並且連接請求隊列的長度由backlog來設置
  • ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException:創建綁定到特定端口的服務器套接字,並且連接請求隊列的長度由backlog來設置,指定本地要綁定的IP地址bindAddr,一臺服務器可能有多個網絡接口,如果設置了bindAddr,則服務器只接收到該地址的連接請求。

3.2 ServerSocket的常用方法

  1. 綁定端口
ServerSocket serverSocket = new ServerSocket(8000);

ServerSocket 對象創建時,如果使用無參構造方法,通常之後會做一些選項的設置,再來進行端口的綁定,則需要使用bind方法。

  1. 設置連接請求隊列的長度
int queue = 100;
serverSocket.bind(s, queue);

若沒有設置,由操作系統指定

  1. 獲取ServerSocket的地址端口信息
  • getInetAddress() : 獲得服務器的本地IP地址
  • getLocalPort() : 獲得服務器本地監聽端口
  • getLocalSocketAddress() : 獲得服務器的本地IP地址和監聽端口
  1. 關閉ServerSocket
    ServerSocket 建立之後,可以顯示地關閉它。同Socket一樣,使用close方法關閉,當關閉時,如果隊列中有尚未accept的客戶端請求,則會觸發SocketException。

  2. 接收客戶端的連接請求
    ServerSocket的accept方法監聽客戶端的連接請求並且接收請求。

  3. ServerSocket的狀態
    ServerSocket 類提供了兩種方法(isBound、isClosed)來判斷Socket的狀態。具體可參考Socket類的相應方法。

3.3 設置ServerSocket選項

字段 含義
SO_TIMEOUT 表示等待客戶端連接的超時時間
SO_REUSEADDR 表示是否允許重複綁定Socket處於timeout狀態的端口
SO_RCVBUF 設置接收數據的緩衝區的大小

4. 基於TCP的BIO通信

4.1 實現功能

服務器創建服務並監聽端口,與客戶端建立連接,並向客戶端轉發消息,客戶端輸入quit命令即可退出。

4.2 程序源代碼

Server類:

package com.company.bio;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;

public class Server {
    public static void main(String[] args){
        final  String QUIT = "quit";
        final int DEFAULT_PORT = 8888;
        ServerSocket serverSocket = null;

        //綁定監聽端口
        try{
            serverSocket = new ServerSocket(DEFAULT_PORT);
            System.out.println("啓動服務器,監聽端口"+DEFAULT_PORT);

            while (true){
                // 等待客戶端連接
                Socket socket = serverSocket.accept();
                System.out.println("客戶端[" + socket.getPort() + "]已連接");
                //創建IO流
                BufferedReader bufferedReader = new BufferedReader(
                        new InputStreamReader(socket.getInputStream())
                );
                BufferedWriter bufferedWriter = new BufferedWriter(
                        new OutputStreamWriter(socket.getOutputStream())
                );

                String msg = null;
                while ((msg = bufferedReader.readLine()) != null) {
                    // 讀取客戶端發送的消息,當對方關閉時返回null

                    System.out.println("客戶端["+socket.getPort()+"]:"+ msg);
                    //回覆客戶發送的消息
                    bufferedWriter.write("服務器:" + msg + "\n");
                    bufferedWriter.flush(); //保證緩衝區的數據發送出去
                    //查看客戶端是否退出
                    if(QUIT.equals(msg)){
                        System.out.println("客戶端["+socket.getPort()+"]已退出");
                        break;
                    }
                }
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if (serverSocket != null){
                try {
                    serverSocket.close();
                    System.out.println("關閉ServerSocket");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

Client類:

package com.company.bio;

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

public class Client {
    public static void main(String[] args)  {
        final  String QUIT = "quit";
        final String DEFAULT_SERVER_HOST = "127.0.0.1";
        final int DEFAULT_SERVER_PORT = 8888;
        Socket socket = null;
        BufferedWriter bufferedWriter = null;

        try{
            // 創建socket
            socket = new Socket(DEFAULT_SERVER_HOST, DEFAULT_SERVER_PORT);
            //創建IO流
            BufferedReader bufferedReader = new BufferedReader(
                new InputStreamReader(socket.getInputStream())
            );
            bufferedWriter = new BufferedWriter(
                 new OutputStreamWriter(socket.getOutputStream())
            );
            //等待用戶輸入信息
            BufferedReader consolReader = new BufferedReader(
                    new InputStreamReader(System.in)
            );
            while (true) {
                String input = consolReader.readLine();
                //發送消息給服務器
                bufferedWriter.write(input + "\n");
                bufferedWriter.flush();
                //讀取服務器返回的消息
                String msg = bufferedReader.readLine();
                System.out.println(msg);

                //查看用戶是否退出
                if(QUIT.equals(input))break;
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            if(bufferedWriter != null){
                try {
                    bufferedWriter.close();
                    System.out.println("關閉socket");
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

4.3 程序效果截圖

Client:
在這裏插入圖片描述
Server:

在這裏插入圖片描述

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