纯手打,总结!
Netty是什么? Netty是当前非常流行的网络通讯开源框架,高并发和高可靠,底层就可以用Netty支撑。
Netty 官网:https://netty.io/
学习视频:https://www.bilibili.com/video/BV1DJ411m7NR?from=search&seid=17854453460358540907
Netty 特点
异步、基于事件驱动的网络应用架构
快速开发高性能、高可用的 IO 程序
针对 TCP/IP 协议下 面向Client端 高并发应用
本质是NIO框架,适用于服务器通讯的多种场景
什么是同步 和 异步
同步: 浏览器发送请求,等待服务端进行相应后浏览器在做操作 这个过程就叫同步
异步:浏览器发送请求,无需服务器响应出结果,继续浏览器渲染接下来的操作,这个过程就叫异步
Netty 结构图
Netty 是基于 JDK下的NIO 的框架
Netty 场景
RPC框架 :阿里的分布式框架Dubbo用到了
手游、网游服务器
可以理解成PRC框架 都会
I/O模型
I/O模型简单理解:就是用什么样的通道进行数据的发送和接收,很大程度决定程序的性能
Java支持3中网络编程模型:BIO、NIO、AIO
什么是BIO?
特点:同步并阻塞、一个连接一个线程:即客户端有连接请求,服务端就开一个线程,如果客户端连接了服务器不操作,就会造成服务端线程的浪费。阻塞:如果没有读取到,那就一直等待。
什么NIO?
特点:同步非阻塞、一个线程处理多个连接:即客户端发送的连接请求都会转到多路复用器上,进行轮询检查连接状态,进行处理,避免连接闲置。提高服务器资源的不必要开支。
什么是AIO?
特点 NIO.2 异步非阻塞,AIO 引入异步通道概念,采用Proactor,简化程序编写,有效的请求,才会启动线程。现在暂时并没有有效的应用。
I/O模型使用场景
BIO:适用于连接数目少且固定的架构,高并发不适用,是JDK1.4之前唯一选择
NIO:适用于连接数目多,但是连接短。JDK1.4 开始支持。场景有:聊天服务器、弹幕服务器、地图服务器之间的通讯。
AIO:适用于连接数目多,连接长。比如相册服务器(这里不理解)。会利用OS惨与并发操作,编程较为复杂。JDK7开始支持
JAVA BIO 基本介绍
BIO :blocking I/O 同步阻塞 实现类和接口在 java.io
编程的简单流程
- 服务器端启动一个ServerSocket。
- 客户端启动一个Secork 连接服务器,默认情况下,服务器端需要对每个客户建立一个线程与之通讯。
- 客户端发出请求,先咨询服务器是否有线程响应,如果没有则会等待,或者被拒绝。
- 如果有相应,客户端线程会等待请求结束后,才继续执行。
BIO 应用实例
实例说明:使用BIO模型编写一个服务器端,使用6666端口,客户端连接时,就启动一个线程与之通讯。
要求使用线程池机制改善,可以连接多个客户端
服务器端可以接收客户端发送的数据(telnet 方式即可)
测试方式 需要安装的内容
代码实现
代码语言:javascript复制package com.zlk.BIO;
import java.io.IOException;
import java.io.InputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class bioServer {
public static void main(String[] args) throws IOException {
/* 线程池机制 思路
1、创建一个线程池
2、如果有客户端连接,就创建一个线程,与之通讯
*/
// 具体实现
ExecutorService newCachedThreadPool = Executors.newCachedThreadPool();
//创建ServerSocket
ServerSocket serverSocket = new ServerSocket(6666);
System.out.println("服务端已经启动了");
while (true) {
// 监听,等待客户端连接
final Socket socket = serverSocket.accept();
System.out.println("连接到一个客户端...");
// 创建一个线程 与之通讯
newCachedThreadPool.execute(new Runnable() {
public void run() {
// 可以和客户端通讯了
handler(socket);
}
});
}
}
// handler方法与客户端通讯
public static void handler(Socket socket) {
try {
System.out.println("线程ID:" Thread.currentThread().getId() " 线程名称:" Thread.currentThread().getName());
byte[] bytes = new byte[1024];
// 通过socket获取输入流
InputStream inputStream = socket.getInputStream();
while (true) {
System.out.println("等待发送信息...");
int read = inputStream.read(bytes);
if (read != -1) {
System.out.print("执行Read ... -->");
System.out.print("线程ID:" Thread.currentThread().getId() " 线程名称:" Thread.currentThread().getName() " 输入了:");
System.out.println(new String(bytes, 0, read)); //输出客户端发送的数据
} else {
break;
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("关闭 和 cilent的连接");
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
测试:
按一下 Ctrl ] 键 ,然后输入命令 send XXXXX 回车 就可以按照一长串输入
控制台结果
Java NIO 编程
JDK 在1.4之后 追加 在java.nio包 及其子包下,并对java.io包很多类进行改写
NIO 三大核心部分
- Channel(通道)
- Buffer(缓冲区)
- Selector(选择器)
具体三者结构逻辑
NIO 是面向缓冲区,或者面向块编程。数据读取到一个 稍后处理的缓冲区,需要时可在缓冲器中前后移动,提高了网络处理过程的灵活性、伸缩性。
在Http2.0 引入的多路复用技术,做到一个连接处理多个请求,并发请求数量提高很多倍
NIO Buff基本使用
代码语言:javascript复制import java.nio.IntBuffer;
/*
举例说明Buffer的使用
*/
public class basicBuffer {
public static void main(String[] args) {
// 创建一个Buffer:可以存放5个int
IntBuffer intBuffer = IntBuffer.allocate(5);
// 向Buffer 存放数据 有序
intBuffer.put(10);
intBuffer.put(11);
intBuffer.put(12);
intBuffer.put(13);
intBuffer.put(14);
// 从Buffer读取数据
// 将Buffer转换:读写切换 ,默认是写
intBuffer.flip();
while (intBuffer.hasRemaining()){
System.out.println(intBuffer.get());
}
}
}
NIO BIO 对比
BIO 是以流的方式 进行处理数据,NIO以块的方式处理数据。块I/O比流I/O效率高很多。
BIO 是同步阻塞、NIO是同步非阻塞
BIO基于 字节、字符流进行操作、NIO基于 Channel、Buffer 进行操作。
NIO 原理分析
NIO是 数据从Buffer 传输到 Channel 再通过 Selector监听很多个Channel事件,所以 单个线程 就做到了非阻塞
NIO 中 一个 Buffer 对应一个Channel,一个Selector 对应多个 Channel 。一个Selector 对应一个线程,
程序切换到那个 Channel 取决于 事件。
Selector会根据不同 Event事件,在各个Channel切换
Buffer就是一个内存块,底层是一个数组
数据读取、写入都是要通过Buffer,这个和BIO有区别(BIO 无Buffer)
BIO 是单向流,而NIO传输Buffer是双向的,需要手动 filp() 切换。
Channel也是双向的,可以很好的反应底层操作系统情况。例如:Linux 。
Buffer
本质就是一个读写数据的内存块,可以理解成一个容器
我们可以点进 java.nio包下的 Buffer 按一下 Alt H 看到
同时上图 有 4个属性,具体含义如下:
代码语言:javascript复制 // Invariants: mark <= position <= limit <= capacity
private int mark = -1; // 标记
private int position = 0;//下一个要被 读或写 元素的索引(每次读或写都会变化):作用是 为下一次读或写做准备
private int limit; // 缓冲区终点
private int capacity; // 容量
先看一下 反转的方法 flip 仔细看
在看一下 clear的 内容
用的最多的子类是 ByteBuffer,因为网络传输是以字节形式传输,下面会详细讲解
通道 Channel
NIO的Channel类似于流 通道与流的区别
通道可以同时写或读,而流是单向的
常见的 通道 方法 Channel(下面方法经常需要复习)
FileChannel 常用方法:
read(ByteBuffer dst) 从通道读取数据放入缓冲 buffer 区
write(ByteBuffer src) 把缓冲区的数据写道通道
transferFrom(ReadableByteChannel src, long position, long count) 从目标通道复制到当前通道
transferTo(long position, long count, WritableByteChannel target) 把数据从当前通道复制给目标通道。
Channel应用实例1 – 写进文件
实例要求:使用ByteBuffer 和 FileChannel 将Hello 世界,写入 file01.txt中,如果文件不存在,就创建。
代码
代码语言:javascript复制import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannel {
public static void main(String[] args) throws IOException {
String str = "hello 世界";
//创建输出流 包装到Channel
FileOutputStream fileOutputStream = new FileOutputStream("d:\file01.txt");
//通过fileOutputStream 整合FileChannel 但是真实的实现是 FileChannelImpl 所有的真实实现都是 XXXImpl
FileChannel fileChannel = fileOutputStream.getChannel();
//创建一个缓冲区
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//数据放入缓存区
byteBuffer.put(str.getBytes());
//写完后 开始反转 开始读
byteBuffer.flip();
//将byteBuffer 写入到Channel
fileChannel.write(byteBuffer);
fileOutputStream.close();
}
}
结果
Channel应用实例2 – 读取文件
实验实例:读取上个实例
代码语言:javascript复制import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannel2 {
public static void main(String[] args) throws IOException {
// 得到文件对象
File file = new File("d:\file01.txt");
// 创建文件输入流
FileInputStream fileInputStream = new FileInputStream(file);
// 根据文件输入流创建通道
FileChannel channel = fileInputStream.getChannel();
// 获取缓冲区,因为知道文件长度,就没必要设置1024
ByteBuffer byteBuffer = ByteBuffer.allocate((int) file.length());
// 通道读取缓冲区数据
channel.read(byteBuffer);
// 将byteBuffer的字节数据转为String
System.out.println(new String(byteBuffer.array()));
// 释放输入流资源
fileInputStream.close();
}
}
结果:
Channel应用实例3 – 复制文件
案例要求: 复制文件内容 放到一个新文件
代码语言:javascript复制import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.FileChannel;
public class NIOFileChannel3 {
public static void main(String[] args) throws IOException {
FileInputStream fileInputStream = new FileInputStream("d:\file01.txt");
FileChannel channelIn = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("d:\file02.txt");
FileChannel channelOut = fileOutputStream.getChannel();
ByteBuffer byteBuffer = ByteBuffer.allocate(512);
// 开始读取数据
while (true){
//重置Buffer属性 不加就会死循环
byteBuffer.clear();
int read = channelIn.read(byteBuffer);
System.out.println("read is " read);
if(read == -1){
break;
}else {
byteBuffer.flip();
channelOut.write(byteBuffer);
}
}
System.out.println("文件复制成功");
fileOutputStream.close();
fileInputStream.close();
}
}
不加clear 会造成死循环 产生的原因 分析:
read(Buffer) 返回的是读取的行数结果
read 一开始从 position = 0 开始读取 一致读到 limit 此时 position 变为最后读取的最后一位
以后 不论怎么样 read 或 write 都是从 position = limit 只有 flip执行 将 position = 0,才读取第一次的内容,并且写入,会就造成文件一些被写入第一次写入的内容,并且不换行!
正常代码最终结果:
Channel应用实例3 – 通道复制文件
实例要求:使用FileChannel的transferFrom() 完成文件拷贝
代码语言:javascript复制import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.nio.channels.FileChannel;
public class NIOFileChannel4 {
public static void main(String[] args) throws IOException {
// 创建流 以及 相关通道
FileInputStream fileInputStream = new FileInputStream("d:\java.png");
FileChannel channelIn = fileInputStream.getChannel();
FileOutputStream fileOutputStream = new FileOutputStream("d:\java2.png");
FileChannel channelOut = fileOutputStream.getChannel();
// 使用transferFrom 完成拷贝
channelOut.transferFrom(channelIn,0,channelIn.size());
System.out.println("拷贝完成");
// 释放资源
channelOut.close();
channelIn.close();
fileOutputStream.close();
fileInputStream.close();
}
}
结果:
Buffer类型化 与 只读
代码语言:javascript复制import java.nio.ByteBuffer;
public class ReadOnlyBuffer {
public static void main(String[] args) {
//创建BUffer
ByteBuffer byteBuffer = ByteBuffer.allocate(64);
//循环添加
for (int i = 0; i < 64; i ) {
// byteBuffer.putInt((byte) i); // 记得 int 转成 byte 他依旧占用4个字节
byteBuffer.put((byte)i); // 一开始 就是byte 就是1字节了
}
//反转去读取
byteBuffer.flip();
//获取只读buffer
ByteBuffer readOnlyBuffer = byteBuffer.asReadOnlyBuffer();
//读取
while (readOnlyBuffer.hasRemaining()) {
System.out.println(readOnlyBuffer.get());
}
// java.nio.ReadOnlyBufferException 一旦设置只读后 就不能在添加数据了 readOnlyBuffer.put((byte) 1);
}
}
Buffer 分散与聚合
Scattering :将数据写入到Buffer。 可以采用buffer数组,依次写入(一个buffer写满了 写入到第二个buffer)
Gathering :从buffer读取数据时。可以采用buffer数组,依次读取()
代码语言:javascript复制import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
/**
* Scattering:将数据写入到Buffer。 可以采用buffer数组,依次写入(一个buffer写满了 写入到第二个buffer)
* Gathering:从buffer读取数据时。可以采用buffer数组,依次读取()
*/
public class ScatteringAndGatheringTest {
public static void main(String[] args) throws IOException {
// 使用ServerSocketChannel 和 SockerChannel
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(7000);
serverSocketChannel.socket().bind(inetSocketAddress);
ByteBuffer[] byteBuffers = new ByteBuffer[2];
// 第一个5 放满了 就会转到 第二个去放
byteBuffers[0] = ByteBuffer.allocate(5);
byteBuffers[1] = ByteBuffer.allocate(3);
System.out.println("服务器已经启动");
// 等待客户端连接
SocketChannel socketChannel = serverSocketChannel.accept();
// 规定只从客户端读取8字节
int messageLength = 8;
while (true) {
int byteRead = 0;
if (byteRead < messageLength) {
long read = socketChannel.read(byteBuffers);
byteRead = read; // 累计读取的字节数
System.out.println("本地读取了" byteRead "个字节");
// 使用流打印,看看当前buffer的 position 和 limit
Arrays.asList(byteBuffers).stream().map(buffer -> "position is " buffer.position() ",limit is " buffer.limit()).forEach(System.out::println);
}
//将所有的buffer 进行反转
Arrays.asList(byteBuffers).forEach(byteBuffer -> {
byteBuffer.flip();
});
//将数据显示到客户端
long bytewrite = 0;
while (bytewrite < messageLength){
long write = socketChannel.write(byteBuffers);
bytewrite = write;
}
// 将所有的Buffer进行clear 操作
Arrays.asList(byteBuffers).forEach(byteBuffer -> {byteBuffer.clear();});
System.out.println("byteRead is " byteRead " ,bytewrite is " bytewrite " messageLength is " messageLength);
}
}
}
测试
Selector
Selector 是个抽象类,常用方法和说明如下:
常用方法
open 得到一个选择器对象
selectorKeys 从内部集合 获取所有sekectorkey
selector(1000) 设置阻塞时间1秒
wakeup() 唤醒selector
selectorNow() 自动返回 不会阻塞
NIO 非阻塞网络编程快速入门
案例要求: 编写一个NIO入门案例,实现服务器端与客户端之间的数据简单通讯(非阻塞)
目的:理解NIO非阻塞网络编程机制
服务端
代码语言:javascript复制import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class NIOServer01 {
public static void main(String[] args) throws IOException {
//创建ServerScocketChannle 并获取 ServerSocker
ServerSocketChannel channel = ServerSocketChannel.open();
//创建Selector对象
Selector selector = Selector.open();
//让 ServerSocker 绑定6666端口,并在服务器端监听
channel.socket().bind(new InetSocketAddress(6666));
//ServerSocker 设置为非阻塞
channel.configureBlocking(false);
//把ServerSocker注册到 selector关心的事件 用枚举 SelectionKey.XXXX
channel.register(selector, SelectionKey.OP_ACCEPT);
//循环等待客户端连接
while (true){
if(selector.select(1000) == 0){ //等待1秒
System.out.println("服务器等待1秒 没有响应");
continue;
}
//如果返回值>0 就去获取关住SelecotrKeys ,以便于便利出 selectorkey获取通道
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
if(key.isAcceptable()){ //如果是OP_ACCEPT ,有新的客户端连接
//为客户端生成一个Socket
SocketChannel newsocketChannel = channel.accept();
//为新客户端设置非阻塞状态
newsocketChannel.configureBlocking(false);
System.out.println("客户端连接成功 生成一个" newsocketChannel.hashCode());
//将新的socket 注册到 Selecotr上
newsocketChannel.register(selector,SelectionKey.OP_READ, ByteBuffer.allocate(1024));
}
if(key.isReadable()) { //发生如果是 OP_READ
//通过key获取channel
SocketChannel newchannel2 = (SocketChannel)key.channel();
//获取该channel绑定的buffer
ByteBuffer byteBuffer = (ByteBuffer)key.attachment();
newchannel2.read(byteBuffer);
System.out.println("从客户端读取到的数据是" new String(byteBuffer.array()));
}
//手动从集合移动当前的SelectionKey ,防止重复操作
iterator.remove();
}
}
}
}
客户端
代码语言:javascript复制import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;
public class NIOClient {
public static void main(String[] args) throws IOException {
//得到一个网络通道
SocketChannel socketChannel = SocketChannel.open();
//设置非阻塞
socketChannel.configureBlocking(false);
//设置服务器的ip和端口
InetSocketAddress inetSocketAddress = new InetSocketAddress("127.0.0.1", 6666);
//连接服务器
if (!socketChannel.connect(inetSocketAddress)) {
while (!socketChannel.finishConnect()) {
System.out.println("连接服务器需要时间,客户端不会阻塞,仍可以做其他工作");
}
}
String str = "Hello 张三";
//wrap放法是将Byte数组 写入到一个buffer
ByteBuffer byteBuffer = ByteBuffer.wrap(str.getBytes());
//发送数据,将buffer数据写入通道channle
int write = socketChannel.write(byteBuffer);
System.in.read();
}
}
在客户端编码设置一下允许并行运行 并应用
最终结果:
Netty 群聊系统
要求:
编写一个NIO群聊系统,实现服务器与客户端之间的数据简单通讯,
实现多人群聊
服务端检测用户上线、离线,并实现消息转发功能
客户端,通过Channle 无阻塞发送消息给其他用户,同时可以接收其他用户发送的消息(由服务器转发)
先编写服务器端
- 服务器启动并监听6667
- 服务器接收客户端消息,实现转发,上下线等
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class chatServer {
//1 定义属性
private Selector selector;
private ServerSocketChannel listenChannle;
private static final int PORT = 6667;
//2 无参构造 对属性进行赋值
public chatServer() {
try {
//创建选择器
selector = Selector.open();
//创建通道
listenChannle = ServerSocketChannel.open();
//通道道绑创建的Socket 定端口
listenChannle.socket().bind(new InetSocketAddress(PORT));
//设置非阻塞
listenChannle.configureBlocking(false);
//将通道注册到Selector 监听事件
listenChannle.register(selector, SelectionKey.OP_ACCEPT);
} catch (
IOException e) {
e.printStackTrace();
}
}
public void listen() {
try {
while (true) {
// 如果count > 0 说明有事件
int count = selector.select(2000);
if (count > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
// 通道发生连接事件
if (key.isAcceptable()) {
SocketChannel sc = listenChannle.accept();
sc.configureBlocking(false);
//将改sc 注册到 selector
sc.register(selector, SelectionKey.OP_READ);
System.out.println(sc.getRemoteAddress() " 已经上线了");
}
// 通道发生Read事件
if (key.isReadable()) {
//处理读 专门写方法
readData(key);
}
// 将当前的key删除,防止重复处理
iterator.remove();
}
} else {
System.out.println("等待中...");
}
}
} catch (
Exception e) {
e.printStackTrace();
} finally {
}
}
// 读取客户端消息 传递 selectionKey的原因是 通过key 获取事件发生的通道
private void readData(SelectionKey selectionKey) {
//定义一个SocketChannel
SocketChannel socketChannel = null;
try {
socketChannel = (SocketChannel) selectionKey.channel();
socketChannel.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
int count = socketChannel.read(byteBuffer);
//根据count的值做处理
if (count > 0) {
//把缓冲区的数据转成String
String s = new String(byteBuffer.array());
System.out.println("客户端消息是:" s);
//向其他客户端发送消息 专门写一个方法处理
sendInfoToOtherClients(s, socketChannel);
}
} catch (IOException e) {
//e.printStackTrace();
try {
System.out.println(socketChannel.getRemoteAddress() "离线了");
// 关闭通道
socketChannel.close();
} catch (IOException e2) {
e2.printStackTrace();
}
}
}
// 转发消息给其他客户(通道) 参数 msg是消息
private void sendInfoToOtherClients(String msg, SocketChannel self) throws IOException {
System.out.println("服务器转发消息中");
// 获取所有注册到Selector的所有通道 并派出self
for (SelectionKey key : selector.keys()) {
//通过 key去除对应的通道
Channel targetchannel = key.channel();
//排除自己
if (targetchannel instanceof SocketChannel && targetchannel != self) {
// 转型
SocketChannel dest = (SocketChannel) targetchannel;
dest.configureBlocking(false);
// 将msg存储到buffer
ByteBuffer buffer = ByteBuffer.wrap(msg.getBytes());
// 将buffer写入通道
dest.write(buffer);
}
}
}
public static void main(String[] args) {
//创建一个服务器对象
chatServer chatServer = new chatServer();
//开始监听
chatServer.listen();
}
}
再编写客户端
代码语言:javascript复制import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Scanner;
import java.util.Set;
public class chatClient {
// 定义一下全局变量
private static final String Host = "127.0.0.1";
private static final int PORT = 6667;
private Selector selector;
private SocketChannel socketChannel;
private String username;
// 构造器赋值
public chatClient() {
try {
selector = selector.open();
socketChannel = SocketChannel.open(new InetSocketAddress(Host, PORT));
// 设置非阻塞
socketChannel.configureBlocking(false);
// 将通道注册到服务器
socketChannel.register(selector, SelectionKey.OP_READ);
username = socketChannel.getRemoteAddress().toString().substring(1);
System.out.println("客户端准备好了");
} catch (Exception e) {
e.printStackTrace();
}
}
//向服务器发送消息
public void sendInfo(String info) {
try {
info = username " 说 " info;
socketChannel.write(ByteBuffer.wrap(info.getBytes()));
} catch (Exception e) {
e.printStackTrace();
}
}
//从服务器端读取数据
public void readInfo() {
try {
int readChannle = selector.select();
// 通道有事件发生了
if (readChannle > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
if (key.isReadable()) {
SocketChannel sc = (SocketChannel) key.channel();
//设置非阻塞
sc.configureBlocking(false);
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
// 将通道数据写入buffer
sc.read(byteBuffer);
//将缓冲区的数据 转成字符串
String s = new String(byteBuffer.array());
System.out.println(s.trim());
}else {
System.out.println("没有可以读取消息的通道");
}
iterator.remove();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
// 启动客户端
chatClient chatClient = new chatClient();
// 开启一个新线程 读取客户端 发送、转发的消息
new Thread(){
public void run(){
while (true){
chatClient.readInfo();
try {
// 不知道为什么这里没有代码提示
Thread.currentThread().sleep(3000);
}catch (Exception e){
e.printStackTrace();
}
}
}
}.start();
// 发送数据 给客户端
Scanner scanner = new Scanner(System.in);
while (scanner.hasNext()){
String s = scanner.nextLine();
// 将控制台结果发送给服务器
chatClient.sendInfo(s);
}
}
}
本片文章过长,后续内容请查看 Netty2 https://www.zanglikun.com/3840.html
特殊说明: 解决问题的光鲜,藏着磕Bug的痛苦。 万物皆入轮回,谁也躲不掉! 以上文章,均是我实际操作,写出来的笔记资料,不会出现全文盗用别人文章!烦请各位,请勿直接盗用!