目录
一、Netty组件介绍
1. Channel (Socket)、EventLoop(控制流、多线程、并发)、ChannelFuture(异步通知)
(1)Channel 接口
基本的I/O操作(bind()、connect()、read()、write())依赖于底层网络传输所提供的原语,在基于Java的网络编程中其基本的构造函数是Socket。
(2)EventLoop接口
EventLoop定义了Netty的核心抽象,用于处理连接生命周期中所发生的事件。
(3)ChannelFuture接口
Netty提供了ChannelFuture接口,其addListener()方法注册了一个ChannelFutureListener,一边在某个操作完成时得到异步通知。
2. ChannelHandler(逻辑事件处理容器)、ChannelPipeline(容器链)
(1)ChannelHandler接口
ChannelHannder是Netty的主要组件,它是处理入站到出站数据的应用程序逻辑的容器(相对服务器,客户端数据进入服务器称为入站,服务器处理完后返回数据给客户端称为出站),ChannelInboundHandlerAdapter是他其中的一个重要的实现方法。该方法可以处理信息的读写,客户端连接状态监听等功能。
(2)ChannelPipeline 接口
ChannelPipeline 提供了ChannelHandler链的容器,并定义了用于在该链上传播入站和出站事件流的API。当Channel被创建时,它会被自动分配到它专属的ChannelPipeline。
3.Bootstrap(引导)
(1)Netty的引导类Bootstrap为应用程序的网络层配置提供了容器,这涉及将一个进程绑定到某个指定端口,或者将一个进程连接到另一个运行在指定主机的指定端口上的进程。引导分为两种,服务器端(ServerBootstrap)和客服端(Bootstrap)。
二、Netty实战实现长连接
添加maven依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.29.Final</version>
</dependency>
1.服务器端代码实现
(1)EchoServerHandler
import io.netty.buffer.ByteBuf;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.channel.socket.SocketChannel;
import io.netty.util.CharsetUtil;
import org.springframework.stereotype.Component;
import java.io.BufferedInputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.util.HashMap;
import java.util.Map;
@Component
@ChannelHandler.Sharable
public class EchoServerHandler extends ChannelInboundHandlerAdapter {
//以客户端ip+端口为键,对应的通道为值,存在map中。便于主动向客户端发信息
public static Map<String, Channel> map=new HashMap<>();
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//当有客户端连上时调用该方法
String client= String.valueOf(ctx.channel().remoteAddress());
Channel channel=ctx.channel();
map.put(client,channel);
System.out.println("client:"+client);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
//当服务器端收到客户端消息时调用该方法
ByteBuf in=(ByteBuf)msg;
String message=in.toString(Charset.forName("UTF-8"));
System.out.println("服务器收到消息:"+message);
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
super.channelReadComplete(ctx);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
//当客户端发生异常时调用该方法
map.remove(ctx.channel().remoteAddress());
System.out.println("host:"+ctx.channel().remoteAddress()+"链接断开");
}
}
(2)EchoServer
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import java.net.InetSocketAddress;
public class EchoServer {
private final int port;
public EchoServer(int port) {
this.port = port;
}
public void start() throws InterruptedException {
EchoServerHandler serverHandler=new EchoServerHandler();
EventLoopGroup group=new NioEventLoopGroup();
try{
ServerBootstrap bootstrap=new ServerBootstrap();
bootstrap.group(group).channel(NioServerSocketChannel.class)
//设置长连接
.childOption(ChannelOption.SO_KEEPALIVE,true)
.localAddress(new InetSocketAddress(port))
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(serverHandler);
}
});
ChannelFuture future=bootstrap.bind().sync();
future.channel().closeFuture().sync();
}
finally {
group.shutdownGracefully().sync();
}
}
}
(3)EchoController
import com.zzx.netty_server.core.EchoServerHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.Charset;
@RestController
public class EchoController {
@Autowired
EchoServerHandler handler;
@GetMapping("/send")
public String send(String host,String message){
ByteBuf byteBuffer= Unpooled.copiedBuffer(message, Charset.forName("UTF-8"));
EchoServerHandler.map.get(host).writeAndFlush(byteBuffer.duplicate());
return "发送成功!";
}
}
2.客户端代码实现
(1)EchoClientHandler
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import org.springframework.stereotype.Component;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;
@Component
public class EchoClientHandler extends SimpleChannelInboundHandler {
public static List<Channel> list=new ArrayList<>();
@Override
protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
ByteBuf in=(ByteBuf)o;
String message=in.toString(Charset.forName("UTF-8"));
System.out.println("客户端收到消息:"+message);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
list.add(ctx.channel());
String s="你好!";
ByteBuf byteBuffer= Unpooled.copiedBuffer(s, Charset.forName("UTF-8"));
ctx.writeAndFlush(byteBuffer.duplicate());
System.out.println("adrss:"+ctx.channel().remoteAddress());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println("服务器:"+ctx.channel().remoteAddress()+"断开链接");
}
}
(2)EvhoClient
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import java.util.concurrent.Future;
public class EvhoClient {
private final String host;
private final int port;
public EvhoClient(String host, int port) {
this.host = host;
this.port = port;
}
public void start() throws InterruptedException {
EchoClientHandler handler=new EchoClientHandler();
EventLoopGroup group=new NioEventLoopGroup();
try {
Bootstrap bootstrap=new Bootstrap();
bootstrap.group(group)
.channel(NioSocketChannel.class)
.remoteAddress(host,port)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(handler);
}
});
ChannelFuture future=bootstrap.connect().sync();
future.channel().closeFuture().sync();
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
group.shutdownGracefully().sync();
}
}
}
(3)ClientController
import com.zzx.netty_client.core.EchoClientHandler;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.nio.charset.Charset;
@RestController
public class ClientController {
@Autowired
EchoClientHandler handler;
@GetMapping("/send")
public String send(String message){
ByteBuf byteBuffer= Unpooled.copiedBuffer(message, Charset.forName("UTF-8"));
EchoClientHandler.list.get(0).writeAndFlush(byteBuffer.duplicate());
return "success";
}
}
三、测试
1.启动服务器端和客服端
分别在8123和8234端口启动应用
2.测试客服端向服务器端发信息
浏览器输入:http://localhost:8123/send?message=啦啦啦啦啦啦
3.测试服务器端向客户端发信息
localhost:8234/send?host=/192.168.0.103:59280&message=啊啊啊啊啊啊