为了账号安全,请及时绑定邮箱和手机立即绑定

用t-io来写一个网页聊天室或客服是个怎样的体验

标签:
Android iOS

摘要: tio-websocket-server的首发教程,并且是以showcase的形式展现的----不仅仅是个教程,还是个可以放心使用的脚手架。                

        在t-io的官方,经常会有用户发出这样的感叹:“用tio写一个聊天室或在线客服,几乎就是个分分钟的事儿”。如果不考虑业务上的细节,这话儿看似浮夸,却又十分真实。

        本文手把手教大家如何用t-io快速做一个网页版聊天室----可能这不仅仅只是个小demo,它更多的可作为项目的一个脚手架,读者可以以此为基础,完成一个真实的网页聊天室,甚至扩展成一个在线客服。

        本文有部分代码并非必须,譬如数据监控相关的listener,但是在大型的项目中,对这些监控数据的处理却是一个必须,所以本文从实用角度出发,加了不少非必须的代码,望读者朋友喜欢并从中获益。

        废话少说,撸起袖子就是干。


    1、从pom.xml开始,引入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.3.0.v20180506-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

        注释都在代码中,各位慢读

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、实现org.tio.websocket.server.WsServerAioListener

        注释都在代码中,各位慢读

/**
 * 
 */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、实现org.tio.core.stat.IpStatListener(非必须)

        注释都在代码中,各位慢读

/**
 * 
 */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、一些配置项

        注释都在代码中,各位慢读

/**
 * 
 */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、一些常量

        注释都在代码中,各位慢读

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


    7、一个启动类

        注释都在代码中,各位慢读

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、运行步骤7中的启动类

        会看到下面启动成功的日志

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


    9、写一个js client

        为了简化js端websocket的开发,本人写了一个简单的小js,它的名字叫tiows.js,它处理了重连、发心跳等很多开发人员不愿意去干的活。它的源代码在:https://gitee.com/tywo45/tio-websocket-showcase,把源代码下载下来后,在page/tio/目录中就能看到tiows.js。

        然后再打开page/index.html,就能看到下面这个界面了(前提是前面8步要完成),如果你不想完成前面8步,你同样可以在https://gitee.com/tywo45/tio-websocket-showcase中找到前8步所需要的java代码。

作者:talent-tan                    

来源:https://my.oschina.net/talenttan/blog/1806324


点击查看更多内容
2人点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消