Java多線程技術:實現多用戶服務端Socket通訊

目錄html

前言回顧java

1、多用戶服務器編程

2、使用線程池實現服務端多線程服務器

一、單線程版本微信

二、多線程版本網絡

3、多用戶與服務端通訊演示多線程

4、多用戶服務器完整代碼併發

最後socket


前言回顧

在上一篇《Java多線程實現TCP網絡Socket編程(C/S通訊)》,咱們解決了服務器端在創建鏈接後,連續發送多條信息給客戶端接收的問題,解決辦法容易理解,將客戶端接收信息的功能集中給線程處理,實現多線程同步進行。ide

同理,上一篇結束語留下來一個問題,簡而言之,至關於多用戶訪問服務器資源,服務器應該與各個客戶端創建鏈接,並進行通訊對話,就像咱們平常使用QQ、微信、視頻等客戶端,就是多用戶與服務器通訊的例子。

而上一篇中服務端只實現了單用戶的功能,本篇將解決這個問題,詳細記錄服務端多線程的實現,目標是多用戶(客戶端)可以同時與服務器創建鏈接並通訊,避免阻塞,進一步完善TCP的Socket網絡通訊,運用Java多線程技術,實現多用戶與服務端Socket通訊!

Java實現socket通訊網絡編程系列文章:

    1. UDP協議網絡Socket編程(java實現C/S通訊案例) 
    2. Java:基於TCP協議網絡socket編程(實現C/S通訊)
    3. Java多線程實現TCP網絡Socket編程(C/S通訊)

1、多用戶服務器

多用戶服務器是指服務器能同時支持多個用戶併發訪問服務器所提供的服務資源,如聊天服務、文件傳輸等。

上一篇的TCPServer是單用戶版本,每次只能和一個用戶對話。咱們能夠嘗試多用戶鏈接,開啓多個客戶端,具體操做以下:

這樣就容許同時並行執行多個客戶端,測試發現,單用戶版本的TCPServer.java程序能同時支持多個用戶併發鏈接(TCP三次握手),但不能同時服務多用戶對話,只有前一個用戶退出後,後面的用戶才能完成服務器鏈接。

多線程技術,線程調用的並行執行。

上一篇提到在java中有兩種實現多線程的方法,一是使用Thread類,二是使用Runnable類並實現run()方法。下面將使用Runnable類對服務端相關操做功能進行封裝,結合上一篇,就學到了兩種多線程實現方法。

//使用Runnable類,做爲匿名內部類
class Handler implements Runnable {
    public void run() {
   //實現run方法
    }
}

服務器面臨不少客戶的併發鏈接,這種狀況的多線程方案通常是:

  1. 主線程只負責監聽客戶請求和接受鏈接請求,用一個線程專門負責和一個客戶對話,即一個客戶請求成功後,建立一個新線程來專門負責該客戶。對於這種方案,能夠用上一篇方式new Thread建立線程,可是頻繁建立線程須要消耗大量系統資源。因此不採用這種方法。
  2. 對於服務器,通常使用線程池來管理和複用線程。線程池內部維護了若干個線程,沒有任務的時候,這些線程都處於等待狀態。若是有新任務,就分配一個空閒線程執行。若是全部線程都處於忙碌狀態,新任務要麼放入隊列等待,要麼增長一個新線程進行處理。

顯然,咱們採用第2種線程池的方法。 常見建立方法以下:

ExecutorService executorService = Executors.newFixedThreadPool(n);//指定線程數量
ExecutorService executorService = Executors.newCachedThreadPool();//動態線程池

接下來就是選擇線程池的類型了。 使用第一個固定線程數的線程池,顯然不夠靈活,第二種方式的線程池會根據任務數量動態調整線程池的大小,做爲小併發使用問題不大,但其在實際生產環境使用並不合適,若是併發量過大,經常會引起超出內存錯誤(OutOfMemoryError),根據咱們的應用場景,能夠用這個動態調整線程池。

2、使用線程池實現服務端多線程

一、單線程版本

