关注我的公众号:【编程朝花夕拾】,可获取首发内容。

01引言
之前分享了关于Socket服务端的代码,这一节我们继续Socket客户端代码的分享。有了之前的代码基础,分享起来客户端端就非常简单了。因为客户端和服务端几位相似,甚至可以直接拷贝,但是有一些细节需要注意。
02 Socket客户端
2.1 代码预览
java
@Slf4j
public class MockClient {
@Getter
private SocketChannel socketChannel;
public void connect() throws InterruptedException {
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup);
bootstrap.channel(NioSocketChannel.class);
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(2048, Unpooled.copiedBuffer("_".getBytes())));
pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
pipeline.addLast(new SimpleChannelInboundHandler<String>(){
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
log.info("client receive: {}", msg);
}
});
}
});
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9091).sync();
this.socketChannel = (SocketChannel) channelFuture.channel();
}
}
同样使用EventLoopGroup线程池,不过这里的线程组只需要一个就可以了,直接用来处理业务。
2.2 引导类
java
EventLoopGroup eventLoopGroup = new NioEventLoopGroup(1);
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(eventLoopGroup);
io.netty.bootstrap.Bootstrap为客户端的引导类和服务端是不一样的。
io.netty.bootstrap.ServerBootstrap:服务端的引导类io.netty.bootstrap.Bootstrap:客户端的引导类
但是引导类对应的方法非常类似。
2.3 配置
java
// 绑定通道
bootstrap.channel(NioSocketChannel.class);
// 保活连接
bootstrap.option(ChannelOption.SO_KEEPALIVE, true);
绑定的通道区别于服务端的通道:
io.netty.channel.socket.nio.NioServerSocketChannel:服务端的通道io.netty.channel.socket.nio.NioSocketChannel:客户端的通道
类似带Server关键字的都属于服务端的属性或方法。
2.4 编解码器
编解码器要和服务端保持一致。
java
bootstrap.handler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
pipeline.addLast(new DelimiterBasedFrameDecoder(2048, Unpooled.copiedBuffer("_".getBytes())));
pipeline.addLast(new StringDecoder(StandardCharsets.UTF_8));
pipeline.addLast(new StringEncoder(StandardCharsets.UTF_8));
pipeline.addLast(new SimpleChannelInboundHandler<String>(){
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
log.info("client receive: {}", msg);
}
});
}
});
这里要说明的是,自定义的业务处理器需要我们根据需要处理对应的方法。客户端主要用来发送消息。接不接收消息需要根据具体情况处理,案例为了验证客户端和服务端的相互通信,这里选择打印接收的消息。
2.5 连接服务端
java
ChannelFuture channelFuture = bootstrap.connect("127.0.0.1", 9091).sync();
this.socketChannel = (SocketChannel) channelFuture.channel();
注意这里的连接服务端的方式和服务端暴漏接口的方式的区别:
bootstrap.connect(ip,port)用来连接服务端的IP和端口,而服务端通过serverBootstrap.bind(9091)来绑定端口。
同时,都需要通过同步的等待端口绑定成功或连接服务端的IP和接口成功。
值得注意的是:
客户端的socketChannel.closeFuture().sync();要不要等待通道关闭?客户端主要用来发布消息,可以完全不同处理。如果还要等待接收服务端的消息,并且会随着方法执行完毕而结束,则需要该代码阻塞。
而实际应用都是嵌套在业务应用中的,本身就不会关闭,更加无需阻塞。
03 SprintBoot配置
前面介绍了服务端,我们结合客户端和服务端集成到SpringBoot中,形成完整的链路。
3.1 服务端初始化
java
@Slf4j
@Component
public class StartConfig {
@Autowired
private SockerServer socketServer;
@PostConstruct
public void init() {
socketServer.start();
}
}
项目启动的时候可以通过@PostConstruct注解,完成项目启动后服务端的初始化。
3.2 客户端调用
java
@Test
void contextLoads() throws Exception {
MockClient mockClient = new MockClient();
mockClient.connect();
SocketChannel socketChannel = mockClient.getSocketChannel();
// 发送消息
socketChannel.writeAndFlush("foo test..._");
}
04 小结
客户端的代码到这里基本就结束了。后面就会介绍WebSocket以及在使用过程中的遇到的一些问题,分享给大家。