非阻塞I/O
Client.java
import java.io.*;
import java.net.Socket;
public class Client {
public static void main (String ... argas){
ClientHandle clientHandle = null;
try {
clientHandle=new ClientHandle("127.0.0.1",9092);
new Thread(clientHandle,"CLIENT-THREAD").start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
ClientHandle.java
import javax.crypto.SecretKey;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.Buffer;
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.Set;
public class ClientHandle implements Runnable{
private int port;
private volatile boolean stop;
private Selector selector;
private String ip;
private SocketChannel socketChannel;
public ClientHandle(String ip ,int port)throws IOException{
this.ip=ip;
this.port=port;
selector=Selector.open();
socketChannel=SocketChannel.open();
socketChannel.configureBlocking(false);
}
@Override
public void run() {
try {
doConnect();
} catch (IOException e) {
e.printStackTrace();
}
while (!stop){
try {
selector.select(2000);
} catch (IOException e) {
e.printStackTrace();
}
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while(iterator.hasNext()){
SelectionKey key=iterator.next();
iterator.remove();
try {
handleKey(key);
} catch (IOException e) {
e.printStackTrace();
}
}
}
//多路复用器关闭后,所有注册在上面的channel和pipe等资源都会自动去注册并关闭,不会浪费资源
if(selector !=null){
try {
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
public void handleKey(SelectionKey key) throws IOException {
if(key.isValid()){
SocketChannel sc = (SocketChannel) key.channel();
if(key.isConnectable()){
if(sc.finishConnect()){
sc.register(selector,SelectionKey.OP_READ);
doWrite(sc);
}else{
System.exit(1);
}
}
if(key.isReadable()){
ByteBuffer buffer= ByteBuffer.allocate(1024);
int read = sc.read(buffer);
if(read > 0 ){
//buffer长度是1024 ,假如但是实际存储的位置10,那我要把0-10的数据读取出来,上线limit设置为10
buffer.flip();
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body = new String(bytes,"UTF-8");
System.out.println("客户端接收到服务数据,内容:"+body);
this.stop=true;
}else if(read <0){
key.cancel();
sc.close();
}
}
}
}
public void doConnect() throws IOException{
//建立间接成功,注册读取时间
if(socketChannel.connect(new InetSocketAddress(ip,port))){
socketChannel.register(selector,SelectionKey.OP_READ);
//成功发送数据给服务器
doWrite(socketChannel);
}else {
//失败继续注册连事件
socketChannel.register(selector,SelectionKey.OP_CONNECT);
}
}
private void doWrite(SocketChannel socketChannel) throws IOException {
byte[] bytes = "我是客户端".getBytes();
ByteBuffer buffer=ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
socketChannel.write(buffer);
//告诉我是否已经达到缓冲区的上界
if(!buffer.hasRemaining()){
System.out.println("客户端发送完毕");
}
}
}
Server.java
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static void main (String ... args ){
ServerHandle serverHandle = null;
try {
serverHandle = new ServerHandle(9092);
} catch (IOException e) {
e.printStackTrace();
}
new Thread(serverHandle,"SERVER-THREAD").start();
}
}
ServerHandle.java
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Iterator;
import java.util.Set;
public class ServerHandle implements Runnable {
private int port;
private volatile boolean stop;
private Selector selector;
private ServerSocketChannel serverSocketChannel;
public ServerHandle(int port) throws IOException {
this.port = port;
selector = Selector.open();
serverSocketChannel = ServerSocketChannel.open();
//设置非阻塞模式
serverSocketChannel.configureBlocking(false);
//监听端口
serverSocketChannel.bind(new InetSocketAddress(port));
//注册ACCEPT 事件,表示关注客户端的注册事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
System.out.println("服务器初始化完毕");
}
public void stop() {
this.stop = true;
}
@Override
public void run() {
while (!stop) {
try {
//每隔1s检查一次
selector.select(1000);
//获取到已经准备的keys,就是已经准备好的事件
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
//获取单个事件
SelectionKey key = iterator.next();
//处理key
try {
handleKey(key);
} catch (Exception ex) {
//出异常了,取消key
if (null != key) {
key.cancel();
if (key.channel() != null) {
try {
key.channel().close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}finally {
//处理后,把key移除掉
iterator.remove();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
if (null != selector) {
try {
//多路复用器关闭后,所有注册在复用器上面的channel和pipe都会自动关闭
selector.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
private void handleKey(SelectionKey key) throws IOException {
//判断key是否有效
if (key.isValid()) {
//key 连接状态
if(key.isAcceptable()){
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
SocketChannel sc = ssc.accept();
sc.configureBlocking(false);
//连接后,注册OP_READ事件
sc.register(selector, SelectionKey.OP_READ);
System.out.println("客户端来连接了,帮他注册读事件");
}
//key是否可读?
if(key.isReadable()){
SocketChannel sc = (SocketChannel) key.channel();
ByteBuffer buffer = ByteBuffer.allocate(1024);
//读取数据
int readData = sc.read(buffer);
// > 0读到了数据
if(readData>0){
//将指针置位0
buffer.flip();
//buffer.reamining()返回流中可用的剩余数据长度
byte[] bytes = new byte[buffer.remaining()];
buffer.get(bytes);
String body = new String(bytes,"UTF-8");
System.out.println("服务器:接收到客户端的数据,内容:"+body);
//发送数据给客户端
doWrite(sc,"现在时间是:"+new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date()));
//将key标记为可读
key.interestOps(SelectionKey.OP_READ);
}else if(readData<0){
//链路关闭,取消key,关闭sc
key.cancel();
sc.close();
}else {
//readData=0 没有取得数据
}
}
}
}
private void doWrite(SocketChannel socketChannel, String hello) throws IOException {
if(hello!=null){
byte[] bytes = hello.getBytes();
//在JVM内存中开辟一块内存 Dirtect方法是在系统内存中开辟
ByteBuffer buffer = ByteBuffer.allocate(bytes.length);
buffer.put(bytes);
buffer.flip();
socketChannel.write(buffer);
}
}
}
基于Netty的客户端与服务端通信
先了解Netty的主要结构
原 https://blog.youkuaiyun.com/H_crab/article/details/96484433
关于ChannelFuture https://blog.youkuaiyun.com/liyuan0323/article/details/79145647
Channel接口
//管道
基本的I/O操作(bing()、connect()、read()、和write())依赖于底层网络传输所提供的原始。在基于Java的网络编程中,其基本的构造是class Socket。Netty的Channel接口所提供的API,大大地降低了直接使用Socket类的复杂性。此外,Channel也是拥有许多预定义的、专门化实现的广泛类层次结构的根,下面是一个简短的部分清单:
- EmbeddedChannel;
- LocalServerChannel;
- NioDatagramChannel;
- NioSctpChannel;
- NioSocketChannel;
EventLoop接口
//处理事件
EventLoop定义了Netty的核心抽象,用于处理连接的生命周期中所发生的事件。
- 一个EventLoopGroup包含一个或者多个EventLoop;
- 一个EventLoop在它的生命周期内只和一个Thread绑定;
- 所有由EventLoop处理得I/O事件都将在它专有的Thread上处理
- 一个Channel在它的生命周期内只注册一个EventLoop;
- 一个EventLoop可能会被分配给一个或多个Channel。
注意,一个给定的Channel的I/O操作都是由相同的Thread执行的,实际上消除了对于同步的需要
ChannelFuture接口
//ChannelFuture的作用是用来保存Channel异步操作的结果。
Netty所有的I/O操作都是异步的。因为一个操作可能不会立即返回,所以我们需要一种用于在之后得某个时间点确定其结果的方法。为此,Netty提供了ChannelFuture接口,其addListener()方法注册了一个ChannelFutureListener,以便在某个操作完成时(无论是否成功)得到通知。
Server.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
public static void main (String ... args ){
int port = 9092;
new Server().bind(port);
}
private void bind(int port){
EventLoopGroup bossGroup = new NioEventLoopGroup();//1 接受请求
EventLoopGroup workGorup = new NioEventLoopGroup();//2 实际处理
ServerBootstrap bootstrap = new ServerBootstrap();
ChannelFuture channelFuture = null;//3
try {
channelFuture = bootstrap.group(bossGroup,workGorup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG,1024)
//ChannelOption.参考
//https://www.cnblogs.com/googlemeoften/p/6082785.html
.childHandler(new ServerChannelHandle())
.bind(port).sync();
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
channelFuture.channel().closeFuture().sync();//4
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();
workGorup.shutdownGracefully();
bootstrap.clone();
}
}
}
ServerChannelHandle.java
import io.netty.channel.ChannelInitializer;
//netty下面的SocketChannel 非nio下面的
import io.netty.channel.socket.SocketChannel;
public class ServerChannelHandle extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ServerHandle());
}
}
ServerHandle.java
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.buffer.ByteBuf;
import java.io.UnsupportedEncodingException;
import java.text.SimpleDateFormat;
import java.util.Date;
public class ServerHandle extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg) throws UnsupportedEncodingException {
ByteBuf buffer = (ByteBuf) msg;
byte[] bytes = new byte[buffer.readableBytes()];
buffer.readBytes(bytes);
String body = new String(bytes,"UTF-8");
System.out.println("客户端发送的数据-》"+body);
String res = "现在时间是:"+ new SimpleDateFormat("yyyy-MM-dd hh:mm:ss").format(new Date());
ByteBuf byteBuf = Unpooled.copiedBuffer(res.getBytes());
ctx.writeAndFlush(byteBuf);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx){
System.out.println("EchoServerHandler.channelReadComplete");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,Throwable cause){
cause.printStackTrace();
ctx.close();
}
}
Client.java
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main (String ... argas){
int port = 9092;
String host = "127.0.0.1";
new Client().connection(host,port);
}
private void connection(String host,int port){
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
Bootstrap b = bootstrap.group(group).channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY,true)
.handler(new ChindChannelHandle());
try {
ChannelFuture f = b.connect(host,port).sync();
System.out.println("客户端启动完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
ClientHandle.java
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import java.io.UnsupportedEncodingException;
public class ClientHandle extends ChannelInboundHandlerAdapter {
@Override
public void channelRead(ChannelHandlerContext ctx,Object msg ) throws UnsupportedEncodingException {
ByteBuf buffer = (ByteBuf) msg;
byte[] bytes = new byte[buffer.readableBytes()];
buffer.readBytes(bytes);
String body = new String (bytes,"UTF-8");
System.out.println("服务器返回-》"+body);
}
@Override
public void channelActive(ChannelHandlerContext ctx){
ByteBuf buffer = Unpooled.copiedBuffer("我是客户端".getBytes());
ctx.writeAndFlush(buffer);
}
}
ChindChannelHandle.java
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
public class ChindChannelHandle extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ClientHandle());
}
}