Netty框架中文编码

13 篇文章 0 订阅
12 篇文章 1 订阅

1. 背景

此篇主要是针对中文传输时的编码问题进行实战。

2. telnet 命令实验

使用命令行,发送汉字“你好”。

2.1 GBK编码

win10系统下,打开命令行使用chcp命令可以看到显示活动代码页:936,936表示是GBK编码,使用telnet 127.0.0.1 8001命令后,输入“你好”回车,看到服务端接收到4个字节如下:

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| c4                                              |.               |
+--------+-------------------------------------------------+----------------+
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| e3                                              |.               |
+--------+-------------------------------------------------+----------------+
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| ba                                              |.               |
+--------+-------------------------------------------------+----------------+
         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| c3                                              |.               |
+--------+-------------------------------------------------+----------------+

2.2 UTF-8编码

打开命令行,使用chcp 65001,将命令行切换为utf-8编码环境,使用telnet 127.0.0.1 8001命令后,输入“你好”回车,不知什么原因闪退,所以无法验证,服务端后台输出日志如下:

13:44:12.494 [nioEventLoopGroup-3-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x03215ed2, L:/127.0.0.1:8001 - R:/127.0.0.1:1525] REGISTERED
13:44:12.495 [nioEventLoopGroup-3-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x03215ed2, L:/127.0.0.1:8001 - R:/127.0.0.1:1525] ACTIVE
13:44:17.153 [nioEventLoopGroup-3-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x03215ed2, L:/127.0.0.1:8001 - R:/127.0.0.1:1525] READ COMPLETE
13:44:17.153 [nioEventLoopGroup-3-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x03215ed2, L:/127.0.0.1:8001 - R:/127.0.0.1:1525] FLUSH
13:44:17.153 [nioEventLoopGroup-3-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x03215ed2, L:/127.0.0.1:8001 ! R:/127.0.0.1:1525] INACTIVE
13:44:17.153 [nioEventLoopGroup-3-2] INFO io.netty.handler.logging.LoggingHandler - [id: 0x03215ed2, L:/127.0.0.1:8001 ! R:/127.0.0.1:1525] UNREGISTERED

3. 客户端单元代码

3.1 jdk原生

写一个main方法,实现java原生socket客户端,头两字节表示报文长度,来验证服务端对两种编码的报文处理。

3.1.1 GBK编码(JDK 原生)

服务端后台日志显示,读取了6B,头俩字节表示报文长度为4B,“你好”汉字的十六进制为“c4 e3 ba c3”,一个汉字占2B。

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 34 c4 e3 ba c3                               |04....          |
+--------+-------------------------------------------------+----------------+

3.1.2 UTF-8编码(JDK 原生)

服务端后台日志显示,读取了8B,头俩字节表示报文长度为6B,“你好”汉字的十六进制为“e4 bd a0 e5 a5 bd”,一个汉字占3B。

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| 30 36 e4 bd a0 e5 a5 bd                         |06......        |
+--------+-------------------------------------------------+----------------+

3.2 netty客户端

使用netty框架编写一个main方法,直接发送报文内容来验证服务端对两种编码的报文处理。

3.2.1 GBK编码(netty客户端)

客户端指定编码为GBK格式。

Bootstrap bootstrap = new Bootstrap();
bootstrap.group(group)
	.channel(NioSocketChannel.class)
	.option(ChannelOption.TCP_NODELAY, true)
	.handler(new ChannelInitializer<NioSocketChannel>() {
		@Override
	    protected void initChannel(NioSocketChannel ch) throws Exception {
	        final ChannelPipeline pipeline = ch.pipeline();
	        pipeline.addLast(new LoggingHandler(LogLevel.INFO));
	        pipeline.addLast(new StringEncoder(Charset.forName("GBK")));
	    }
	});

服务端后台读取报文日志如下:

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| c4 e3 ba c3                                     |....            |
+--------+-------------------------------------------------+----------------+

3.2.2 UTF-8编码(netty客户端)

