jQuery火箭图标返回顶部代码

Java网络编程:以一个基于BIO模型的多线程网络聊天室为例

网络编程的目的在于远程发送数据,发送接收数据就涉及到I/O的操作,这里因为涉及到比较底层字节和字符的操作,所以不可以使用java.nio.file.Files 操作文件。那就先说说I/O吧,I/O流分为字节流和字符流。字节即Byte,包含8位二进制数,一个二进制数就是1bit,中文名称叫位。字符即一个字母或者一个汉字。一个字母由一个字节组成,而汉字根据编码多字节个组成。

 Java I/O类的实现原理是装饰者设计模式:

FileInputStream fileInputStream = new FileInputStream(filePath);
BufferedInputStream bufferedInputStream = new BufferedInputStream(fileInputStream);

优点:装饰类和被装饰类可以独立发展,不会相互耦合,装饰模式是继承的一个替代模式,装饰模式可以动态扩展一个实现类的功能。

使用场景: 1、扩展一个类的功能。 2、动态增加功能,动态撤销。

 

Java BIO:同步阻塞

Java NIO:同步非阻塞

多个的进程的IO可以注册到一个复用器(select)上,然后用一个进程调用该select, select会监听所有注册进来的IO。如果select监听的IO在内核缓冲区都没有可读数据,select调用进程会被阻塞;而当任一IO在内核缓冲区中有可数据时,select调用就会返回。而后select调用进程可以自己或通知另外的进程(注册进程)来再次发起读取IO,读取内核中准备好的数据。可以看到,多个进程注册IO后,只有另一个select调用进程被阻塞。

Java AIO:异步非阻塞

BIO通信模型

所谓BIO,就是Block IO,阻塞式的IO。这个阻塞主要发生在:ServerSocket接收请求时、InputStream、OutputStream(输入输出流的读和写)都是阻塞的。

服务端ChatServer类:

 1 package top.rqfreefly.chatroom.bio;
 2 
 3 /**
 4  * 功能实现:服务器端,这个类有两个关键的全局变量,一个就是存储在线用户信息的Map,一个就是线程池
 5  * 这个类会监听端口,接收客户端的请求,然后为客户端分配工作线程
 6  * 还会提供一些常用的工具方法给每个工作线程调用,比如:添加和移除用户,发送消息。
 7  */
 8 
 9 import java.io.*;
10 import java.net.*;
11 import java.util.Map;
12 import java.util.concurrent.*;
13 
14 public class ChatServer {
15 
16     private int SEVER_PORT = 8888;
17     /**
18      * 创建一个Map存储在线用户的信息。这个userMap可以统计在线用户、针对这些用户可以转发其他用户发送的消息
19      * 因为会有多个线程操作这个userMap,所以为了安全起见用ConcurrentHashMap
20      * 在这里key就是客户端的端口号,但在实际中肯定不会用端口号区分用户,如果是web的话一般用session
21      * value是用户的IO的Writer,用以给客户端发送消息
22      */
23     private Map<Integer, Writer> userMap = new ConcurrentHashMap<>();
24     /**
25      * 创建线程池,线程上限为40个,如果第41个客户端请求进来,服务器会接收但是不会去分配线程处理它。
26      */
27     private ExecutorService executorService = Executors.newFixedThreadPool(40);
28 
29     //客户端连接时往map添加客户端
30     public void addClient(Socket socket) throws IOException {
31         if (socket != null) {
32             BufferedWriter writer = new BufferedWriter(
33                     new OutputStreamWriter(socket.getOutputStream())
34             );
35             userMap.put(socket.getPort(), writer);
36             System.out.println("Client["+socket.getPort()+"]:Online");
37         }
38     }
39 
40     //断开连接时map里移除客户端
41     public void removeClient(Socket socket) throws Exception {
42         if (socket != null) {
43             if (userMap.containsKey(socket.getPort())) {
44                 userMap.get(socket.getPort()).close();
45                 userMap.remove(socket.getPort());
46             }
47             System.out.println("Client [" + socket.getPort() + "] Offline");
48         }
49     }
50 
51     //转发客户端消息,这个方法就是把消息发送给在线的其他的所有客户端
52     public void sendMessage(Socket socket, String msg) throws IOException {
53         //遍历在线客户端
54         for (Integer port : userMap.keySet()) {
55             //发送给在线的其他客户端
56             if (port != socket.getPort()) {
57                 Writer writer = userMap.get(port);
58                 writer.write(msg);
59                 writer.flush();
60             }
61         }
62     }
63 
64     //接收客户端请求,并分配Handler去处理请求
65     public void start() {
66         try (ServerSocket serverSocket = new ServerSocket(SEVER_PORT)) {
67             System.out.println("Server Start,The Port is:" + SEVER_PORT);
68             while (true) {
69                 //等待客户端连接
70                 Socket socket = serverSocket.accept();
71                 //为客户端分配一个ChatHandler线程
72                 executorService.execute(new ChatHandler(this, socket));
73             }
74         } catch (IOException e) {
75             e.printStackTrace();
76         }
77     }
78 
79     public static void main(String[] args) {
80         ChatServer server = new ChatServer();
81         server.start();
82     }
83 
84 }

 服务端的ChatHandler类:

 1 package top.rqfreefly.chatroom.bio;
 2 
 3 /**
 4  * 功能实现:这个类就是工作线程类,把接收到的消息转发给其他客户端,还可以添加和移除用户。
 5  */
 6 
 7 import java.io.*;
 8 import java.net.*;
 9 
