构建一个简单的socket程序
这里采用的是空maven项目,构建client端和server端,实现一个客户端建立连接后发送消息,然后服务端返回一个消息的简单程序
server端代码
基本套路:
- 在Server类中创建分发线程组和工作线程组,创建启动类Bootstrap服务端是ServerBootstrap)
- 为Bootstrap进行初始化,指定channel,初始化channel(初始化channel有两个函数,一个是childHandler,还有个是handler,其中childHandler对应的是工作线程组)
- 编写初始化channel的类,装填handler在管道中。(这里我把channel初始化类卸载Server类中了,使用了匿名内部类创建并初始化)
- 编写业务handler
Server类代码:
public class Server {
public static void main(String[] args) {
EventLoopGroup bossGroup = new NioEventLoopGroup();//分发线程组
EventLoopGroup workerGroup = new NioEventLoopGroup();//工作线程组
ServerBootstrap cb = new ServerBootstrap();//服务启动对象
try {
//这里直接用匿名内部类构造channel初始化类
//绑定线程组,设置channel,初始化channel
cb.group(bossGroup,workerGroup).channel(NioServerSocketChannel.class).childHandler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();//初始化管道
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4))
.addLast(new LengthFieldPrepender(4))
.addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new ServerHandler());//最后执行我们的业务handler
}
});
ChannelFuture channelFuture = cb.bind( 9999).sync();//绑定端口
channelFuture.channel().closeFuture().sync();//关闭
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
bossGroup.shutdownGracefully();//优雅关闭
workerGroup.shutdownGracefully();//优雅关闭
}
}
}
handler代码:
public class ServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
//等待五秒后发送给客户端消息
Thread.sleep(5000);
System.out.println(ctx.channel().remoteAddress()+":"+msg);
ctx.writeAndFlush("服务端端回复");
}
//处理异常
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();//遇到异常关闭连接
}
}
client端代码
client端代码和server端代码很相似,这也是源于netty的巧妙设计,很强大
client端的server代码:
public class ClientServer {
public static void main(String[] args) {
EventLoopGroup group = new NioEventLoopGroup();//客户端不需要分发线程,所以只需要一个线程组就行了
Bootstrap cb = new Bootstrap();//这里不再使用ServerBootstrap了,因为是客户端
try {
//这里直接用匿名内部类构造channel初始化类
//同样这里的channel对象,用的也不是NioServerSocketChannel进行反射构造了。因为只有一个线程组就使用了handler
cb.group(group).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
protected void initChannel(SocketChannel socketChannel) throws Exception {
ChannelPipeline pipeline = socketChannel.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4))
.addLast(new LengthFieldPrepender(4))
.addLast(new StringDecoder())
.addLast(new StringEncoder())
.addLast(new ClientHandler());//这里基本上和服务端一样
}
});
ChannelFuture channelFuture = cb.connect("localhost", 9999).sync();
channelFuture.channel().writeAndFlush("hello");//这里是手动发送消息,其实还可以通过handler中的回调触发发送消息
channelFuture.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}
handler代码:
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress()+":"+msg);//显示服务端的消息
ctx.writeAndFlush("客户端回复");//然后再回复,这里就会不断的相互发送消息,因为服务端也会回复,互相发送
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
运行结果
先启动server再启动client,查看结果
Server端
Client端
总结
可以看出netty不仅对服务端有很好的封装,相应的对于服务端也能同样的套路编写。方便了我们使用底层的网络编程API,netty的设计理念只是为了提供一个更好的使用底层网络编程的框架,可以供我们做更多的上层搭建。
通过之前的基于Http协议的程序和现在的C/S模式的程序,也能看出,这其中变化最大的部分应该是对channel初始化的配置,其中很多都是使用netty提供好的handler进行管道的配置,能让我们更容易的解析socket中传输的数据,这将会是使用netty的重点学习部分。