文章目录
- 一.NIO结合Scoket的网络通信
- 1.NIO通道客户端
- 2. NIO通道服务端
- 3. NIO通道练习
- 4. NIO通道练习优化
- 5. NIO选择器
- 6. NIO选择器改写服务端
一.NIO结合Scoket的网络通信
1.NIO通道客户端
- 客户端实现步骤
- 打开通道
- 指定IP和端口号
- 写出数据
- 释放资源
- 示例代码
public class NIOClient {
public static void main(String[] args) throws IOException {
//1.打开通道
SocketChannel socketChannel = SocketChannel.open();
//2.指定IP和端口号
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
//3.写出数据
ByteBuffer byteBuffer = ByteBuffer.wrap("NIO结合Scoket的网络通信".getBytes());
socketChannel.write(byteBuffer);
//4.释放资源
socketChannel.close();
}
}
2. NIO通道服务端
- NIO通道
服务端通道
只负责建立建立,不负责传递数据
客户端通道
建立建立并将数据传递给服务端
缓冲区
客户端发送的数据都在缓冲区中
服务端通道内部创建出来的客户端通道
相当于客户端通道的延伸用来传递数据
- 服务端实现步骤
打开一个服务端通道
绑定对应的端口号
通道默认是阻塞的,需要设置为非阻塞
此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
给客户端回写数据
释放资源
- 示例代码
public class NIOServer {
public static void main(String[] args) throws IOException {
// 1.打开一个服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2.绑定对应的端口号
serverSocketChannel.bind(new InetSocketAddress(10000));
// 3.通道默认是阻塞的,需要设置为非阻塞
//如果传递true 表示通道设置为阻塞通道...默认值
//如果传递false 表示通道设置为非阻塞通道
serverSocketChannel.configureBlocking(false);
// 4.此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
while (true) {
// 5.如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
//此时已经设置了通道为非阻塞
//所以在调用方法的时候,如果有客户端来连接,那么会创建一个SocketChannel对象.
//如果在调用方法的时候,没有客户端来连接,那么他会返回一个null
SocketChannel socketChannel = serverSocketChannel.accept();
//System.out.println(socketChannel);
if(socketChannel != null){
// 6.客户端将缓冲区通过通道传递给服务端,就到了这个延伸通道socketChannel里面
// 7.服务端创建一个空的缓冲区装数据并输出
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
//获取传递过来的数据,并把他们放到byteBuffer缓冲区中.
//返回值:
//正数: 表示本次读到的有效字节个数.
//0 : 表示本次没有读到有效字节.
//-1 : 表示读到了末尾
int len = socketChannel.read(byteBuffer);
System.out.println(new String(byteBuffer.array(),0,len));
//8.释放资源
socketChannel.close();
}
}
}
}
3. NIO通道练习
客户端
- 实现步骤
- 打开通道
- 指定IP和端口号
- 写出数据
- 读取服务器写回的数据
- 释放资源
- 示例代码
public class Clinet {
public static void main(String[] args) throws IOException {
// 1.打开通道
SocketChannel socketChannel = SocketChannel.open();
// 2.指定IP和端口号
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
// 3.写出数据
ByteBuffer byteBuffer1 = ByteBuffer.wrap("NIO结合Scoket的网络通信双向通信版".getBytes());
socketChannel.write(byteBuffer1);
// 手动写入结束标记
socketChannel.shutdownOutput();
System.out.println("数据已经写给服务器");
// 4.读取服务器写回的数据
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len = socketChannel.read(byteBuffer2)) != -1){
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len));
byteBuffer2.clear();
}
// 5.释放资源
socketChannel.close();
}
}
- 服务端
实现步骤
打开一个服务端通道
绑定对应的端口号
通道默认是阻塞的,需要设置为非阻塞
此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
给客户端回写数据
释放资源
- 示例代码
public class Clinet {
public static void main(String[] args) throws IOException {
// 1.打开通道
SocketChannel socketChannel = SocketChannel.open();
// 2.指定IP和端口号
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
// 3.写出数据
ByteBuffer byteBuffer1 = ByteBuffer.wrap("NIO结合Scoket的网络通信双向通信版".getBytes());
socketChannel.write(byteBuffer1);
// 手动写入结束标记
socketChannel.shutdownOutput();
System.out.println("数据已经写给服务器");
// 4.读取服务器写回的数据
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len = socketChannel.read(byteBuffer2)) != -1){
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len));
byteBuffer2.clear();
}
// 5.释放资源
socketChannel.close();
}
}
- 服务端
实现步骤
打开一个服务端通道
绑定对应的端口号
通道默认是阻塞的,需要设置为非阻塞
此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
给客户端回写数据
释放资源
- 示例代码
public class Sever {
public static void main(String[] args) throws IOException {
// 1,打开一个服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
// 2,绑定对应的端口号
serverSocketChannel.bind(new InetSocketAddress(10000));
// 3,通道默认是阻塞的,需要设置为非阻塞
serverSocketChannel.configureBlocking(false);
// 4,此时没有门卫大爷,所以需要经常看一下有没有连接发过来没?
while(true){
// 5,如果有客户端来连接了,则在服务端通道内部,再创建一个客户端通道,相当于是客户端通道的延伸
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){
System.out.println("此时有客户端来连接了");
// 6,获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
//socketChannel.read(byteBuffer1);
int len;
//针对于缓冲区来讲
//如果 从添加数据 ----> 获取数据 flip
//如果 从获取数据 ----> 添加数据 clear
while((len = socketChannel.read(byteBuffer1)) != -1){
byteBuffer1.flip();
System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
System.out.println("接收数据完毕,准备开始往客户端回写数据");
// 7,给客户端回写数据
ByteBuffer byteBuffer2 = ByteBuffer.wrap("您来了!!".getBytes());
socketChannel.write(byteBuffer2);
// 8,释放资源
socketChannel.close();
}
}
}
}
4. NIO通道练习优化
- 存在问题 服务端内部获取的客户端通道在读取时,如果读取不到结束标记就会一直阻塞
- 解决方案 将服务端内部获取的客户端通道设置为非阻塞的
- 示例代码
// 客户端
public class Clinet {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
ByteBuffer byteBuffer1 = ByteBuffer.wrap("NIO结合Scoket的网络通信".getBytes());
socketChannel.write(byteBuffer1);
System.out.println("数据已经写给服务器");
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len = socketChannel.read(byteBuffer2)) != -1){
System.out.println("客户端接收回写数据");
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len));
byteBuffer2.clear();
}
socketChannel.close();
}
}
// 服务端
public class Sever {
public static void main(String[] args) throws IOException {
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(10000));
serverSocketChannel.configureBlocking(false);
while(true){
SocketChannel socketChannel = serverSocketChannel.accept();
if(socketChannel != null){
System.out.println("此时有客户端来连接了");
// 将服务端内部获取的客户端通道设置为非阻塞的
socketChannel.configureBlocking(false);
//获取客户端传递过来的数据,并把数据放在byteBuffer1这个缓冲区中
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
//socketChannel.read(byteBuffer1);
int len;
//针对于缓冲区来讲
//如果 从添加数据 ----> 获取数据 flip
//如果 从获取数据 ----> 添加数据 clear
while((len = socketChannel.read(byteBuffer1)) > 0){
System.out.println("服务端接收发送数据");
byteBuffer1.flip();
System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
System.out.println("接收数据完毕,准备开始往客户端回写数据");
ByteBuffer byteBuffer2 = ByteBuffer.wrap("您来了!!!".getBytes());
socketChannel.write(byteBuffer2);
socketChannel.close();
}
}
}
}
5. NIO选择器
- 概述 选择器可以监视通道的状态,多路复用
- 选择器对象
- Selector 选择器对象
- SelectionKey 绑定的key
- SelectableChannel
能使用选择器的通道
- SocketChannel
- ServerSocketChannel
6. NIO选择器改写服务端
- 实现步骤
- 打开一个服务端通道(open)
- 绑定对应的端口号
- 通道默认是阻塞的,需要设置为非阻塞
- 打开一个选择器(门卫大爷)
- 将选择器绑定服务端通道,并监视服务端是否准备好
- 如果有客户端来连接了,大爷会遍历所有的服务端通道,谁准备好了,就让谁来连接 连接后,在服务端通道内部,再创建一个客户端延伸通道
- 如果客户端把数据传递过来了,大爷会遍历所有的延伸通道,谁准备好了,谁去接收数据
- 代码实现
// 客户端
public class Clinet {
public static void main(String[] args) throws IOException {
SocketChannel socketChannel = SocketChannel.open();
socketChannel.connect(new InetSocketAddress("127.0.0.1",10000));
ByteBuffer byteBuffer1 = ByteBuffer.wrap("NIO结合Scoket的网络通信".getBytes());
socketChannel.write(byteBuffer1);
System.out.println("数据已经写给服务器");
ByteBuffer byteBuffer2 = ByteBuffer.allocate(1024);
int len;
while((len = socketChannel.read(byteBuffer2)) != -1){
System.out.println("客户端接收回写数据");
byteBuffer2.flip();
System.out.println(new String(byteBuffer2.array(),0,len));
byteBuffer2.clear();
}
socketChannel.close();
}
}
// 服务端
public class Server {
public static void main(String[] args) throws IOException {
//1.打开服务端通道
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
//2.让这个通道绑定一个端口
serverSocketChannel.bind(new InetSocketAddress(10000));
//3.设置通道为非阻塞
serverSocketChannel.configureBlocking(false);
//4.打开一个选择器
//Selector --- 选择器
// SelectionKey --- 绑定通道后返回那个令牌
// SelectableChannel --- 可以使用选择器的通道
Selector selector = Selector.open();
//5.绑定选择器和服务端通道
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
while(true){
System.out.println("11");
//选择器会监视客户端通道的状态.
//6.返回值就表示此时有多少个客户端来连接.
int count = selector.select();
System.out.println("222");
if(count != 0){
System.out.println("有客户端来连接了");
//7.会遍历所有的服务端通道.看谁准备好了,谁准备好了,就让谁去连接.
//获取所有服务端通道的令牌,并将它们都放到一个集合中,将集合返回.
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
//selectionKey 依次表示每一个服务端通道的令牌
SelectionKey selectionKey = iterator.next();
if(selectionKey.isAcceptable()){
//可以通过令牌来获取到了一个已经就绪的服务端通道
ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
//客户端的延伸通道
SocketChannel socketChannel = ssc.accept();
//将客户端延伸通道设置为非阻塞的
socketChannel.configureBlocking(false);
socketChannel.register(selector,SelectionKey.OP_READ);
//当客户端来连接的时候,所有的步骤已经全部执行完毕.
}else if(selectionKey.isReadable()){
//当前通道已经做好了读取的准备(延伸通道)
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
ByteBuffer byteBuffer1 = ByteBuffer.allocate(1024);
//socketChannel.read(byteBuffer1);
int len;
while((len = socketChannel.read(byteBuffer1)) > 0){
byteBuffer1.flip();
System.out.println(new String(byteBuffer1.array(),0,len));
byteBuffer1.clear();
}
//给客户端的回写数据
socketChannel.write(ByteBuffer.wrap("您来了!!!".getBytes()));
socketChannel.close();
}
iterator.remove();
}
}
}
}
}