What kind of experience is it to use t-io to write a web chat room or customer service

        In the official main group of t-io, users often sigh like this: " It is almost a matter of minutes to write a chat room or online customer service with tio ." If you don't consider the details of the business, this may seem exaggerated, but it is very true.

        This article will teach you how to use t-io to quickly create a web-based chat room----maybe this is not just a small demo, it can be used as a scaffold for the project, and readers can use this as a basis to complete a real The web chat room, and even expanded into an online customer service.

        Some codes in this article are not necessary, such as listeners related to data monitoring, but in large-scale projects, the processing of these monitoring data is a must, so this article adds a lot of non-essential codes from a practical point of view. Friends like and benefit from it.

        Don't talk nonsense, just roll up your sleeves and do it.

    1. Start from pom.xml and introduce tio-websocket-server

<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>
	<artifactId>tio-websocket-showcase</artifactId>
	<name>${project.artifactId}</name>

	<parent>
		<groupId>org.t-io</groupId>
		<artifactId>tio-parent</artifactId>
		<version>2.2.0.v20180405-RELEASE</version>
	</parent>

	<dependencies>
		<dependency>
			<groupId>org.t-io</groupId>
			<artifactId>tio-websocket-server</artifactId>
		</dependency>

		<!-- slf4j-logback绑定 -->
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-access</artifactId>
		</dependency>


		<!-- redirect apache commons logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jcl-over-slf4j</artifactId>
		</dependency>
		<!-- redirect jdk util logging -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>jul-to-slf4j</artifactId>
		</dependency>
		<!-- redirect log4j -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>log4j-over-slf4j</artifactId>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<scope>test</scope>
		</dependency>
		<dependency>
			<groupId>org.testng</groupId>
			<artifactId>testng</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
</project>

    2 实现 org.tio.websocket.server.handler.IWsMsgHandler

        Comments are all in the code, please read slowly

package org.tio.showcase.websocket.server;

import java.util.Objects;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.Aio;
import org.tio.core.ChannelContext;
import org.tio.http.common.HttpRequest;
import org.tio.http.common.HttpResponse;
import org.tio.websocket.common.WsRequest;
import org.tio.websocket.common.WsResponse;
import org.tio.websocket.common.WsSessionContext;
import org.tio.websocket.server.handler.IWsMsgHandler;

/**
 * @author tanyaowu
 * 2017年6月28日 下午5:32:38
 */
public class ShowcaseWsMsgHandler implements IWsMsgHandler {
	private static Logger log = LoggerFactory.getLogger(ShowcaseWsMsgHandler.class);

	public static ShowcaseWsMsgHandler me = new ShowcaseWsMsgHandler();

	private ShowcaseWsMsgHandler() {

	}

	/**
	 * 握手时走这个方法,业务可以在这里获取cookie,request参数等
	 */
	@Override
	public HttpResponse handshake(HttpRequest request, HttpResponse httpResponse, ChannelContext channelContext) throws Exception {
		String clientip = request.getClientIp();
		log.info("收到来自{}的ws握手包\r\n{}", clientip, request.toString());
		return httpResponse;
	}

	/**
	 * 字节消息(binaryType = arraybuffer)过来后会走这个方法
	 */
	@Override
	public Object onBytes(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
		return null;
	}

	/**
	 * 当客户端发close flag时,会走这个方法
	 */
	@Override
	public Object onClose(WsRequest wsRequest, byte[] bytes, ChannelContext channelContext) throws Exception {
		Aio.remove(channelContext, "receive close flag");
		return null;
	}

	/*
	 * 字符消息(binaryType = blob)过来后会走这个方法
	 */
	@Override
	public Object onText(WsRequest wsRequest, String text, ChannelContext channelContext) throws Exception {
		WsSessionContext wsSessionContext = (WsSessionContext) channelContext.getAttribute();
		HttpRequest httpRequest = wsSessionContext.getHandshakeRequestPacket();//获取websocket握手包
		if (log.isDebugEnabled()) {
			log.debug("握手包:{}", httpRequest);
		}

		log.info("收到ws消息:{}", text);

		if (Objects.equals("心跳内容", text)) {
			return null;
		}

		String msg = channelContext.getClientNode().toString() + " 说:" + text;
		//用tio-websocket,服务器发送到客户端的Packet都是WsResponse
		WsResponse wsResponse = WsResponse.fromText(msg, ShowcaseServerConfig.CHARSET);
		//群发
		Aio.sendToGroup(channelContext.getGroupContext(), Const.GROUP_ID, wsResponse);

		//返回值是要发送给客户端的内容,一般都是返回null
		return null;
	}
}

    3. Implement org.tio.websocket.server.WsServerAioListener

        Comments are all in the code, please read slowly

/**
 * 
 */
