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
相关文章
相关标签/搜索