一.NIO(一)
(一).简介:
NIO 是 Java SE 1.4 引入的一组新的 I/O 相关的 API,它提供了非阻塞式 I/O、选择器、通道、缓冲区等新的概念和机制。相比与传统的 I/O 多出的 N 不是单纯的 New,更多的是代表了 Non-blocking 非阻塞,NIO具有更高的并发性、可扩展性以及更少的资源消耗等优点。
(二).NIO 与传统BIO:
NIO:是同步非阻塞的,服务器实现模式为 一个线程处理多个连接。服务端只会创建一个线程负责管理Selector(多路复用器),Selector(多路复用器)不断的轮询注册其上的Channel(通道)中的 I/O 事件,并将监听到的事件进行相应的处理。每个客户端与服务端建立连接时会创建一个 SocketChannel 通道,通过 SocketChannel 进行数据交互。
BIO:全称是Blocking IO,同步阻塞式IO,是JDK1.4之前的传统IO模型,服务器实现模式为一个连接一个线程。每当客户端有连接请求时服务器端就需要启动一个线程进行处理。
两者主要区别如下:
- 阻塞和非阻塞:NIO 使用非阻塞式 I/O,而 BIO 使用阻塞式 I/O。在阻塞式 I/O 中,当一个 I/O 操作完成之前,线程会一直被阻塞,直到 I/O 操作完成;在非阻塞式 I/O 中,线程可以继续执行其他任务,直到 I/O 操作完成并返回结果。
- 线程模型:NIO 中的线程模型是基于事件驱动的,当一个 I/O 操作完成时,会触发相应的事件通知线程处理;而在 BIO 中,每个线程都负责处理一个客户端连接,需要不断地轮询客户端的输入输出流,以便及时响应客户端的请求。
- 内存消耗:NIO 中使用的缓冲区(Buffer)可以重复利用,减少了频繁的内存分配和回收,从而减少了内存的消耗;而在 BIO 中,每个客户端连接都需要单独分配一个缓冲区,容易造成内存的浪费。
- 并发性能:NIO 中使用非阻塞式 I/O,可以同时处理多个客户端连接,从而提高了并发处理能力;而在 BIO 中,由于每个客户端连接都需要一个线程来处理,当连接数量增加时,容易出现线程饥饿和资源耗尽的问题。
(三).NIO的核心原理
工作流程:
- 创建 Selector:Selector 是 NIO 的核心组件之一,它可以同时监听多个通道上的 I/O 事件,并且可以通过 select() 方法等待事件的发生。
- 注册 Channel:通过 Channel 的 register() 方法将 Channel 注册到 Selector 上,这样 Selector 就可以监听 Channel 上的 I/O 事件。
- 等待事件:调用 Selector 的 select() 方法等待事件的发生,当有事件发生时,Selector 就会通知相应的线程进行处理。
- 处理事件:根据不同的事件类型,调用对应的处理逻辑。
- 关闭 Channel:当 Channel 不再需要使用时,需要调用 Channel 的 close() 方法关闭 Channel,同时也需要调用 Buffer 的 clear() 方法清空 Buffer 中的数据,以释放内存资源。
二.java聊天室控制台实现公聊私聊
其私聊核心思路就是hashmap将每个人的id(用户名)和其对应的输出流,形成键值对,存到hashmap中,在需要的时候,直接根据用户名就可以调出其对应的输出流.
公聊的核心思路在于使用ArrayList集合,将所有用户的输出流都存入集合中,然后遍历对每个用户进行输出.
实例代码:
客户端:
public class Client {public static void main(String[] args) {//连接服务器try {System.out.println("连接服务端");Socket socket = new Socket("localhost",8088);System.out.println("连接服务端成功");Thread t1 = new Thread(new ClientWriteData(socket));Thread t2 = new Thread(new ClientReadData(socket));t1.start();t2.start();} catch (IOException e) {e.printStackTrace();}}
}
class ClientWriteData implements Runnable{private Scanner scan;private Socket socket;public ClientWriteData(Socket socket){this.socket = socket;}public void run(){try {OutputStream out = socket.getOutputStream();OutputStreamWriter osw;osw = new OutputStreamWriter(out,"GBK");PrintWriter pw = new PrintWriter(osw,true);System.out.println("请输入用户名:");scan = new Scanner(System.in);String str0 = scan.next();pw.println(str0);while(true){System.out.println("请选择私聊或者群发:1表示私聊\t2表示群发");int n = scan.nextInt();pw.println(n);switch(n){case 1:System.out.println("请输入私聊对象: ");String name = scan.next();pw.println(name);System.out.println("私聊的信息:");String str = scan.next();pw.println(str);break;case 2:System.out.println("请输入群发消息:");System.out.println(str0+"正在发消息:");String str1 = scan.next();pw.println(str1);break;default:System.out.println("退出消息发送:");System.exit(0);}}} catch (IOException e) {e.printStackTrace();}}
}
class ClientReadData implements Runnable{private Socket socket;public ClientReadData(Socket socket){this.socket = socket;}public void run(){try {InputStream is = socket.getInputStream();InputStreamReader isr = new InputStreamReader(is,"GBK");BufferedReader br = new BufferedReader(isr);String str = null;while((str = br.readLine())!=null){System.out.println(str);}} catch (IOException e) {System.err.println("服务端已断开!!!");e.printStackTrace();}}
}
服务端:
public class Server {//群发//定义一个集合,用于存储所有客户端的输出流List<PrintWriter> allPw = new ArrayList<>();//用于根据用户名存储客户端输出流Map<String,PrintWriter> map = new HashMap();//创建线程池对象//ExecutorService pool = Executors.newCachedThreadPool();public static void main(String[] args) {try {//向系统申请端口号 端口号是系统中所有程序没有使用过的端口,才能使用成功//端口号的范围:0-65535之间 0-1023之间是系统端口ServerSocket server = new ServerSocket(8088);System.out.println("服务端开启成功!!!");Server s = new Server();while(true){System.out.println("等待客户端连接:");//阻塞方法 accept():等待客户端的连接Socket accept = server.accept();System.out.println("和客户端连接成功!!!");//获取IP地址InetAddress address = accept.getInetAddress();String ip = address.getHostAddress();System.out.println(ip);Thread t = new Thread(s.new ClientHandler(accept));t.start();}} catch (IOException e) {e.printStackTrace();}}class ClientHandler implements Runnable{private Socket socket;public ClientHandler(Socket socket){this.socket = socket;}public void run(){try{InputStream is = socket.getInputStream();InputStreamReader isr = new InputStreamReader(is,"GBK");BufferedReader br = new BufferedReader(isr);OutputStream os = socket.getOutputStream();OutputStreamWriter osw = new OutputStreamWriter(os,"GBK");PrintWriter pw = new PrintWriter(osw,true);//一个客户端连接服务器,则把输出流存入到集合中String str = null;//用户名String name = br.readLine();allPw.add(pw);map.put(name, pw);while((str = br.readLine())!=null){if(str.equals("1")){//私聊工作//需要私聊的对象str = br.readLine();//私聊对象的输出流PrintWriter p = map.get(str);str = br.readLine();p.println(str);}else{//群发System.out.println("");str = br.readLine();for(PrintWriter p:allPw){if(p == pw){continue;}p.println(str);}}}}catch(IOException e){e.printStackTrace();}}}
}