目錄
前言回顧
一、多用戶服務器
二、使用執行緒池實作服務端多執行緒
1、單執行緒版本
2、多執行緒版本
三、多用戶與服務端通信演示
四、多用戶服務器完整代碼
最后
前言回顧
在上一篇《Java多執行緒實作TCP網路Socket編程(C/S通信)》,我們解決了服務器端在建立連接后,連續發送多條資訊給客戶端接收的問題,解決辦法容易理解,將客戶端接收資訊的功能集中給執行緒處理,實作多執行緒同步進行,
同理,上一篇結束語留下來一個問題,簡而言之,相當于多用戶訪問服務器資源,服務器應該與各個客戶端建立連接,并進行通信對話,就像我們日常使用QQ、微信、視頻等客戶端,就是多用戶與服務器通信的例子,
而上一篇中服務端只實作了單用戶的功能,本篇將解決這個問題,詳細記錄服務端多執行緒的實作,目標是多用戶(客戶端)能夠同時與服務器建立連接并通信,避免阻塞,進一步完善TCP的Socket網路通信,運用Java多執行緒技術,實作多用戶與服務端Socket通信!
Java實作socket通信網路編程系列文章:
- 基于UDP協議網路Socket編程(java實作C/S通信案例)【https://blog.csdn.net/Charzous/article/details/109016215】
- 基于TCP協議網路socket編程(java實作C/S通信)【https://blog.csdn.net/Charzous/article/details/109016215】
- Java多執行緒實作TCP網路Socket編程(C/S通信)【https://blog.csdn.net/Charzous/article/details/109283697】
一、多用戶服務器
多用戶服務器是指服務器能同時支持多個用戶并發訪問服務器所提供的服務資源,如聊天服務、檔案傳輸等,
上一篇的TCPServer是單用戶版本,每次只能和一個用戶對話,我們可以嘗試多用戶連接,開啟多個客戶端,具體操作如下:


這樣就允許同時并行執行多個客戶端,測驗發現,單用戶版本的TCPServer.java程式能同時支持多個用戶并發連接(TCP三次握手),但不能同時服務多用戶對話,只有前一個用戶退出后,后面的用戶才能完成服務器連接,
多執行緒技術,執行緒呼叫的并行執行,

上一篇提到在java中有兩種實作多執行緒的方法,一是使用Thread類,二是使用Runnable類并實作run()方法,下面將使用Runnable類對服務端相關操作功能進行封裝,結合上一篇,就學到了兩種多執行緒實作方法,
//使用Runnable類,作為匿名內部類
class Handler implements Runnable {
public void run() {
//實作run方法
}
}
服務器面臨很多客戶的并發連接,這種情況的多執行緒方案一般是:
- 主執行緒只負責監聽客戶請求和接受連接請求,用一個執行緒專門負責和一個客戶對話,即一個客戶請求成功后,創建一個新執行緒來專門負責該客戶,對于這種方案,可以用上一篇方式new Thread創建執行緒,但是頻繁創建執行緒需要消耗大量系統資源,所以不采用這種方法,
- 對于服務器,一般使用執行緒池來管理和復用執行緒,執行緒池內部維護了若干個執行緒,沒有任務的時候,這些執行緒都處于等待狀態,如果有新任務,就分配一個空閑執行緒執行,如果所有執行緒都處于忙碌狀態,新任務要么放入佇列等待,要么增加一個新執行緒進行處理,
顯然,我們采用第2種執行緒池的方法, 常見創建方法如下:
ExecutorService executorService = Executors.newFixedThreadPool(n);//指定執行緒數量
ExecutorService executorService = Executors.newCachedThreadPool();//動態執行緒池
接下來就是選擇執行緒池的型別了, 使用第一個固定執行緒數的執行緒池,顯然不夠靈活,第二種方式的執行緒池會根據任務數量動態調整執行緒池的大小,作為小并發使用問題不大,但其在實際生產環境使用并不合適,如果并發量過大,常常會引發超出記憶體錯誤(OutOfMemoryError),根據我們的應用場景,可以用這個動態調整執行緒池,
二、使用執行緒池實作服務端多執行緒
1、單執行緒版本
首先,與之前的單執行緒通信對比一下,下面代碼只能實作單用戶與服務端通信,如果多用戶與服務器通信,則出現阻塞,
//單客戶版本,每次只能與一個用戶建立通信連接
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();
}
}
}
}
所以,根據上面的分析,將該單執行緒版本服務端與客戶端通信對話的功能獨立處理,由一個執行緒來處理,這樣就不會阻塞主行程的執行,具體實作如下面,
2、多執行緒版本
1、創建匿名內部類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();
}
}
}
}
2、使用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);
}
}
三、多用戶與服務端通信演示
之前服務端只支持單用戶通信對話時候,新用戶發送的資訊阻塞,服務器無法回傳,

很有趣發現一點,另外一端結束通信,與此同時,另一端則立即收到服務器的回復資訊,

從顯示的時間上初步觀察,可以判斷之前發送的資訊是阻塞在服務端行程,斷開一方連接后,服務端才將阻塞佇列的資訊發送到客戶端,那使用多執行緒之后,結果是怎么樣呢?

動圖演示進一步體會:

四、多用戶服務器完整代碼
/*
* TCPThreadServer.java
* Copyright (c) 2020-11-02
* 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通信網路編程系列文章:
- 基于UDP協議網路Socket編程(java實作C/S通信案例)【https://blog.csdn.net/Charzous/article/details/109016215】
- 基于TCP協議網路socket編程(java實作C/S通信)【https://blog.csdn.net/Charzous/article/details/109016215】
- Java多執行緒實作TCP網路Socket編程(C/S通信)【https://blog.csdn.net/Charzous/article/details/109283697】
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/109440277
轉載請註明出處,本文鏈接:https://www.uj5u.com/qita/200930.html
標籤:其他
上一篇:ArcGIS Server 10.0,10.1,10.2,10.3,10.4,10.5,10.7.ecp全套下載
