这一节我们用的是netty5的版本,但使用的是netty4的API去实现的代码
- Server
package com.xiyou.server;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
public class Server {
public static void main(String[] args) throws InterruptedException {
System.out.println("服务器端已经启动....");
// 1.创建2个线程,一个负责接收客户端连接, 一个负责进行 传输数据
NioEventLoopGroup pGroup = new NioEventLoopGroup();
NioEventLoopGroup cGroup = new NioEventLoopGroup();
// 2. 创建服务器辅助类
ServerBootstrap b = new ServerBootstrap();
b.group(pGroup, cGroup).channel(NioServerSocketChannel.class).option(ChannelOption.SO_BACKLOG, 1024)
// 3.设置缓冲区与发送区大小
.option(ChannelOption.SO_SNDBUF, 32 * 1024).option(ChannelOption.SO_RCVBUF, 32 * 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new StringEncoder());
sc.pipeline().addLast(new ServerHandler());
}
});
ChannelFuture cf = b.bind(10101).sync();
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
cGroup.shutdownGracefully();
}
}
- ServerHandler
package com.xiyou.server;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
class ServerHandler extends ChannelHandlerAdapter {
/**
* 当通道被调用,执行该方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 接收数据
String value = (String) msg;
System.out.println("from client msg:" + value);
// 回复给客户端 “您好!”
String res = "I am ok...";
ctx.writeAndFlush(res);
}
}
- Client
package com.xiyou.client;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import java.io.BufferedReader;
import java.io.InputStreamReader;
public class Client {
public static void main(String[] args) throws Exception {
System.out.println("客户端已经启动....");
// 创建负责接收客户端连接
NioEventLoopGroup pGroup = new NioEventLoopGroup();
Bootstrap b = new Bootstrap();
b.group(pGroup).channel(NioSocketChannel.class).handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
sc.pipeline().addLast(new StringDecoder());
sc.pipeline().addLast(new StringEncoder());
sc.pipeline().addLast(new ClientHandler());
}
});
ChannelFuture cf = b.connect("127.0.0.1", 10101).sync();
// 关闭端口号,否则客户端程序没法退出
// cf.channel().close();
cf.channel().writeAndFlush("one");
cf.channel().writeAndFlush("two");
cf.channel().writeAndFlush("three");
Thread.sleep(1000);
cf.channel().writeAndFlush("four");
cf.channel().writeAndFlush("five");
// 等待客户端端口号关闭
cf.channel().closeFuture().sync();
pGroup.shutdownGracefully();
System.out.println("已经关闭了");
}
}
- ClientHandler
package com.xiyou.client;
import io.netty.channel.ChannelHandlerAdapter;
import io.netty.channel.ChannelHandlerContext;
class ClientHandler extends ChannelHandlerAdapter {
/**
* 当通道被调用,执行该方法
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
// 接收数据
String value = (String) msg;
System.out.println("from server msg:" + value);
}
}
上面的代码我们看下,客户端发送给服务端的时候直接是连着写了五个发送数据的方法,中间Thread.sleep(1000)睡眠一秒、
cf.channel().writeAndFlush("one");
cf.channel().writeAndFlush("two");
cf.channel().writeAndFlush("three");
Thread.sleep(1000);
cf.channel().writeAndFlush("four");
cf.channel().writeAndFlush("five");
我们在开启服务端之后,启动客户端,可以发现,服务端的接受信息是
服务器端已经启动....
from client msg:onetwothree
from client msg:fourfive
上面的现象表示了,其实客户端只是发送了两次请求给服务端,一次是Thread.sleep之前的,一次是之后,将信息一起发送,而不是单条发送。这是为什么呢?
上面只发送两次的原因其实是因为粘包导致的。
- 粘包
将多个包合在一起进行发送,其实也就是将多个连续发送的信息放在一起,一块发送。- 拆包
将一个包拆分成多个包- 粘包与拆包的场景只会在TCP中出现,而不会再UDP中出现。
上面造成粘包的原因是发送的数据速度过快,netty进行优化,将多个数据放到同一个包中进行数据打包发送,因此将休眠之前的数据打包发送,将休眠之后的数据打包发送。netty这么做的目的就是减轻服务端的压力,无需重新建立连接请求,重新建立channel通道。
粘包其实是看服务器的,无法具体确定可以同时发送多少个。
如何解决粘包问题?
如果我们不想发生粘包现象,应该怎么做?
- 定义消息长度
服务器规定了接受的长度,用接收的长度进行控制(不常用,不推荐使用,如果发送的当前数据过大,无法处理)- 将消息分为消息头和消息体,消息头中包含表示信息的总长度(或消息体的长度)的字段。
该方法使用的较多- 可以设定规定的字符进行拆包处理:(我们推荐用这种)
(1)设定过滤器的时候加入过滤器ByteBuf buf = Unpooled.copiedBuffer("_mayi".getBytes()); socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024, buf));
发送的数据应该是:
channelFuture.channel().writeAndFlush("hello_mayi");
发送的时候以_mayi 为后缀进行发送,如果发送的数据没有_mayi 则无法正常接收数据。因为没有这个规定的分割符号,则会在通道中一直被阻塞,所以无法发送到服务端。
该方法的作用是只要我们找到了_mayi的字符串,我们就对其内容进行发送,如果没有找到,我们就会进行等待
(2)应该同时在服务端和客户端都添加该代码
- 还可以用长度去解决粘包问题,但是该方法不好,因为如果发送的数据小于规定的长度不会对其进行发送。发送的数据如果过长,就是只要到达规定长度就进行发送。