IX ServerSocket 服务器端同时与多个客户端交互方案
将与单个客户端交互的操作封装到线程中 , 每当与一个新的客户端建立连接 , 就开启一个异步线程处理与该客户端之间的交互 ;
客户端处理线程 :
/** * 异步线程 , 处理单个客户端连接 * 如果多个客户端连接 , 就需要创建多个该类, 同时处理多个客户端连接 */ public static class ClientHandler extends Thread{ //1. 客户端 Socket 连接 private Socket clientSocket; public ClientHandler(Socket clientSocket) { this.clientSocket = clientSocket; } }
无限循环等待客户端连接 , 一旦连接成功 , 就开启一个异步线程 ;
//II. 等待客户端连接, 注意此处是无限循环连接 while(true){ //1. 收到一个客户端连接请求 , 获取 客户端 Socket 连接 Socket clientSocket = serverSocket.accept(); //2. 将 Socket 连接传入 ClientHandler 线程 , 异步处理与客户端交互操作 ClientHandler clientHandler = new ClientHandler(clientSocket); //3. 启动 与客户端 Socket 交互处理 异步线程 clientHandler.start(); }
X Socket 客户端代码示例
import java.io.*; import java.net.*; /** * 客户端 */ public class Client { /** * 客户端入口函数 * @param args */ public static void main(String[] args){ try { //I. 连接 //1. 创建 Socket 对象 Socket socket = new Socket(); //2. 设置 Socket 的超时时间 socket.setSoTimeout(5000); //3. 创建连接的端点 , 该端点包括 IP 地址和端口号 InetSocketAddress inetSocketAddress = new InetSocketAddress( Inet4Address.getLocalHost(), //本机IP地址 8000 //端口号 ); //4.. 连接服务器端点 , 并设置超时时间 socket.connect(inetSocketAddress, 5000); //5. 如果连接成功会继续执行下面的操作, 如果失败会根据失败情况抛出异常 //II. 获取 Socket 连接两个端点的属性, IP 地址和端口号 //1. 获取服务器 IP 地址 InetAddress serverAddress = socket.getInetAddress(); //2. 获取服务器端口号 int serverPort = socket.getPort(); //3. 获取客户端 IP 地址 InetAddress clientAddress = socket.getLocalAddress(); //4. 获取客户端端口号 int clientPort = socket.getLocalPort(); System.out.println("服务器连接成功\n服务器地址 : " + serverAddress + " , 服务器端口号 : " + serverAddress + "\n客户端地址 : " + clientAddress + " , 客户端端口号 : " + clientPort); //向服务器端发送数据 sendToServer(socket); //III. 关闭 Socket 连接 socket.close(); System.out.println("客户端 Socket 连接关闭"); } catch (SocketException e) { e.printStackTrace(); } catch (UnknownHostException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } finally { //客户端可能挂了 , 需要重启 } } public static void sendToServer(Socket socket){ try { //I. 获取用户控制台输入信息 //1. 获取控制台输入流 InputStream is = System.in; //2. 该输入流会阻塞 , 等待用户控制台输入 BufferedReader br = new BufferedReader(new InputStreamReader(is)); //II. 将用户输入信息上传到服务器 //4. 获取客户端的输出流 , 用于向服务器端写出数据 OutputStream os = socket.getOutputStream(); //5. 创建打印流 , 用于向服务器端写出字符 PrintStream ps = new PrintStream(os); //III. 从服务器获取信息 , 这里循环读取数据, 接收到 服务器端的 quit 字符串才退出 //1. 获取服务器端输入流 InputStream isFromServer = socket.getInputStream(); //2. 将输入流转为 BufferedReader BufferedReader brFromServer = new BufferedReader(new InputStreamReader(isFromServer)); //3. 循环控制变量 , 退出时设置为 false boolean isReadFromServer = true; while (isReadFromServer){ //4. 阻塞命令行 , 等待用户输入一行数据, 并存入 string 对象中 String string = br.readLine(); //5. 通过打印流 , 将该字符串传输给服务器端 ps.println(string); //6. 阻塞控制台 , 从服务器读取一行数据 String stringFromServer = brFromServer.readLine(); //7. 根据服务器返回的数据进行不同操作 if("quit".equals(stringFromServer)){ //停止循环 isReadFromServer = false; }else{ System.out.println(stringFromServer); } } //IV. 释放资源 br.close(); ps.close(); brFromServer.close(); } catch (IOException e) { e.printStackTrace(); } } }
XI Socket 服务器端代码示例
import java.io.*; import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; /** * 服务器端 */ public class Server { /** * 服务器端入口函数 * @param args */ public static void main(String[] args){ try { //I. 创建服务器套接字, 并监听端口 //1. 创建服务器套接字 , 只需要指定端口即可 , 不需要指定 IP 地址 // 其 IP 地址就是本机的 IP 地址 , 如果机器有多个 IP 地址 // 如果没有指定 IP 地址 , 那么会监听所有的 IP 地址的指定端口号 ServerSocket serverSocket = new ServerSocket(8000); //2. 获取服务器端 IP 地址 InetAddress inetAddress = serverSocket.getInetAddress(); //3. 获取服务器端口号 int localPort = serverSocket.getLocalPort(); System.out.println("服务器开启\nIP 地址 : " + inetAddress + " , 端口号 : " + localPort); //II. 等待客户端连接, 注意此处是无限循环连接 while(true){ //1. 收到一个客户端连接请求 , 获取 客户端 Socket 连接 Socket clientSocket = serverSocket.accept(); //2. 将 Socket 连接传入 ClientHandler 线程 , 异步处理与客户端交互操作 ClientHandler clientHandler = new ClientHandler(clientSocket); //3. 启动 与客户端 Socket 交互处理 异步线程 clientHandler.start(); } } catch (IOException e) { e.printStackTrace(); } } /** * 异步线程 , 处理单个客户端连接 * 如果多个客户端连接 , 就需要创建多个该类, 同时处理多个客户端连接 */ public static class ClientHandler extends Thread{ //1. 客户端 Socket 连接 private Socket clientSocket; //2. 循环控制变量 private boolean loopFlag = true; public ClientHandler(Socket clientSocket) { this.clientSocket = clientSocket; } @Override public void run() { super.run(); //I. 获取客户端相关信息 //1. 获取客户端的 IP 地址 InetAddress inetAddress = clientSocket.getInetAddress(); //2. 获取客户端的端口号 int port = clientSocket.getPort(); //3. 打印客户端的信息 System.out.println("客户端信息 : \nIP 地址 : " + inetAddress + " , 端口号 : " + port); try { //II. 创建与客户端交互的输入输出流 //1. 获取客户端 Socket 输出流 , 用于向客户端发送数据 OutputStream os = clientSocket.getOutputStream(); //2. 创建打印流 , 用于向客户端输出数据 PrintStream ps = new PrintStream(os); //3. 获取客户端 Socket 输入流 , 用于接收客户端数据 InputStream is = clientSocket.getInputStream(); //4. 获取客户端的字符输入流 , 该输入流可以阻塞等待客户端输入 BufferedReader br = new BufferedReader(new InputStreamReader(is)); //III. 循环处理与客户端交互的信息 while (loopFlag){ //1. 等待客户端输入 String string = br.readLine(); //2. 处理客户端输入 if("quit".equals(string)){ //如果客户端发送退出, 那么停止循环, 将该信息在送回客户端 loopFlag = false; //将信息发送到客户端 ps.println("quit"); }else{ //将信息打印到控制台 System.out.println(string); //将发送成功信息返回给客户端 ps.println("发送成功 , 大小 " + string.length() + " 字节"); } } //IV. 关闭相关资源 ps.close(); br.close(); } catch (IOException e) { e.printStackTrace(); }finally { try { //如果出现异常 , 将该 Socket 关闭 clientSocket.close(); } catch (IOException e) { e.printStackTrace(); } } System.out.println("客户端退出 : \nIP 地址 : " + inetAddress + " , 端口号 : " + port); } } }
XII 运行客户端与服务器端代码
1. 编译两个 Java 代码 : 选择菜单栏 -> Build -> Rebuild Project 选项 , 获取到 Client.class 和 Server.class 文件 ;
2. 字节码文件地址 : 编译后的 class 字节码文件在工程目录的 out\production\classes 目录下 ;
打开两个命令行界面 , 首先进入该目录 , 先运行服务器端 , 在命令行中输入 java Server , 即开启了服务器端 ; 再打开客户端 , 在另一个命令行中运行 java Client , 即开启了客户端 ;
客户端输入文字 , 与服务器端交互 :