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. 啟動看效果
- 先執行服務端的
Main
方法 - 執行客戶端的
Main
方法
如果想打成
jar
包,則需要在pom
檔案中修改Main-Verticle
改成你想要打包的那個類,然後mvn clean package
。