第5讲 多用户服务器程序设计
**教学与实践目的:**学会服务器支持多用户并发访问的程序设计技术。
多用户服务器是指服务器能同时支持多个用户并发访问服务器所提供的服务资源,如聊天服务、文件传输等。
第二讲的TCPServer是单用户版本,每次只能和一个用户对话。(请仔细阅读TCPServer.java程序,了解其中原理,找出关键语句),只有前一个用户退出后,后面的用户才能完成服务器连接。
一、单用户服务器程序演示
单用户版本TCPServer.java部分代码:
public class TCPServer {
private int port = 8008; *//服务器监听端口*
private ServerSocket serverSocket; *//定义服务器套接字*
public TCPServer() throws IOException {
serverSocket = new ServerSocket(port);
System.*out*.println("服务器启动监听在 " + port + " 端口");
}
*//省略封装的返回输入输出流的相关代码……*
*//单客户版本,主服务方法,每一次只能与一个客户建立通信连接*
public void Service() {
while (true) {
Socket socket = null;
try {
*//此处程序阻塞,监听并等待客户发起连接,有连接请求就生成一个套接字。*
socket = serverSocket.accept();
*//本地服务器控制台显示客户端连接的用户信息*
System.*out*.println("New connection accepted: " + socket.getInetAddress());
*//省略输入输出流获取的代码,以下pw是字符输出流,br是字符输入流*
pw.println("From 服务器:欢迎使用本服务!");
String msg = null;
*//此处程序阻塞,每次从输入流中读入一行字符串*
**while ((msg = br.readLine()) != null) {
**
**//如果客户发送的消息为"bye",就结束通信**
**if (msg.equals("bye")) {
**
**//向输出流中输出一行字符串,远程客户端可以读取该字符串**
**pw.println("From服务器:服务器断开连接,结束服务!");**
**System.out.println("客户端离开");**
**break; //结束循环,结束通信**
**}**
**//向输出流中输出一行字符串,远程客户端可以读取该字符串**
**pw.println("From服务器:" + msg);**
**}**
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if(socket != null)
socket.close(); *//关闭socket连接及相关的输入输出流*
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public static void main(String[] args) throws IOException{
new TCPServer().Service();
}
}
27-37行是其中的核心代码,用于提供和客户端的对话功能。
演示过程:
(1)启动你的TCPServer.java程序(程序详见第二讲的附录代码);
(2)启动第三讲的TCPClientThreadFX.java程序,完成一次对话,并保持连接;
(3)再启动一份TCPClientThreadFX.java程序实例(即同时运行两个相同的程序),并完成连接,尝试对话,发现没有服务器的返回信息(服务器和客户端各阻塞在哪条语句?)
**注意:**现在的新版本idea默认不允许同时运行多个相同的程序,需要修改默认配置才能多实例运行:
首先进入右上角的edit Configurations,如图5.1所示:
图5.1 修改运行配置
然后如图5.2所示,将此程序的“Allow paralle run”复选框勾选。
图5.2 改为允许多实例运行
(4)退出第一次启动的TCPClientThreadFX.java程序,发现第二次启动的客户端有返回信息了,说明在一个客户退出后,第二个客户才能和服务器进行对话。
**原因:**服务器的主进程一次只能处理一个客户,其它已连接的客户等候在监听队列中。
二、多用户服务器程序设计
单用户版本的TCPServer.java程序能同时支持多个用户并发连接(TCP三次握手),但不能同时服务多用户对话。
1. 线程池解决多用户对话问题
解决思路就是用多线程。服务器可能面临很多客户的并发连接,这种情况的方案一般是:主线程只负责监听客户请求和接受连接请求,用一个线程专门负责和一个客户对话,即一个客户请求成功后,创建一个新线程来专门负责该客户。对于这种多用户的情况,用第三讲的方式new
Thread创建线程,频繁创建大量线程需要消耗大量系统资源。对于服务器,一般是使用线程池来管理和复用线程。线程池内部维护了若干个线程,没有任务的时候,这些线程都处于等待状态。如果有新任务,就分配一个空闲线程执行。如果所有线程都处于忙碌状态,新任务要么放入队列等待,要么增加一个新线程进行处理。ExecutorService代表线程池,其创建方式常见的有两种:
ExecutorService executorService = Executors.newFixedThreadPool(n);
ExecutorService executorService = Executors. newCachedThreadPool( );
创建后,就可以使用executorService.execute