10 public class ChatHandler implements Runnable {
11     private ChatServer server;
12     private Socket socket;
13     BufferedReader reader;
14 
15     //构造函数,ChatServer通过这个分配Handler线程
16     public ChatHandler(ChatServer server, Socket socket) {
17         this.server = server;
18         this.socket = socket;
19     }
20 
21     @Override
22     public void run() {
23         try {
24             //往map里添加这个客户端
25             server.addClient(socket);
26             //读取这个客户端发送的消息
27             reader = new BufferedReader(
28                     new InputStreamReader(socket.getInputStream())
29             );
30             String msg = null;
31             while ((msg = reader.readLine()) != null) {
32                 //这样拼接是为了让其他客户端也能看清是谁发送的消息
33                 String sendmsg = "Client [" + socket.getPort() + "]: " + msg;
34                 //服务器打印这个消息
35                 System.out.println(sendmsg);
36                 //将收到的消息转发给其他在线客户端
37                 server.sendMessage(socket, sendmsg + "\n");
38                 if (msg.equals("exit")) {
39                     break;
40                 }
41             }
42         } catch (IOException e) {
43             e.printStackTrace();
44         } finally {
45             //如果用户退出或者发生异常,就在map中移除该客户端
46             try {
47                 server.removeClient(socket);
48             } catch (Exception e) {
49                 e.printStackTrace();
50             }
51             try {
52                 if(reader != null) {
53                     reader.close();
54                 }
55             } catch (IOException e) {
56                 e.printStackTrace();
57             }
58         }
59     }
60 }

客户端ChatClient类:

 1 package top.rqfreefly.chatroom.bio;
 2 
 3 /**
 4  * 功能实现:客户端,会通过Socket和服务器连接。提供了两个工具方法:发送消息和接收消息。
 5  */
 6 
 7 import java.io.*;
 8 import java.net.*;
 9 
10 public class ChatClient {
11 
12     private BufferedReader reader;
13     private BufferedWriter writer;
14     private Socket socket;
15 
16     //发送消息给服务器
17     public void sendToServer(String msg) throws IOException {
18         //发送之前,判断socket的输出流是否关闭
19         if (!socket.isOutputShutdown()) {
20             //如果没有关闭就把用户键入的消息放到writer里面
21             writer.write(msg + "\n");
22             writer.flush();
23         }
24     }
25 
26     //从服务器接收消息
27     public String receive() throws IOException {
28         String msg = null;
29         //判断socket的输入流是否关闭
30         if (!socket.isInputShutdown()) {
31             //没有关闭的话就可以通过reader读取服务器发送来的消息。注意:如果没有读取到消息线程会阻塞在这里
32             msg = reader.readLine();
33         }
34         return msg;
35     }
36 
37     public void start() {
38         //和服务创建连接
39         try {
40             socket = new Socket("127.0.0.1", 8888);
41             reader = new BufferedReader(
42                     new InputStreamReader(socket.getInputStream())
43             );
44             writer = new BufferedWriter(
45                     new OutputStreamWriter(socket.getOutputStream())
46             );
47             //新建一个线程去监听用户输入的消息
48             new Thread(new UserInputHandler(this)).start();
49 
50             //不停的读取服务器转发的其他客户端的信息
51             String msg=null;
52             while ((msg=receive())!=null){
53                 System.out.println(msg);
54             }
55         } catch (IOException e) {
56             e.printStackTrace();
57         }finally {
58             try {
59                 if(writer != null) {
60                     writer.close();
61                 }
62             } catch (IOException e) {
63                 e.printStackTrace();
64             }
65             try {
66                 if(reader != null) {
67                     reader.close();
68                 }
69             } catch (IOException e) {
70                 e.printStackTrace();
71             }
72             if (socket != null) {
73                 try {
74                     socket.close();
75                 } catch (IOException e) {
76                     e.printStackTrace();
77                 }
78             }
79         }
80     }
81 
82     public static void main(String[] args) {
83         ChatClient chatClient = new ChatClient();
84         chatClient.start();
85     }
86 
87 }

客户端UserInputHandler类:

 1 package top.rqfreefly.chatroom.bio;
 2 
 3 /**
 4  * 功能实现:专门负责监听用户输入信息,一旦有信息键入,就马上发送给服务器。
 5  */
 6 
 7 import java.io.*;
 8 
 9 public class UserInputHandler implements Runnable {
10 
11     private ChatClient client;
12 
13     public UserInputHandler(ChatClient client) {
14         this.client = client;
15     }
16 
17     @Override
18     public void run() {
19         try {
20             //接收用户输入的消息
21             BufferedReader reader = new BufferedReader(
22                     new InputStreamReader(System.in)
23             );
24             //不停的获取reader中的System.in,实现了等待用户输入的效果
25             while (true) {
26                 String input = reader.readLine();
27                 //向服务器发送消息
28                 client.sendToServer(input);
29                 if (input.equals("exit"))
30                     break;
31             }
32         } catch (IOException e) {
33             e.printStackTrace();
34         }
35     }
36 
37 }

 

posted @ 2020-08-22 11:24  rongqing2019  阅读(206)  评论(0编辑  收藏  举报