1.多线程TCP服务器:
改写socket服务器端程序,设计编写出一个TCP服务器端程序:要求使用多线程处理多个客户端的连接请求(每个线程实例处理一个客户端连接)。客户端与服务器端之间的通信内容,以及服务器端的处理功能等可自由设计拓展。
(1)主要思路:
- 多线程处理客户端连接:
修改服务器端程序,作为多线程TCP服务器时,首先需要确保服务器能够同时处理多个客户端的连接请求,即在服务器端程序中,需要为每个客户端连接创建一个独立的线程来处理通信。具体为其在接受到新的客户端连接请求时,为每个连接创建一个新的线程来处理。这样可以确保每个客户端连接都有独立的线程来处理通信,避免阻塞主线程。
- 客户端与服务器端通信:
确保客户端和服务器端之间的通信能够正常进行。客户端应该能够向服务器发送消息,并且服务器能够接收到并处理这些消息。同样地,服务器应该能够向客户端发送消息。
(2)具体步骤与实现代码:
- TCPServer类:
多线程TCP服务器能够处理多个客户端的连接请求,并且能够与客户端进行通信。
- 服务器初始化:创建一个TCPServer类,其中包括一个构造函数和一个main方法。构造函数初始化了服务器的GUI实例和客户端处理器映射。服务器的GUI实例用于显示服务器日志和在线用户列表,客户端处理器映射用于存储连接到服务器的客户端处理器。
- 启动服务器:tartTCPServer()方法用于启动服务器,监听客户端的连接请求。它创建了一个ServerSocket实例,指定了服务器要监听的端口号9999。然后,通过一个无限循环持续接受客户端连接。
- 处理客户端连接:每当有新的客户端连接时,服务器会创建一个新的ClientHandler实例来处理该客户端的连接。ClientHandler负责与客户端进行通信,并在接收到消息时转发给服务器。为了处理多个客户端连接,服务器为每个客户端连接创建一个独立的线程。这样可以确保每个客户端连接都能够独立地进行通信,不会受到其他连接的影响。
- 发送消息:服务器提供了向所有客户端发送消息和向特定客户端发送消息的方法。这些方法在接收到消息后,将消息发送给相应的客户端处理器,并在日志中记录发送的消息。
- 客户端断开连接处理:当客户端断开连接时,服务器会从在线用户列表中移除相应的客户端处理器,并在日志中记录该客户端已断开连接的消息。
- 主方法:main方法创建了TCPServer实例,并调用startTCPServer()方法启动服务器。
- 实现代码:
- package experiment2;
- import java.io.IOException;
- import java.net.ServerSocket;
- import java.net.Socket;
- import java.util.HashMap;
- import java.util.Map;
- /*
- 服务器
- */
- public class TCPServer {
- //服务器GUI实例
- private static ServerGUI serverGUI;
- //存储客户端处理器的映射(实现为指定客户端服务)
- private static Map<String,ClientHandler> clients;
- //初始化服务器
- public TCPServer(){
- serverGUI = new ServerGUI(this);
- clients = new HashMap<>();//使用哈希Map
- }
- //启动服务器,监听客户端连接
- public void startTCPServer(){
- try{
- //创建服务器的Socket,监听指定端口号
- ServerSocket serverSocket = new ServerSocket(9999);
- serverGUI.logMessage("服务器已启动,等待客户端连接...");
- //给每个用户分配特定编号
- int clientCounter = 0;
- //创建一个新线程来处理这个客户端的连接(客户端处理器)
- while(true){
- //持续接受客户端连接
- Socket clientSocket = serverSocket.accept();
- clientCounter ++;
- String clientName = String.valueOf(clientCounter);
- serverGUI.logMessage("客户端"+clientName+"已连接");
- ClientHandler clientHandler = new ClientHandler(clientSocket,serverGUI,clientName);
- //将客户端唯一编号和此客户端处理器一一对应
- clients.put(clientName,clientHandler);
- serverGUI.addClientHandler(clientHandler);
- //启动此线程
- new Thread(clientHandler).start();
- }
- }catch (IOException e){
- e.printStackTrace();
- }
- }
- //向所有客户端发送消息
- public void sendToAllClients(String message){
- serverGUI.logMessage("服务器发送给所有客户端:"+message);
- //获取所有客户端处理线程,向每个客户端发送消息
- for(ClientHandler clientHandler : serverGUI.getClientHandlers()){
- clientHandler.setToClient(message);
- }
- }
- //向特定客户端发送消息
- public void sendToOneClient(String clientName, String message) {
- if (clients.containsKey(clientName)) {
- serverGUI.logMessage("服务器发送给客户端"+clientName+": "+message);
- clients.get(clientName).setToClient(message);
- } else {
- serverGUI.logMessage("客户端 " + clientName + " 不存在或已断开连接");
- }
- }
- //客户端断开连接时从在线用户列表中移除对应的客户端处理器
- public static void clientDisconnected(String clientName){
- serverGUI.removeClientHandler(clients.get(clientName));
- clients.remove(clientName);
- serverGUI.logMessage("客户端"+clientName+"已断开连接");
- }
- //服务器需要main可独立启动
- public static void main(String[] args){
- TCPServer tcpServer = new TCPServer();
- tcpServer.startTCPServer();
- }
- }
- TCPClient类:
客户端能够连接到服务器并进行消息通信。收到的服务器消息会显示在客户端GUI中,并且客户端能够通过GUI向服务器发送消息。
- 客户端初始化:TCPClient类有一个构造函数,接受三个参数:客户端名称、服务器地址“localhost”和端口号9999。同时,客户端GUI实例被创建,并且连接到服务器。
- 连接到服务器:在构造函数中,客户端创建了一个Socket实例,用于连接到指定的服务器地址和端口号。如果连接成功,客户端GUI会显示连接成功的消息。
- 获取输入输出流:一旦连接建立,客户端通过socket.getInputStream()和socket.getOutputStream()获取输入输出流。这样客户端就能够接收从服务器发送的消息,并向服务器发送消息。
- 处理接收消息:客户端通过创建一个新的线程来处理接收消息。在这个线程中,客户端持续地从输入流中读取消息,并将其显示在客户端GUI中。如果接收到的消息为null,说明服务器关闭了连接,客户端停止接收消息并退出线程。
- 发送消息:客户端GUI提供了一个方法setMessageSender,用于设置消息发送器。这个方法接受一个PrintWriter作为参数,用于将消息发送给服务器。客户端可以通过调用这个方法发送消息给服务器。
- 实现代码:
- package experiment2;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.Socket;
- public class TCPClient {
- private ClientGUI clientGUI;
- public TCPClient(String clientName,String serverAddress,int port){
- this.clientGUI = new ClientGUI(clientName);
- try{
- Socket socket = new Socket(serverAddress,port);
- clientGUI.logMessage("连接到服务器成功");
- // 获取输入输出流
- BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
- PrintWriter writer = new PrintWriter(socket.getOutputStream(),true);
- // 创建一个新线程来处理接收消息
- Thread receiveThread = new Thread(()->{
- String message;
- try{
- while((message = reader.readLine()) != null){
- clientGUI.logMessage("服务器发来消息: " + message);
- }
- }catch(IOException e){
- e.printStackTrace();
- }
- });
- // 主线程用于发送消息
- receiveThread.start();
- clientGUI.setMessageSender(writer::println);
- }catch(IOException e){
- e.printStackTrace();
- }
- }
- }
- ClientHandler类:
客户端处理器能够处理客户端的连接,接收到客户端消息时将其显示在服务器GUI中。
- 客户端处理器初始化:ClientHandler类有一个构造函数,接受三个参数:客户端Socket、服务器GUI实例和客户端名称。在构造函数中,客户端处理器保存了对客户端Socket和服务器GUI的引用,并初始化了一个用于向客户端发送消息的PrintWriter。
- 向客户端发送消息:setToClient()方法用于向客户端发送消息。它接受一个消息参数,并通过客户端的PrintWriter将消息发送给客户端。
- 处理客户端消息接收:在run()方法中,客户端处理器创建了一个用于读取客户端消息的BufferedReader。然后,通过一个无限循环持续监听客户端发送的消息。当客户端发送消息时,服务器GUI会记录该消息,并在服务端窗口中显示客户端名称和消息内容。
- 实现代码:
- package experiment2;
- import java.io.BufferedReader;
- import java.io.IOException;
- import java.io.InputStreamReader;
- import java.io.PrintWriter;
- import java.net.Socket;
- /*
- 处理客户端的连接的线程(客户端处理器)
- */
- public class ClientHandler implements Runnable{
- private final Socket clientSocket;//客户端Socket
- private final ServerGUI serverGUI;