群聊案例
服务端
package login;import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;public class Server {public static List<Socket> onlineSockets = new ArrayList<>(); // 用于存储"在线用户"所对应的socket对象public static void main(String[] args) throws Exception {System.out.println("---服务端启动---");// 创建 ServerSocket 的对象,同时为服务端注册端口ServerSocket serverSocket = new ServerSocket(8888);while (true) {// 使用 serverSocket 对象,调用一个 accept 方法,等待客户端的连接请求Socket socket = serverSocket.accept();onlineSockets.add(socket); // 用户一上线,就将其 socket 存到"在线集合"里面System.out.println(socket.getRemoteSocketAddress() + "连接到了服务端");// 使用一个独立的线程,把当前的 socket 对象交给它负责处理new ServerReaderThread(socket).start();}}
}class ServerReaderThread extends Thread {private Socket socket;public ServerReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {// 从 socket 通信管道中得到一个字节输入流InputStream is = socket.getInputStream();// 把原始的字节输入流包装成数据输入流DataInputStream dis = new DataInputStream(is);while (true) {try {// 使用数据输入流读取客户端发送过来的消息String msg = dis.readUTF();// 把消息分发给全部客户端sendMsgtoAll(msg);} catch (Exception e) {Server.onlineSockets.remove(socket); // 用户想要下线,就将其 socket 从 onlineSockets 里面删除System.out.println(socket.getRemoteSocketAddress() + "断开了连接");dis.close(); // 关闭流管道socket.close(); // 关闭连接管道break;}}} catch (Exception e) {e.printStackTrace();}}private void sendMsgtoAll(String msg) throws Exception {// 将消息发送给所有在线的 socket 管道for (Socket onlineSocket : Server.onlineSockets) {OutputStream os = onlineSocket.getOutputStream();DataOutputStream dos = new DataOutputStream(os);dos.writeUTF(msg);dos.flush(); // 注意!这里不能替换为 dos.close(),否则,程序无法运行!}}
}
客户端
package login;import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;public class Client {public static void main(String[] args) throws Exception {// 创建 Socket 对象,并同时请求与服务器程序的连接Socket socket = new Socket("127.0.0.1", 8888);// 创建独立的线程,负责随机从socket中接收服务器发送过来的消息new ClientReaderThread(socket).start();// 从 socket 通信管道中得到一个字节输出流,用来发数据给服务端程序OutputStream os = socket.getOutputStream();// 把低级的字节输出流包装成数据输出流DataOutputStream dos = new DataOutputStream(os);Scanner sc = new Scanner(System.in);while (true) {System.out.println("请输入>>> ");String msg = sc.nextLine();if ("exit".equals(msg)) {System.out.println("欢迎再来!");dos.close(); // 关闭数据输出流管道socket.close(); // 释放连接资源break;}// 4. 开始写数据出去了dos.writeUTF(msg);dos.flush();}}
}class ClientReaderThread extends Thread {private Socket socket;public ClientReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {// 从 socket 通信管道中得到一个字节输入流,接收来自服务器的数据InputStream is = socket.getInputStream();// 把低级的字节输入流包装成数据输入流DataInputStream dis = new DataInputStream(is);while (true) {try {// 使用数据输入流读取服务端发送过来的消息String msg = dis.readUTF();System.out.println(msg);} catch (Exception e) {System.out.println("服务端断开了连接");dis.close(); // 关闭流管道socket.close(); // 关闭连接管道break;}}} catch (Exception e) {e.printStackTrace();}}
}
简易版 BS 架构
BS 架构是不需要客户端的,因为浏览器就是客户端,但是必须要有服务端!
快速实现
// 服务端import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;public class Server {public static void main(String[] args) throws Exception {System.out.println("---服务端启动---");// 创建 ServerSocket 的对象,同时为服务端注册端口ServerSocket serverSocket = new ServerSocket(80);while (true) {// 使用 serverSocket 对象,调用一个 accept 方法,等待客户端的连接请求Socket socket = serverSocket.accept();// 使用一个独立的线程,把当前的 socket 对象交给它负责处理new ServerReaderThread(socket).start();}}
}class ServerReaderThread extends Thread {private Socket socket;public ServerReaderThread(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {// 从 socket 通信管道中得到一个字节输出流OutputStream os = socket.getOutputStream();// 把原始的字节输出流包装成打印输出流PrintStream ps = new PrintStream(os);ps.println("HTTP/1.1 200 OK");ps.println("Content-Type:text/html;charset=UTF-8");ps.println(); // 必须换行ps.println("<div style='color:red;font-size:120px;text-align:center'>我们都是追梦人</div>");// 由于 HTTP 协议是连接一次的,无记忆响应,响应后请关闭本次连接ps.close(); // 关闭打印管道socket.close(); // 关闭连接管道} catch (Exception e) {e.printStackTrace();}}
}
优化架构
// 服务端import java.io.OutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;public class Server {public static void main(String[] args) throws Exception {System.out.println("---服务端启动---");// 创建 ServerSocket 的对象,同时为服务端注册端口ServerSocket serverSocket = new ServerSocket(80);// 创建一个线程池,负责处理通信管道的任务ThreadPoolExecutor pool = new ThreadPoolExecutor(16 * 2, 16 * 2, 0, TimeUnit.SECONDS,new ArrayBlockingQueue<>(8), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());while (true) {// 使用 serverSocket 对象,调用一个 accept 方法,等待客户端的连接请求Socket socket = serverSocket.accept();// 使用一个独立的线程,把当前的ServerReaderRunnable任务对象交给它负责处理pool.submit(new ServerReaderRunnable(socket));// 在服务器上显示连接对象System.out.println(socket.getRemoteSocketAddress() + "访问了服务器 [" + new Date() + "]\n");}}
}class ServerReaderRunnable implements Runnable {private Socket socket;public ServerReaderRunnable(Socket socket) {this.socket = socket;}@Overridepublic void run() {try {// 从 socket 通信管道中得到一个字节输出流OutputStream os = socket.getOutputStream();// 把原始的字节输出流包装成打印输出流PrintStream ps = new PrintStream(os);ps.println("HTTP/1.1 200 OK");ps.println("Content-Type:text/html;charset=UTF-8");ps.println(); // 必须换行ps.println("<div style='color:red;font-size:120px;text-align:center'>我们都是追梦人</div>");// 由于 HTTP 协议是连接一次的,无记忆响应,响应后请关闭本次连接ps.close(); // 关闭打印管道socket.close(); // 关闭连接管道} catch (Exception e) {e.printStackTrace();}}
}