java使用Netty实现TCP收发消息的例子,多线程并且含断线自动重连

需求:有一个TCP的服务,需要使用Netty开发一个TCP连接并收发消息的程序。要求 多线程并且含断线自动重连 能力。

组织结构,使用 Java Maven 编程方式

功能还包含 读取配置文件 和 log4j2写日志 部分

 完整代码:

App.java

package com.LSpbxServer;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.ResourceBundle;

/**  pbxServer  */
public class App implements INettyClientEventListener
{
    protected static final Logger logger = LoggerFactory.getLogger(App.class);

    public static void main( String[] args )
    {
        //System.out.println( "Hello World!" );
        logger.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"  );
        logger.info("@@@@@@@@@@@@@@@@@@@ 启动 pbxServer @@@@@@@@@@@@@@@@@@@@@@@@@@@@@"  );
        logger.info("@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@"  );
        //读取参数
        ResourceBundle resource = ResourceBundle.getBundle("config");
        String strPbxIp = resource.getString("pbxIp");//
        String strPbxPort = resource.getString("pbxPort");
        String strApiPwd = resource.getString("apiPwd");
        //System.out.println( "pbx "+strPbxIp+" " + strPbxPort +" "+ strApiPwd +"---------- ");
        logger.info("pbx 参数: " + strPbxIp + " " + strPbxPort + " " + strApiPwd + "  ---------- ");

        try {
            new NettyClient().connect();

        }catch (Exception ex) {
            System.out.println("Error1 " + ex.toString());
        }
    }

    //pbx收到消息事件----------------------------------------------
    public void NettyClientEvent_RecMessage(String message) {
        // 执行你希望执行的逻辑
        System.out.println("Received message: " + message);
    }

}
NettyClient.java
package com.LSpbxServer;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
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;


public class NettyClient {
    private static final String HOST = "192.168.1.150"; // 服务器地址
    private static final int PORT = 8444; // 服务器端口

    public void connect() {
        // 创建主从线程组
        final EventLoopGroup group = new NioEventLoopGroup();
        // 创建客户端引导类
        final Bootstrap bootstrap = new Bootstrap();
        try {
            bootstrap.group(group)
                    .channel(NioSocketChannel.class) // 使用NIO传输
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            // 配置通道流水线
                            ChannelPipeline pipeline = ch.pipeline();
                            pipeline.addLast(new StringDecoder()); // 添加字符串解码器
                            pipeline.addLast(new StringEncoder()); // 添加字符串编码器
                            pipeline.addLast(new NettyClientHandler(bootstrap, group)); // 添加客户端处理器

                        }
                    });
            // 发起连接
            ChannelFuture future = bootstrap.connect(HOST, PORT).sync();
            // 等待连接关闭
            future.channel().closeFuture().sync();

        } catch (Exception ex) {
            try {
                System.out.println("连接pbx失败 NettyClient().connect() 20秒后 再次连接 =========================");

                for (int i = 1; i <= 20; i++) {
                    System.out.println("连接pbx失败 NettyClient().connect()  线程 " + Thread.currentThread().getName() + "   " + i);
                    // 线程休眠1秒
                    Thread.sleep(1000);
                }
                new NettyClient().connect();
            } catch (Exception ex1) {
                System.out.println("再次尝试连接出错,退出程序=========================");
            }

        } finally {
            // 优雅地退出,释放线程池资源
            System.out.println("优雅地退出,释放线程池资源=========");
            group.shutdownGracefully();
            //throw new Exception("这是一个检查型异常");
        }

    }

    public void reRunClient() {
        try {
            System.out.println(" pbx连接已断开 " + " 5秒后 再次连接pbx ");
            // 线程休眠5秒
            Thread.sleep(5000);
            System.out.println(" " + " 开始连接pbx--------- ");
            connect();//再次连接pbx

        } catch (Exception ex) {
            System.out.println("重新连接pbx时出现异常 " + ex.toString());
        }
    }

}

NettyClientHandler.java

package com.LSpbxServer;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;

public class NettyClientHandler extends ChannelInboundHandlerAdapter {

    private final Bootstrap bootstrap;
    private final EventLoopGroup group;

    private INettyClientEventListener nettyClientEventListener;

    public NettyClientHandler(Bootstrap bootstrap,EventLoopGroup group ) {
        this.bootstrap = bootstrap;
        this.group=group;

        this.nettyClientEventListener = new App();
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws InterruptedException {
        //当通道活跃时发送消息,第一次连接成功时 执行
        //ctx.writeAndFlush("Hello, Netty Server!");
        try {
            String strMsg = "Action: login\r\n Username: api\r\n Secret: F6jFt82g\r\n Version: 2.0.0\r\n \r\n\r\n";
            ctx.writeAndFlush(strMsg);//发消息
            System.out.println("开始连接pbx 发消息 " + strMsg);

        }catch (Exception ex){
            System.out.println( "NettyClientHandler channelActive() 连接时出错 "+ ex.toString() );
        }

    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        //当通道不活跃时,发出连接断开时执行
        try {
            System.out.println("与pbx连接断开,20秒后再次尝试连接=========================");
            for (int i = 1; i <= 20; i++) {
                System.out.println("channelInactive() 线程 " + Thread.currentThread().getName() + "   " + i);
                // 线程休眠1秒
                Thread.sleep(1000);
            }
            new NettyClient().connect();

        } catch (Exception ex) {
            System.out.println("NettyClientHandler channelInactive() 与pbx连接断开,20秒后再次尝试连接时,发生异常 "+ ex.toString() );
        }
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) {
        //收到pbx消息
        try {
            // 处理接收到的消息
            System.out.println("收到pbx消息: " + msg);
//触发回调函数
            nettyClientEventListener.NettyClientEvent_RecMessage(msg.toString());
        }catch (Exception ex){
            System.out.println( "NettyClientHandler channelRead() 收到pbx消息时,出现异常 "+ ex.toString() );
        }
    }
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        //与pbx的连接出现异常,自动关闭连接
        try {
            System.out.println("与pbx的连接出现异常,自动关闭连接=========================");
            // 出现异常时关闭连接
            cause.printStackTrace();
            ctx.close();

        }catch (Exception ex){
            System.out.println( "NettyClientHandler channelRead() 收到pbx消息时,出现异常 "+ ex.toString() );
        }

    }
}

