Netty实例
Netty是基于JDK NIO的网络框架
简化了NIO编程, 不用程序自己维护selector, 将网络通信和数据处理的部分做了分离
多用于做底层的数据通信, 心跳检测(keepalived)
1. 数据通信
1.1 Hello World
public class Server { public static void main(String[] args) throws Exception {
// 1 创建线两个事件循环组
// 一个是用于处理服务器端接收客户端连接的
// 一个是进行网络通信的(网络读写的)
EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup(); // 2 创建辅助工具类ServerBootstrap,用于服务器通道的一系列配置
ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup) // 绑定俩个线程组
.channel(NioServerSocketChannel.class) // 指定NIO的模式.NioServerSocketChannel对应TCP, NioDatagramChannel对应UDP
.option(ChannelOption.SO_BACKLOG, 1024) // 设置TCP缓冲区
.option(ChannelOption.SO_SNDBUF, 32 * 1024) // 设置发送缓冲大小
.option(ChannelOption.SO_RCVBUF, 32 * 1024) // 这是接收缓冲大小
.option(ChannelOption.SO_KEEPALIVE, true) // 保持连接
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception { //SocketChannel建立连接后的管道
// 3 在这里配置 通信数据的处理逻辑, 可以addLast多个...
sc.pipeline().addLast(new ServerHandler());
}
}); // 4 绑定端口, bind返回future(异步), 加上sync阻塞在获取连接处
ChannelFuture cf1 = b.bind(8765).sync();
//ChannelFuture cf2 = b.bind(8764).sync(); //可以绑定多个端口
// 5 等待关闭, 加上sync阻塞在关闭请求处
cf1.channel().closeFuture().sync();
//cf2.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
SO_BACKLOG详解:
服务器的TCP内核维护两个队列A和B
客户端向服务端请求connect时, 发送SYN(第一次握手)
服务端收到SYN后, 向客户端发送SYN ACK(第二次握手), TCP内核将连接放入队列A
客户端收到后向服务端发送ACK(第三次握手), TCP内核将连接从A->B, accept返回, 连接完成
A/B队列的长度和即为BACKLOG, 当accept速度跟不上, A/B队列使得BACKLOG满了, 客户端连接就会被TCP内核拒绝
可以调大backlog缓解这一现象, 经验值~100
public class ServerHandler extends ChannelHandlerAdapter { @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("server channel active... ");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "utf-8");
System.out.println("Server :" + body );
String response = "返回给客户端的响应:" + body ;
ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
// future完成后触发监听器, 此处是写完即关闭(短连接). 因此需要关闭连接时, 要通过server端关闭. 直接关闭用方法ctx[.channel()].close()
//.addListener(ChannelFutureListener.CLOSE);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
System.out.println("读完了");
ctx.flush();
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable t)
throws Exception {
ctx.close();
}
}
public class Client { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new ClientHandler());
}
}); ChannelFuture cf1 = b.connect("127.0.0.1", 8765).sync();
//ChannelFuture cf2 = b.connect("127.0.0.1", 8764).sync(); //可以使用多个端口
//发送消息, Buffer类型. write需要flush才发送, 可用writeFlush代替
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("777".getBytes()));
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("666".getBytes()));
Thread.sleep(2000);
cf1.channel().writeAndFlush(Unpooled.copiedBuffer("888".getBytes()));
//cf2.channel().writeAndFlush(Unpooled.copiedBuffer("999".getBytes())); cf1.channel().closeFuture().sync();
//cf2.channel().closeFuture().sync();
group.shutdownGracefully();
}
}
public class ClientHandler extends ChannelHandlerAdapter{ @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
ByteBuf buf = (ByteBuf) msg;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "utf-8");
System.out.println("Client :" + body );
} finally {
// 记得释放xxxHandler里面的方法的msg参数: 写(write)数据, msg引用将被自动释放不用手动处理; 但只读数据时,!必须手动释放引用数
ReferenceCountUtil.release(msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
}
}
1.2 拆包粘包问题
TCP/IP确保了包的传送, 包的顺序等, 但编程中还需要解决拆包粘包问题
-> 接收的一连串包中的数据, 处理的分隔在哪里? 基本解决方案:
1)特殊字符作为结束分隔符
2)消息定长. 固定包的长度, 长度不够用空格补全. 接收方需要trim, 效率不高不推荐
3)自定义协议. 在消息头中包含消息总长度的字段. 需要安全性时可以考虑.
特殊字符
public class Server { public static void main(String[] args) throws Exception {
EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_SNDBUF, 32*1024)
.option(ChannelOption.SO_RCVBUF, 32*1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// 使用DelimiterBasedFrameDecoder设置结尾分隔符$_
ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
// 设置字符串形式的解码. 经过StringDecoder, Handler回调方法中接收的msg的具体类型就是String了(不再是ByteBuffer). 但写时仍需要传入ByteBuffer
sc.pipeline().addLast(new StringDecoder());
// 通信数据的处理逻辑
sc.pipeline().addLast(new ServerHandler());
}
});
//4 绑定连接
ChannelFuture cf = b.bind(8765).sync(); //等待服务器监听端口关闭
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
public class ServerHandler extends ChannelHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(" server channel active... ");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("Server :" + msg);
String response = "服务器响应: " + msg + "$_";
ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) throws Exception {
ctx.close();
}
}
public class Client { public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
ByteBuf buf = Unpooled.copiedBuffer("$_".getBytes());
sc.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ClientHandler());
}
}); ChannelFuture cf = b.connect("127.0.0.1", 8765).sync(); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("bbbb$_".getBytes()));
cf.channel().writeAndFlush(Unpooled.wrappedBuffer("cccc$_".getBytes())); cf.channel().closeFuture().sync();
group.shutdownGracefully(); }
}
public class ClientHandler extends ChannelHandlerAdapter { @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client channel active... ");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
String response = (String) msg;
System.out.println("Client: " + response);
} finally {
ReferenceCountUtil.release(msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
定长
public class Server { public static void main(String[] args) throws Exception{
EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.option(ChannelOption.SO_SNDBUF, 32*1024)
.option(ChannelOption.SO_RCVBUF, 32*1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
//设置定长字符串接收, 定长为5, 积累到5个字节才会把数据发出去
sc.pipeline().addLast(new FixedLengthFrameDecoder(5));
//设置字符串形式的解码
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ServerHandler());
}
}); ChannelFuture cf = b.bind(8765).sync();
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
public class ServerHandler extends ChannelHandlerAdapter { @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(" server channel active... ");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String request = (String)msg;
System.out.println("Server :" + msg);
String response = request ;
ctx.writeAndFlush(Unpooled.copiedBuffer(response.getBytes()));
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable t) throws Exception {
}
}
public class Client { public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new FixedLengthFrameDecoder(5));
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new ClientHandler());
}
}); ChannelFuture cf = b.connect("127.0.0.1", 8765).sync(); cf.channel().writeAndFlush(Unpooled.wrappedBuffer("aaa".getBytes()));
cf.channel().writeAndFlush(Unpooled.copiedBuffer("bbccccc".getBytes())); cf.channel().closeFuture().sync();
group.shutdownGracefully();
}
}
public class ClientHandler extends ChannelHandlerAdapter { @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println("client channel active... ");
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String response = (String) msg;
System.out.println("Client: " + response);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
}
}
1.3 编解码
即对象序列化技术, 目的是为了实现对象的网络传输和本地持久化
如果使用java的序列化, 码流较大. 因此多用Marshalling, Kyro(基于Protobuf)
下面的例子, 使用编解码传输javabean(Marshalling的javabean需要实现serializable), 并将message进行gzip压缩
自定义编解码器
public final class MarshallingCodeCFactory {
// 解码
public static MarshallingDecoder buildMarshallingDecoder() {
//创建工厂对象, 参数serial指创建的是java对象序列化的工厂对象
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
//创建配置对象,版本号为5
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
//根据工厂对象和配置对象创建解码provider
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
//创建解码器对象. 第一个参数是provider, 第二个参数是单个消息序列化后的最大长度, 超过后拒绝处理
MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1);
return decoder;
} // 编码
public static MarshallingEncoder buildMarshallingEncoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
//创建编码器对象. 用于将实现Serializable接口的JavaBean序列化为二进制数组
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
javabean
public class Request implements Serializable { // 标记Serializable接口 private String id ;
private String name ;
private String requestMessage ;
private byte[] attachment; public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRequestMessage() {
return requestMessage;
}
public void setRequestMessage(String requestMessage) {
this.requestMessage = requestMessage;
}
public byte[] getAttachment() {
return attachment;
}
public void setAttachment(byte[] attachment) {
this.attachment = attachment;
}
}
public class Response implements Serializable { // 标记Serializable接口 private String id;
private String name;
private String responseMessage; public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
}
GZip压缩的Util
public class GzipUtils { public static byte[] gzip(byte[] data) throws Exception {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
GZIPOutputStream gzip = new GZIPOutputStream(bos);
gzip.write(data);
gzip.finish();
gzip.close();
byte[] ret = bos.toByteArray();
bos.close();
return ret;
} public static byte[] ungzip(byte[] data) throws Exception{
ByteArrayInputStream bis = new ByteArrayInputStream(data);
GZIPInputStream gzip = new GZIPInputStream(bis);
byte[] buf = new byte[1024];
int num = -1;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((num = gzip.read(buf)) != -1 ){
bos.write(buf, 0, num);
}
gzip.close();
bis.close();
byte[] ret = bos.toByteArray();
bos.close();
return ret;
}
}
服务端与客户端
public class Server {
public static void main(String[] args) throws Exception{ EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
//设置日志
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel sc) throws Exception {
// 添加编解码. 发送自定义的类型, 而Handler的方法接收的msg参数的实际类型也是相应的自定义类了
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ServerHandler());
}
}); ChannelFuture cf = b.bind(8765).sync();
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
public class ServerHandler extends ChannelHandlerAdapter{ @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Request req = (Request)msg;
System.out.println("Server : " + req.getId() + ", " + req.getName() + ", " + req.getRequestMessage());
byte[] attachment = GzipUtils.ungzip(req.getAttachment()); String path = System.getProperty("user.dir") + File.separatorChar + "receive" + File.separatorChar + "001.jpg";
FileOutputStream fos = new FileOutputStream(path);
fos.write(attachment);
fos.close(); Response resp = new Response();
resp.setId(req.getId());
resp.setName("resp" + req.getId());
resp.setResponseMessage("响应内容" + req.getId());
ctx.writeAndFlush(resp);//.addListener(ChannelFutureListener.CLOSE);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
public class Client {
public static void main(String[] args) throws Exception{ EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ClientHandler());
}
}); ChannelFuture cf = b.connect("127.0.0.1", 8765).sync(); for(int i = 0; i < 5; i++){
Request req = new Request();
req.setId("" + i);
req.setName("req" + i);
req.setRequestMessage("数据信息" + i);
String path = System.getProperty("user.dir") + File.separatorChar + "sources" + File.separatorChar + "001.jpg";
File file = new File(path);
FileInputStream in = new FileInputStream(file);
byte[] data = new byte[in.available()];
in.read(data);
in.close();
req.setAttachment(GzipUtils.gzip(data)); //压缩
cf.channel().writeAndFlush(req);
} cf.channel().closeFuture().sync();
group.shutdownGracefully();
}
}
public class ClientHandler extends ChannelHandlerAdapter{ @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
Response resp = (Response) msg;
System.out.println("Client : " + resp.getId() + ", " + resp.getName() + ", " + resp.getResponseMessage());
} finally {
ReferenceCountUtil.release(msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
1.4 长连接/短连接
1.长连接, 一致保持着连接不主动中断, 实时性强
2.短连接. 数据放在缓存, 一次性批量提交所有数据, 服务端接收后即关闭连接
以上两种根据是否给ChannelHandlerContext添加ChannelFutureListener.ClOSE监听器实现
3.长连接, 一定时间不活跃则关闭连接. 给SocketChannel添加ReadTimeoutHandler实现. 实例如下:
public final class MarshallingCodeCFactory {
public static MarshallingDecoder buildMarshallingDecoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024);
return decoder;
}
public static MarshallingEncoder buildMarshallingEncoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
public class Request implements Serializable{
private String id ;
private String name ;
private String requestMessage ; public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getRequestMessage() {
return requestMessage;
}
public void setRequestMessage(String requestMessage) {
this.requestMessage = requestMessage;
}
}
public class Response implements Serializable{
private String id;
private String name;
private String responseMessage; public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getResponseMessage() {
return responseMessage;
}
public void setResponseMessage(String responseMessage) {
this.responseMessage = responseMessage;
}
}
public class Server { public static void main(String[] args) throws Exception{ EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
//设置日志
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ReadTimeoutHandler(5)); // 时限, 读客户端超时没数据则断开
sc.pipeline().addLast(new ServerHandler());
}
}); ChannelFuture cf = b.bind(8765).sync();
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
public class ServerHandler extends ChannelHandlerAdapter{ @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
Request request = (Request) msg;
System.out.println("Server : " + request.getId() + ", " + request.getName() + ", " + request.getRequestMessage());
Response response = new Response();
response.setId(request.getId());
response.setName("response" + request.getId());
response.setResponseMessage("响应内容" + request.getId());
ctx.writeAndFlush(response);
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
public class Client {
private EventLoopGroup group;
private Bootstrap b;
private ChannelFuture cf ; // 单例
private static class SingletonHolder {
static final Client instance = new Client();
}
public static Client getInstance(){
return SingletonHolder.instance;
} private Client(){
group = new NioEventLoopGroup();
b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
//超时handler(当服务器端与客户端在指定时间以上没有任何进行通信,则会关闭通道)
sc.pipeline().addLast(new ReadTimeoutHandler(5)); // 时限5s, 读服务端超时没数据则断开
sc.pipeline().addLast(new ClientHandler());
}
});
} public void connect(){
try {
this.cf = b.connect("127.0.0.1", 8765).sync();
System.out.println("远程服务器已经连接, 可以进行数据交换");
} catch (Exception e) {
e.printStackTrace();
}
} public ChannelFuture getChannelFuture(){
if(this.cf == null) { //初次连接
this.connect();
}
if(!this.cf.channel().isActive()){ //重连
this.connect();
}
return this.cf;
} public static void main(String[] args) throws Exception{
final Client c = Client.getInstance(); ChannelFuture cf = c.getChannelFuture();
for(int i = 1; i <= 3; i++ ){
Request request = new Request();
request.setId("" + i);
request.setName("request" + i);
request.setRequestMessage("数据信息" + i);
cf.channel().writeAndFlush(request);
TimeUnit.SECONDS.sleep(4); //间隔4s发送一次数据
} cf.channel().closeFuture().sync(); //阻塞至超时关闭 // 这里用子线程重连并发送数据一次
new Thread(new Runnable() {
@Override
public void run() {
try {
System.out.println("进入子线程重连一次");
ChannelFuture cf = c.getChannelFuture();
assert true == cf.channel().isActive(); //断言
//再次发送数据
Request request = new Request();
request.setId("" + 4);
request.setName("request" + 4);
request.setRequestMessage("数据信息" + 4);
cf.channel().writeAndFlush(request);
cf.channel().closeFuture().sync();
System.out.println("子线程完成");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start(); System.out.println("断开连接,主线程结束..");
} }
public class ClientHandler extends ChannelHandlerAdapter{ @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
Response resp = (Response) msg;
System.out.println("Client : " + resp.getId() + ", " + resp.getName() + ", " + resp.getResponseMessage());
} finally {
ReferenceCountUtil.release(msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
}
1.5 使用UDP (较少使用)
public class Server {
public void run(int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioDatagramChannel.class) // UDP: NioDatagramChannel
.option(ChannelOption.SO_BROADCAST, true) // 广播
.handler(new ServerHandler());
b.bind(port).sync().channel().closeFuture().await();
} finally {
group.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
new Server().run(8765);
}
}
public class ServerHandler extends SimpleChannelInboundHandler<DatagramPacket> { // 谚语列表
private static final String[] DICTIONARY = {
"只要功夫深,铁棒磨成针。",
"旧时王谢堂前燕,飞入寻常百姓家。",
"洛阳亲友如相问,一片冰心在玉壶。",
"一寸光阴一寸金,寸金难买寸光阴。",
"老骥伏枥,志在千里。烈士暮年,壮心不已!"
}; private String nextQuote() {
int quoteId = ThreadLocalRandom.current().nextInt(DICTIONARY.length);
return DICTIONARY[quoteId];
} @Override
public void messageReceived(ChannelHandlerContext ctx, DatagramPacket packet) throws Exception {
String req = packet.content().toString(CharsetUtil.UTF_8);
System.out.println(req);
if ("谚语字典查询?".equals(req)) {
ctx.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("谚语查询结果: " + nextQuote(), CharsetUtil.UTF_8), packet.sender()));
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
ctx.close();
cause.printStackTrace();
}
}
public class Client { public void run(int port) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
try {
Bootstrap b = new Bootstrap();
b.group(group).channel(NioDatagramChannel.class)
.option(ChannelOption.SO_BROADCAST, true)
.handler(new ClientHandler());
Channel ch = b.bind(0).sync().channel();
// 向网段内的所有机器广播UDP消息
ch.writeAndFlush(new DatagramPacket(Unpooled.copiedBuffer("谚语字典查询?", CharsetUtil.UTF_8), new InetSocketAddress("255.255.255.255", port))).sync();
if (!ch.closeFuture().await(15000)) {
System.out.println("查询超时!");
}
} finally {
group.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
new Client().run(8765);
}
}
public class ClientHandler extends SimpleChannelInboundHandler<DatagramPacket> { @Override
public void messageReceived(ChannelHandlerContext ctx, DatagramPacket msg) throws Exception {
String response = msg.content().toString(CharsetUtil.UTF_8);
if (response.startsWith("谚语查询结果: ")) {
System.out.println(response);
ctx.close();
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
2. 心跳检测
集群中主服务器需要知道从服务器的状态
因此client每隔5~10秒给server发送心跳包
可通过netty与定时任务来实现
public final class MarshallingCodeCFactory {
public static MarshallingDecoder buildMarshallingDecoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
UnmarshallerProvider provider = new DefaultUnmarshallerProvider(marshallerFactory, configuration);
MarshallingDecoder decoder = new MarshallingDecoder(provider, 1024 * 1024 * 1);
return decoder;
}
public static MarshallingEncoder buildMarshallingEncoder() {
final MarshallerFactory marshallerFactory = Marshalling.getProvidedMarshallerFactory("serial");
final MarshallingConfiguration configuration = new MarshallingConfiguration();
configuration.setVersion(5);
MarshallerProvider provider = new DefaultMarshallerProvider(marshallerFactory, configuration);
MarshallingEncoder encoder = new MarshallingEncoder(provider);
return encoder;
}
}
public class RequestInfo implements Serializable {
private String ip ;
private HashMap<String, Object> cpuPercMap ;
private HashMap<String, Object> memoryMap; public String getIp() {
return ip;
}
public void setIp(String ip) {
this.ip = ip;
}
public HashMap<String, Object> getCpuPercMap() {
return cpuPercMap;
}
public void setCpuPercMap(HashMap<String, Object> cpuPercMap) {
this.cpuPercMap = cpuPercMap;
}
public HashMap<String, Object> getMemoryMap() {
return memoryMap;
}
public void setMemoryMap(HashMap<String, Object> memoryMap) {
this.memoryMap = memoryMap;
}
}
public class Server { public static void main(String[] args) throws Exception{ EventLoopGroup pGroup = new NioEventLoopGroup();
EventLoopGroup cGroup = new NioEventLoopGroup(); ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
//设置日志
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ServerHeartBeatHandler());
}
}); ChannelFuture cf = b.bind(8765).sync();
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
public class ServerHeartBeatHandler extends ChannelHandlerAdapter { private static HashMap<String, String> AUTH_IP_MAP = new HashMap<String, String>();
private static final String SUCCESS_KEY = "auth_success_key"; static {
AUTH_IP_MAP.put("127.0.0.1", "1234");
} private boolean auth(ChannelHandlerContext ctx, Object msg){
String [] ret = ((String) msg).split(",");
String auth = AUTH_IP_MAP.get(ret[0]);
if(auth != null && auth.equals(ret[1])){
// 认证成功, 返回确认信息
ctx.writeAndFlush(SUCCESS_KEY);
return true;
} else {
ctx.writeAndFlush("auth failure !").addListener(ChannelFutureListener.CLOSE);
return false;
}
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
if(msg instanceof String){
auth(ctx, msg);
} else if (msg instanceof RequestInfo) {
RequestInfo info = (RequestInfo) msg;
System.out.println("--------------------------------------------");
System.out.println("当前主机ip为: " + info.getIp());
System.out.println("当前主机cpu情况: ");
HashMap<String, Object> cpu = info.getCpuPercMap();
System.out.println("总使用率: " + cpu.get("combined"));
System.out.println("用户使用率: " + cpu.get("user"));
System.out.println("系统使用率: " + cpu.get("sys"));
System.out.println("等待率: " + cpu.get("wait"));
System.out.println("空闲率: " + cpu.get("idle")); System.out.println("当前主机memory情况: ");
HashMap<String, Object> memory = info.getMemoryMap();
System.out.println("内存总量: " + memory.get("total"));
System.out.println("当前内存使用量: " + memory.get("used"));
System.out.println("当前内存剩余量: " + memory.get("free"));
System.out.println("--------------------------------------------"); ctx.writeAndFlush("info received!");
} else {
ctx.writeAndFlush("connect failure!").addListener(ChannelFutureListener.CLOSE);
}
}
}
public class Client {
public static void main(String[] args) throws Exception {
EventLoopGroup group = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingDecoder());
sc.pipeline().addLast(MarshallingCodeCFactory.buildMarshallingEncoder());
sc.pipeline().addLast(new ClienHeartBeatHandler());
}
}); ChannelFuture cf = b.connect("127.0.0.1", 8765).sync();
cf.channel().closeFuture().sync();
group.shutdownGracefully();
}
}
public class ClienHeartBeatHandler extends ChannelHandlerAdapter { private ScheduledExecutorService scheduler = Executors.newScheduledThreadPool(1);
private ScheduledFuture<?> heartBeat; //定时任务
//主动向服务器发送认证信息
private InetAddress addr ;
private static final String SUCCESS_KEY = "auth_success_key"; @Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
addr = InetAddress.getLocalHost();
//String ip = addr.getHostAddress();
String ip = "127.0.0.1";
String key = "1234";
//证书
String auth = ip + "," + key;
// 发送认证
ctx.writeAndFlush(auth);
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
try {
if(msg instanceof String){
String ret = (String) msg;
if(SUCCESS_KEY.equals(ret)){
// 收到认证 确认信息,设置每隔5秒发送心跳消息
this.heartBeat = this.scheduler.scheduleWithFixedDelay(new HeartBeatTask(ctx), 0, 5, TimeUnit.SECONDS);
System.out.println(msg);
} else {
// 收到心跳包 确认信息
System.out.println(msg);
}
}
} finally {
// 只读, 需要手动释放引用计数
ReferenceCountUtil.release(msg);
}
} private class HeartBeatTask implements Runnable {
private final ChannelHandlerContext ctx;
public HeartBeatTask(final ChannelHandlerContext ctx) {
this.ctx = ctx;
}
@Override
public void run() {
try {
RequestInfo info = new RequestInfo();
//ip
info.setIp(addr.getHostAddress());
Sigar sigar = new Sigar();
//cpu prec
CpuPerc cpuPerc = sigar.getCpuPerc();
HashMap<String, Object> cpuPercMap = new HashMap<String, Object>();
cpuPercMap.put("combined", cpuPerc.getCombined());
cpuPercMap.put("user", cpuPerc.getUser());
cpuPercMap.put("sys", cpuPerc.getSys());
cpuPercMap.put("wait", cpuPerc.getWait());
cpuPercMap.put("idle", cpuPerc.getIdle());
// memory
Mem mem = sigar.getMem();
HashMap<String, Object> memoryMap = new HashMap<String, Object>();
memoryMap.put("total", mem.getTotal() / 1024L);
memoryMap.put("used", mem.getUsed() / 1024L);
memoryMap.put("free", mem.getFree() / 1024L);
info.setCpuPercMap(cpuPercMap);
info.setMemoryMap(memoryMap); ctx.writeAndFlush(info);
} catch (Exception e) {
e.printStackTrace();
}
} public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
// 取消定时发送心跳包的任务
if (heartBeat != null) {
heartBeat.cancel(true);
heartBeat = null;
}
ctx.fireExceptionCaught(cause);
}
}
}
3. HTTP
3.1 Hello World
public final class HttpHelloWorldServer { static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080")); public static void main(String[] args) throws Exception {
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
} else {
sslCtx = null;
} EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.option(ChannelOption.SO_BACKLOG, 1024);
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.handler(new LoggingHandler(LogLevel.INFO))
.childHandler(new HttpHelloWorldServerInitializer(sslCtx)); Channel ch = b.bind(PORT).sync().channel(); System.err.println("Open your web browser and navigate to " +
(SSL? "https" : "http") + "://127.0.0.1:" + PORT + '/'); ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class HttpHelloWorldServerInitializer extends ChannelInitializer<SocketChannel> { private final SslContext sslCtx; public HttpHelloWorldServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
} @Override
public void initChannel(SocketChannel ch) {
ChannelPipeline p = ch.pipeline();
if (sslCtx != null) {
p.addLast(sslCtx.newHandler(ch.alloc()));
}
p.addLast(new HttpServerCodec()); // !使用http通信, HttpRequest和HttpResponse
p.addLast(new HttpHelloWorldServerHandler());
}
}
public class HttpHelloWorldServerHandler extends ChannelHandlerAdapter {
private static final byte[] CONTENT = "HELLO WORLD".getBytes(); @Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
} @Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
if (msg instanceof HttpRequest) {
HttpRequest req = (HttpRequest) msg; if (HttpHeaderUtil.is100ContinueExpected(req)) {
ctx.write(new DefaultFullHttpResponse(HTTP_1_1, CONTINUE));
}
boolean keepAlive = HttpHeaderUtil.isKeepAlive(req);
// 构造响应
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK, Unpooled.wrappedBuffer(CONTENT));
response.headers().set(CONTENT_TYPE, "text/plain;charset=UTF-8");
response.headers().setInt(CONTENT_LENGTH, response.content().readableBytes()); if (!keepAlive) {
// Request短连接, 写完后直接关闭
ctx.write(response).addListener(ChannelFutureListener.CLOSE);
} else {
// 长连接, response也设置为KEEP_ALIVE
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
ctx.write(response);
}
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
cause.printStackTrace();
ctx.close();
}
}
3.2 HTTP下载文件
public class HttpDownloadServer { private static final String DEFAULT_URL = "/sources/"; public void run(final int port, final String url) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// addLast的第一项为key, 自定义的
// request解码器
ch.pipeline().addLast("http-decoder", new HttpRequestDecoder());
// response的编码器
ch.pipeline().addLast("http-encoder", new HttpResponseEncoder());
// chunked, 传输文件时分多个response分解地传输文件
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
// ObjectAggregator, 将多个response合并为一个FullHttpResponse
ch.pipeline().addLast("http-aggregator", new HttpObjectAggregator(65536));
// 自定义业务逻辑handler
ch.pipeline().addLast("fileServerHandler", new HttpDownoadServerHandler(url));
}
});
ChannelFuture future = b.bind("127.0.0.1", port).sync();
System.out.println("HTTP文件目录服务器启动,网址是 : " + "http://localhost:" + port + url);
future.channel().closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
} public static void main(String[] args) throws Exception {
int port = 8765;
new HttpDownloadServer().run(port, DEFAULT_URL);
}
}
// 注意这里继承了SimpleChannelInboundHandler<T>, 含泛型, 即指定了传入参数msg的类型
public class HttpDownoadServerHandler extends SimpleChannelInboundHandler<FullHttpRequest> { private final String url; public HttpDownoadServerHandler(String url) {
this.url = url;
} @Override
public void messageReceived(ChannelHandlerContext ctx, FullHttpRequest request) throws Exception {
//是否能理解(解码)请求
if (!request.decoderResult().isSuccess()) {
// 400
sendError(ctx, BAD_REQUEST);
return;
}
//对请求的方法进行判断:如果不是GET方法则返回异常
if (request.method() != GET) {
// 405
sendError(ctx, METHOD_NOT_ALLOWED);
return;
}
//获取请求uri路径
final String uri = request.uri();
//对url进行分析,返回本地路径
final String path = parseURI(uri);
//如果 路径构造不合法,则path为null
if (path == null) {
//403
sendError(ctx, FORBIDDEN);
return;
} // 创建file对象
File file = new File(path);
// 文件隐藏或不存在
if (file.isHidden() || !file.exists()) {
// 404
sendError(ctx, NOT_FOUND);
return;
}
// 是文件夹
if (file.isDirectory()) {
if (uri.endsWith("/")) {
//如果以正常"/"结束 说明是访问的一个文件目录:则进行展示文件列表
sendListing(ctx, file);
} else {
//如果非"/"结束 则重定向,让客户端补全"/"并再次请求
sendRedirect(ctx, uri + '/');
}
return;
}
// 如果所创建的file对象不是文件类型
if (!file.isFile()) {
// 403
sendError(ctx, FORBIDDEN);
return;
} //随机文件读写对象
RandomAccessFile randomAccessFile = null;
try {
randomAccessFile = new RandomAccessFile(file, "r");// 以只读的方式打开文件
} catch (FileNotFoundException fnfe) {
// 404
sendError(ctx, NOT_FOUND);
return;
} //获取文件长度
long fileLength = randomAccessFile.length();
//建立响应对象
HttpResponse response = new DefaultHttpResponse(HTTP_1_1, OK);
//设置响应信息
HttpHeaderUtil.setContentLength(response, fileLength);
//设置Content-Type
setContentTypeHeader(response, file);
//设置为KeepAlive
if (HttpHeaderUtil.isKeepAlive(request)) {
response.headers().set(CONNECTION, HttpHeaderValues.KEEP_ALIVE);
}
//输出response header, HttpObjectAggregator能将其与下面输出整合合并
ctx.write(response); //写出ChunkedFile. 创建ChunkedFile需要使用RandomAccessFile并设置分段. 这里每次传输8192个字节
ChannelFuture sendFileFuture = ctx.write(new ChunkedFile(randomAccessFile, 0, fileLength, 8192), ctx.newProgressivePromise());
//添加传输监听
sendFileFuture.addListener(new ChannelProgressiveFutureListener() {
@Override
public void operationProgressed(ChannelProgressiveFuture future, long progress, long total) {
if (total < 0) {
System.err.println("Transfer progress: " + progress);
} else {
System.err.println("Transfer progress: " + progress + " / " + total);
}
}
@Override
public void operationComplete(ChannelProgressiveFuture future) throws Exception {
System.out.println("Transfer complete.");
}
}); //使用Chunked, 完成时需要发送标记结束的空消息体!
ChannelFuture lastContentFuture = ctx.writeAndFlush(LastHttpContent.EMPTY_LAST_CONTENT);
//如果当前连接请求非Keep-Alive, 最后一包消息发送完后, 服务器主动关闭连接
if (!HttpHeaderUtil.isKeepAlive(request)) {
lastContentFuture.addListener(ChannelFutureListener.CLOSE);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
if (ctx.channel().isActive()) {
// 500
sendError(ctx, INTERNAL_SERVER_ERROR);
ctx.close();
}
} //判断非法URI的正则
private static final Pattern INSECURE_URI = Pattern.compile(".*[<>&\"].*");
private String parseURI(String uri) {
try {
//使用UTF-8字符集
uri = URLDecoder.decode(uri, "UTF-8");
} catch (UnsupportedEncodingException e) {
try {
//尝试ISO-8859-1
uri = URLDecoder.decode(uri, "ISO-8859-1");
} catch (UnsupportedEncodingException e1) {
//抛出预想外异常信息
throw new Error();
}
}
// 对uri进行细粒度判断:4步验证操作
// step 1 基础验证
if (!uri.startsWith(url)) {
return null;
}
// step 2 基础验证
if (!uri.startsWith("/")) {
return null;
}
// step 3 将文件分隔符替换为本地操作系统的文件路径分隔符
uri = uri.replace('/', File.separatorChar);
// step 4 验证路径合法性
if (uri.contains(File.separator + '.') || uri.contains('.' + File.separator) ||
uri.startsWith(".") || uri.endsWith(".") ||
INSECURE_URI.matcher(uri).matches()) {
return null;
}
//利用当前工程所在目录 + URI相对路径 构造绝对路径
return System.getProperty("user.dir") + File.separator + uri;
} //用正则表达式过滤文件名
private static final Pattern ALLOWED_FILE_NAME = Pattern.compile("[A-Za-z0-9][-_A-Za-z0-9\\.]*");
//文件列表, 拼html文件
private static void sendListing(ChannelHandlerContext ctx, File dir) {
// 设置响应对象
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, OK);
// 响应头
response.headers().set(CONTENT_TYPE, "text/html; charset=UTF-8");
// 构造文本内容
StringBuilder ret = new StringBuilder();
String dirPath = dir.getPath();
ret.append("<!DOCTYPE html>\r\n");
ret.append("<html><head><title>");
ret.append(dirPath);
ret.append(" 目录:");
ret.append("</title></head><body>\r\n");
ret.append("<h3>");
ret.append(dirPath).append(" 目录:");
ret.append("</h3>\r\n");
ret.append("<ul>");
ret.append("<li>链接:<a href=\"../\">..</a></li>\r\n"); // 遍历文件, 生成超链接
for (File f : dir.listFiles()) {
//step 1: 跳过隐藏文件和不可读文件
if (f.isHidden() || !f.canRead()) {
continue;
}
String name = f.getName();
//step 2: 跳过正则过滤的文件名
if (!ALLOWED_FILE_NAME.matcher(name).matches()) {
continue;
}
ret.append("<li>链接:<a href=\"");
ret.append(name);
ret.append("\">");
ret.append(name);
ret.append("</a></li>\r\n");
}
ret.append("</ul></body></html>\r\n");
//构造ByteBuf,写入缓冲区
ByteBuf buffer = Unpooled.copiedBuffer(ret, CharsetUtil.UTF_8);
//进行写出操作
response.content().writeBytes(buffer);
//重置ByteBuf
buffer.release();
//发送完成并主动关闭连接
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} //重定向操作
private static void sendRedirect(ChannelHandlerContext ctx, String newUri) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, FOUND);
response.headers().set(LOCATION, newUri);
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} //错误信息
private static void sendError(ChannelHandlerContext ctx, HttpResponseStatus status) {
FullHttpResponse response = new DefaultFullHttpResponse(HTTP_1_1, status, Unpooled.copiedBuffer("Failure: " + status.toString()+ "\r\n", CharsetUtil.UTF_8));
response.headers().set(CONTENT_TYPE, "text/plain; charset=UTF-8");
ctx.writeAndFlush(response).addListener(ChannelFutureListener.CLOSE);
} private static void setContentTypeHeader(HttpResponse response, File file) {
//使用mime对象获取文件对应的Content-Type
MimetypesFileTypeMap mimeTypesMap = new MimetypesFileTypeMap();
response.headers().set(CONTENT_TYPE, mimeTypesMap.getContentType(file.getPath()));
}
}
3.3 HTTP上传文件 (较少使用)
实际应用中文件上传服务端有成熟的框架fastDFS(小文件)和HDFS(大文件)
如要实现断点续传, 需要记录上传进度. 参考HTTP头的Range和Content-Range
public final class HttpUploadServer { static final boolean SSL = System.getProperty("ssl") != null;
static final int PORT = Integer.parseInt(System.getProperty("port", SSL? "8443" : "8080")); public static void main(String[] args) throws Exception {
// Configure SSL.
final SslContext sslCtx;
if (SSL) {
SelfSignedCertificate ssc = new SelfSignedCertificate();
sslCtx = SslContext.newServerContext(ssc.certificate(), ssc.privateKey());
} else {
sslCtx = null;
} EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup);
b.channel(NioServerSocketChannel.class);
b.handler(new LoggingHandler(LogLevel.INFO));
b.childHandler(new HttpUploadServerInitializer(sslCtx)); Channel ch = b.bind(PORT).sync().channel();
System.err.println("Open your web browser and navigate to " + (SSL ? "https" : "http") + "://127.0.0.1:" + PORT + '/'); ch.closeFuture().sync();
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
public class HttpUploadServerInitializer extends ChannelInitializer<SocketChannel> { private final SslContext sslCtx; public HttpUploadServerInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
} @Override
public void initChannel(SocketChannel ch) {
ChannelPipeline pipeline = ch.pipeline();
if (sslCtx != null) {
pipeline.addLast(sslCtx.newHandler(ch.alloc()));
}
pipeline.addLast(new HttpRequestDecoder());
pipeline.addLast(new HttpResponseEncoder());
// 压缩
pipeline.addLast(new HttpContentCompressor());
pipeline.addLast(new HttpUploadServerHandler());
}
}
public class HttpUploadServerHandler extends SimpleChannelInboundHandler<HttpObject> { private static final Logger logger = Logger.getLogger(HttpUploadServerHandler.class.getName()); private HttpRequest request; private boolean readingChunks; private final StringBuilder responseContent = new StringBuilder(); private static final HttpDataFactory factory = new DefaultHttpDataFactory(DefaultHttpDataFactory.MINSIZE); // 大小超过minsize放磁盘上 private HttpPostRequestDecoder decoder; static {
DiskFileUpload.deleteOnExitTemporaryFile = true; //退出时是否删除临时文件
DiskFileUpload.baseDirectory = "D:" + File.separatorChar + "aa"; //文件存储路径 DiskAttribute.deleteOnExitTemporaryFile = true; //退出时是否删除临时文件
DiskAttribute.baseDirectory = "D:" + File.separatorChar + "aa"; //文件存储路径
} @Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
if (decoder != null) {
decoder.cleanFiles();
}
} @Override
public void messageReceived(ChannelHandlerContext ctx, HttpObject msg) throws Exception {
if (msg instanceof HttpRequest) { // HttpRequest传输头
HttpRequest request = this.request = (HttpRequest) msg;
URI uri = new URI(request.uri());
if (!uri.getPath().startsWith("/form")) {
// 返回上传菜单
writeMenu(ctx);
return;
}
// 拼接反馈内容
responseContent.setLength(0);
responseContent.append("WELCOME TO THE WILD WILD WEB SERVER\r\n");
responseContent.append("===================================\r\n");
responseContent.append("VERSION: " + request.protocolVersion().text() + "\r\n");
responseContent.append("REQUEST_URI: " + request.uri() + "\r\n\r\n");
responseContent.append("\r\n\r\n"); for (Entry<CharSequence, CharSequence> entry : request.headers()) {
responseContent.append("HEADER: " + entry.getKey() + '=' + entry.getValue() + "\r\n");
}
responseContent.append("\r\n\r\n"); Set<Cookie> cookies = null;
String value = request.headers().getAndConvert(HttpHeaderNames.COOKIE);
if (value == null) {
cookies = Collections.emptySet();
} else {
cookies = ServerCookieDecoder.decode(value);
}
for (Cookie cookie : cookies) {
responseContent.append("COOKIE: " + cookie + "\r\n");
}
responseContent.append("\r\n\r\n"); QueryStringDecoder decoderQuery = new QueryStringDecoder(request.uri());
Map<String, List<String>> uriAttributes = decoderQuery.parameters();
for (Entry<String, List<String>> attr: uriAttributes.entrySet()) {
for (String attrVal: attr.getValue()) {
responseContent.append("URI: " + attr.getKey() + '=' + attrVal + "\r\n");
}
}
responseContent.append("\r\n\r\n"); // GET方法, 就此return
if (request.method().equals(HttpMethod.GET)) {
responseContent.append("\r\n\r\nEND OF GET CONTENT\r\n");
return;
} // POST方法
try {
decoder = new HttpPostRequestDecoder(factory, request);
} catch (ErrorDataDecoderException e1) {
e1.printStackTrace();
responseContent.append(e1.getMessage());
writeResponse(ctx.channel());
ctx.channel().close();
return;
} readingChunks = HttpHeaderUtil.isTransferEncodingChunked(request);
responseContent.append("Is Chunked: " + readingChunks + "\r\n");
responseContent.append("IsMultipart: " + decoder.isMultipart() + "\r\n");
if (readingChunks) {
responseContent.append("Chunks: ");
}
} if (decoder != null) {
if (msg instanceof HttpContent) { //HttpContent具体传输的内容
// 读取到一个chunk
HttpContent chunk = (HttpContent) msg;
try {
decoder.offer(chunk);
} catch (ErrorDataDecoderException e1) {
e1.printStackTrace();
responseContent.append(e1.getMessage());
writeResponse(ctx.channel());
ctx.channel().close();
return;
}
responseContent.append('o'); //每读一个chunk标记一个'o'
readHttpDataChunkByChunk();
// 最后一块chunk
if (chunk instanceof LastHttpContent) {
writeResponse(ctx.channel());
readingChunks = false;
reset();
}
}
} else {
writeResponse(ctx.channel());
}
} private void reset() {
request = null;
decoder.destroy(); //释放资源
decoder = null;
} private void readHttpDataChunkByChunk() throws Exception {
try {
while (decoder.hasNext()) {
InterfaceHttpData data = decoder.next();
if (data != null) {
try {
writeHttpData(data);
} finally {
data.release();
}
}
}
} catch (EndOfDataDecoderException e1) {
responseContent.append("\r\n\r\nEND OF CONTENT CHUNK BY CHUNK\r\n\r\n");
}
} private void writeHttpData(InterfaceHttpData data) throws Exception {
if (data.getHttpDataType() == HttpDataType.Attribute) {
Attribute attribute = (Attribute) data;
String value = null;
try {
value = attribute.getValue();
} catch (IOException e1) {
e1.printStackTrace();
responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + attribute.getName() + " Error while reading value: " + e1.getMessage() + "\r\n");
return;
}
if (value.length() > 100) {
responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + attribute.getName() + " data too long\r\n");
} else {
responseContent.append("\r\nBODY Attribute: " + attribute.getHttpDataType().name() + ": " + attribute + "\r\n");
}
} else {
responseContent.append("\r\n -----------start-------------" + "\r\n");
responseContent.append("\r\nBODY FileUpload: " + data.getHttpDataType().name() + ": " + data + "\r\n");
responseContent.append("\r\n ------------end------------" + "\r\n");
if (data.getHttpDataType() == HttpDataType.FileUpload) {
FileUpload fileUpload = (FileUpload) data;
if (fileUpload.isCompleted()) {
System.out.println("file name : " + fileUpload.getFilename());
System.out.println("file length: " + fileUpload.length());
System.out.println("file maxSize : " + fileUpload.getMaxSize());
System.out.println("file path :" + fileUpload.getFile().getPath());
System.out.println("file absolutepath :" + fileUpload.getFile().getAbsolutePath());
System.out.println("parent path :" + fileUpload.getFile().getParentFile()); if (fileUpload.length() < 1024 * 1024 * 10) {
responseContent.append("\tContent of file\r\n");
try {
responseContent.append(fileUpload.getString(fileUpload.getCharset()));
} catch (Exception e1) {
e1.printStackTrace();
}
responseContent.append("\r\n");
} else {
responseContent.append("\tFile too long to be printed out:" + fileUpload.length() + "\r\n");
}
fileUpload.renameTo(new File(fileUpload.getFile().getPath())); // 核心操作, 写文件
decoder.removeHttpDataFromClean(fileUpload);
} else {
responseContent.append("\tFile to be continued but should not!\r\n");
}
}
}
} private void writeResponse(Channel channel) {
ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
responseContent.setLength(0); // 是否是短连接
boolean close = request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.CLOSE, true)
|| request.protocolVersion().equals(HttpVersion.HTTP_1_0)
&& !request.headers().contains(HttpHeaderNames.CONNECTION, HttpHeaderValues.KEEP_ALIVE, true); FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/plain; charset=UTF-8");
// 最后一次连接不需要Content-Length
if (!close) {
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes());
} Set<Cookie> cookies = null;
String value = request.headers().getAndConvert(HttpHeaderNames.COOKIE);
if (value == null) {
cookies = Collections.emptySet();
} else {
cookies = ServerCookieDecoder.decode(value);
}
if (!cookies.isEmpty()) {
for (Cookie cookie : cookies) {
response.headers().add(HttpHeaderNames.SET_COOKIE, ServerCookieEncoder.encode(cookie));
}
}
ChannelFuture future = channel.writeAndFlush(response);
if (close) {
future.addListener(ChannelFutureListener.CLOSE);
}
} //拼接上传页html菜单
private void writeMenu(ChannelHandlerContext ctx) {
responseContent.setLength(0); // create Pseudo Menu
responseContent.append("<html>");
responseContent.append("<head>");
responseContent.append("<title>Netty Test Form</title>\r\n");
responseContent.append("</head>\r\n");
responseContent.append("<body bgcolor=white><style>td{font-size: 12pt;}</style>"); responseContent.append("<table border=\"0\">");
responseContent.append("<tr>");
responseContent.append("<td>");
responseContent.append("<h1>Netty Test Form</h1>");
responseContent.append("Choose one FORM");
responseContent.append("</td>");
responseContent.append("</tr>");
responseContent.append("</table>\r\n"); // GET
responseContent.append("<CENTER>GET FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
responseContent.append("<FORM ACTION=\"/formget\" METHOD=\"GET\">");
responseContent.append("<input type=hidden name=getform value=\"GET\">");
responseContent.append("<table border=\"0\">");
responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
responseContent.append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
responseContent.append("</td></tr>");
responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
responseContent.append("</table></FORM>\r\n");
responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); // POST
responseContent.append("<CENTER>POST FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
responseContent.append("<FORM ACTION=\"/formpost\" METHOD=\"POST\">");
responseContent.append("<input type=hidden name=getform value=\"POST\">");
responseContent.append("<table border=\"0\">");
responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
responseContent.append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
responseContent.append("<tr><td>Fill with file (only file name will be transmitted): <br> <input type=file name=\"myfile\">");
responseContent.append("</td></tr>");
responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
responseContent.append("</table></FORM>\r\n");
responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); // POST with enctype="multipart/form-data"
responseContent.append("<CENTER>POST MULTIPART FORM<HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>");
responseContent.append("<FORM ACTION=\"/formpostmultipart\" ENCTYPE=\"multipart/form-data\" METHOD=\"POST\">");
responseContent.append("<input type=hidden name=getform value=\"POST\">");
responseContent.append("<table border=\"0\">");
responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"info\" size=10></td></tr>");
responseContent.append("<tr><td>Fill with value: <br> <input type=text name=\"secondinfo\" size=20>");
responseContent.append("<tr><td>Fill with value: <br> <textarea name=\"thirdinfo\" cols=40 rows=10></textarea>");
responseContent.append("<tr><td>Fill with file: <br> <input type=file name=\"myfile\">");
responseContent.append("</td></tr>");
responseContent.append("<tr><td><INPUT TYPE=\"submit\" NAME=\"Send\" VALUE=\"Send\"></INPUT></td>");
responseContent.append("<td><INPUT TYPE=\"reset\" NAME=\"Clear\" VALUE=\"Clear\" ></INPUT></td></tr>");
responseContent.append("</table></FORM>\r\n");
responseContent.append("<CENTER><HR WIDTH=\"75%\" NOSHADE color=\"blue\"></CENTER>"); responseContent.append("</body>");
responseContent.append("</html>"); ByteBuf buf = copiedBuffer(responseContent.toString(), CharsetUtil.UTF_8);
FullHttpResponse response = new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.OK, buf);
response.headers().set(HttpHeaderNames.CONTENT_TYPE, "text/html; charset=UTF-8");
response.headers().setInt(HttpHeaderNames.CONTENT_LENGTH, buf.readableBytes()); ctx.channel().writeAndFlush(response);
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
logger.log(Level.WARNING, responseContent.toString(), cause);
ctx.channel().close();
}
}
3.4 WebSocket(较少使用)
public class WebSocketServer {
public void run(int port) throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("http-codec", new HttpServerCodec());
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());
pipeline.addLast("handler", new WebSocketServerHandler());
}
}); Channel ch = b.bind(port).sync().channel();
System.out.println("Web socket server started at port " + port + '.');
System.out.println("Open your browser and navigate to http://localhost:" + port + '/'); ch.closeFuture().sync();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
} public static void main(String[] args) throws Exception {
new WebSocketServer().run(8765);
}
}
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
private static final Logger logger = Logger.getLogger(WebSocketServerHandler.class.getName()); private WebSocketServerHandshaker handshaker; @Override
public void messageReceived(ChannelHandlerContext ctx, Object msg) throws Exception {
// 传统的HTTP接入
if (msg instanceof FullHttpRequest) {
handleHttpRequest(ctx, (FullHttpRequest) msg);
}
// WebSocket接入
else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
} @Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
} private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req) throws Exception {
// 如果HTTP解码失败,返回HTTP异常
if (!req.decoderResult().isSuccess() || (!"websocket".equals(req.headers().get("Upgrade")))) {
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
return;
} // 构造握手响应返回,本机测试
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://localhost:8080/websocket", null, false);
handshaker = wsFactory.newHandshaker(req); if (handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), req);
}
} private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) { // 判断是否是关闭链路的指令
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return;
}
// 判断是否是Ping消息
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
// 本例程仅支持文本消息,不支持二进制消息
if (!(frame instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException(String.format("%s frame types not supported", frame.getClass().getName()));
} // 返回应答消息
String request = ((TextWebSocketFrame) frame).text();
if (logger.isLoggable(Level.FINE)) {
logger.fine(String.format("%s received %s", ctx.channel(), request));
}
ctx.channel().write(
new TextWebSocketFrame(request + " , 欢迎使用Netty WebSocket服务,现在时刻:" + new java.util.Date().toString()));
} private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest req, FullHttpResponse res) {
// 返回应答给客户端
if (res.status().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
res.content().writeBytes(buf);
buf.release();
HttpHeaderUtil.setContentLength(res, res.content().readableBytes());
} // 如果是非Keep-Alive,关闭连接
ChannelFuture f = ctx.channel().writeAndFlush(res);
if (!HttpHeaderUtil.isKeepAlive(req) || res.status().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
} @Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端是网页
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
Netty WebSocket 时间服务器
</head>
<br>
<body>
<br>
<script type="text/javascript">
var socket;
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
socket = new WebSocket("ws://localhost:8765/websocket");
socket.onmessage = function(event) {
var ta = document.getElementById('responseText');
ta.value = "";
ta.value = event.data
};
socket.onopen = function(event) {
var ta = document.getElementById('responseText');
ta.value = "打开WebSocket服务正常,浏览器支持WebSocket!";
};
socket.onclose = function(event) {
var ta = document.getElementById('responseText');
ta.value = "";
ta.value = "WebSocket 关闭!";
};
} else {
alert("抱歉,您的浏览器不支持WebSocket协议!");
} function send(message) {
if (!window.WebSocket) {
return;
}
if (socket.readyState == WebSocket.OPEN) {
socket.send(message);
} else {
alert("WebSocket连接没有建立成功!");
}
}
</script>
<form onsubmit="return false;">
<input type="text" name="message" value="Netty最佳实践" /> <br>
<br> <input type="button" value="发送WebSocket请求消息"
onclick="send(this.form.message.value)" />
<hr color="blue" />
<h3>服务端返回的应答消息</h3>
<textarea id="responseText" style="width: 500px; height: 300px;"></textarea>
</form>
</body>
</html>
Netty实例的更多相关文章
- Netty实例-简单的服务端-client实现,凝视具体
书籍推荐: 实例代码 :http://download.csdn.net/detail/jiangtao_st ...
- Netty实例几则
Netty是基于JDK NIO的网络框架 简化了NIO编程, 不用程序自己维护selector, 将网络通信和数据处理的部分做了分离 多用于做底层的数据通信, 心跳检测(keepalived) 1. ...
- Netty 核心组件笔记
Netty是一款高效的NIO框架和工具,基于JAVA NIO提供的API实现. 在JAVA NIO方面Selector给Reactor模式提供了基础,Netty结合Selector和Reactor模式 ...
- 基于netty实现rpc框架-spring boot客户端
上篇讲了RPC服务端的实现.原理就是解析netty通道数据拿到类.方法及入参等信息,然后通过java反射机制调用本地接口返回结果.没有用到很复杂的技术. 这篇我们将客户端的实现.说白了客户端的任务很简 ...
- Vert.x 线程模型揭秘
来源:鸟窝, colobu.com/2016/03/31/vertx-thread-model/ 如有好文章投稿,请点击 → 这里了解详情 Vert.x是一个在JVM开发reactive应用的框架,可 ...
- SLG手游Java服务器的设计与开发——网络通信
文章版权归腾讯GAD所有,禁止匿名转载:禁止商业使用:禁止个人使用. 一.前言 上文分析了我们这款SLG的架构,本章着重讲解我们的网络通信架构,由上文的功能分析我们可以得知,游戏的所有功能基本上属于非 ...
- 基础知识redis详解--【Foam番茄】
Redis 学习方式: 上手就用 基本的理论先学习,然后将知识融汇贯通 nosql讲解 为什么要用Nosql 现在都是大数据时代 大数据一般的数据库无法进行分析处理了 至少要会Springboot+S ...
- Redis系统学习之SpringBoot集成Redis操作API(集成SpringDataRedis及其分析)
SpringDataRedis调用Redis底层解读 在SpringBoot2.X之前还是直接使用的官方推荐的Jedis连接的Redis 在2.X之后换为了lettuce Jedis:采用直接连接,多 ...
- SprinBoot-SpringData-整合
目录 SpringData 整合JDBC JDBCTemplate 整合Druid 配置数据源 配置Druid数据源监控 整合MyBatis 整合测试 整合Redis 测试整合 序列化配置 自定义re ...
随机推荐
- CentOS-6.x系列查看cpu核数
使用CentOS7.x使用习惯了后用top命令,然后按1就可以查看相关的cpu核心数等相关信息 相关概念: 物理CPU:实际Server中插槽上的CPU个数. 物理cpu数量:可以数不重复的 phys ...
- HDU 5391Z ball in Tina Town 数论
题目链接: hdu:http://acm.hdu.edu.cn/showproblem.php?pid=5391 bc: http://bestcoder.hdu.edu.cn/contests/c ...
- alpha8/10
队名:Boy Next Door 燃尽图 晗(组长) 今日完成 和队友讨论alpha版的最终界面. 明日工作 确定alpha版既定功能的正常使用. 还剩下哪些任务 账号绑定功能以及账单信息的下载. 困 ...
- Redis的sentinel机制(sentinel节点IP为:192.168.23.10) “哨兵”
万一主节点打击,主从模型将会停止工作,为了解决这个问题,Redis提供了一个sentinel(哨兵),以此来实现主从切换的功能,一旦主节点宕机了,sentinel将会在从节点中挑一个作为主节点.与zo ...
- Splash广告界面
在软件开始启动时都是会使用一个splashActivity实现联网判断和相关资源的加载,在一款网络软件上开始时的缓存加载和网络判断可以为用户节省不必要的流量开销. 使用handler延时启动下一个ac ...
- 三层神经网络自编码算法推导和MATLAB实现 (转载)
转载自:http://www.cnblogs.com/tornadomeet/archive/2013/03/20/2970724.html 前言: 现在来进入sparse autoencoder的一 ...
- 设计模式php篇(一)————单例模式
话不多说,直接上代码: <?php namespace DesignPattern; /** * php设计模式之单例模式 */ class SingleInstance { private s ...
- PHP中is_null()方法
is_null — 检测变量是否为 NULL bool is_null ( mixed $var ) 如果 var 是 null 则返回 TRUE,否则返回 FALSE. 举例: $x=" ...
- ubuntu下搭建openGL环境
1. 建立基本编译环境 sudo apt-get install build-essential 2. 安装OpenGL Library sudo apt-get install ...
- Spring Cloud Zuul(服务网关)
新建应用:api-gateway pom.xml <?xml version="1.0" encoding="UTF-8"?> <projec ...