客户端指定编码为UTF-8格式,代码同上,只把编码格式改了即可。
服务端后台读取报文日志如下:

         +-------------------------------------------------+
         |  0  1  2  3  4  5  6  7  8  9  a  b  c  d  e  f |
+--------+-------------------------------------------------+----------------+
|00000000| e4 bd a0 e5 a5 bd                               |......          |
+--------+-------------------------------------------------+----------------+

4. 小结

不同编码的中文字节是不同的,我们实际开发中在报文通信标准中会明确表示中文采用哪一种编码方式,避免通信两端出现中文乱码问题。
假如针对老系统升级,面向的客户端是不同的主体,并且因为各种原因出现了编码不一致的问题(实际情况下很难出现的场景),那么我们该如何处理?之前我想到一种办法是使用Nginx代理stream,但是经过验证不可行。后边我还有一种想法是通过程序来实现,根据对端的IP来指定不同的编码方式,采用配置化的方式实现。此种方式有待于进一步学习动态handler来进行验证。

5. 附

5.1 服务端代码

public class FirstHandler extends ChannelInboundHandlerAdapter {
	public static final Logger log = LoggerFactory.getLogger(FirstHandler.class);

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        log.info("服务端接收消息是:{}", msg);
        ctx.write(msg);
    }
	
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) {
        ctx.flush();
    }
    
    @Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		log.info("exceptionCaught: " + cause.getLocalizedMessage());
	}
}
public class FirstServer {
	public static final Logger log = LoggerFactory.getLogger(FirstServer.class);
    private final String ip = "127.0.0.1";
    private final String port = "8001";

