BIO

1. 同步vs异步/阻塞vs非阻塞

在这里插入图片描述

① 同步VS异步

同步
同步和异步关注的是消息通信机制,所谓同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了

换句话说,就是由调用者主动等待这个调用的结果

异步
而异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用

举个通俗的例子:你打电话问书店老板有没有《分布式系统》这本书,如果是同步通信机制,书店老板会说,你稍等,”我查一下",然后开始查啊查,等查好了(可能是5秒,也可能是一天)告诉你结果(返回结果)。而异步通信机制,书店老板直接告诉你我查一下啊,查好了打电话给你,然后直接挂电话了(不返回结果)。然后查好了,他会主动打电话给你。在这里老板通过“回电”这种方式来回调

② 阻塞VS非阻塞

阻塞
阻塞和非阻塞关注的是程序在等待调用结果(消息,返回值)时的状态
阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回

非阻塞
非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程

还是上面的例子,你打电话问书店老板有没有《分布式系统》这本书,你如果是阻塞式调用,你会一直把自己“挂起”,直到得到这本书有没有的结果,如果是非阻塞式调用,你不管老板有没有告诉你,你自己先一边去玩了, 当然你也要偶尔过几分钟check一下老板有没有返回结果。在这里阻塞与非阻塞与是否同步异步无关。跟老板通过什么方式回答你结果无关

③ 同步阻塞BIO

想想之前基于BIO做的多人聊天室的例子,client和服务器建立了socket连接后,client在调用read()方法时,stream里没有数据可读,线程停止向下执行,直至stream有数据

既然是我调用了read()以后我什么都不干了,就等你的数据,就算对方没有准备好发送数据我也一直等待不做自己的事情,那么可想而知是阻塞的

而对于同步的概念则体现在stream有没有数据是需要我自己来判断的,而不是让别人来通过回调告诉我

④ 同步非阻塞NIO

对于NIO来说,我们可以设置非阻塞,但是他还是同步的,client采用循环的方式去判断当前的状态,依旧是自己判断
由于同步/异步,阻塞/非阻塞描述的不是同一个层面上的东西,所以可以两两组合,也就是最上面的那张表

2. BIO模型

采用BIO通信模型的服务端,通常由一个独立的Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后,为每个客户端创建一个新的线程,对请求进行处理,处理完成后,通过输出流返回应答给客户端,线程销毁。即典型的一请求一应答模型

传统的BIO模型

在这里插入图片描述
该模型最大的问题就是缺乏弹性伸缩能力,当客户端并发访问量增加后,服务端的线程个数和客户端并发访问数呈1:1的正比关系,Java中的线程也是比较宝贵的系统资源,线程数量快速膨胀后,系统的性能将急剧下降,随着访问量的继续增大,系统最终就死掉了

伪异步I/O模型

为了改进这种一连接一线程的模型,我们可以使用线程池来管理这些线程,实现一个或多个线程处理多个客户端请求的模型(服务端的线程个数和客户端并发访问数呈M:N的关系,N可以远远大于M,但是底层还是使用的同步阻塞I/O),通常被称为“伪异步I/O模型“
在这里插入图片描述

3. BIO多人聊天室

服务端:

  • 主线程Acceptor线程负责监听客户端的连接,它接收到客户端连接请求之后,为每个客户端创建一个新的线程接收用户的输入;
  • 为了能把收到的消息发送给进入房间,也就是建立了连接的其他用户,我们还需要一个集合存放创建的所有连接

客户端:

  • 首先要可以和服务端创建连接
  • 客户端要能够输入,而且要接收消息
  • 由于输入是阻塞的(发送也是阻塞的),所有在客户端需要两条线程,一条用于接收消息,一条发送消息

在这里插入图片描述

3.1 服务端

首先是服务端的编码,像上面分析的服务端包括两个部分
① 一个主线程接收所有的连接并且维护创建的的连接
② 为每个连接新启一个线程处理他的业务

ChatServer

先来看主线程,在主线程中维护一个map用来保存所有的连接,其中key是客户端的端口号,value是服务端和客户端之间建立的io输出流

private Map<Integer, Writer> connectedClient;//key:port  value:用于写入客户端的Writer

再为Map提供增加用户和删除用户的方法
要注意线程安全的问题,这里直接使用同步方法

//添加用户
    public synchronized void addClient(Socket socket) throws IOException {
        if(socket!=null){
            int port = socket.getPort();
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            connectedClient.put(port, writer);
            System.out.println("客户端"+port+"进入房间");
        }
    }

    //移除用户
    public synchronized void removeClient(Socket socket) throws IOException {
        if(socket!=null){
            int port = socket.getPort();
            if(connectedClient.get(port)!=null){
                connectedClient.get(port).close();
                connectedClient.remove(port);
                System.out.println("客户端"+port+"离开房间");
            }

        }
    }

