java线程多用户_Java多线程技术:实现多用户服务端Socket通信

本文介绍了如何使用Java多线程技术实现多用户服务器,解决服务器与多个客户端并发通信的问题。通过创建线程池处理客户端请求,避免阻塞,确保多用户可以同时与服务器建立连接并通信。详细展示了从单线程版本到多线程版本的实现过程,并给出了完整的服务器端代码示例。
摘要由CSDN通过智能技术生成

目录

前言回顾

在上一篇《Java多线程实现TCP网络Socket编程(C/S通信)》,我们解决了服务器端在建立连接后,连续发送多条信息给客户端接收的问题,解决办法容易理解,将客户端接收信息的功能集中给线程处理,实现多线程同步进行。

同理,上一篇结束语留下来一个问题,简而言之,相当于多用户访问服务器资源,服务器应该与各个客户端建立连接,并进行通信对话,就像我们日常使用QQ、微信、视频等客户端,就是多用户与服务器通信的例子。

而上一篇中服务端只实现了单用户的功能,本篇将解决这个问题,详细记录服务端多线程的实现,目标是多用户(客户端)能够同时与服务器建立连接并通信,避免阻塞,进一步完善TCP的Socket网络通信,运用Java多线程技术,实现多用户与服务端Socket通信!

一、多用户服务器

多用户服务器是指服务器能同时支持多个用户并发访问服务器所提供的服务资源,如聊天服务、文件传输等。

上一篇的TCPServer是单用户版本,每次只能和一个用户对话。我们可以尝试多用户连接,开启多个客户端,具体操作如下:

8d254636c16de7afe1f18f02731360ac.png

237becb433392621a926372db8458e41.png

这样就允许同时并行执行多个客户端,测试发现,单用户版本的TCPServer.java程序能同时支持多个用户并发连接(TCP三次握手),但不能同时服务多用户对话,只有前一个用户退出后,后面的用户才能完成服务器连接。

多线程技术,线程调用的并行执行。

f96136301f7926c48b24894931657c6c.png

上一篇提到在java中有两种实现多线程的方法,一是使用Thread类,二是使用Runnable类并实现run()方法。下面将使用Runnable类对服务端相关操作功能进行封装,结合上一篇,就学到了两种多线程实现方法。

//使用Runnable类,作为匿名内部类

class Handler implementsRunnable {public voidrun() {//实现run方法

}

}

服务器面临很多客户的并发连接,这种情况的多线程方案一般是:

主线程只负责监听客户请求和接受连接请求,用一个线程专门负责和一个客户对话,即一个客户请求成功后,创建一个新线程来专门负责该客户。对于这种方案,可以用上一篇方式new Thread创建线程,但是频繁创建线程需要消耗大量系统资源。所以不采用这种方法。

对于服务器,一般使用线程池来管理和复用线程。线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态。如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。

显然,我们采用第2种线程池的方法。 常见创建方法如下:

ExecutorService executorService = Executors.newFixedThreadPool(n);//指定线程数量

ExecutorService executorService = Executors.newCachedThreadPool();//动态线程池

接下来就是选择线程池的类型了。 使用第一个固定线程数的线程池,显然不够灵活,第二种方式的线程池会根据任务数量动态调整线程池的大小,作为小并发使用问题不大,但其在实际生产环境使用并不合适,如果并发量过大,常常会引发超出内存错误(OutOfMemoryError),根据我们的应用场景,可以用这个动态调整线程池。

二、使用线程池实现服务端多线程

1、单线程版本

首先,与之前的单线程通信对比一下,下面代码只能实现单用户与服务端通信,如果多用户与服务器通信,则出现阻塞。

//单客户版本,每次只能与一个用户建立通信连接

public voidService(){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 implementsRunnable {privateSocket socket;publicHandler(Socket socket) {this.socket =socket;

}public voidrun() {//本地服务器控制台显示客户端连接的用户信息

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() throwsIOException {while (true){

Socket socket=null;

socket=serverSocket.accept();//将服务器和客户端的通信交给线程池处理

Handler handler=newHandler(socket);

executorService.execute(handler);

}

}

三、多用户与服务端通信演示

之前服务端只支持单用户通信对话时候,新用户发送的信息阻塞,服务器无法返回。

450d4b3f555c8524e5f7da4c7add8971.png

很有趣发现一点,另外一端结束通信,与此同时,另一端则立即收到服务器的回复信息。

28dcd9359690bd10dde57aa33ec04746.png

从显示的时间上初步观察,可以判断之前发送的信息是阻塞在服务端进程,断开一方连接后,服务端才将阻塞队列的信息发送到客户端。那使用多线程之后,结果是怎么样呢?

473d13823d639591c83aacc160d9e95a.png

动图演示进一步体会:

7140e3049c22cad93cb76180a98da933.gif

四、多用户服务器完整代码

/** TCPThreadServer.java

* Copyright (c) 2020-11-14

* author : Charzous

* All right reserved.*/

packagechapter05;import java.io.*;importjava.net.ServerSocket;importjava.net.Socket;importjava.util.concurrent.ExecutorService;importjava.util.concurrent.Executors;public classTCPThreadServer {private int port =8008;//服务器监听窗口

private ServerSocket serverSocket;//定义服务器套接字//创建动态线程池,适合小并发量,容易出现OutOfMemoryError

private ExecutorService executorService=Executors.newCachedThreadPool();public TCPThreadServer() throwsIOException{

serverSocket=new ServerSocket(8008);

System.out.println("服务器启动监听在"+port+"端口...");

}private PrintWriter getWriter(Socket socket) throwsIOException{//获得输出流缓冲区的地址

OutputStream socketOut=socket.getOutputStream();//网络流写出需要使用flush,这里在printWriter构造方法直接设置为自动flush

return new PrintWriter(new OutputStreamWriter(socketOut,"utf-8"),true);

}private BufferedReader getReader(Socket socket) throwsIOException{//获得输入流缓冲区的地址

InputStream socketIn=socket.getInputStream();return new BufferedReader(new InputStreamReader(socketIn,"utf-8"));

}//多客户版本,可以同时与多用户建立通信连接

public void Service() throwsIOException {while (true){

Socket socket=null;

socket=serverSocket.accept();//将服务器和客户端的通信交给线程池处理

Handler handler=newHandler(socket);

executorService.execute(handler);

}

}class Handler implementsRunnable {privateSocket socket;publicHandler(Socket socket) {this.socket =socket;

}public voidrun() {//本地服务器控制台显示客户端连接的用户信息

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) throwsIOException{newTCPThreadServer().Service();

}

}

最后

本篇将解决了服务端多用户通信的问题,详细记录服务端多线程的实现,目标是多用户(客户端)能够同时与服务器建立连接并通信,避免阻塞,进一步完善TCP的Socket网络通信,运用Java多线程技术,实现多用户与服务端Socket通信!简而言之,相当于多用户访问服务器资源,服务器应该与各个客户端建立连接,就像我们日常使用QQ、微信、视频等客户端,就是多用户与服务器通信的例子。

老问题了,๑乛◡乛๑,好像完成这个之后,可以来实现一个什么有趣的呢?这里停留思考3秒!

……

……

……

就是:实现一个群组聊天房间,类似QQ、微信的群聊,可以多个用户之间的对话交流,是不是感觉挺有趣的。

基于本篇多线程技术实现多用户服务器端的功能,是否能够解决群组聊天房间的功能呢?实现这个功能,等待更新下一篇!

如果觉得不错欢迎“一键三连”哦,点赞收藏关注,有问题直接评论,交流学习!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值