一、实验目的

学会服务器支持多用户并发访问的程序设计技术。多用户服务器是指服务器能同时支持多个用户并发访问服务器所提供的服务资源,如聊天服务、文件传输等。TCPServer是单用户版本,每次只能和一个用户对话,原因是第一个线程进入while循环,一直等待发来的消息,只有退出循环后才能执行新的线程。只有前一个用户退出后,后面的用户才能完成服务器连接。

二、修改运行配置

我的idea是2021.3.2,需要修改成允许多实例运行

三、多用户服务器程序设计

存在的问题:单用户版本的TCPServer.java程序不能同时服务多用户对话

1、线程池解决多用户对话问题

服务器可能面临很多客户的并发连接,因此,主线程负责监听客户请求和接受连接请求,用一个线程专门负责和一个客户对话。当一个客户请求成功后,创建一个新线程来专门负责该客户。对于服务器,一般是使用线程池来管理和复用线程。线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态。如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。

2、在服务程序中支持群组聊天技术

本实验采用简单的“在线方式”记录客户套接字,即采用集合来保存用户登录的套接字信息,用于跟踪客户连接。因为每一个客户端的IP地址+端口组合不一样,用户套接字socket作为key来标识一个在线用户是比较方便的选择(可以结合泛型,将集合可存储的类型限制为Socket类型)。

四、实验过程

1、TCPClient.java

和之前代码一样

package chapter05.client;import java.io.*;
import java.net.Socket;public class TCPClient {private Socket socket; //定义套接字//定义字符输入流和输出流private PrintWriter pw;private BufferedReader br;public TCPClient(String ip, String port) throws IOException {//主动向服务器发起连接,实现TCP的三次握手过程//如果不成功,则抛出错误信息,其错误信息交由调用者处理socket = new Socket(ip, Integer.parseInt(port));//得到网络输出字节流地址,并封装成网络输出字符流OutputStream socketOut = socket.getOutputStream();pw = new PrintWriter( // 设置最后一个参数为true,表示自动flush数据new OutputStreamWriter(//设置utf-8编码socketOut, "utf-8"), true);//得到网络输入字节流地址,并封装成网络输入字符流InputStream socketIn = socket.getInputStream();br = new BufferedReader(new InputStreamReader(socketIn, "utf-8"));}public void send(String msg) {//输出字符流,由Socket调用系统底层函数,经网卡发送字节流pw.println(msg);}public String receive() {String msg = null;try {//从网络输入字符流中读信息,每次只能接受一行信息//如果不够一行(无行结束符),则该语句阻塞,// 直到条件满足,程序才往下运行msg = br.readLine();} catch (IOException e) {e.printStackTrace();}return msg;}public void close() {try {if (socket != null) {//关闭socket连接及相关的输入输出流,实现四次握手断开socket.close();}} catch (IOException e) {e.printStackTrace();}}//本机模块内测试与运行,需先运行TCPServerpublic static void main(String[] args) throws IOException {TCPClient tcpClient = new TCPClient("127.0.0.1", "8008");tcpClient.send("hello");//发送一串字符//接收服务器返回的字符串并显示System.out.println(tcpClient.receive());}
}

2、TCPClientThreadFX.java

注意需要修改IP地址为:202.116.195.71
端口号为:8008

