Java多线程技术:实现多用户服务端Socket通信
目录
前言回顾
在上一篇《Java多线程实现TCP网络Socket编程(C/S通信)》,我们解决了服务器端在建立连接后,连续发送多条信息给客户端接收的问题,解决办法容易理解,将客户端接收信息的功能集中给线程处理,实现多线程同步进行。
同理,上一篇结束语留下来一个问题,简而言之,相当于多用户访问服务器资源,服务器应该与各个客户端建立连接,并进行通信对话,就像我们日常使用QQ、微信、视频等客户端,就是多用户与服务器通信的例子。
而上一篇中服务端只实现了单用户的功能,本篇将解决这个问题,详细记录服务端多线程的实现,目标是多用户(客户端)能够同时与服务器建立连接并通信,避免阻塞,进一步完善TCP的Socket网络通信,运用Java多线程技术,实现多用户与服务端Socket通信!
Java实现socket通信网络编程系列文章:
一、多用户服务器
多用户服务器是指服务器能同时支持多个用户并发访问服务器所提供的服务资源,如聊天服务、文件传输等。
上一篇的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-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通信网络编程系列文章:
如果觉得不错欢迎“一键三连”哦,点赞收藏关注,有问题直接评论,交流学习!
我的博客园:https://www.cnblogs.com/chenzhenhong/p/13972517.html
我的CSDN博客:https://blog.csdn.net/Charzous/article/details/109440277
Java多线程技术:实现多用户服务端Socket通信的更多相关文章
- 客户端技术:Cookie 服务端技术:HttpSession
客户端技术:Cookie 服务端技术:HttpSession 07. 五 / android基础 / 没有评论 一.会话技术1.什么是会话:客户打开浏览器访问一个网站,访问完毕之后,关闭浏览器.这 ...
- 赶紧收藏!王者级别的Java多线程技术笔记,我java小菜鸡愿奉你为地表最强!
Java多线程技术概述 介绍多线程之前要介绍线程,介绍线程则离不开进程. 首先 , 进程 :是一个正在执行中的程序,每一个进程执行都有一个执行顺序,该顺序是一个执行路径,或者叫一个控制单元: 线程:就 ...
- socket模块实现基于UDP聊天模拟程序;socketserver模块实现服务端 socket客户端代码示例
socket模块 serSocket.setblocking(False) 设置为非阻塞: #coding=utf-8 from socket import * import time # 用来存储所 ...
- 用PHP的socket实现客户端到服务端的通信
服务端 <?php error_reporting(E_ALL); set_time_limit(0); ob_implicit_flush(); //本地IP $address = 'loca ...
- Java多线程技术学习笔记(二)
目录: 线程间的通信示例 等待唤醒机制 等待唤醒机制的优化 线程间通信经典问题:多生产者多消费者问题 多生产多消费问题的解决 JDK1.5之后的新加锁方式 多生产多消费问题的新解决办法 sleep和w ...
- tcp服务端socket
import socket if __name__ == '__main__': # 创建tcp服务端socket tcp_server_socket = socket.socket(socket.A ...
- linux网络编程之用socket实现简单客户端和服务端的通信(基于UDP)
单客户端和服务端的通信(基于UDP) 代码 服务端代码socket3.c #include<sys/types.h> #include<sys/socket.h> #inc ...
- Java 实现Redis客户端,服务端
Java 实现Redis客户端,服务端 1.Java实现Redis发布订阅 1.1实例 2.[Redis]Java实现redis消息订阅/发布(PubSub) 3.java实现 redis的发布订阅 ...
- 自己用java实现飞鸽传书 1 - 实现socket通信
第一步: 建立服务端客户端,实现端到端通信.因为要传递文件,信号量较大,故使用TCP/IP协议. 服务端和客户端都要建立socket,而后通过socket进行通信.目前只实现服务端到客户端的单向通信. ...
随机推荐
- 创建好maven项目以后发现无法创建scala文件
今天创建了一个maven项目 然后准备创建scala文件的时候发现没有Scala 然后只好上网上找方法了 下面是一种解决方法 1.点击file,选择settings 进去之后,选择build 进去 ...
- 《流畅的Python》第二部分 数据结构 【序列构成的数组】【字典和集合】【文本和字节序列】
第二部分 数据结构 第2章 序列构成的数组 内置序列类型 序列类型 序列 特点 容器序列 list.tuple.collections.deque - 能存放不同类型的数据:- 存放的是任意类型的对象 ...
- day44 Pyhton 数据库Mysql
内容回顾 什么是进程? 就是为了形容执行中的程序的一种称呼 它是操作系统中资源分配的最小单位 进程之间是数据隔离的,占用操作系统资源相对多 独立存在的 谈谈你对并发的理解 同时有多个任务需要执行,但是 ...
- 五分钟详解MySQL并发控制及事务原理
在如今互联网业务中使用范围最广的数据库无疑还是关系型数据库MySQL,之所以用"还是"这个词,是因为最近几年国内数据库领域也取得了一些长足进步,例如以TIDB.OceanBase等 ...
- js函数工具总结
小写字母转大写 var UP = function(s){ return s.toUpperCase().split(' ').join('_') } UP('Hedge number') 将格式为 ...
- MVC登录
前言 最近没什么好写的,准备把MVC的登录再写一下,巩固一下 HTML @{ Layout = null; } <!DOCTYPE html> <html> <head& ...
- Python 从入门到精通:一个月就够了
毫无疑问,Python 是当下最火的编程语言之一.对于许多未曾涉足计算机编程的领域「小白」来说,深入地掌握 Python 看似是一件十分困难的事.其实,只要掌握了科学的学习方法并制定了合理的学习计划, ...
- Hive LLAP
body { margin: 0 auto; font: 13px / 1 Helvetica, Arial, sans-serif; color: rgba(68, 68, 68, 1); padd ...
- 基于ssm的客户管理系统
查看更多系统:系统大全,课程设计.毕业设计,请点击这里查看 01 概述 一个简单的客户关系管理系统 管理用户的基本数据 客户的分配 客户的流失 已经客户的状态 02 技术 ssm + jdk1.8 + ...
- 国云数据:中国版的Snowflake,国内数据中台领导者
[股神巴菲特加持,今年最受关注美股IPO ] 这段时间, 由股神巴菲特54年来首次打新的美股IPO公司Snowflake迅速得到业界重点关注.Snowflake已于2020年9月16日正式上市,发行价 ...