Java网络编程-Socket编程(三伪异步I/O模型的简版多人聊天室)

什么是伪异步I/O模型?

BIO模型与伪异步I/O模型

现在应该了解了什么是伪异步I/O模型了吧,看起来它和BIO模型其实差别不大,只不过伪异步I/O模型是用线程池来管理线程去和客户端进行数据交互,而BIO模型是每次客户端的连接请求成功后,都创建新的线程去与客户端进行数据交互,虽然差别不大,但伪异步I/O模型的优势还是很明显的,尤其当客户端连接请求并发数比较大时。

两个模型差别不大,也就意味着需要改动代码的地方很少,如果需要代码解释,请看下面这篇博客

 Java 网络编程-Socket编程(二基于BIO模型的简版多人聊天室)

改进

只需要改动服务器端的代码,将每次客户端连接成功后都创建新线程来与客户端进行数据交互,改进成由线程池来管理线程去与客户端进行数据交互。

ChatServer类(有改动)。

package bio.chatroom.server;

import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ChatServer {

    private final int DEFAULT_PORT = 8888;
    private final String QUIT = "quit";

    private ExecutorService executorService;
    private ServerSocket serverSocket;

    // 把客户端的port当作客户端的id
    private Map<Integer , Writer> connectedClients;

    public ChatServer(){
        executorService = Executors.newFixedThreadPool(10);
        connectedClients = new HashMap<>();
    }

    public synchronized void addClient(Socket socket) throws IOException {
        if(socket != null){
            int port = socket.getPort();
            BufferedWriter writer = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );
            connectedClients.put(port , writer);
            System.out.println("客户端["+port+"]已连接到服务器");
        }
    }

    public synchronized void removeClient(Socket socket) throws IOException {
        if(socket != null){
            int port = socket.getPort();
            if(connectedClients.containsKey(port)){
                connectedClients.get(port).close();
                connectedClients.remove(port);
                System.out.println("客户端["+port+"]已断开连接");
            }
        }
    }

    public synchronized void forwardMessage(Socket socket , String fwdMsg) throws IOException {
        // 发送消息的端口
        int sendMessagePort = socket.getPort();
        for(Integer port : connectedClients.keySet()){
            if(!port.equals(sendMessagePort)){
                Writer writer = connectedClients.get(port);
                writer.write(fwdMsg);
                writer.flush();
            }
        }
    }

    public boolean readyToQuit(String msg){
        return QUIT.equalsIgnoreCase(msg);
    }

    public synchronized void close(){
        if(serverSocket != null){
            try {
                serverSocket.close();
                System.out.println("关闭了ServerSocket");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void start(){
        try {
            // 创建ServerSocket,绑定和监听端口
            serverSocket = new ServerSocket(DEFAULT_PORT);
            System.out.println("启动服务器,监听端口"+DEFAULT_PORT+"...");

            while(true){
                // 等待客户端连接
                Socket socket = serverSocket.accept();
                // 创建ChatHandler线程
//                new Thread(new ChatHandler(this , socket)).start();
                executorService.execute(new ChatHandler(this , socket));
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            close();
        }
    }

    public static void main(String[] args) {
        ChatServer server = new ChatServer();
        server.start();
    }
}

ChatHandler类(无改动)。

package bio.chatroom.server;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.Socket;

public class ChatHandler implements Runnable{

    private ChatServer server;
    private Socket socket;

    public ChatHandler(ChatServer server , Socket socket){
        this.server = server;
        this.socket = socket;
    }

    @Override
    public void run() {
        try {
            // 存储新上线用户
            server.addClient(socket);

            // 读取用户发送的消息
            BufferedReader reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );

            String msg = null;
            while((msg = reader.readLine()) != null){
                String fwdMsg = "客户端["+socket.getPort()+"]:"+msg+"\n";
                System.out.print(fwdMsg);

                // 将消息转发给聊天室里在线的其他用户
                server.forwardMessage(socket , fwdMsg);

                // 检查用户是否准备退出
                if(server.readyToQuit(msg)){
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                // 从服务器移除退出的用户
                server.removeClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

ChatClient类(无改动)。

package bio.chatroom.client;

import java.io.*;
import java.net.Socket;

public class ChatClient {

    private final String DEFAULT_SERVER_HOST = "127.0.0.1";
    private final int DEFAULT_PORT = 8888;
    private final String QUIT = "quit";

    private Socket socket;
    private BufferedReader reader;
    private BufferedWriter writer;

    // 发送消息给服务器
    public void send(String msg) throws IOException {
        if(!socket.isOutputShutdown()){
            writer.write(msg+"\n");
            writer.flush();
        }
    }

    // 接收服务器的消息
    public String receive() throws IOException {
        String msg = null;
        if(!socket.isInputShutdown()){
            msg = reader.readLine();
        }
        return msg;
    }

    // 检查用户是否准备退出
    public boolean readyToQuit(String msg){
        return QUIT.equalsIgnoreCase(msg);
    }

    public void close(){
        if(writer != null){
            try {
                writer.close();
                System.out.println("关闭socket");
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    public void start(){
        try {
            // 创建socket
            socket = new Socket(DEFAULT_SERVER_HOST , DEFAULT_PORT);

            // 创建IO流
            reader = new BufferedReader(
                    new InputStreamReader(socket.getInputStream())
            );
            writer = new BufferedWriter(
                    new OutputStreamWriter(socket.getOutputStream())
            );

            // 处理用户的输入
            new Thread(new UserInputHandler(this)).start();

            // 读取服务器转发的消息
            String msg = null;
            while((msg = receive()) != null){
                System.out.println(msg);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally{
            close();
        }
    }

    public static void main(String[] args) {
        ChatClient client = new ChatClient();
        client.start();
    }
}

UserInputHandler类(无改动)。

package bio.chatroom.client;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;

public class UserInputHandler implements Runnable{

    private ChatClient client;

    public UserInputHandler(ChatClient client){
        this.client = client;
    }

    @Override
    public void run() {
        try {
            // 等待用户输入消息
            BufferedReader consoleReader = new BufferedReader(
                    new InputStreamReader(System.in)
            );

            while(true){
                String input = consoleReader.readLine();

                // 向服务器发送消息
                client.send(input);

                //检查用户是否准备退出
                if(client.readyToQuit(input)){
                    break;
                }
            }
        } catch (IOException e){
            e.printStackTrace();
        }
    }
}

改动的地方
增加了一个属性。

private ExecutorService executorService;

构造器也需要改,需要初始化这个executorService

    public ChatServer(){
        executorService = Executors.newFixedThreadPool(10);
        connectedClients = new HashMap<>();
    }

当客户端连接成功后,创建新线程的地方也需要改。

                // 创建ChatHandler线程
//                new Thread(new ChatHandler(this , socket)).start();
                executorService.execute(new ChatHandler(this , socket));

就需要改这三个地方。

由改进的代码可以知道,当大量客户端向服务器发出连接请求后,服务器虽然可以与这些客户端进行连接,但最多只有10个客户端同时在线,而其他的客户端会处于等待状态,也就是服务器最多同时存在10个线程来与客户端进行数据交互

关于线程池的使用,这里也只是涉及皮毛,也不打算多讲,相信大家都看得懂。

这里便完成了伪异步I/O模型的简易多人聊天室,大家可以自己实现一下。

测试

为了方便测试,将线程池允许容纳的线程数设置为2。

 

 

 

测试是没问题的。

如果有说错的地方,请大家不吝赐教

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值