添加代码示例springboot-socketio

This commit is contained in:
yidao620 2018-02-28 15:23:38 +08:00
parent 74757c1628
commit 7db9f11a9c
21 changed files with 5864 additions and 2 deletions

View File

@ -13,7 +13,7 @@ Spring Boot 现在已经成为Java 开发领域的一颗璀璨明珠,它本身
## 子项目列表
每个项目一篇博客文章的详细讲解,点击链接可以查看文章
每个项目一篇博客文章的详细讲解,点击链接可以查看文章 :point_right:
项目名称 | 文章地址
----------------------------|------------------------------------------------------------------------------------------
@ -40,7 +40,7 @@ springboot-batch | [批处理](https://www.xncoding.com/2017/08/01/sp
springboot-rabbitmq | [集成消息队列RabbitMQ](https://www.xncoding.com/2017/08/06/spring/sb-rabbitmq.html)
springboot-echarts | [集成Echarts导出图片](https://www.xncoding.com/2017/08/19/spring/sb-echarts.html)
app-manage | [一个完整的Web后台管理系统组合使用了多种技术]
app-manage-api | [同时实现需要认证授权访问的RESTful API接口和WebSocket接口]
app-manage-api | [实现需要认证授权访问的RESTful API接口和WebSocket接口]
## 环境

17
springboot-socketio/.gitignore vendored Normal file
View File

@ -0,0 +1,17 @@
# 此为注释– 将被Git 忽略
# /结尾表示是目录,忽略目录和目录下的所有件
# /开头表示根目录,否则是.gitignore的相对目录
# !开头表示反选
.idea/
target/
*.iml
*.ipr
*.iws
*.log
.svn/
.project
rebel.xml
.rebel-remote.xml.*
swagger.json
swagger.adoc

View File

@ -0,0 +1,20 @@
The MIT License (MIT)
Copyright (c) 2018 Xiong Neng
Permission is hereby granted, free of charge, to any person obtaining a copy of
this software and associated documentation files (the "Software"), to deal in
the Software without restriction, including without limitation the rights to
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
the Software, and to permit persons to whom the Software is furnished to do so,
subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -0,0 +1,39 @@
## 简介
在SpringBoot中有两种实现WebSocket实时通信的方式
1. 一个是使用WebSocket的一个子协议stomp
2. 另外一个是使用Socket.IO协议实现。
本项目演示如何通过Socket.IO协议实现
## WeSocket长连接
Socket.IO服务器端和客户端使用Java语言实现用于和浏览器或Android客户端进行长连接的WebSocket通信。
服务器端可以实时将消息推送给客户端,以此来取代客户端轮询机制。
Sockt.io官网<https://socket.io/>
服务器端使用 [netty-socketio](https://github.com/mrniko/netty-socketio)
客户端使用 [socket.io-client-java](https://github.com/socketio/socket.io-client-java)
### WebSocket js客户端测试
```
/client/html/index.html
```
### WebSocket Java客户端测试
```
com.xncoding.pos.socket.client.SocketClient
```
## 许可证
Copyright (c) 2018 Xiong Neng
基于 MIT 协议发布: <http://www.opensource.org/licenses/MIT>

137
springboot-socketio/pom.xml Normal file
View File

@ -0,0 +1,137 @@
<?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.xncoding</groupId>
<artifactId>springboot-socketio</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-socketio</name>
<description>集成SocketIO实时通信</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
<relativePath/>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<netty.version>4.1.19.Final</netty.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<!-- netty-socketio-->
<dependency>
<groupId>com.corundumstudio.socketio</groupId>
<artifactId>netty-socketio</artifactId>
<version>1.7.13</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-buffer</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec-http</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-common</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-handler</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-resolver</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-epoll</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-transport-native-unix-common</artifactId>
<version>${netty.version}</version>
</dependency>
<dependency>
<groupId>io.socket</groupId>
<artifactId>socket.io-client</artifactId>
<version>1.0.0</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<!--<proc>none</proc>-->
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<systemPropertyVariables>
<swaggerOutputDir>${project.basedir}/src/main/resources/swagger</swaggerOutputDir>
<asciiDocOutputDir>${project.basedir}/src/main/resources/swagger/swagger</asciiDocOutputDir>
</systemPropertyVariables>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>

View File

@ -0,0 +1,72 @@
#!/bin/bash
# 项目自动更新脚本
# 先clone相应的分支下来
# git clone ssh://git@120.24.173.142:7999/xxx.git
# 远程调试启动:
# nohup java -agentlib:jdwp=transport=dt_socket,server=y,suspend=n,address=5005 -Xms512m -Xmx1024m -jar -Dspring.profiles.active=${profile} ${jarfile} >/dev/null 2>&1 &
function start {
profile="$1"
echo "启动环境profile=${profile}"
jarfile=$(ls target/*.jar)
if [[ "$?" == "0" ]]; then
stop $profile $jarfile
fi
branch=$(git branch |awk '{print $2}')
git pull origin ${branch}
echo "更新完代码开始重新打包"
mvn clean && mvn clean && mvn package -DskipTests=true
if [[ "$?" != "0" ]]; then
echo "编译出错,退出!"
exit 1
fi
echo "nohup java -Xms512m -Xmx1024m -jar -Dspring.profiles.active=${profile} ${jarfile} >/dev/null 2>&1 &"
nohup java -Xms512m -Xmx1024m -jar -Dspring.profiles.active=${profile} ${jarfile} >/dev/null 2>&1 &
echo "启动应用中,请查看日志文件..."
}
function stop {
profile="$1"
jarfile="$2"
ps aux | grep "${jarfile}" | grep "spring.profiles.active=${profile}" | grep -v grep > /dev/null
if [[ "$?" == "0" ]]; then
echo "该应用还在跑,我先停了它"
pid=$(ps aux | grep "${jarfile}" | grep "spring.profiles.active=${profile}" | grep -v grep |awk '{print $2}')
if [[ "$pid" != "" ]]; then
kill -9 $pid
fi
echo "停止应用成功..."
fi
}
if [[ "$1" == "start" ]]; then
if [[ "$#" < 2 ]]; then
echo "请输入正确参数:./epay.sh start {profile}"
exit 1
fi
profile="$2"
if [[ "$profile" != "dev" && "$profile" != "test" && "$profile" != "show" && "$profile" != "production" ]]; then
echo "参数错误请输入正确的profile参数使用方法"
echo "./epay.sh start {profile} ==> 启动应用,{profile}取值dev|test|show|production"
exit 1
fi
start "${profile}"
elif [[ "$1" == "stop" ]]; then
if [[ "$#" < 2 ]]; then
echo "请输入正确参数:./epay.sh stop {profile}"
exit 1
fi
profile="$2"
if [[ "$profile" != "dev" && "$profile" != "test" && "$profile" != "show" && "$profile" != "production" ]]; then
echo "参数错误请输入正确的profile参数使用方法"
echo "./epay.sh stop {profile} ==> 停止应用,{profile}取值dev|test|show|production"
exit 1
fi
jarfile=$(ls target/*.jar)
stop $profile $jarfile
else
echo "参数错误,使用方法:{}参数是必填的,[]参数可选"
echo "./epay.sh start {profile} ==> 启动应用,{profile}取值dev|test|show|production"
echo "./epay.sh stop {profile} ==> 停止应用,{profile}取值dev|test|show|production"
exit 1
fi

View File

@ -0,0 +1,12 @@
package com.xncoding.jwt;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}

View File

@ -0,0 +1,47 @@
package com.xncoding.jwt.common;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import java.io.IOException;
/**
* 对象和Json转换器
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/18
*/
public class JsonConverter {
public static JSONObject objectToJSONObject(Object object) {
try {
String jsonString = new ObjectMapper().writeValueAsString(object);
return new JSONObject(jsonString);
} catch (JSONException | JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
public static JSONArray objectToJSONArray(Object object) {
try {
String jsonString = new ObjectMapper().writeValueAsString(object);
return new JSONArray(jsonString);
} catch (JSONException | JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
public static <T> T jsonObjectToObject(Object jsonObject, Class<T> clazz) {
try {
// List<Car> listCar = objectMapper.readValue(jsonCarArray, new TypeReference<List<Car>>(){});
return new ObjectMapper().readValue(jsonObject.toString(), clazz);
} catch (IOException e) {
return null;
}
}
}

View File

@ -0,0 +1,34 @@
package com.xncoding.jwt.common;
import com.corundumstudio.socketio.SocketIOServer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* SpringBoot启动之后执行
*
* @author XiongNeng
* @version 1.0
* @since 2017/7/31
*/
@Component
@Order(1)
public class ServerRunner implements CommandLineRunner {
private final SocketIOServer server;
private static final Logger logger = LoggerFactory.getLogger(ServerRunner.class);
@Autowired
public ServerRunner(SocketIOServer server) {
this.server = server;
}
@Override
public void run(String... args) {
logger.info("ServerRunner 开始启动啦...");
server.start();
}
}

View File

@ -0,0 +1,57 @@
package com.xncoding.jwt.config;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.SpringAnnotationScanner;
import com.xncoding.jwt.config.properties.MyProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.annotation.Resource;
/**
* NettySocketConfig
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/19
*/
@Configuration
public class NettySocketConfig {
@Resource
private MyProperties myProperties;
private static final Logger logger = LoggerFactory.getLogger(NettySocketConfig.class);
@Bean
public SocketIOServer socketIOServer() {
/*
* 创建Socket并设置监听端口
*/
com.corundumstudio.socketio.Configuration config = new com.corundumstudio.socketio.Configuration();
// 设置主机名默认是0.0.0.0
// config.setHostname("localhost");
// 设置监听端口
config.setPort(myProperties.getSocketPort());
// 协议升级超时时间毫秒默认10000HTTP握手升级为ws协议超时时间
config.setUpgradeTimeout(10000);
// Ping消息间隔毫秒默认25000客户端向服务器发送一条心跳消息间隔
config.setPingInterval(myProperties.getPingInterval());
// Ping消息超时时间毫秒默认60000这个时间间隔内没有接收到心跳消息就会发送超时事件
config.setPingTimeout(myProperties.getPingTimeout());
// 握手协议参数使用JWT的Token认证方案
config.setAuthorizationListener(data -> {
// 可以使用如下代码获取用户密码信息
String token = data.getSingleUrlParam("token");
return true;
});
return new SocketIOServer(config);
}
@Bean
public SpringAnnotationScanner springAnnotationScanner(SocketIOServer socketServer) {
return new SpringAnnotationScanner(socketServer);
}
}

View File

@ -0,0 +1,64 @@
package com.xncoding.jwt.config.properties;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
/**
* 本项目自定义配置
*
* @author xiongneng
* @since 2018/01/06 21:09
*/
@Component
@ConfigurationProperties(prefix = "xncoding")
public class MyProperties {
/**
* socket端口
*/
private Integer socketPort;
/**
* Ping消息间隔毫秒
*/
private Integer pingInterval;
/**
* Ping消息超时时间毫秒
*/
private Integer pingTimeout;
/**
* APK文件访问URL前缀
*/
private String apkUrlPrefix;
public Integer getSocketPort() {
return socketPort;
}
public void setSocketPort(Integer socketPort) {
this.socketPort = socketPort;
}
public Integer getPingInterval() {
return pingInterval;
}
public void setPingInterval(Integer pingInterval) {
this.pingInterval = pingInterval;
}
public Integer getPingTimeout() {
return pingTimeout;
}
public void setPingTimeout(Integer pingTimeout) {
this.pingTimeout = pingTimeout;
}
public String getApkUrlPrefix() {
return apkUrlPrefix;
}
public void setApkUrlPrefix(String apkUrlPrefix) {
this.apkUrlPrefix = apkUrlPrefix;
}
}

View File

@ -0,0 +1,75 @@
package com.xncoding.jwt.handler;
import com.corundumstudio.socketio.AckRequest;
import com.corundumstudio.socketio.SocketIOClient;
import com.corundumstudio.socketio.SocketIOServer;
import com.corundumstudio.socketio.annotation.OnConnect;
import com.corundumstudio.socketio.annotation.OnDisconnect;
import com.corundumstudio.socketio.annotation.OnEvent;
import com.xncoding.jwt.model.ChatMessage;
import com.xncoding.jwt.model.LoginRequest;
import io.socket.client.Socket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
/**
* 消息事件处理器
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/19
*/
@Component
public class MessageEventHandler {
private final SocketIOServer server;
private static final Logger logger = LoggerFactory.getLogger(MessageEventHandler.class);
@Autowired
public MessageEventHandler(SocketIOServer server) {
this.server = server;
}
//添加connect事件当客户端发起连接时调用
@OnConnect
public void onConnect(SocketIOClient client) {
if (client != null) {
String username = client.getHandshakeData().getSingleUrlParam("username");
String password = client.getHandshakeData().getSingleUrlParam("password");
String sessionId = client.getSessionId().toString();
logger.info("连接成功, username=" + username + ", password=" + password + ", sessionId=" + sessionId);
} else {
logger.error("客户端为空");
}
}
//添加@OnDisconnect事件客户端断开连接时调用刷新客户端信息
@OnDisconnect
public void onDisconnect(SocketIOClient client) {
logger.info("客户端断开连接, sessionId=" + client.getSessionId().toString());
client.disconnect();
}
// 消息接收入口
@OnEvent(value = "chatevent")
public void onEvent(SocketIOClient client, AckRequest ackRequest, ChatMessage chat) {
logger.info("接收到客户端消息");
if (ackRequest.isAckRequested()) {
// send ack response with data to client
ackRequest.sendAckData("服务器回答chatevent, userName=" + chat.getUserName() + ",message=" + chat.getMessage());
}
}
// 登录接口
@OnEvent(value = "login")
public void onLogin(SocketIOClient client, AckRequest ackRequest, LoginRequest message) {
logger.info("接收到客户端登录消息");
if (ackRequest.isAckRequested()) {
// send ack response with data to client
ackRequest.sendAckData("服务器回答login", message.getCode(), message.getBody());
}
}
}

View File

@ -0,0 +1,29 @@
package com.xncoding.jwt.model;
/**
* ChatMessage
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/30
*/
public class ChatMessage {
private String userName;
private String message;
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,40 @@
package com.xncoding.jwt.model;
import java.io.Serializable;
/**
* LoginRequest
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/18
*/
public class LoginRequest implements Serializable {
private int code;
private String body;
public LoginRequest() {
}
public LoginRequest(int code, String body) {
super();
this.code = code;
this.body = body;
}
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getBody() {
return body;
}
public void setBody(String body) {
this.body = body;
}
}

View File

@ -0,0 +1,45 @@
##########################################################
################## 所有profile共有的配置 #################
##########################################################
################### 自定义项目配置 ###################
xncoding:
socket-port: 9099 #socket端口
ping-interval: 60000 #Ping消息间隔毫秒
ping-timeout: 180000 #Ping消息超时时间毫秒
################### spring配置 ###################
spring:
profiles:
active: dev
---
#####################################################################
######################## 开发环境profile ##########################
#####################################################################
spring:
profiles: dev
logging:
level:
ROOT: INFO
com:
xncoding: DEBUG
file: E:/logs/app.log
---
#####################################################################
######################## 测试环境profile ##########################
#####################################################################
spring:
profiles: test
logging:
level:
ROOT: INFO
com:
xncoding: DEBUG
file: /var/logs/app.log

View File

@ -0,0 +1,66 @@
package com.xncoding.jwt.socket.client;
import com.xncoding.jwt.common.JsonConverter;
import com.xncoding.jwt.model.LoginRequest;
import io.socket.client.Ack;
import io.socket.client.IO;
import io.socket.client.Socket;
import io.socket.emitter.Emitter;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.URISyntaxException;
import java.util.Arrays;
import java.util.stream.Collectors;
/**
* SocketClient
*
* @author XiongNeng
* @version 1.0
* @since 2018/1/18
*/
public class SocketClient {
private static Socket socket;
private static final Logger logger = LoggerFactory.getLogger(SocketClient.class);
public static void main(String[] args) throws URISyntaxException {
IO.Options options = new IO.Options();
options.transports = new String[]{"websocket"};
options.reconnectionAttempts = 2;
options.reconnectionDelay = 1000; // 失败重连的时间间隔(ms)
options.timeout = 20000; // 连接超时时间(ms)
options.forceNew = true;
options.query = "username=test1&password=test1";
socket = IO.socket("http://localhost:9099/", options);
socket.on(Socket.EVENT_CONNECT, new Emitter.Listener() {
@Override
public void call(Object... args) {
// 客户端一旦连接成功开始发起登录请求
LoginRequest message = new LoginRequest(12, "这是客户端消息体");
socket.emit("login", JsonConverter.objectToJSONObject(message), (Ack) args1 -> {
logger.info("回执消息=" + Arrays.stream(args1).map(Object::toString).collect(Collectors.joining(",")));
});
}
}).on(Socket.EVENT_CONNECT_ERROR, new Emitter.Listener() {
@Override
public void call(Object... args) {
logger.info("Socket.EVENT_CONNECT_ERROR");
socket.disconnect();
}
}).on(Socket.EVENT_CONNECT_TIMEOUT, new Emitter.Listener() {
@Override
public void call(Object... args) {
logger.info("Socket.EVENT_CONNECT_TIMEOUT");
socket.disconnect();
}
}).on(Socket.EVENT_DISCONNECT, new Emitter.Listener() {
@Override
public void call(Object... args) {
logger.info("客户端断开连接啦。。。");
socket.disconnect();
}
});
socket.connect();
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,110 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Demo Chat</title>
<link href="bootstrap.css" rel="stylesheet">
<style>
body {
padding: 20px;
}
#console {
height: 400px;
overflow: auto;
}
.username-msg {
color: orange;
}
.connect-msg {
color: green;
}
.disconnect-msg {
color: red;
}
.send-msg {
color: #888
}
</style>
<script src="js/socket.io/socket.io.js"></script>
<script src="js/moment.min.js"></script>
<script src="js/jquery-1.10.1.min.js"></script>
<script>
var userName = 'user' + Math.floor((Math.random() * 1000) + 1);
var socket = io.connect('http://127.0.0.1:9099?username=' + userName + '&password=123456');
socket.on('connect', function () {
output('<span class="connect-msg">Client has connected to the server!</span>');
});
socket.on('chatevent', function (data) {
output('<span class="username-msg">' + data.userName + ':</span> ' + data.message);
});
socket.on('disconnect', function () {
output('<span class="disconnect-msg">The client has disconnected!</span>');
});
function sendDisconnect() {
socket.disconnect();
}
function sendMessage() {
var message = $('#msg').val();
$('#msg').val('');
var jsonObject = {
userName: userName,
message: message
};
socket.emit('chatevent', jsonObject, function (data) {
output('<span class="username-msg">' + data + '</span> ');
});
}
function output(message) {
var currentTime = "<span class='time'>" + moment().format('HH:mm:ss.SSS') + "</span>";
var element = $("<div>" + currentTime + " " + message + "</div>");
$('#console').prepend(element);
}
$(document).keydown(function (e) {
if (e.keyCode == 13) {
$('#send').click();
}
});
</script>
</head>
<body>
<h1>Netty-socketio Demo Chat</h1>
<br/>
<div id="console" class="well">
</div>
<form class="well form-inline" onsubmit="return false;">
<input id="msg" class="input-xlarge" type="text" placeholder="Type something..."/>
<button type="button" onClick="sendMessage()" class="btn" id="send">Send</button>
<button type="button" onClick="sendDisconnect()" class="btn">Disconnect</button>
</form>
</body>
</html>

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long