package org.tio.showcase.websocket.server;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.Aio;
import org.tio.core.ChannelContext;
import org.tio.core.intf.Packet;
import org.tio.websocket.server.WsServerAioListener;

/**
 * @author tanyaowu
 * 用户根据情况来完成该类的实现
 */
public class ShowcaseServerAioListener extends WsServerAioListener {
	private static Logger log = LoggerFactory.getLogger(ShowcaseServerAioListener.class);

	public static final ShowcaseServerAioListener me = new ShowcaseServerAioListener();

	private ShowcaseServerAioListener() {

	}

	@Override
	public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect) throws Exception {
		super.onAfterConnected(channelContext, isConnected, isReconnect);
		if (log.isInfoEnabled()) {
			log.info("onAfterConnected\r\n{}", channelContext);
		}

		//绑定到群组,后面会有群发
		Aio.bindGroup(channelContext, Const.GROUP_ID);
	}

	@Override
	public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess) throws Exception {
		super.onAfterSent(channelContext, packet, isSentSuccess);
		if (log.isInfoEnabled()) {
			log.info("onAfterSent\r\n{}\r\n{}", packet.logstr(), channelContext);
		}
	}

	@Override
	public void onBeforeClose(ChannelContext channelContext, Throwable throwable, String remark, boolean isRemove) throws Exception {
		super.onBeforeClose(channelContext, throwable, remark, isRemove);
		if (log.isInfoEnabled()) {
			log.info("onBeforeClose\r\n{}", channelContext);
		}
	}

	@Override
	public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize) throws Exception {
		super.onAfterDecoded(channelContext, packet, packetSize);
		if (log.isInfoEnabled()) {
			log.info("onAfterDecoded\r\n{}\r\n{}", packet.logstr(), channelContext);
		}
	}

	@Override
	public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes) throws Exception {
		super.onAfterReceivedBytes(channelContext, receivedBytes);
		if (log.isInfoEnabled()) {
			log.info("onAfterReceivedBytes\r\n{}", channelContext);
		}
	}

	@Override
	public void onAfterHandled(ChannelContext channelContext, Packet packet, long cost) throws Exception {
		super.onAfterHandled(channelContext, packet, cost);
		if (log.isInfoEnabled()) {
			log.info("onAfterHandled\r\n{}\r\n{}", packet.logstr(), channelContext);
		}
	}

}

    4. Implement org.tio.core.stat.IpStatListener (not required)

        Comments are all in the code, please read slowly

/**
 * 
 */
package org.tio.showcase.websocket.server;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.tio.core.ChannelContext;
import org.tio.core.GroupContext;
import org.tio.core.intf.Packet;
import org.tio.core.stat.IpStat;
import org.tio.core.stat.IpStatListener;
import org.tio.utils.json.Json;

/**
 * 
 * @author tanyaowu
 *
 */
public class ShowcaseIpStatListener implements IpStatListener {
	private static Logger log = LoggerFactory.getLogger(ShowcaseIpStatListener.class);

	public static final ShowcaseIpStatListener me = new ShowcaseIpStatListener();

	/**
	 * 
	 */
	private ShowcaseIpStatListener() {
	}

	@Override
	public void onExpired(GroupContext groupContext, IpStat ipStat) {
		//在这里把统计数据入库中或日志
		if (log.isInfoEnabled()) {
			log.info("可以把统计数据入库\r\n{}", Json.toFormatedJson(ipStat));
		}
	}

	@Override
	public void onAfterConnected(ChannelContext channelContext, boolean isConnected, boolean isReconnect, IpStat ipStat) throws Exception {
		if (log.isInfoEnabled()) {
			log.info("onAfterConnected\r\n{}", Json.toFormatedJson(ipStat));
		}
	}

	@Override
	public void onDecodeError(ChannelContext channelContext, IpStat ipStat) {
		if (log.isInfoEnabled()) {
			log.info("onDecodeError\r\n{}", Json.toFormatedJson(ipStat));
		}
	}

