public class ClientInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE,0,4,0,4));
pipeline.addLast(new LengthFieldPrepender(4));
pipeline.addLast(new StringDecoder(CharsetUtil.UTF_8));
pipeline.addLast(new StringEncoder(CharsetUtil.UTF_8));
pipeline.addLast(new ClientHandler());
}
}
因为主体还是字符串传输,所以再复述一遍。
StringEncode
和StringDecode
都是可以指定编码的,默认也是utf8
。
客户端和服务端的编解码要对应,所以一样的就不多说了。
server
public class ServerHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println(ctx.channel().remoteAddress() + "请求");
ctx.writeAndFlush(LocalDateTime.now().toString());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
简单逻辑就是如此,返回当前时间即可。
更多操作后面添加。
我们写入的时候是
toString
过后的,也就是字符串对象。当不进行
toString
的时候,你会发现客户端根本就不能够进行接收。同样的,客户端
writeAndFlush
写入的不是String
的话,服务器的方法也没有触发。encodedecodedecodeencodesourcebinarytarget所以前面说
编码
和解码
要一一对应,编码当然简单,基本没有失败一说。但是如果解码失败,这时候解码的
handler
就真的是拦截器
了,这个信息并不会向后传播。所以也就不会触发后续的业务逻辑。
当业务逻辑比较简单的时候,编解码的
handler
就会是服务器的主体代码。
规范一点,异常处理也要覆盖到,否则你会看到这个提示
It usually means the last handler in the pipeline did not handle the exception.
不陌生吧,这些地方要注意,业务逻辑和异常处理,也就是channelRead0
和exceptionCaugth
缺一不可。
client
public class ClientHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
System.out.println("time from server:" + msg);
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
ctx.writeAndFlush("ask for time");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
嗯,额外增加了一个部分,从名称上看来是链接激活
时执行的。
之前的代码其实已经准备就绪,只是看不到效果而已。
状态大致类比于此:
A:你先动手我一定打死你
B:有种你动手,我保证打不死你
叫嚣了半天,没人动手,虽然准备打架了,但是就是没有打起来。
这个时候我们channelActive
悄悄的挑拨一下,然后就开始动手了,也就是去触发
。
因为channelRead0
是接收到数据才能够进行处理的。
代码有有一个最大的漏洞,那就是身份问题。
可以看到,不论是哪个服务器,也不论是谁,都能够调用我们的服务。
我们当然能够去做一些限定条件。
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
if (!"godme-godme".equals(msg)) {
ctx.writeAndFlush("你没有权限访问该服务:username-password");
return;
}
System.out.println(ctx.channel().remoteAddress() + "发起请求");
ctx.writeAndFlush(LocalDateTime.now().toString());
}
ip
限定protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
InetSocketAddress address = (InetSocketAddress) ctx.channel().remoteAddress();
if (!"127.0.0.1".equals(address.getHostName())){
ctx.close();
return;
}
if (!"godme-godme".equals(msg)) {
ctx.writeAndFlush("你没有权限访问该服务:username-password");
return;
}
System.out.println(address.getHostName() + "发起请求");
ctx.writeAndFlush(LocalDateTime.now().toString());
}
不是规定的ip
,直接关闭连接。
这算得上是一个完整的客户端-服务端
代码了。
麻雀虽小五脏俱全,基本流程就是如此了。
至于校验之类的,可以自己随便定义。
垃圾代码在此;