网络编程三部曲--BIO(一)

本文介绍了网络编程的核心——IO模型,特别是BIO(BlockingI/O)模型。BIO是一种阻塞式编程模型,每个客户端连接都会创建新的线程。通过ChatServer和ChatHandler类的代码示例,展示了如何使用BIO实现聊天室功能。文章指出BIO在高并发场景下效率低,因为它会为每个连接分配线程,导致资源浪费。文章最后提及NIO作为BIO的优化,能提高并发性能。
摘要由CSDN通过智能技术生成

一、介绍

网络编程的本质是进程间通信,通信的基础是IO模型。

IO模型分为三种,依次是BIO、NIO、AIO

不了解同步异步,阻塞和非阻塞的概念的可以看这里

同步异步、阻塞非阻塞_夏天0101的博客-CSDN博客

Java中的BIO编程模型,是一中阻塞式的编程模型,它使用同步阻塞的方式进行网络通信。在BIO模型中,服务端采用一个线程来处理一个客户端的连接请求,并且通过创建一个新的线程来处理每个客户端的请求。

二、代码实现

我们通过一个聊天室来看一下BIO模型的实现过程,这里用到网络编程的内容

服务器端分为两个类ChatServer 和 ChatHandler

ChatServer负责接收客户端连接,为每个客户端分配一个线程;ChatHandler负责服务器的主要功能,接受消息并转发,代码如下:

import java.io.*;
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;
    //QUIT,负责判断,当接收到客户端发来的quit时,关闭这个客户端的连接
    private final String QUIT = "quit";

    private ServerSocket serverSocket;
    //这里使用hashmap存储获取到每个客户端的端口号和每个客户端的字符流,用来群聊转发数据
    private Map<Integer,Writer> connectedClients;
    //这里使用线程池,为每个连接进来的客户端分配一个线程
    private ExecutorService executorService;

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

    /**
     * 添加客户
     * @param socket
     * @throws IOException
     */
    //在每个操作hashmap的方法前加了synchronized关键字,保证hashmap的线程安全
    public synchronized void addClient(Socket socket) throws IOException {
        if(socket != null) {
            int port = socket.getPort();
            Writer writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
            connectedClients.put(port,writer);
            System.out.println("客户端【" + port + "】已连接到服务器");
        }
    }

    /**
     * 移除客户
     * @param socket
     * @throws IOException
     */
    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 + "】已断开连接");
        }
    }

    /**
     * 消息转发
     * @param socket
     * @param fwdMsg
     * @throws IOException
     */
    public synchronized void forwardMessage(Socket socket, String fwdMsg) throws IOException{
        for(Integer id : connectedClients.keySet()) {
            if(!id.equals(socket.getPort())) {     //不是发送者
                Writer writer = connectedClients.get(id);
                writer.write(fwdMsg);
                writer.flush();
            }
        }
    }

    /**
     * 关闭serverSocket连接
     * @param serverSocket
     */

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

    /**
     * 开启服务器
     */

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

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

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

    public static void main(String[] args) {
        ChatServer chatServer = new ChatServer();
        chatServer.start();
    }
}
import java.io.*;
import java.net.Socket;

public class ChatHandler implements Runnable{
    //调用聊天服务器中的方法
    private ChatServer chatServer;
    //拿到每个客户端的套接字
    private Socket socket;

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

    @Override
    public void run() {
        try {
            // 存储新上线用户
            chatServer.addClient(socket);
            // 读取用户发过来的消息
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            String msg = null;
            while ((msg = reader.readLine()) != null) {
                String fwdMsg = "客户端【" + socket.getPort() + "】:" + msg;
                System.out.println(fwdMsg);
                //转发消息
                chatServer.forwardMessage(socket,fwdMsg + "\n");

                //检查用户是否退出
                if(chatServer.readyToQUit(msg)) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                chatServer.removeClient(socket);
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

客户端分为两个类,ChatClient与UserInputHandler

注意:由于BIO是同步阻塞的,所以要给客户端发消息和收消息各分配一条线程

这里我们用主线程负责收消息,新开一条线程负责发消息

ChatClient 负责连接服务器,UserInputHandler负责处理用户输入的数据并发给客户端

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

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 ChatClient() {

    }

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

    // 从客户端接收消息
    public String receive() throws IOException{
        String revMsg = null;
        if(!socket.isInputShutdown()) {
            revMsg = reader.readLine();

        }
        return revMsg;
    }

    // 检查用户是否准备退出
    public boolean readyToQuit(String msg) {
        return QUIT.equals(msg);
    }
    public void close() {
        if(writer != null) {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    //启动客户端
    public void start() {
        try {
            //创建socket
            socket = new Socket(DEFAULT_SERVER_HOST,DEFAULT_SERVER_PORT);
            //获取输入输出流
            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 chatClient = new ChatClient();
        chatClient.start();

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

public class UserInputHandler implements Runnable{
    private ChatClient chatClient;

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


    @Override
    public void run() {
        //从控制台获取输入信息
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
        try {
            while (true) {
                String msg = reader.readLine();
                System.out.println("【发送成功】" + msg);
                //把消息发给服务器
                chatClient.send(msg);
                if(chatClient.readyToQuit(msg)) {
                    break;
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

三、总结

BIO模型的优点是编程简单,易于理解与实现。缺点是由于需要给每个客户端都分配一条线程,当连接数较多时会占用太大的系统资源,会导致系统性能的下降;当连接长时间没有数据传输时,一方会一直处于阻塞状态,会导致系统资源的浪费。

因此对于高并发的场景,NIO(Non-blocking I/O)编程模型,它能够使用单线程来处理多个客户端连接,并且采用同步非阻塞方式进行网络通信,从而提高系统的并发性能。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值