Vert.x TCP 通訊實踐

sweetmain發表於2018-01-05

1. 先畫個圖,理一下思路

圖片

訊息分隔符用的 \n

2. 服務端程式碼

import com.xiaoniu.MsgUtils;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.net.NetServer;
import io.vertx.core.net.NetServerOptions;
import io.vertx.core.net.NetSocket;
import io.vertx.core.parsetools.RecordParser;
import io.vertx.core.streams.ReadStream;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;

import java.util.*;

/**
 * Created by sweet on 2018/1/2.
 * ---------------------------
 */
public class TcpServerVerticle extends AbstractVerticle {
    
    public static void main(String[] args) {
        Vertx.vertx().deployVerticle(new TcpServerVerticle());
    }

    private static final Logger LOGGER = LogManager.getLogger(TcpServerVerticle.class);

    private static final Map<String, NetSocket> SOCKET_MAP = new HashMap<>();
    private static final Map<String, Long> ACTIVE_SOCKET_MAP = new HashMap<>();

    @Override
    public void start() throws Exception {
        NetServerOptions options = new NetServerOptions();
        options.setTcpKeepAlive(true);

        final RecordParser parser = RecordParser.newDelimited("\n", h -> {
            final String msg = h.toString();
            LOGGER.debug("伺服器解包: " + msg);

            final String[] msgSplit = msg.split("\\*");
            final String socketId = msgSplit[0];
            final String msgBody = msgSplit[1];

            if ("PING".equals(msgBody)) { // 心跳
                ACTIVE_SOCKET_MAP.put(socketId, System.currentTimeMillis());
                SOCKET_MAP.get(socketId).write(MsgUtils.joinMsg(socketId, "PING"));
            } else {
                // 其他資訊,這裡簡單模擬一下,原樣返回給客戶端
                SOCKET_MAP.get(socketId).write(MsgUtils.joinMsg(socketId, msgBody));
            }
        });

        NetServer server = vertx.createNetServer(options);

        ReadStream<NetSocket> stream = server.connectStream();
        stream.handler(netSocket -> {
            String socketId = netSocket.writeHandlerID();
            LOGGER.debug("New socket Id: " + socketId);
            netSocket.handler(parser::handle);
            netSocket.write(socketId + "*" + "Server\n");

            SOCKET_MAP.put(socketId, netSocket);
            ACTIVE_SOCKET_MAP.put(socketId, System.currentTimeMillis());
        });

        stream.endHandler(end -> {
            System.out.println("stream end");
        });

        stream.exceptionHandler(ex -> {
            System.out.println("stream ex : " + ex.getMessage());
            ex.printStackTrace();
        });

        // 檢查心跳
        vertx.setPeriodic(1000l * 60, t -> {
            System.out.println("SOCKET MAP");
            System.out.println(SOCKET_MAP);
            System.out.println("ACTIVE MAP");
            System.out.println(ACTIVE_SOCKET_MAP);

            Iterator<Map.Entry<String, Long>> iterator = ACTIVE_SOCKET_MAP.entrySet().iterator();
            while (iterator.hasNext()) {
                Map.Entry<String, Long> entry = iterator.next();
                long time = System.currentTimeMillis() - entry.getValue();
                if (time > (1000 * 60)) {
                    LOGGER.debug("SocketId: " + entry.getKey() + ", 被清除");
                    SOCKET_MAP.remove(entry.getKey()).close();
                    iterator.remove();
                }
            }
        });

        server.listen(8080, res -> {
            if (res.succeeded()) {
                System.out.println("Server start !!!");
            } else {
                res.cause().printStackTrace();
            }
        });
    }
}
複製程式碼

關於日誌配置可以看我其他的文章,這裡就不再重複了。

3. 客戶端程式碼

import com.xiaoniu.MsgUtils;
import io.vertx.core.AbstractVerticle;
import io.vertx.core.Vertx;
import io.vertx.core.net.NetClient;
import io.vertx.core.net.NetClientOptions;
import io.vertx.core.net.NetSocket;
import io.vertx.core.parsetools.RecordParser;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;


/**
 * Created by sweet on 2018/1/2.
 * ---------------------------
 */
public class TcpClientVerticle extends AbstractVerticle {
    
    public static void main(String[] args) {
        Vertx.vertx().deployVerticle(new TcpClientVerticle());
    }
    
    private static final Logger LOGGER = LogManager.getLogger(TcpClientVerticle.class);

    private String socketId;

    private NetSocket netSocket;

