this.selector=selector;
this.key=key;
this.realtimeFailureMessageParse=realtimeFailureMessageParse;
}
@Override
public void run() {
try {
process();
} catch (Exception e) {
e.printStackTrace();
}
}
private void process() throws Exception {
try{
//判断key 是否有效
if (key.isValid()) {
try {
//6.判断是否可以连接
if (key.isAcceptable()) {
accept(key);
}
} catch (CancelledKeyException e) {
//出现异常断开连接
key.cancel();
}
try {
//7.判断是否可读
if (key.isReadable()) {
read(key);
}
} catch (CancelledKeyException e) {
//出现异常断开连接
key.cancel();
}
try {
//8.判断是否可写
if (key.isWritable()) {
write(key);
}
} catch (CancelledKeyException e) {
//出现异常断开连接
key.cancel();
}
}
}catch (ClosedChannelException e){
key.cancel();
}
}
/**
*给通道中写数据。从buffer 中给通道写数据。
- @param key
*/
private void write(SelectionKey key) throws IOException {
SocketChannel channel = (SocketChannel) key.channel();
//重新标记为可读
channel.register(selector,SelectionKey.OP_READ);
}
/**
*使用通道读取数据。主要就是将通道中的数据读取到读缓存中。
- @param key
*/
private void read(SelectionKey key) throws IOException {
readBuffer.clear();
SocketChannel channel = (SocketChannel)key.channel();
int len = channel.read(readBuffer);
//如果通道没有数据
if(len==-1){
//关闭通道
key.channel().close();
//关闭key
key.cancel();
return;
}
//Buffer中有游标,游标不会重置,需要我们调用flip重置. 否则读取不一致
readBuffer.flip();
//创建有效字节长度数组
byte[] bytes = new byte[readBuffer.remaining()];
//读取buffer中数据保存在字节数组
readBuffer.get(bytes);
String clientMessage = new String(bytes, “UTF-8”);
//System.out.println("accepted client message are "+clientMessage);
onMessage(clientMessage);
//注册通道,标记为可读操作
channel.register(selector,SelectionKey.OP_WRITE);
}
public void onMessage( String message) {
//处理消息
if(messageHandle(message)){
//成功
log.info(“消息接收成功”);
}else{
//失败
log.error(“消息接收失败:数据格式不正确!”);
}
}
/**
-
处理消息
-
@param message
-
@return
*/
private boolean messageHandle(String message) {
try {
return realtimeFailureMessageParse.parse(message);
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
/**
*设置通道接受客户端数据,并设置通道为可读。
- @param key
*/
private void accept(SelectionKey key) throws IOException {
//1.获取通道
ServerSocketChannel socketChannel = (ServerSocketChannel) key.channel();
//阻塞方法,获取客户端的请求
SocketChannel channel = socketChannel.accept();
if(channel!=null){
//设置为非阻塞
channel.configureBlocking(false);
//设置对应客户端的通道标记,设置次通道为可读时使用
channel.register(selector,SelectionKey.OP_READ);
}
}
}
采用 nio 的方式,代码逻辑写得太复杂了,仅仅是接收就这么费劲,我简单的测了一下,发现还是不行。
继续改进,采用了 netty 进行接收处理。
public void start(String host,int port) {
//负责监听连接
EventLoopGroup boss = new NioEventLoopGroup();
ServerBootstrap boot = new ServerBootstrap();
try {
boot.group(boss)
.option(ChannelOption.SO_REUSEADDR, true)
.option(ChannelOption.SO_BACKLOG,1024)
.childOption(ChannelOption.TCP_NODELAY,true)
.channel(NioServerSocketChannel.class)
.localAddress(host, port)
.childHandler(
new ChannelInitializer() {
@Override
protected void initChannel(NioSocketChannel socketChannel)
throws Exception {
log.info(
“有一个新的客户端连接到服务器,ip={},port={}”,
socketChannel.remoteAddress().getHostName(),
socketChannel.remoteAddress().getPort());
ByteBuf byteBuf = Unpooled.copiedBuffer(“”, StandardCharsets.UTF_8);
socketChannel.pipeline().addLast(new DelimiterBasedFrameDecoder(1024*50, byteBuf));
socketChannel.pipeline().addLast(new StringDecoder(StandardCharsets.UTF_8));
socketChannel.pipeline().addLast(new IdleStateHandler(4,2,0, TimeUnit.SECONDS));
socketChannel.pipeline().addLast(new MyNettyHandle());
}
});
ChannelFuture channel = boot.bind().sync();
channel.channel().closeFuture().sync();
} catch (Exception e) {
log.error(“服务器运行中发生异常!”, e);
} finally {
boss.shutdownGracefully();
}
}
自己的处理器代码如下:
@Component
public class MyNettyHandle extends ChannelInboundHandlerAdapter {
private static Logger log = LoggerFactory.getLogger(SocketServer.class);
private static ExecutorService threadPoolExecutor= CrateTreadPool.crateTreadPool() ;//= Executors.newFixedThreadPool(100);
private static RealtimeFailureMessageParse realtimeFailureMessageParse;
@Autowired
public void setRealtimeFailureMessageParse(RealtimeFailureMessageParse realtimeFailureMessageParse) {
this.realtimeFailureMessageParse = realtimeFailureMessageParse;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String message = (String) msg+“”;
//int index = message.indexOf(“EventTime”);
//log.info(“接收到客户端数据:{}”, message.substring(index,index+30));
RequestProcessor requestProcessor=new RequestProcessor(message,realtimeFailureMessageParse);
threadPoolExecutor.execute(requestProcessor);
}
private void sendMsg(ChannelHandlerContext ctx, String reply) {
reply = reply + “\r\n”;
ByteBuf byteBuf = Unpooled.copiedBuffer(reply.getBytes(StandardCharsets.UTF_8));
ctx.writeAndFlush(byteBuf);
}
private boolean check(String msg) {
return (msg.startsWith(“”) && msg.endsWith(“”));
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
log.error(“当前客户端已经断开连接!{}”,cause.getMessage());
}
}
我刚开始是开日志跑的,还是发现有消息堆积,后来我通过测试,不进行任何的处理,接收消息后,我就打印核心消息进行对比。
int index = message.indexOf(“EventTime”);
log.info(“接收到客户端数据:{}”, message.substring(index,index+30));
发现速度是可以跟得上的,所以这边将不必要的日志都进行清理,不再打印日志。
但是这样做了之后我们发现,数据解析后入库的操作还是不行,每条消息都需要进行插入或者更新操作。这样效率就太低了,也会导致消息堆积处理不完。
后面采用自定义队列,进行批量入库或者更新操作。
自定义队列方案:采用 ConcurrentHashMap 和 Collections.synchronizedList 实现线程安全。
小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长,但自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年最新Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频
如果你觉得这些内容对你有帮助,可以添加下面V无偿领取!(备注Java)
最后,附一张自己面试前准备的脑图:
面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典
- Java核心知识整理
- Spring全家桶(实战系列)
Step3:刷题
既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。
以下是我私藏的面试题库:
很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。
最后祝愿各位身体健康,顺利拿到心仪的offer!
最后,附一张自己面试前准备的脑图:
[外链图片转存中…(img-19iLpXt2-1711018911290)]
面试前一定少不了刷题,为了方便大家复习,我分享一波个人整理的面试大全宝典
- Java核心知识整理
[外链图片转存中…(img-Gj5bF4Fd-1711018911290)]
- Spring全家桶(实战系列)
[外链图片转存中…(img-7AsPavrj-1711018911291)]
Step3:刷题
既然是要面试,那么就少不了刷题,实际上春节回家后,哪儿也去不了,我自己是刷了不少面试题的,所以在面试过程中才能够做到心中有数,基本上会清楚面试过程中会问到哪些知识点,高频题又有哪些,所以刷题是面试前期准备过程中非常重要的一点。
以下是我私藏的面试题库:
[外链图片转存中…(img-BKBACy1K-1711018911291)]
很多人感叹“学习无用”,实际上之所以产生无用论,是因为自己想要的与自己所学的匹配不上,这也就意味着自己学得远远不够。无论是学习还是工作,都应该有主动性,所以如果拥有大厂梦,那么就要自己努力去实现它。
最后祝愿各位身体健康,顺利拿到心仪的offer!