HTML5新特性中,最令我激动的就是WebSocket,当时学习Silverlight的原因中,这也是很重要的一条。以前初步看了一下基本的原理,感觉也挺容易实现,这两天抽了个空,尝试着写了一下,还是碰到了几个问题,现在把基本的情况总结一下,以备自己将来或有兴趣的朋友查阅。
我的机器环境:
Windows 7 / Visual Studio 2010 SP1 C#/ 谷歌Chrome浏览器
关于WebSocket原理的文章,大家可以在网上找找,非常多,但让人琢磨不透的也不少,这几个是我认为对我有帮助的几篇,也留下来,供各位查看,并在此对作者表示感谢:
http://blog.csdn.net/fenglibing/article/details/6852497
http://tools.ietf.org/html/draft-ietf-hybi-thewebsocketprotocol-10
初步总结一下流程:
客户端发起连接请求,向服务器发送如同下面格式的信息:
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: 192.168.1.36:8050
Sec-WebSocket-Origin: http://localhost:5113
Sec-WebSocket-Key: YZgRBqBF5a5uWll/N8/R+Q==
Sec-WebSocket-Version: 8
Upgrade: websocket
Connection: Upgrade
Host: 192.168.1.36:8050
Sec-WebSocket-Origin: http://localhost:5113
Sec-WebSocket-Key: YZgRBqBF5a5uWll/N8/R+Q==
Sec-WebSocket-Version: 8
服务端收到后,返回如同下面格式的信息:
HTTP/1.1 101 Web Socket Protocol Handshake
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: EailQ5Var3+aJmxVsqnNoxUc3sU=
WebSocket-Origin: http://localhost:5113
WebSocket-Location: ws://192.168.1.36:8050
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: EailQ5Var3+aJmxVsqnNoxUc3sU=
WebSocket-Origin: http://localhost:5113
WebSocket-Location: ws://192.168.1.36:8050
服务器端将客户发送的Sec-WebSocket-Key(YZgRBqBF5a5uWll/N8/R+Q==),经过一定的计算,得出一个Sec-WebSocket-Accept(EailQ5Var3+aJmxVsqnNoxUc3sU=),返回给客户,这其实就是一个握手的过程,一旦认证通过,即建立了真正的Socket连接,就可以正常的传送数据了。
了解了这个过程,要开发服务端就相对容易了,对于这个版本中Accept的算法,可能是刚接触的人最不想去看,但又必须有的过程,还好我在网上找到了,并为此开发了一个专门的握手类,简化使用。
代码如下:
public
class Handshake
{
// 用于保存请求串的键值对
private Hashtable KeyValues;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="request"> 请求串 </param>
public Handshake( string request)
{
// 初始化哈希表
KeyValues= new Hashtable();
// 分割字符串,用于分割每一行
string[] separator1 = { " \r\n "};
string[] rows = request.Split(separator1, StringSplitOptions.RemoveEmptyEntries);
foreach ( string row in rows)
{
// ':'在每一行的第一个匹配项索引
int splitIndex = row.IndexOf( ' : ');
if (splitIndex > 0)
{
// 是键值对,保存到哈希表
string key1 = row.Substring( 0, splitIndex).Trim(); // 键
string value1 = row.Substring(splitIndex + 1).Trim(); // 值
KeyValues.Add(key1, value1); // 保存到哈希表KeyValues
}
}
}
/// <summary>
/// 返回的验证码
/// </summary>
public string KeyAccept
{
get
{
string secWebSocketKey = GetValue( " Sec-WebSocket-Key ");
string m_Magic = " 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ";
return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(secWebSocketKey + m_Magic)));
}
}
/// <summary>
/// 根据键获取对应值
/// </summary>
/// <param name="key"> 键 </param>
/// <returns></returns>
public string GetValue( string key)
{
// 在哈希表中查询是否存在对应的键值
if(KeyValues.ContainsKey(key))
return KeyValues[key].ToString();
else
return string.Empty; // 没有匹配的键值
}
/// <summary>
/// 响应串
/// </summary>
public string Response
{
get
{
StringBuilder response = new StringBuilder(); // 响应串
response.Append( " HTTP/1.1 101 Web Socket Protocol Handshake\r\n ");
// 将请求串的键值转换为对应的响应串的键值并添加到响应串
response.AppendFormat( " Upgrade: {0}\r\n ", GetValue( " Upgrade "));
response.AppendFormat( " Connection: {0}\r\n ", GetValue( " Connection "));
response.AppendFormat( " Sec-WebSocket-Accept: {0}\r\n ", KeyAccept);
response.AppendFormat( " WebSocket-Origin: {0}\r\n ", GetValue( " Sec-WebSocket-Origin "));
response.AppendFormat( " WebSocket-Location: {0}\r\n ", GetValue( " Host "));
response.Append( " \r\n ");
return response.ToString();
}
}
}
{
// 用于保存请求串的键值对
private Hashtable KeyValues;
/// <summary>
/// 构造函数
/// </summary>
/// <param name="request"> 请求串 </param>
public Handshake( string request)
{
// 初始化哈希表
KeyValues= new Hashtable();
// 分割字符串,用于分割每一行
string[] separator1 = { " \r\n "};
string[] rows = request.Split(separator1, StringSplitOptions.RemoveEmptyEntries);
foreach ( string row in rows)
{
// ':'在每一行的第一个匹配项索引
int splitIndex = row.IndexOf( ' : ');
if (splitIndex > 0)
{
// 是键值对,保存到哈希表
string key1 = row.Substring( 0, splitIndex).Trim(); // 键
string value1 = row.Substring(splitIndex + 1).Trim(); // 值
KeyValues.Add(key1, value1); // 保存到哈希表KeyValues
}
}
}
/// <summary>
/// 返回的验证码
/// </summary>
public string KeyAccept
{
get
{
string secWebSocketKey = GetValue( " Sec-WebSocket-Key ");
string m_Magic = " 258EAFA5-E914-47DA-95CA-C5AB0DC85B11 ";
return Convert.ToBase64String(SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(secWebSocketKey + m_Magic)));
}
}
/// <summary>
/// 根据键获取对应值
/// </summary>
/// <param name="key"> 键 </param>
/// <returns></returns>
public string GetValue( string key)
{
// 在哈希表中查询是否存在对应的键值
if(KeyValues.ContainsKey(key))
return KeyValues[key].ToString();
else
return string.Empty; // 没有匹配的键值
}
/// <summary>
/// 响应串
/// </summary>
public string Response
{
get
{
StringBuilder response = new StringBuilder(); // 响应串
response.Append( " HTTP/1.1 101 Web Socket Protocol Handshake\r\n ");
// 将请求串的键值转换为对应的响应串的键值并添加到响应串
response.AppendFormat( " Upgrade: {0}\r\n ", GetValue( " Upgrade "));
response.AppendFormat( " Connection: {0}\r\n ", GetValue( " Connection "));
response.AppendFormat( " Sec-WebSocket-Accept: {0}\r\n ", KeyAccept);
response.AppendFormat( " WebSocket-Origin: {0}\r\n ", GetValue( " Sec-WebSocket-Origin "));
response.AppendFormat( " WebSocket-Location: {0}\r\n ", GetValue( " Host "));
response.Append( " \r\n ");
return response.ToString();
}
}
}
客户端的关键代码:
function connect()
{
try
try
{
var readyStatus = new Array("正在连接", "已建立连接", "正在关闭连接", "已关闭连接");
var host = "ws://192.168.1.36:8050";
conn = new WebSocket(host);
var message = document.getElementById("message");
message.innerHTML += "<p>Socket 状态:" + readyStatus[conn.readyState] + "</p>";
conn.onopen = function ()
var readyStatus = new Array("正在连接", "已建立连接", "正在关闭连接", "已关闭连接");
var host = "ws://192.168.1.36:8050";
conn = new WebSocket(host);
var message = document.getElementById("message");
message.innerHTML += "<p>Socket 状态:" + readyStatus[conn.readyState] + "</p>";
conn.onopen = function ()
{
message.innerHTML += "<p>Socket状态:" + readyStatus[conn.readyState] + "</p>";
}
conn.onmessage = function (msg) {
message.innerHTML += "<p>接收消息:" + msg.data + "</p>";
}
conn.onclose = function (event) {
message.innerHTML += "<p>Socket状态:" + readyStatus[conn.readyState] + "</p>";
}
}
catch (exception) {
message.innerHTML += "<p>有错误发生.</p>";
}
}
message.innerHTML += "<p>Socket状态:" + readyStatus[conn.readyState] + "</p>";
}
conn.onmessage = function (msg) {
message.innerHTML += "<p>接收消息:" + msg.data + "</p>";
}
conn.onclose = function (event) {
message.innerHTML += "<p>Socket状态:" + readyStatus[conn.readyState] + "</p>";
}
}
catch (exception) {
message.innerHTML += "<p>有错误发生.</p>";
}
}
这样,大家可以试试,一个基于原生Socket支持的Web应用就迈出了他的第一步了。
下一步,我会实现一个能进行简单沟通的聊天室。