1. 为什么会有这个冲动?
最近使用到了netty需要进行测试,所以想着编写一个客户端。刚开始的时候编写了一个简单地main方法。每测试一次就需要重启一下,太麻烦了……,所以就想着如果像接口一样每次都调用一次接口那就不用重启了。其中代码参考了网络上找到的,但忘记是谁的了,如有需要删除,请私信。
2. 目录结构
3.代码
记录顺序就按照包的顺序
package com.bkht.testcollect.modules.channel;
import com.bkht.testcollect.modules.handler.MessageHandler;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.concurrent.TimeUnit;
/**
* Netty 通道初始化
*
* @author lipw
*/
@Component
@RequiredArgsConstructor
public class ChannelInit extends ChannelInitializer<SocketChannel> {
private final MessageHandler messageHandler;
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline()
// 每隔60s的时间触发一次userEventTriggered的方法,并且指定IdleState的状态位是WRITER_IDLE,事件触发给服务器发送ping消息
.addLast("idle", new IdleStateHandler(0, 60, 0, TimeUnit.SECONDS))
// 添加解码器
.addLast(new StringDecoder())
// 添加编码器
.addLast(new StringEncoder())
// 添加消息处理器
.addLast("messageHandler", messageHandler);
}
}
package com.bkht.testcollect.modules.config;
import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
/**
* 读取YML中的服务配置
*
* @author lipw
*/
@Configuration
@ConfigurationProperties(prefix = ClientProperties.PREFIX)
@Data
public class ClientProperties {
public static final String PREFIX = "netty";
/**
* 客户端IP
*/
private Integer clientPort;
/**
* 默认连接的服务器Ip
*/
private String serverIp;
/**
* 默认;连接的服务器端口
*/
private Integer serverPort;
}
package com.bkht.testcollect.modules.controller;
import com.alibaba.fastjson.JSONObject;
import com.bkht.testcollect.modules.server.TcpClient;
import com.bkht.testcollect.modules.test.NettyClient;
import io.netty.bootstrap.Bootstrap;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;
import javax.annotation.Resource;
/**
* 模拟发送api
*
* @author lipw
*/
@RequiredArgsConstructor
@RestController
@Slf4j
public class HttpApi {
@Resource
private TcpClient tcpClient;
/**
* 消息发布
*/
@GetMapping("/send")
public String send(String message) {
System.out.println("send:" + message);
tcpClient.getSocketChannel().writeAndFlush(message);
return "已经发送:" + message + ",成功!";
}
/**
* 消息发布
*/
@PostMapping("/send/json")
public String send(@RequestBody JSONObject body) {
tcpClient.getSocketChannel().writeAndFlush(body.toJSONString());
return "发送成功";
}
/**
* 连接
*/
@GetMapping("connect")
public String connect(String ip, Integer port) throws Exception {
tcpClient.connect(ip, port);
return "重启指令发送成功";
}
/**
* 重连
*/
@GetMapping("reconnect")
public String reconnect() throws Exception {
tcpClient.reconnect();
return "重启指令发送成功";
}
}
package com.bkht.testcollect.modules.handler;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
/**
* 息处理,单例启动
*
* @author lipw
*/
@Slf4j
@Component
@ChannelHandler.Sharable
@RequiredArgsConstructor
public class MessageHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception {
log.debug("\n");
log.debug("channelId:" + ctx.channel().id());
System.out.println("channelId:" + ctx.channel().id());
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
log.debug("\n");
log.debug("开始连接");
System.out.println("开始连接");
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.debug("\n");
log.info("成功建立连接,channelId:{}", ctx.channel().id());
System.out.println("成功建立连接,channelId:{}"+ ctx.channel().id());
super.channelActive(ctx);
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
log.info("心跳事件时触发");
System.out.println("心跳事件时触发");
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
// 当我们长时间没有给服务器发消息时,发送ping消息,告诉服务器我们还活跃
if (event.state().equals(IdleState.WRITER_IDLE)) {
log.debug("发送心跳");
System.out.println("发送心跳");
ctx.writeAndFlush("ping");
}
} else {
super.userEventTriggered(ctx, evt);
}
}
}
package com.bkht.testcollect.modules.init;
import com.bkht.testcollect.modules.server.TcpClient;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
@Component
public class InitNettyClient implements CommandLineRunner {
@Resource
private TcpClient tcpClient;
@Override
public void run(String... args) throws Exception {
tcpClient.start();
}
}
package com.bkht.testcollect;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class TestcollectApplication {
public static void main(String[] args) {
SpringApplication.run(TestcollectApplication.class, args);
}
}
server:
port: 8087
spring:
application:
name: tcp-client
# tcp
netty:
# 客户端监听端口
client-port: 8088
# 默认连接的服务器 ip
server-ip: 127.0.0.1
# 默认连接的服务器端口
server-port: 9099
# 日志配置
logging:
level:
com.netty: debug