接收到用户发送的消息,我们轮询整个map,把消息发送给除自己以外的用户

    //向在线用户发送消息(除了发送消息的人)
    public synchronized void forwardMessage(Socket socket, String fsdMsg) throws IOException {
        for (Integer id : connectedClient.keySet()) {
            if(!id.equals(socket.getPort())){//除了自己
                Writer writer = connectedClient.get(id);
                writer.write(fsdMsg);
                writer.flush();
            }
        }
    }

主要逻辑:绑定监听端口,如果有用户来建立了连接则为他新开一个线程,并把用户添加到map中

    public void start(){//主要逻辑
        //绑定监听端口
        try {
            serverSocket = new ServerSocket(DEFAULT_PORT);
            System.out.println("启动服务器,监听端口"+DEFAULT_PORT+"...");
            while (true){
                Socket socket = serverSocket.accept();
                //为每一个用户创建一个线程
                new Thread(new ChatHandler(this,socket)).start();
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                if(serverSocket!=null){
                    serverSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

完整代码

public class ChatServer {
    private final int DEFAULT_PORT = 8888;
    private final String QUIT = "quit";//退出关键词
    private ServerSocket serverSocket;
    private Map<Integer, Writer> connectedClient;//key:port  value:用于写入客户端的Writer

    public ChatServer() {
        connectedClient = new HashMap<Integer, Writer>();
    }
    //添加用户
    public synchronized void addClient(Socket socket) throws IOException {
        if(socket!=null){
            int port = socket.getPort();
            BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            connectedClient.put(port, writer);
            System.out.println("客户端"+port+"进入房间");
        }
    }

    //移除用户
    public synchronized void removeClient(Socket socket) throws IOException {
        if(socket!=null){
            int port = socket.getPort();
            if(connectedClient.get(port)!=null){
                connectedClient.get(port).close();
                connectedClient.remove(port);
                System.out.println("客户端"+port+"离开房间");
            }

        }
    }

    //向在线用户发送消息(除了发送消息的人)
    public synchronized void forwardMessage(Socket socket, String fsdMsg) throws IOException {
        for (Integer id : connectedClient.keySet()) {
            if(!id.equals(socket.getPort())){//除了自己
                Writer writer = connectedClient.get(id);
                writer.write(fsdMsg);
                writer.flush();
            }
        }
    }

    public synchronized boolean readyToQuit(String msg){
        if (msg.equals(QUIT)){
            return true;
        }
        return false;
    }

    public void start(){//主要逻辑
        //绑定监听端口
        try {
            serverSocket = new ServerSocket(DEFAULT_PORT);
            System.out.println("启动服务器,监听端口"+DEFAULT_PORT+"...");
            while (true){
                Socket socket = serverSocket.accept();
                //为每一个用户创建一个线程
                new Thread(new ChatHandler(this,socket)).start();
            }
        }catch (IOException e){
            e.printStackTrace();
        }finally {
            try {
                if(serverSocket!=null){
                    serverSocket.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

ChatHandler

这一部分是对每一个新连接的线程新起的线程代码,里面就是使用了ChatServer的各种方法把自己加入map,并发送消息

public class ChatHandler implements Runnable{//在服务端对于每一个客户对应一个ChatHandler线程
    private ChatServer server;
    private Socket socket;

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

    public void run() {
        BufferedReader reader = null;
        try {
            //存储新上线客户
            server.addClient(socket);
            //读取用户发送的消息
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg = null;
            while ((msg=reader.readLine())!=null){//流被关闭读取的是null
                String newmsg = "客户端["+socket.getPort()+"]: "+msg+"/n";
                System.out.println(newmsg);
                //转发消息给聊天室的在线用户
                server.forwardMessage(socket, newmsg);
                if(server.readyToQuit(msg)){
                    break;
                }
            }

        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            try {
                server.removeClient(socket);
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

3.2 客户端

客户端要可以和服务端创建连接,并向服务端发送消息,接收消息;为了在发送或接收的时候不阻塞,为发送和接收创建的是不同的线程

ChatClient
public class ChatClient {
    private final String DEFAULT_SERVER_HOST = "127.0.0.1";
    private final int DEFAULT_SERVER_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.equals(msg);
    }

	//主要逻辑
    public void start(){
        try {
            //创建socket
            socket = new Socket(DEFAULT_SERVER_HOST,DEFAULT_SERVER_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 {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

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

UserInputHandler

处理用户的输入


public class UserInputHandler implements Runnable{//处理用户的输入
    private ChatClient chatClient;

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

    public void run() {

        try {
            BufferedReader consoleReader = new BufferedReader(new InputStreamReader(System.in));
            while(true){
                String input = consoleReader.readLine();
                chatClient.send(input);
                if(chatClient.readyToQuit(input)){
                    break;
                }
            }

        }catch (IOException e){
            e.printStackTrace();
        }

    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值