如何使用HTML5的WebSocket实现网页与服务器的双工通信(一)
本系列服务端双工通信包括两种实现方式:一、使用Socket构建;二、使用WCF构建。本文为使用Socket构建服务端的双工通信,客户端同样使用Html5的WebSocket技术进行调用。
一、网页客户端:
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title></title>
<script src="scripts/jquery-1.10.2.min.js"></script>
<script>
var socket;
//url必须使用ws或者wss(加密)作为头,这个url设定好后,在javascript脚本中可以通过访问websocket对象的url来重新获取
//通信建立连接后,就可以双向通信了,使用websocket对象的send方法加json数据便可将任何形式数据传往服务器 //通过onmessage事件来接收服务器传送过来数据:
//通过onopern事件监听socket打开事件
//通过onclose监听socket关闭事件
//通过webSocket.close()方法关闭socket连接;
//通过readyState属性获取websocket对象状态:
//CONNECTION 0 正在连接
//OPEN 1 已经连接
//CLOSING 2 正在关闭
//CLOSE 3 已经关闭
$(function () {
$('#conn').click(function () {
//ws = new WebSocket('ws://' + window.location.hostname + ':' + '4649/Echo/');
socket = new WebSocket('ws://localhost:8021/');
$('#tips').text('正在连接'); socket.addEventListener("open", function (e) {
$('#tips').html(
'<div>Connected. Waiting for messages...</div>');
//window.setInterval(function () {
// socket.send("the time is " + new Date());
//}, 1000);
}, false) socket.addEventListener("message", function (evt) {
$('#tips').text(evt.data);
}); socket.onerror = function (evt) {
$('#tips').text(JSON.stringify(evt));
}
socket.onclose = function () {
$('#tips').text('已经关闭');
}
}); $('#close').click(function () {
socket.close();
}); $('#send').click(function () {
if (socket.readyState == WebSocket.OPEN) {
socket.send($('#content').val());
}
else {
$('#tips').text('连接已经关闭');
}
});
});
</script>
</head>
<body>
<form id="form1">
<div>
<input id="conn" type="button" value="连接" />
<input id="close" type="button" value="关闭" />
<span id="tips"></span>
<input id="content" type="text" />
<input id="send" type="button" value="发送" />
</div>
</form>
</body>
</html>
二、服务端:
创建控制台应用程序,代码如下:
class Program
{
//客户端集合
static List<Socket> clients = new List<Socket>();
static byte[] buffer = new byte[];
//static bool IsWebSocketClient = false; //客户端连接是否为websocket
static void Main(string[] args)
{
//创建一个新的Socket,这里我们使用最常用的基于TCP的Stream Socket(流式套接字)
Socket SeverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//将该socket绑定到主机上面的某个端口
SeverSocket.Bind(new IPEndPoint(IPAddress.Any, ));
//设置Socket为监听状态并设置允许的最大队列数为4
SeverSocket.Listen(); //服务端开始异步接受客户端的连接请求
SeverSocket.BeginAccept(AsyncAcceptCallback, SeverSocket); Console.WriteLine("Sever is ready"); //创建一个时钟,每隔1分钟发送一个心跳包给客户端
SendHeartPackToClients(); Console.Read();
}
#region 创建一个时钟,每隔10秒发送一个心跳包给客户端 private static void SendHeartPackToClients()
{
System.Timers.Timer time = new System.Timers.Timer();
time.Interval = * ;
time.Enabled = true;
time.Elapsed += time_Elapsed;
time.Start();
} static void time_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
{
SendMsgToAllClients("hi," + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"));
}
/// <summary>
/// 发送消息给所有连接的客户端
/// </summary>
private static void SendMsgToAllClients(string msg)
{
try
{
foreach (var client in clients)
{
if (client.Connected)
{
client.Send(PackageServerData(msg));
}
}
}
catch (Exception)
{
//TODO
} } #endregion
/// <summary>
/// 服务端异步接受连接的回调处理方法
/// </summary>
/// <param name="ar"></param>
private static void AsyncAcceptCallback(IAsyncResult ar)
{
var ServerSocket = ar.AsyncState as Socket;
//异步接受传入的连接,并创建客户端Socket
var ClientSocket = ServerSocket.EndAccept(ar); //将客户端加入集合中
clients.Add(ClientSocket); //开始异步接收该客户端发送的消息
ClientSocket.BeginReceive(buffer, , buffer.Length, SocketFlags.None, AsyncReceiveCallback, ClientSocket); //服务端开始异步接受下一个客户端的连接请求
ServerSocket.BeginAccept(AsyncAcceptCallback, ServerSocket);
}
/// <summary>
/// 异步接收消息的回调处理方法
/// </summary>
/// <param name="ar"></param>
private static void AsyncReceiveCallback(IAsyncResult ar)
{
try
{
var ClientSocket = ar.AsyncState as Socket;
int RevLength = ClientSocket.EndReceive(ar); string RevMsg = Encoding.UTF8.GetString(buffer, , RevLength); #region WebSocket处理代码
//判断是否为浏览器websocket发过来的请求,若是,则打包服务器握手数据,实现第4次握手
if (RevMsg.Contains("Sec-WebSocket-Key"))
{
//IsWebSocketClient = true;
Console.WriteLine(RevMsg);
ClientSocket.Send(PackageHandShakeData(buffer, RevLength));
}
else {
string AnalyzeMsg = AnalyzeClientData(buffer, RevLength);
Console.WriteLine(AnalyzeMsg);
ClientSocket.Send(PackageServerData("收到您的信息!"));
}
#endregion //继续接收该客户端下一条发送的消息
ClientSocket.BeginReceive(buffer, , buffer.Length, SocketFlags.None, AsyncReceiveCallback, ClientSocket);
}
catch (Exception ex)
{
Console.WriteLine(ex.Message);
} }
#region 客户端和服务端的响应
/*
* 客户端向服务器发送请求
*
* GET / HTTP/1.1
* Origin: http://localhost:1416
* Sec-WebSocket-Key: vDyPp55hT1PphRU5OAe2Wg==
* Connection: Upgrade
* Upgrade: Websocket
*Sec-WebSocket-Version: 13
* User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; Trident/7.0; rv:11.0) like Gecko
* Host: localhost:8064
* DNT: 1
* Cache-Control: no-cache
* Cookie: DTRememberName=admin
*
* 服务器给出响应
*
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: xsOSgr30aKL2GNZKNHKmeT1qYjA=
*
* 在请求中的“Sec-WebSocket-Key”是随机的,服务器端会用这些数据来构造出一个SHA-1的信息摘要。把“Sec-WebSocket-Key”加上一个魔幻字符串
* “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”。使用 SHA-1 加密,之后进行 BASE-64编码,将结果做为 “Sec-WebSocket-Accept” 头的值,返回给客户端
*/
#endregion
/// <summary>
/// 打包服务器握手数据
/// </summary>
/// <returns>The hand shake data.</returns>
/// <param name="handShakeBytes">Hand shake bytes.</param>
/// <param name="length">Length.</param>
private static byte[] PackageHandShakeData(byte[] handShakeBytes, int length)
{
string handShakeText = Encoding.UTF8.GetString(handShakeBytes, , length);
string key = string.Empty;
Regex reg = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");
Match m = reg.Match(handShakeText);
if (m.Value != "")
{
key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
} byte[] secKeyBytes = SHA1.Create().ComputeHash(
Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
string secKey = Convert.ToBase64String(secKeyBytes); var responseBuilder = new StringBuilder();
responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + "\r\n");
responseBuilder.Append("Upgrade: websocket" + "\r\n");
responseBuilder.Append("Connection: Upgrade" + "\r\n");
responseBuilder.Append("Sec-WebSocket-Accept: " + secKey + "\r\n\r\n"); //如果把上一行换成下面两行,才是thewebsocketprotocol-17协议,但居然握手不成功,目前仍没弄明白!
//responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine);
//responseBuilder.Append("Sec-WebSocket-Protocol: chat" + Environment.NewLine); return Encoding.UTF8.GetBytes(responseBuilder.ToString());
} #region 处理接收的数据
/// <summary>
/// 处理接收的数据
/// 参考 http://www.cnblogs.com/smark/archive/2012/11/26/2789812.html
/// </summary>
/// <param name="recBytes"></param>
/// <param name="length"></param>
/// <returns></returns>
private static string AnalyzeClientData(byte[] recBytes, int length)
{
int start = ;
// 如果有数据则至少包括3位
if (length < ) return "";
// 判断是否为结束针
bool IsEof = (recBytes[start] >> ) > ;
// 暂不处理超过一帧的数据
if (!IsEof) return "";
start++;
// 是否包含掩码
bool hasMask = (recBytes[start] >> ) > ;
// 不包含掩码的暂不处理
if (!hasMask) return "";
// 获取数据长度
UInt64 mPackageLength = (UInt64)recBytes[start] & 0x7F;
start++;
// 存储4位掩码值
byte[] Masking_key = new byte[];
// 存储数据
byte[] mDataPackage;
if (mPackageLength == )
{
// 等于126 随后的两个字节16位表示数据长度
mPackageLength = (UInt64)(recBytes[start] << | recBytes[start + ]);
start += ;
}
if (mPackageLength == )
{
// 等于127 随后的八个字节64位表示数据长度
mPackageLength = (UInt64)(recBytes[start] << ( * ) | recBytes[start] << ( * ) | recBytes[start] << ( * ) | recBytes[start] << ( * ) | recBytes[start] << ( * ) | recBytes[start] << ( * ) | recBytes[start] << | recBytes[start + ]);
start += ;
}
mDataPackage = new byte[mPackageLength];
for (UInt64 i = ; i < mPackageLength; i++)
{
mDataPackage[i] = recBytes[i + (UInt64)start + ];
}
Buffer.BlockCopy(recBytes, start, Masking_key, , );
for (UInt64 i = ; i < mPackageLength; i++)
{
mDataPackage[i] = (byte)(mDataPackage[i] ^ Masking_key[i % ]);
}
return Encoding.UTF8.GetString(mDataPackage);
}
#endregion #region 发送数据
/// <summary>
/// 把发送给客户端消息打包处理(拼接上谁什么时候发的什么消息)
/// </summary>
/// <returns>The data.</returns>
/// <param name="message">Message.</param>
private static byte[] PackageServerData(string msg)
{
byte[] content = null;
byte[] temp = Encoding.UTF8.GetBytes(msg);
if (temp.Length < )
{
content = new byte[temp.Length + ];
content[] = 0x81;
content[] = (byte)temp.Length;
Buffer.BlockCopy(temp, , content, , temp.Length);
}
else if (temp.Length < 0xFFFF)
{
content = new byte[temp.Length + ];
content[] = 0x81;
content[] = ;
content[] = (byte)(temp.Length & 0xFF);
content[] = (byte)(temp.Length >> & 0xFF);
Buffer.BlockCopy(temp, , content, , temp.Length);
}
return content;
}
#endregion
}
同系列其他文章:如何使用HTML5的WebSocket实现网页与服务器的双工通信(二)
如何使用HTML5的WebSocket实现网页与服务器的双工通信(一)的更多相关文章
- 如何使用HTML5的WebSocket实现网页与服务器的双工通信(二)
本系列服务端双工通信包括两种实现方式:一.使用Socket构建:二.使用WCF构建.本文为使用WCF构建服务端的双工通信,客户端同样使用Html5的WebSocket技术进行调用. 一.创建WCF服务 ...
- Java和WebSocket开发网页聊天室
小编心语:咳咳咳,今天又是聊天室,到现在为止小编已经分享了不下两个了,这一次跟之前的又不大相同,这一次是网页聊天室,具体怎么着,还请各位看官往下看~ Java和WebSocket开发网页聊天室 一.项 ...
- html5利用websocket完成的推送功能(tomcat)
html5利用websocket完成的推送功能(tomcat) 利用websocket和java完成的消息推送功能,服务器用的是tomcat7.0.42,一些东西是自己琢磨的,也不知道恰不恰当,不恰当 ...
- Springboot整合WebSocket实现网页版聊天,快来围观!
- 15款免费的 HTML5/CSS3 响应式网页模板
如果你想快速制作出一个优秀的网站,网站模板一定是必不可少的.网页设计师和开发人员也可以从网站模板入手,学习先进的布局方式和编码风格.下面这个列表为大家挑选了15款免费的 HTML5/CSS3 响应式网 ...
- 【HTML5+MVC4】xhEditor网页编辑器图片上传
准备工作: 创建一个MVC项目中,添加好xhEditor插件 相关用法:http://www.cnblogs.com/xcsn/p/4701497.html 注意事项:xhEditor分为v1.1.1 ...
- 认识HTML5的WebSocket
在HTML5规范中,我最喜欢的Web技术就是正迅速变得流行的WebSocket API.WebSocket提供了一个受欢迎的技术,以替代我们过去几年一直在用的Ajax技术.这个新的API提供了一个方法 ...
- HTML5之WebSocket
在HTML5规范中,我最喜欢的Web技术就是正迅速变得流行的WebSocket API.WebSocket提供了一个受欢迎的技术,以替代我们过去几年一直在用的Ajax技术.这个新的API提供了一个方法 ...
- 基于html5实现的愤怒的小鸟网页游戏
之前给大家分享一款基于html5 canvas和js实现的水果忍者网页版,今天给大家分享一款基于html5实现的愤怒的小鸟网页游戏.这款游戏适用浏览器:360.FireFox.Chrome.Safar ...
随机推荐
- SVN-Failed to run the WC DB work queue associated with
解决方法:清空svn的队列 1.下载sqlite3.exe 2.找到你项目的.svn文件,查看是否存在wc.db 3.将sqlite3.exe放到.svn的同级目录 4.启动cmd执行sqlite3 ...
- Unable to resolve dependency问题解决
Unable to resolve dependency 是一个让我头疼的问题 之前总是阴差阳错调试好 但是也没有总结出来方法 但是今天找到了 方法来源 https://jingyan.baidu.c ...
- HttpUrlConnection流传输问题(正确传输包含中文的JSON字符串)
目前在写一个功能,主要是使用 HttpURLConnection 发送http请求调用外部接口.本来一切正常的,可是在发送post请求上传数据给服务端时,服务端返回错误信息:获取的JSON请求是乱码的 ...
- ibm产品系列架构师技术路线
- Matlab-6:解非线性方程组newton迭代法
函数文件: function x=newton_Iterative_method(f,n,Initial) x0=Initial; tol=1e-11; x1=x0-Jacobian(f,n,x0)\ ...
- 微信小程序分包跳转主包页面
由于公司项目比较多,我们事业部的微信小程序就在一个分包里.那分包页面要回到主包的首页,该怎么跳转呢,有以下两种方法 wx.switchTab(Object object) 跳转到 tabBar 页面, ...
- Case 条件运算符
Case运算有两种写法,平常用的都比较多,这里只简单复习下,Case的语法帮助里就好,这里我尝试用颜色区分的方式让大家一眼就能了解其结构: 写法一:) select case when PriceTy ...
- 使用Github Composer Packagist编写及发布扩展包
1.在github中创建自己的仓库,然后本地clone,初始化composer init ,在composer.json中增加autoload "autoload": { &quo ...
- sql server中如何修改视图中的数据?
sql server中如何修改视图中的数据? 我做个测试,视图的数据有标记字段时,如果是这种方式(0 as FlagState),是无法修改的 --创建视图语句 --- create view V_E ...
- os.path官方文档(附翻译)
This module implements some useful functions on pathnames. To read or write files see open(), and fo ...