INettyClientEventListener.java

package com.LSpbxServer;

public interface INettyClientEventListener {
    void NettyClientEvent_RecMessage(String message);
}

config.properties

pbxIp=192.168.1.150
pbxPort=8000
apiPwd=123456

log4j2.xml

<?xml version="1.0" encoding="UTF-8"?>
<!--日志级别以及优先级排序: OFF > FATAL > ERROR > WARN > INFO > DEBUG > TRACE > ALL -->
<!--Configuration后面的status,这个用于设置log4j2自身内部的信息输出,可以不设置,当设置成trace时,你会看到log4j2内部各种详细输出-->
<!--monitorInterval:Log4j能够自动检测修改配置 文件和重新配置本身,设置间隔秒数-->
<Configuration status="info" monitorInterval="60">
    <!--定义了两个常量方便后面复用 -->
    <properties>
        <!--生成的日志文件目录地址  -->
        <property name="LOG_HOME">logs/</property>
        <!--日志文件名称 -->
        <property name="FILE_NAME">LogFile</property>
    </properties>
    <!--先定义所有的appender-->
    <Appenders>
        <!-- 定义控制台输出 -->
        <Console name="Console" target="SYSTEM_OUT">
            <!--输出日志的格式-->
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %l - %msg%n" />
        </Console>
        <!--满足一定条件后,就重命名原日志文件用于备份,并从新生成一个新的日志文件 -->
        <!--fileName:指定当前日志文件的位置和文件名称   filePattern:指定当发生Rolling时,文件的转移和重命名规则-->
        <RollingFile name="uleWalletEjbLogFile"
                     fileName="${LOG_HOME}/${FILE_NAME}.log"
                     filePattern="${LOG_HOME}/$${date:yyyy-MM-dd}/${FILE_NAME}-%d{yyyy-MM-dd HH}-%i.log">
            <PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} [%t] %-5level %l - %msg%n" />
            <Policies>
                <!--TimeBasedTriggeringPolicy这个配置需要和filePattern结合使用,
                注意filePattern中配置的文件重命名规则是${FILE_NAME}-%d{yyyy-MM-dd HH-mm}-%i,
                最小的时间粒度是mm,即分钟,TimeBasedTriggeringPolicy指定的size是1,结合起来就是每1分钟生成一个新文件。
                如果改成%d{yyyy-MM-dd HH},最小粒度为小时,则每一个小时生成一个文件。  -->
                <TimeBasedTriggeringPolicy interval="1" />
                <!--SizeBasedTriggeringPolicy  指定当文件体积大于size指定的值时,触发Rolling 2048 MB -->
                <SizeBasedTriggeringPolicy size="20 MB" />
            </Policies>
            <DefaultRolloverStrategy max="30" />
        </RollingFile>
    </Appenders>
    <Loggers>
        <Root level="DEBUG">
            <AppenderRef ref="Console" />
            <AppenderRef ref="uleWalletEjbLogFile" />
        </Root>
        <!-- 过滤掉 指定类的debug -->
        <Logger name="io.netty" level="info" additivity="false">
            <AppenderRef ref="Console" />
        </Logger>
        <Logger name="org.eclipse.jetty" level="info" additivity="false">
            <AppenderRef ref="Console" />
        </Logger>
        <!-- 收到坐席消息 -->
        <Logger name="com.joincall.j3c.agentservice.AbstractAgentConnector" level="info" additivity="false">
            <AppenderRef ref="Console" />
        </Logger>
        <!-- 每5秒向pbx请求队列状态 -->
        <Logger name="com.joincall.j3c.pbxlangshi.pbxaction" level="info" additivity="false">
            <AppenderRef ref="Console" />
            <AppenderRef ref="uleWalletEjbLogFile" />
        </Logger>

    </Loggers>
</Configuration>

pom.xml 主要代码

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
      <dependency>
          <groupId>io.netty</groupId>
          <artifactId>netty-all</artifactId>
          <version>4.1.30.Final</version>
      </dependency>

    <!-- log4j2 -->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-api</artifactId>
      <version>2.3</version>
    </dependency>
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-core</artifactId>
      <version>2.3</version>
      <!-- <scope>provided</scope> -->
    </dependency>

    <!-- slf4j核心包 -->
    <dependency>
      <groupId>org.slf4j</groupId>
      <artifactId>slf4j-api</artifactId>
      <version>1.7.2</version>
    </dependency>
    <!--用于与slf4j保持桥接-->
    <dependency>
      <groupId>org.apache.logging.log4j</groupId>
      <artifactId>log4j-slf4j-impl</artifactId>
      <version>2.3</version>
    </dependency>
  </dependencies>

配置文件路径

    </plugins>
 </pluginManagement>

    <resources>
      <resource>
        <filtering>true</filtering>
        <directory>src/main/resources</directory>
        <includes>
          <include>**/**.properties</include>
          <include>**/**.xml</include>
        </includes>
        <!-- <targetPath>/resources</targetPath> -->
      </resource>
    </resources>

  </build>
</project>

 

posted @ 2024-07-04 15:42  海乐学习  阅读(459)  评论(0编辑  收藏  举报