    @Override
    public void start() throws Exception {
        NetClientOptions options = new NetClientOptions();
        options.setTcpKeepAlive(true);

        NetClient client = vertx.createNetClient();

        final RecordParser parser = RecordParser.newDelimited("\n", h -> {
            String msg = h.toString();
            LOGGER.debug("客戶端解包: " + msg);
            String[] msgSplit = msg.split("\\*");

            String socketId1 = msgSplit[0];
            String body = msgSplit[1];

            if ("Server".equals(body)) {
                socketId = socketId1;
            }
        });

        client.connect(8080, "127.0.0.1", conn -> {
            if (conn.succeeded()) {
                System.out.println("client ok");
                netSocket = conn.result();
                netSocket.handler(parser::handle);
            } else {
                conn.cause().printStackTrace();
            }
        });

        // 客戶端每 20 秒 傳送一次心跳包
        vertx.setPeriodic(1000l * 20, t -> {
            if (netSocket != null)
                netSocket.write(MsgUtils.joinMsg(socketId, "PING"));
        });

        // 模擬客戶端發訊息 每次10秒鐘
        vertx.setPeriodic(1000l * 10, t -> {
           if (netSocket != null) {
               netSocket.write(MsgUtils.joinMsg(socketId, "Hello Vert.x"));
           }
        });
    }
}
複製程式碼

客戶端就簡單寫寫,沒有模擬重連機制,如果想看到心跳是否起作用可以手動關閉 客戶端,然後再連線,過一會會看到服務端控制檯列印清除舊的連線。

4. 工具類

public final class MsgUtils {

    private MsgUtils() {
    }

    public static String joinMsg(String socketId, String body) {
        return socketId+"*"+body+"\n";
    }
}
複製程式碼

5. 依賴

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.xiaoniu.tcp</groupId>
    <artifactId>tcp-demo</artifactId>
    <version>1.0.0-SNAPSHOT</version>

    <properties>
        <java.version>1.8</java.version>
        <vertx.version>3.5.0</vertx.version>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.5.1</version>
                <configuration>
                    <source>${java.version}</source>
                    <target>${java.version}</target>
                </configuration>
            </plugin>

            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-shade-plugin</artifactId>
                <version>2.4.3</version>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>shade</goal>
                        </goals>
                        <configuration>
                            <transformers>
                                <transformer
                                    implementation="org.apache.maven.plugins.shade.resource.ManifestResourceTransformer">
                                    <manifestEntries>
                                        <Main-Class>io.vertx.core.Launcher</Main-Class>
                                        <Main-Verticle>com.xiaoniu.tcp.MainVerticle</Main-Verticle>
                                    </manifestEntries>
                                </transformer>
                                <transformer
                                    implementation="org.apache.maven.plugins.shade.resource.AppendingTransformer">
                                    <resource>META-INF/services/io.vertx.core.spi.VerticleFactory</resource>
                                </transformer>
                            </transformers>
                            <artifactSet>
                            </artifactSet>
                            <outputFile>${project.build.directory}/${project.artifactId}-${project.version}-fat.jar
                            </outputFile>
                        </configuration>
                    </execution>
                </executions>
            </plugin>

            <plugin>
                <groupId>org.codehaus.mojo</groupId>
                <artifactId>exec-maven-plugin</artifactId>
                <version>1.5.0</version>
                <configuration>
                    <mainClass>io.vertx.core.Launcher</mainClass>
                    <arguments>
                        <argument>run</argument>
                        <argument>com.xiaoniu.tcp.tcp-demo.MainVerticle</argument>
                    </arguments>
                </configuration>
            </plugin>

        </plugins>
    </build>

    <dependencies>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web-client</artifactId>
            <version>${vertx.version}</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-core</artifactId>
            <version>${vertx.version}</version>
        </dependency>

        <dependency>
            <groupId>io.vertx</groupId>
            <artifactId>vertx-web</artifactId>
            <version>${vertx.version}</version>
        </dependency>

        <!-- log42j start-->
        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-api</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-core</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-slf4j-impl</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-jcl</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>org.apache.logging.log4j</groupId>
            <artifactId>log4j-jul</artifactId>
            <version>2.8.2</version>
        </dependency>

        <dependency>
            <groupId>com.lmax</groupId>
            <artifactId>disruptor</artifactId>
            <version>3.3.6</version>
        </dependency>
        <!-- log42j end -->

    </dependencies>

</project>
複製程式碼

6. 啟動看效果

  1. 先執行服務端的 Main 方法
  2. 執行客戶端的 Main 方法

如果想打成 jar 包,則需要在 pom 檔案中修改 Main-Verticle 改成你想要打包的那個類,然後 mvn clean package

相關文章