首先,與以前的單線程通訊對比一下,下面代碼只能實現單用戶與服務端通訊,若是多用戶與服務器通訊,則出現阻塞。

    //單客戶版本,每次只能與一個用戶創建通訊鏈接
    public void Service(){
        while (true){
            Socket socket=null;
            try {
                //此處程序阻塞,監聽並等待用戶發起鏈接,有鏈接請求就生成一個套接字
                socket=serverSocket.accept();
 
                //本地服務器控制檯顯示客戶鏈接的用戶信息
                System.out.println("New connection accepted:"+socket.getInetAddress());
                BufferedReader br=getReader(socket);//字符串輸入流
                PrintWriter pw=getWriter(socket);//字符串輸出流
                pw.println("來自服務器消息:歡迎使用本服務!");
 
                String msg=null;
                //此處程序阻塞,每次從輸入流中讀入一行字符串
                while ((msg=br.readLine())!=null){
                    //若是用戶發送信息爲」bye「,就結束通訊
                    if(msg.equals("bye")){
                        pw.println("來自服務器消息:服務器斷開鏈接,結束服務!");
                        System.out.println("客戶端離開。");
                        break;
                    }
                    msg=msg.replace("?","!").replace("?","!")
                            .replace("嗎","").replace("嗎?","").replace("在","沒");
                    pw.println("來自服務器消息:"+msg);
                    pw.println("來自服務器,重複消息:"+msg);
                }
            }catch (IOException e){
                e.printStackTrace();
            }finally {
                try {
                    if (socket!=null)
                        socket.close();//關閉socket鏈接以及相關的輸入輸出流
                }catch (IOException e){
                    e.printStackTrace();
                }
            }
        }
    }

因此,根據上面的分析,將該單線程版本服務端與客戶端通訊對話的功能獨立處理,由一個線程來處理。這樣就不會阻塞主進程的執行。具體實現以下面。

二、多線程版本