    public void init(){
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap bs = new ServerBootstrap();
        bs.group(bossGroup, workerGroup);
        bs.channel(NioServerSocketChannel.class);
        bs.childHandler(new ChannelInitializer<Channel>(){
            @Override
            protected void initChannel(Channel ch) throws Exception {
                final ChannelPipeline pipeline = ch.pipeline();
                pipeline.addLast(new LoggingHandler(LogLevel.INFO));
                pipeline.addLast(new FirstHandler());
            }
        });

        try {
            ChannelFuture channelFuture = bs.bind(ip, Integer.parseInt(port)).sync();
            log.info("Netty Server 启动成功! Ip: " + channelFuture.channel().localAddress().toString() + " ! ");;

            channelFuture.channel().closeFuture().sync();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

5.2 客户端代码

5.2.1 netty客户端

public class CodeClient {

	public void connect(int port, String host) throws InterruptedException {
		EventLoopGroup group = new NioEventLoopGroup();
		
		Bootstrap bootstrap = new Bootstrap();
		bootstrap.group(group)
		    .channel(NioSocketChannel.class)
		    .option(ChannelOption.TCP_NODELAY, true)
		    .handler(new ChannelInitializer<NioSocketChannel>() {
		    	@Override
	            protected void initChannel(NioSocketChannel ch) throws Exception {
	                final ChannelPipeline pipeline = ch.pipeline();
	                pipeline.addLast(new LoggingHandler(LogLevel.INFO));
	                pipeline.addLast(new StringEncoder(Charset.forName("UTF-8")));
	            }
		    });
		
		ChannelFuture future = bootstrap.connect("127.0.0.1", 8001).sync();
		
		future.channel().writeAndFlush("你好");
		
		future.syncUninterruptibly();
		
		future.channel().closeFuture().sync();
	}
	
	public static void main(String[] args) throws InterruptedException {
		new CodeClient().connect(8001, "127.0.0.1");
	}
}

5.2.2 jdk原生

public class InData extends BufferedInputStream {
	
	private static final Logger logger = LoggerFactory.getLogger(InData.class);
	
	private byte[] bdat;
    private int head;
    private int len;
    private String decode;

    public InData(InputStream in, int head, String decode) {
        super(in);
        this.head = head;
        bdat = new byte[this.head];
        this.decode = decode;
    }

    public String readLine() throws Exception {
        String indata;
        int ret = 0;
        try {
        	ret = read(bdat, 0, head);
        } catch (IOException ioe) {
            throw ioe;
        }
        indata = new String(bdat, decode);
        len = (new Integer(indata.trim())).intValue();
        logger.info("接收数据长度 <<[" + indata + "]");
        bdat = new byte[len];
        try {
            ret = read(bdat, 0, len);
        } catch (IOException e) {
            logger.info("ioe : " + e.toString());
            throw e;
        }
        if (ret != len || ret <= 0) {
            throw new Exception("报文包头长度错误");
        }
        indata = new String(bdat, decode);
        logger.info("接收数据 <<[" + indata+ "]");
        return indata;
    }
}
public class OutData extends BufferedOutputStream {

	private static final Logger logger = LoggerFactory.getLogger(OutData.class);
	
    private byte[] bdat;
    private int head_len;
    private String outdata = "";
    private String encode;

    public OutData(OutputStream out, int head_len, String encode) {
        super(out);
        this.head_len = head_len;
        this.encode = encode;
    }

    public int writeLine(String outdata) {
        String tmp;
        int len_src;
        try {
            tmp = new String(outdata.getBytes(), encode);
            
        } catch (UnsupportedEncodingException ex) {
            return -1;
        }
        len_src = tmp.getBytes().length;
        
        for (int i = 0; i < (head_len - new Integer(len_src).toString().length()); i++)
            this.outdata = this.outdata.concat(" ");
        this.outdata = this.outdata + new Integer(len_src).toString() + outdata;
        
        len_src = len_src + head_len;
        
        try {
            bdat = new byte[len_src];
            bdat = this.outdata.getBytes();
            this.write(bdat, 0, len_src);
            this.flush();
        } catch (IOException ioe) {
            logger.info("发送服务器数据时 异常 : " + ioe.toString());
            return -1;
        }
        return 0;
    }
}
public class SocketUtils {

	private Socket serv;
    private InData inServer;
    private OutData outServer;
    private String codeing;
    
    public void init(String ip, int port, int head, String codeing) throws Exception {
        try {
            serv = new Socket(ip, port);
            serv.setSoTimeout(3600000);
        } catch (UnknownHostException uhe) {
            throw new Exception(uhe.toString());
        } catch (IOException ioe) {
            throw new Exception(ioe.toString());
        } catch (Exception e) {
            throw new Exception(e.toString());
        }
        this.codeing = codeing;
        inServer = new InData(serv.getInputStream(), head, this.codeing);
        outServer = new OutData(serv.getOutputStream(), head, this.codeing);
    }
    
    
    public String send(String sendline) throws Exception {
        String recvline;
        try {
            if (outServer.writeLine(sendline) != 0) {
                throw new Exception("发送服务器数据异常");
            }
            recvline = inServer.readLine();
        } catch (IOException e) {
            throw e;
        } catch (Exception e) {
            throw e;
        }
        return recvline;
    }

    public void close() throws Exception {
        if (inServer != null) {
        	try {
        		inServer.close();
            } catch (Exception e) {
            	throw e;
            }
        }
        if (outServer != null) {
        	try {
        		outServer.close();
            } catch (Exception e) {
            	throw e;
            }
        }
        if (serv != null) {
        	try {
                serv.close();
            } catch (Exception e) {
            	throw e;
            }
        }
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
以下是一个简单的 Java RPC Netty 框架代码实现: ```java // 定义 RPC 请求实体类 public class RpcRequest implements Serializable { private String className; private String methodName; private Object[] parameters; // 省略 getter 和 setter 方法 } // 定义 RPC 响应实体类 public class RpcResponse implements Serializable { private Object result; private String error; // 省略 getter 和 setter 方法 } // 定义 RPC 服务接口 public interface RpcService { // 定义服务方法 public int add(int a, int b); } // 实现 RPC 服务接口 public class RpcServiceImpl implements RpcService { @Override public int add(int a, int b) { return a + b; } } // 定义 RPC 服务器端 public class RpcServer { private static final Logger LOGGER = LoggerFactory.getLogger(RpcServer.class); private String host; private int port; public RpcServer(String host, int port) { this.host = host; this.port = port; } public void start() throws InterruptedException { // 创建线程池 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 public void initChannel(SocketChannel ch) throws Exception { // 添加解码器和编码器 ch.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null))); ch.pipeline().addLast(new ObjectEncoder()); // 添加业务处理类 ch.pipeline().addLast(new RpcServerHandler()); } }) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); // 启动服务器 ChannelFuture f = b.bind(host, port).sync(); LOGGER.info("Server started on {}:{}", host, port); // 等待直到服务器 socket 关闭 f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } // 定义业务处理类 private class RpcServerHandler extends ChannelInboundHandlerAdapter { @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { RpcRequest request = (RpcRequest) msg; LOGGER.info("Received request: {}", request); // 调用服务 RpcResponse response = new RpcResponse(); try { Class<?> clazz = Class.forName(request.getClassName()); Method method = clazz.getMethod(request.getMethodName(), request.getParameters().getClass()); Object result = method.invoke(clazz.newInstance(), request.getParameters()); LOGGER.info("Result: {}", result); response.setResult(result); } catch (Exception e) { LOGGER.error("Error occurred while invoking service: {}", e.getMessage()); response.setError(e.getMessage()); } // 返回响应 ctx.writeAndFlush(response); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.error("Error occurred in server: {}", cause.getMessage()); ctx.close(); } } } // 定义 RPC 客户端 public class RpcClient { private static final Logger LOGGER = LoggerFactory.getLogger(RpcClient.class); private String host; private int port; public RpcClient(String host, int port) { this.host = host; this.port = port; } public Object invoke(String className, String methodName, Object... parameters) { // 创建连接 EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap b = new Bootstrap(); b.group(group) .channel(NioSocketChannel.class) .handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { // 添加解码器和编码器 ch.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null))); ch.pipeline().addLast(new ObjectEncoder()); // 添加业务处理类 ch.pipeline().addLast(new RpcClientHandler()); } }) .option(ChannelOption.SO_KEEPALIVE, true); // 连接服务器 ChannelFuture f = b.connect(host, port).sync(); LOGGER.info("Connected to server {}:{}", host, port); // 发送请求 RpcRequest request = new RpcRequest(); request.setClassName(className); request.setMethodName(methodName); request.setParameters(parameters); f.channel().writeAndFlush(request); // 等待服务器响应 RpcResponse response = RpcClientHandler.getResponse(); if (response.getError() != null) { LOGGER.error("Error occurred while invoking service: {}", response.getError()); } else { LOGGER.info("Result: {}", response.getResult()); return response.getResult(); } // 关闭连接 f.channel().closeFuture().sync(); } catch (Exception e) { LOGGER.error("Error occurred in client: {}", e.getMessage()); } finally { group.shutdownGracefully(); } return null; } // 定义业务处理类 private static class RpcClientHandler extends ChannelInboundHandlerAdapter { private static RpcResponse response; public static RpcResponse getResponse() { return response; } @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { response = (RpcResponse) msg; LOGGER.info("Received response: {}", response); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { LOGGER.error("Error occurred in client: {}", cause.getMessage()); ctx.close(); } } } // 测试代码 public class RpcTest { public static void main(String[] args) throws InterruptedException { // 启动服务器 RpcServer server = new RpcServer("localhost", 8080); server.start(); // 等待服务器启动 Thread.sleep(1000); // 创建客户端并调用服务 RpcClient client = new RpcClient("localhost", 8080); RpcService service = (RpcService) client.invoke("RpcServiceImpl", "add", 1, 2); System.out.println(service.add(1, 2)); } } ``` 以上代码实现了一个简单的 Java RPC Netty 框架,包括 RPC 请求和响应实体类、RPC 服务接口和实现类、RPC 服务器端和客户端。其中,RPC 服务器端和客户端都使用了 Netty 框架,通过序列化和反序列化实现了数据的传输和处理。在测试代码中,我们启动了一个 RPC 服务器,并通过客户端调用了 RPC 服务。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Jinwen5290

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值