package chapter05.client;import javafx.application.Application;
import javafx.application.Platform;
import javafx.event.EventHandler;
import javafx.geometry.Insets;
import javafx.geometry.Pos;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.Label;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.input.KeyCode;
import javafx.scene.input.KeyEvent;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.HBox;
import javafx.scene.layout.Priority;
import javafx.scene.layout.VBox;
import javafx.stage.Stage;public class TCPClientThreadFX extends Application {private Button btnExit = new Button("退出");private Button btnSend = new Button("发送");private TextField tfSend = new TextField();private TextArea taDisplay = new TextArea();private TextField tfIP = new TextField("202.116.195.71");private TextField tfPort = new TextField("8008");private Button btnConnect = new Button("连接");private TCPClient tcpClient;private Thread readThread;public static void main(String[] args) {launch(args);}@Overridepublic void start(Stage primaryStage) {BorderPane mainPane = new BorderPane();HBox connHbox = new HBox();connHbox.setAlignment(Pos.CENTER);connHbox.setSpacing(10);connHbox.getChildren().addAll(new Label("IP地址:"), tfIP, new Label("端口:"), tfPort, btnConnect);mainPane.setTop(connHbox);VBox vBox = new VBox();vBox.setSpacing(10);vBox.setPadding(new Insets(10, 20, 10, 20));// 设置发送信息的文本框// 自动换行taDisplay.setWrapText(true);// 只读taDisplay.setEditable(false);vBox.getChildren().addAll(new Label("信息显示区: "), taDisplay, new Label("信息输入区:"), tfSend);VBox.setVgrow(taDisplay, Priority.ALWAYS);mainPane.setCenter(vBox);HBox hBox = new HBox();hBox.setSpacing(10);hBox.setPadding(new Insets(10, 20, 10, 20));hBox.setAlignment(Pos.CENTER_RIGHT);// 按钮事件绑定btnConnect.setOnAction(event -> {String ip = tfIP.getText().trim();String port = tfPort.getText().trim();try {//tcpClient不是局部变量,是本程序定义的一个TCPClient类型的成员变量tcpClient = new TCPClient(ip, port);//成功连接服务器,接收服务器发来的第一条欢迎信息String firstMsg = tcpClient.receive();taDisplay.appendText(firstMsg + "\n");// 启用发送按钮btnSend.setDisable(false);// 停用连接按钮btnConnect.setDisable(true);// 启用接收信息进程readThread = new Thread(() -> {String msg = null;// 新增线程是否中断条件 解决退出时出现异常问题while ((msg = tcpClient.receive()) != null) {String msgTemp = msg;Platform.runLater(() -> {taDisplay.appendText(msgTemp + "\n");});}Platform.runLater(() -> {taDisplay.appendText("对话已关闭!\n");// 连接断开后重新开放连接按钮btnSend.setDisable(true);btnConnect.setDisable(false);});});readThread.start();} catch (Exception e) {taDisplay.appendText("服务器连接失败!" + e.getMessage() + "\n");}});btnExit.setOnAction(event -> {exit();});btnSend.setOnAction(event -> {String sendMsg = tfSend.getText();tcpClient.send(sendMsg);//向服务器发送一串字符taDisplay.appendText("客户端发送:" + sendMsg + "\n");tfSend.clear();// 发送bye后重新启用连接按钮,禁用发送按钮if (sendMsg.equals("bye")) {btnConnect.setDisable(false);btnSend.setDisable(true);}});// 未连接时禁用发送按钮btnSend.setDisable(true);hBox.getChildren().addAll(btnSend, btnExit);mainPane.setBottom(hBox);Scene scene = new Scene(mainPane, 700, 400);// 回车响应功能scene.addEventFilter(KeyEvent.KEY_RELEASED, new EventHandler<KeyEvent>() {@Overridepublic void handle(KeyEvent event) {if (event.getCode() == KeyCode.ENTER) {sendText();}}});// 响应窗体关闭primaryStage.setOnCloseRequest(event -> {exit();});primaryStage.setScene(scene);primaryStage.show();}public void exit() {if (tcpClient != null) {tcpClient.send("bye");tcpClient.close();}// 系统退出时,单独的读线程没有结束,因此会出现异常。// 解决方案:在这里通知线程中断,在线程循环中增加条件检测当前线程是否被中断。// p.s. 此处使用的thread.stop()为deprecated的函数,应使用interrupt,正确写法见chapter03/TCPClientThreadFXreadThread.stop();System.exit(0);}public void sendText() {String sendMsg = tfSend.getText();tcpClient.send(sendMsg);//向服务器发送一串字符taDisplay.appendText("客户端发送:" + sendMsg + "\n");tfSend.clear();// 发送bye后重新启用连接按钮,禁用发送按钮if (sendMsg.equals("bye")) {btnConnect.setDisable(false);btnSend.setDisable(true);}}
}

3、TCPThreadServer.java

在TCPThreadServer类中定义内部类Handler implements Runnable

Handler内部类

class Handler implements Runnable {private Socket socket;public Handler(Socket socket) {this.socket = socket;}@Overridepublic void run() {//本地服务器控制台显示客户端连接的用户信息System.out.println("New connection accepted: " + socket.getInetAddress());try {BufferedReader br = getReader(socket);//定义字符串输入流PrintWriter pw = getWriter(socket);//定义字符串输出流//客户端正常连接成功,则发送服务器欢迎信息,然后等待客户发送信息pw.println("From 服务器:欢迎使用本服务!");String msg = null;//此处程序阻塞,每次从输入流中读入一行字符串while ((msg = br.readLine()) != null) {//如果客户发送的消息为"bye",就结束通信if (msg.trim().equalsIgnoreCase("bye")) {//向输出流中输出一行字符串,远程客户端可以读取该字符串pw.println("From 服务器:服务器已断开连接,结束服务!");System.out.println("客户端离开");break;//跳出循环读取}if (msg.trim().equalsIgnoreCase("来自教师服务器的连接")){pw.println("1");}else if(msg.trim().equalsIgnoreCase("教师服务器再次发送信息")) {pw.println("2");}//向输出流中回传字符串,远程客户端可以读取该字符串pw.println("From 服务器:" + msg);}} catch (IOException e) {e.printStackTrace();} finally {try {if (socket != null) {socket.close(); //关闭socket连接及相关的输入输出流}} catch (IOException e) {e.printStackTrace();}}}}

与【互联网程序设计】网络对话程序设计结合即完整代码

4、GroupServer.java

在GroupServer类中添加核心的群组发送方法sendToAllMembers,给所有在线客服转发信息

private void sendToAllMembers(String msg, String hostAddress) throws IOException {PrintWriter pw;OutputStream out;for (Socket tempSocket : members) {out = tempSocket.getOutputStream();pw = new PrintWriter(new OutputStreamWriter(out, "utf-8"), true);pw.println(hostAddress + " 发言:" + msg);}![请添加图片描述](https://img-blog.csdnimg.cn/d9799bd46a394248a7dd0c6da182f529.png)}

5、运行结果

上课来不及截图,这个是样例的图片

五、总结

  1. 在连接老师服务器前,需要开启自己的服务器。我的客户端连接老师服务器后,老师服务器会向我的服务器发送消息,我的服务器会给予老师服务器反馈。
 if (msg.trim().equalsIgnoreCase("来自教师服务器的连接")){pw.println("1");}else if(msg.trim().equalsIgnoreCase("教师服务器再次发送信息")) {pw.println("2");}
  1. 为什么要使用多用户并发访问?
    单用户的程序中,每一次只能与一个客户建立通信连接,主线程在while中一直运行,无法进行多用户访问。
    以下代码为单用户程序:
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("From 服务器:欢迎使用本服务!");String msg = null;//此处程序阻塞,每次从输入流中读入一行字符串while ((msg = br.readLine()) != null) {//如果客户发送的消息为"bye" 结束通信if (msg.equals("bye")) {//向输出流中输出一行字符串,远程客户端可以读取该字符串pw.println("From服务器:服务器断开连接,结束服务!");System.out.println("客户端离开");break; //结束循环}//向输出流中输出一行字符串,远程客户端可以读取该字符串// 正则表达式实现“人工智能”(扩展练习)msg = msg.replaceAll("[吗?]", "") + "!";pw.println("From服务器:" + msg);}

当使用多用户服务器程序设计后,主程序负责接收消息,开启副线程,交给副线程处理。

多用户服务器程序设计(详解)相关推荐

  1. nc 模拟服务器_NC集群服务器使用详解

    NC 集群服务器使用详解 NC220 发版时,对中间件做了一项重大改进,对集群服务器应用提供了全面解决方案 的支持, 使产品更加能够适应集团企业的大规模应用. 本文力争以通俗的语言, 描述集群服 务器 ...

  2. hp服务器修改bios设置,HP服务器BIOS详解.pdf

    HP服务器BIOS详解 HPP ProoLiantt DL3380 G77 服务务器 -- BIOOS 选选项与功能详详解 问题 HP ProLiant DL380 G7 服务器 BIIOS 选项与与 ...

  3. Solr系列二:solr-部署详解(solr两种部署模式介绍、独立服务器模式详解、SolrCloud分布式集群模式详解)...

    一.solr两种部署模式介绍 Standalone Server 独立服务器模式:适用于数据规模不大的场景 SolrCloud  分布式集群模式:适用于数据规模大,高可靠.高可用.高并发的场景 二.独 ...

  4. 虚拟机2012搭建DNS服务器,Windows Server2012 安装配置DNS服务器方法详解

    Windows Server2012 安装配置DNS服务器方法详解 在云服务器 Windows Server2012 上安装配置DNS服务器方法,安装与配置非常简单,在这里写个完整教程方便大家查询 一 ...

  5. 什么是云服务器ECS?云服务器ECS详解

    什么是云服务器ECS?云服务器ECS详解 一.前言 二.云服务器ECS详解 为什么选择云服务器ECS? 产品架构 产品定价 管理工具 部署建议 相关服务 三.写在后面的话 叮嘟!这里是小啊呜的学习课程 ...

  6. 我的世界手机版服务器文件,《我的世界手机版》服务器配置文件详解教程攻略...

    原标题:<我的世界手机版>服务器配置文件详解教程攻略 我的世界手机版中,我们开设了服务器之后需要如何设置服务器的各种基础配置呢?今天当乐网小编给大家收集了一篇关于我的世界手机版的服务器po ...

  7. DHCP原理及服务器搭建详解(固定IP地址,DHCP中继服务)

    DHCP原理及服务器搭建详解 一.引子: DHCP在网络中的作用非常重要,简单来说就像给每台服务器配身份证的机构,你有合格的身份才能做合格的事情,要不然连火车都坐不了.服务器只有被DHCP服务配置了I ...

  8. 阴阳师最新的服务器,阴阳师跨区服务器大全 集结之境新增跨区服务器汇总详解...

    阴阳师跨区服务器有哪些,阴阳师集结之境新增服务器哪些可以玩呢?下面是小编为大家带来的阴阳师跨区服务器大全,集结之境新增跨区服务器汇总详解,希望能帮助到大家! 亲爱的阴阳师大人: 为了让更多的大人们能够 ...

  9. 【干货】NTP时间同步服务器技术详解

    [干货]NTP时间同步服务器技术详解 [干货]NTP时间同步服务器技术详解 A.1 时间同步原理 时间同步的原理就是按照接收到的时间来调控设备内部的时钟和时刻.在将时刻校对到 秒后,时间同步的调控原理 ...

最新文章

  1. php 贝瑟尔曲线,贝塞尔曲线的应用详解
  2. RocketMQ 4.5.1 双主双从异步复制环境搭建
  3. FTP协议、电子邮件系统、Telnet远程控制
  4. 内核线程和用户线程(SMP)
  5. live555学习笔记-RTP打包与发送
  6. Hadoop安装目录
  7. 钉钉webhook小笔记
  8. 利用user-agent取得浏览器版本号
  9. qq空间进入游戏显示服务器拒绝,打开QQ空间出现ptlogin2.qq.com的解决方法大全
  10. 脱机使用打印机是什么意思?
  11. 小米公司开源 MIUI 6 第三方适配工具 『Patchrom』
  12. .NET爬虫获取拼多多商品价格
  13. java 设计模式之设计原则篇
  14. 沃尔玛账号被冻结后如何进行申诉?
  15. 软考答题卡的填涂注意事项?须知
  16. 云呐|如何对酒店固定资产进行日常管理
  17. lect01_codes02_numpy
  18. 杭电OJ第11页2075~2079算法题(C语言)
  19. 【bzoj 1340】 Escape逃跑问题 【Baltic2007】
  20. 【蓝桥杯Python组】既约分数

热门文章

  1. matlab产生er随机图,ER随机图模型 | 集智百科
  2. vue+electron 跨平台桌面应用开发实战教程
  3. 用 Python Turtle画图学习(紫色樱花树) 附源代码
  4. 工具资源 - 收藏集 - 掘金
  5. 如何获取iphone的硬件版本以及系统信息
  6. 证监会回应易会满召开记者招待会传闻:纯属谣言
  7. 27 岁的人生到底有多无力?
  8. Linux开发十五_lcd驱动
  9. 《Adobe After Effects CS4经典教程》——1.10 控制用户界面的亮度
  10. 网络编程3:反应堆与百万链接