一、建立匿名內部類Handler,實現Runnable類的run方法,將通訊對話放到run()裏面:

    class Handler implements Runnable {
        private Socket socket;
 
        public Handler(Socket socket) {
            this.socket = socket;
        }
 
        public void run() {
            //本地服務器控制檯顯示客戶端鏈接的用戶信息
            System.out.println("New connection accept:" + socket.getInetAddress());
            try {
                BufferedReader br = getReader(socket);
                PrintWriter pw = getWriter(socket);
 
                pw.println("From 服務器:歡迎使用服務!");
 
                String msg = null;
                while ((msg = br.readLine()) != null) {
                    if (msg.trim().equalsIgnoreCase("bye")) {
                        pw.println("From 服務器:服務器已斷開鏈接,結束服務!");
 
                        System.out.println("客戶端離開。");
                        break;
                    }
                    pw.println("From 服務器:" + msg);
                    pw.println("來自服務器,重複消息:"+msg);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (socket != null)
                        socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }

 二、使用newCachedThreadPool( )動態建立線程池

線程池做爲成員變量:

    //建立動態線程池,適合小併發量,容易出現OutOfMemoryError
    private ExecutorService executorService=Executors.newCachedThreadPool();

 服務端的Service方法中建立新線程,交給線程池處理。

    //多客戶版本,能夠同時與多用戶創建通訊鏈接
    public void Service() throws IOException {
        while (true){
            Socket socket=null;
                socket=serverSocket.accept();
                //將服務器和客戶端的通訊交給線程池處理
                Handler handler=new Handler(socket);
                executorService.execute(handler);
            }
    }

3、多用戶與服務端通訊演示

以前服務端只支持單用戶通訊對話時候,新用戶發送的信息阻塞,服務器沒法返回。

頗有趣發現一點,另一端結束通訊,與此同時,另外一端則當即收到服務器的回覆信息。

從顯示的時間上初步觀察,能夠判斷以前發送的信息是阻塞在服務端進程,斷開一方鏈接後,服務端纔將阻塞隊列的信息發送到客戶端。那使用多線程以後,結果是怎麼樣呢?

動圖演示進一步體會:

 

4、多用戶服務器完整代碼

/*
 * TCPThreadServer.java
 * Copyright (c) 2020-11-14
 * author : Charzous
 * All right reserved.
 */
 
package chapter05;
 
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
public class TCPThreadServer {
    private int port =8008;//服務器監聽窗口
    private ServerSocket serverSocket;//定義服務器套接字
    //建立動態線程池,適合小併發量,容易出現OutOfMemoryError
    private ExecutorService executorService=Executors.newCachedThreadPool();
 
    public TCPThreadServer() throws IOException{
        serverSocket =new ServerSocket(8008);
        System.out.println("服務器啓動監聽在"+port+"端口...");
 
    }
 
    private PrintWriter getWriter(Socket socket) throws IOException{
        //得到輸出流緩衝區的地址
        OutputStream socketOut=socket.getOutputStream();
        //網絡流寫出須要使用flush,這裏在printWriter構造方法直接設置爲自動flush
        return new PrintWriter(new OutputStreamWriter(socketOut,"utf-8"),true);
    }
 
    private BufferedReader getReader(Socket socket) throws IOException{
        //得到輸入流緩衝區的地址
        InputStream socketIn=socket.getInputStream();
        return new BufferedReader(new InputStreamReader(socketIn,"utf-8"));
    }
 
    //多客戶版本,能夠同時與多用戶創建通訊鏈接
    public void Service() throws IOException {
        while (true){
            Socket socket=null;
                socket=serverSocket.accept();
                //將服務器和客戶端的通訊交給線程池處理
                Handler handler=new Handler(socket);
                executorService.execute(handler);
            }
    }
 
 
    class Handler implements Runnable {
        private Socket socket;
 
        public Handler(Socket socket) {
            this.socket = socket;
        }
 
        public void run() {
            //本地服務器控制檯顯示客戶端鏈接的用戶信息
            System.out.println("New connection accept:" + socket.getInetAddress());
            try {
                BufferedReader br = getReader(socket);
                PrintWriter pw = getWriter(socket);
 
                pw.println("From 服務器:歡迎使用服務!");
 
                String msg = null;
                while ((msg = br.readLine()) != null) {
                    if (msg.trim().equalsIgnoreCase("bye")) {
                        pw.println("From 服務器:服務器已斷開鏈接,結束服務!");
 
                        System.out.println("客戶端離開。");
                        break;
                    }
 
                    pw.println("From 服務器:" + msg);
                    pw.println("來自服務器,重複消息:"+msg);
                }
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (socket != null)
                        socket.close();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
    public static void main(String[] args) throws IOException{
        new TCPThreadServer().Service();
    }
 
}
 
 

最後

本篇將解決了服務端多用戶通訊的問題,詳細記錄服務端多線程的實現,目標是多用戶(客戶端)可以同時與服務器創建鏈接並通訊,避免阻塞,進一步完善TCP的Socket網絡通訊,運用Java多線程技術,實現多用戶與服務端Socket通訊!簡而言之,至關於多用戶訪問服務器資源,服務器應該與各個客戶端創建鏈接,就像咱們平常使用QQ、微信、視頻等客戶端,就是多用戶與服務器通訊的例子。

老問題了,๑乛◡乛๑,好像完成這個以後,能夠來實現一個什麼有趣的呢?這裏停留思考3秒!

……

……

……

就是:實現一個羣組聊天房間,相似QQ、微信的羣聊,能夠多個用戶之間的對話交流,是否是感受挺有趣的。

基於本篇多線程技術實現多用戶服務器端的功能,是否可以解決羣組聊天房間的功能呢?實現這個功能,等待更新下一篇!

Java實現socket通訊網絡編程系列文章:

  1. UDP協議網絡Socket編程(java實現C/S通訊案例) 
  2. Java:基於TCP協議網絡socket編程(實現C/S通訊) 
  3. Java多線程實現TCP網絡Socket編程(C/S通訊)

若是以爲不錯歡迎「一鍵三連」哦,點贊收藏關注,有問題直接評論,交流學習!

個人博客園:https://www.cnblogs.com/chenzhenhong/p/13972517.html

個人CSDN博客:https://blog.csdn.net/Charzous/article/details/109440277


 

版權聲明:本文爲博主原創文章,遵循 CC 4.0 BY-SA 版權協議,轉載請附上原文出處連接和本聲明。
本文連接: https://blog.csdn.net/Charzous/article/details/109440277
相關文章
相關標籤/搜索