java Socket 實踐

基於TCP鏈接的Socket通訊

TCP協議提供可靠的數據傳輸服務是經過創建TCP鏈接實現的。一條「TCP鏈接」鏈接的兩端是Internet上分別在兩臺主機運行的兩個進程,一個是發送進程,一個是接收進程,每一個進程用一個Socket(IP地址和端口)惟一肯定。一對Socket惟一標識一條TCP鏈接。TCP鏈接是全雙工和點對點的,全雙工指數據可雙向傳輸,點對點是指每條TCP鏈接只有兩個端點。

image.png

使用一對socket進行tcp鏈接,鏈接成功後,就能夠經過socket發送或接收字節流數據,完成點到點、全雙工的通訊。segmentfault

Java的TCP Socket通訊

clipboard.png

服務端經過建立指定端口號的ServerSocket對象,調用accept()方法等待客戶端鏈接請求,等待期間當前進程處於堵塞狀態。當服務端接收到客戶端的鏈接請求時,進程繼續運行,並創建一條TCP鏈接,accept()完成並返回一個Socket對象,經過該Socket對象與客戶端的Socket對象實現實時的數據通訊。服務器

ServerSocket serverSocket = new ServerSocket(7000);
Socket socket = serverSocket.accept();

客戶端建立Socket對象,指定服務器主機的IP和端口,發出TCP鏈接請求,待服務器接受鏈接請求後Socket對象建立成功,此時能夠經過此Socket對象與服務端Socket對象實時通訊。socket

Socket socket = new Socket("127.0.0.1", 7000);

使用Socket對象輸出/讀取字節流數據

從控制檯獲取輸入的字符串,字符串數據將從PrintWriter"流入"OutputStreamWriter在"流入"socket的OutputStream中,經過TCP創建的鏈路,流到與之對應的Socket的InputStream中。注意,Socket老是成對的。tcp

PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
message = scanner.nextLine();
writer.println(message);
writer.flush();

在另外一端的Socket中,即可以經過InputStream把發送的數據讀取出來,此時數據的流向爲InputStream->InputStreamReader->BufferedReader->控制檯。spa

BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String line = "";
while ((line = bufferedReader.readLine()) != null) {
        System.out.println(line);
}

整個的數據流向爲:客戶端控制檯->PrintWriter->OutputStreamWriter->客戶端Socket的OutputStream->TCP鏈路->服務端Socekt的InputStream->InputStreamReader->BufferedReader->服務端控制檯。經過socket即可以輕易的將數據從客戶端發送到服務端了。線程

示例

瞭解了Socekt通訊後,咱們能夠使用Socket仿照QQ羣聊效果:當某個用戶發送消息時,全部用戶都接收到此消息並顯示到控制檯上。3d

客戶端

客戶端須要兩個進程,一個進程不停的從控制檯中讀取數據,讀取到用戶輸入時即向服務端發送讀取到的數據,把它稱爲寫進程。另外一個進程接收服務端發送的數據,並把它顯示到控制檯上,稱爲讀進程。code

public static void main(String[] args) throws IOException {
        Scanner scanner = new Scanner(System.in);
        Socket socket = new Socket("127.0.0.1", 7000);

        // 獲取輸入流和輸出流
        PrintWriter writer = new PrintWriter(new OutputStreamWriter(socket.getOutputStream()));
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

        // 開啓兩個線程分別讀寫
        
        // 寫線程
        new Thread(() -> {
            // 循環從控制檯讀取數據
            String message = "";
            while (!message.equals("exit")) {
                message = scanner.nextLine();
                writer.println(message);
                writer.flush();
            }
            writer.close();
        }).start();

        // 讀線程
        new Thread(() -> {
            // 不停從input流獲取數據
            String line = "";
            try {
                while ((line = bufferedReader.readLine()) != null) {
                    System.out.format("%50s", line);
                    System.out.println();
                }
                bufferedReader.close();
            } catch (IOException e) {

            }
        }).start();

    }

服務端

爲了響應多個客戶端鏈接,須要在循環中不停的調用accept()方法,每當獲取到一個新TCP鏈接時,把獲取到的Socket對象存入set中,對每一個Socket對象都開啓一個線程,主要的任務是不停的從InputStream中讀取數據,即接收發送客戶端數據。獲取到數據後,再發送給全部已鏈接的客戶端Socket(除了發送數據的客戶端Socket),即遍歷set發送數據。orm

public class Server {
    public static int userCount = 0;

    public static void main(String[] args) throws IOException {
        ServerSocket serverSocket = new ServerSocket(7000);
        Set<Socket> socketSet = new HashSet<>();

        while (true) {
            Socket socket = serverSocket.accept();
            socketSet.add(socket);
            userCount++;
            // 開啓一個新線程
            Thread thread = new Thread(() -> {
                // 不停從input流獲取數據
                BufferedReader bufferedReader = null;
                try {
                    bufferedReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
                    String userName = Thread.currentThread().getName();
                    String line = "";
                    System.out.println("start Read");
                    while ((line = bufferedReader.readLine()) != null) {
                        System.out.println(userName + "消息:" + line);
                        for (Socket tem :
                                socketSet) {
                            if (!tem.equals(socket)) {
                                PrintWriter writer = new PrintWriter(new OutputStreamWriter(tem.getOutputStream()));
                                writer.println(line + ":" + userName);
                                writer.flush();
                            }
                        }
                    }
                    bufferedReader.close();
                } catch (IOException e) {
                }
                System.out.println("finish Read");
            });
            thread.setName("用戶" + userCount);
            thread.start();
        }

    }
}

在終端運行的效果圖:
image.png
image.png
image.pngserver

相關文章
相關標籤/搜索