一.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();
}
}
}
}