	@Override
	public void onAfterSent(ChannelContext channelContext, Packet packet, boolean isSentSuccess, IpStat ipStat) throws Exception {
		if (log.isInfoEnabled()) {
			log.info("onAfterSent\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
		}
	}

	@Override
	public void onAfterDecoded(ChannelContext channelContext, Packet packet, int packetSize, IpStat ipStat) throws Exception {
		if (log.isInfoEnabled()) {
			log.info("onAfterDecoded\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
		}
	}

	@Override
	public void onAfterReceivedBytes(ChannelContext channelContext, int receivedBytes, IpStat ipStat) throws Exception {
		if (log.isInfoEnabled()) {
			log.info("onAfterReceivedBytes\r\n{}", Json.toFormatedJson(ipStat));
		}
	}

	@Override
	public void onAfterHandled(ChannelContext channelContext, Packet packet, IpStat ipStat, long cost) throws Exception {
		if (log.isInfoEnabled()) {
			log.info("onAfterHandled\r\n{}\r\n{}", packet.logstr(), Json.toFormatedJson(ipStat));
		}
	}

}

    5. Some configuration items

        Comments are all in the code, please read slowly

/**
 * 
 */
package org.tio.showcase.websocket.server;

import org.tio.utils.time.Time;

/**
 * @author tanyaowu
 *
 */
public abstract class ShowcaseServerConfig {
	/**
	 * 协议名字(可以随便取,主要用于开发人员辨识)
	 */
	public static final String PROTOCOL_NAME = "showcase";
	
	public static final String CHARSET = "utf-8";
	/**
	 * 监听的ip
	 */
	public static final String SERVER_IP = null;//null表示监听所有,并不指定ip

	/**
	 * 监听端口
	 */
	public static final int SERVER_PORT = 9326;

	/**
	 * 心跳超时时间,单位:毫秒
	 */
	public static final int HEARTBEAT_TIMEOUT = 1000 * 60;

	/**
	 * ip数据监控统计,时间段
	 * @author tanyaowu
	 *
	 */
	public static interface IpStatDuration {
		public static final Long DURATION_1 = Time.MINUTE_1 * 5;
		public static final Long[] IPSTAT_DURATIONS = new Long[] { DURATION_1 };
	}

}

    6. Some constants

        Comments are all in the code, please read slowly

/**
 * 
 */
package org.tio.showcase.websocket.server;

/**
 * @author tanyaowu
 *
 */
public class Const {
	/**
	 * 用于群聊的group id
	 */
	public static final String GROUP_ID = "showcase-websocket";
}

    7. A startup class

        Comments are all in the code, please read slowly

package org.tio.showcase.websocket.server;

import java.io.IOException;

import org.tio.server.ServerGroupContext;
import org.tio.websocket.server.WsServerStarter;

/**
 * @author tanyaowu
 * 2017年6月28日 下午5:34:04
 */
public class ShowcaseWebsocketStarter {

	private WsServerStarter wsServerStarter;
	private ServerGroupContext serverGroupContext;

	/**
	 *
	 * @author tanyaowu
	 */
	public ShowcaseWebsocketStarter(int port, ShowcaseWsMsgHandler wsMsgHandler) throws IOException {
		wsServerStarter = new WsServerStarter(port, wsMsgHandler);

		serverGroupContext = wsServerStarter.getServerGroupContext();
		serverGroupContext.setName(ShowcaseServerConfig.PROTOCOL_NAME);
		serverGroupContext.setServerAioListener(ShowcaseServerAioListener.me);

		//设置ip统计时间段
		serverGroupContext.ipStats.addDurations(ShowcaseServerConfig.IpStatDuration.IPSTAT_DURATIONS);
		//设置ip监控
		serverGroupContext.setIpStatListener(ShowcaseIpStatListener.me);
		//设置心跳超时时间
		serverGroupContext.setHeartbeatTimeout(ShowcaseServerConfig.HEARTBEAT_TIMEOUT);
	}

	/**
	 * @param args
	 * @author tanyaowu
	 * @throws IOException
	 */
	public static void start() throws IOException {
		ShowcaseWebsocketStarter appStarter = new ShowcaseWebsocketStarter(ShowcaseServerConfig.SERVER_PORT, ShowcaseWsMsgHandler.me);
		appStarter.wsServerStarter.start();
	}

	/**
	 * @return the serverGroupContext
	 */
	public ServerGroupContext getServerGroupContext() {
		return serverGroupContext;
	}

	public WsServerStarter getWsServerStarter() {
		return wsServerStarter;
	}
	
	public static void main(String[] args) throws IOException {
		start();
	}

}

    8. Run the startup class in step 7

        You will see the following log of successful startup

2018-05-03 17:12:47,004 WARN  org.tio.server.AioServer[109]: showcase started, listen on 0.0.0.0:9326

    9. Write a js client

        In order to simplify the development of js-side websocket, I wrote a simple little js, its name is tiows.js , which handles reconnection, heartbeat and other tasks that many developers are unwilling to do. Its source code is at: https://gitee.com/tywo45/tio-websocket-showcase , after downloading the source code, you can see tiows.js in the page/tio/ directory.

        Then open page/index.html, you can see the following interface (provided that the first 8 steps have to be completed), if you do not want to complete the first 8 steps, you can also go to https://gitee.com/tywo45/tio Find the java code required for the first 8 steps in -websocket-showcase .

    10. Conclusion

        So far (2018-05-03), this tutorial is the best tutorial for learning tio-websocket-server, because I have only published such a tutorial ^_^.

        Finally, don't forget to pay attention: let the world have no difficult to develop network communication

 

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325218800&siteId=291194637