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應用就邁出了他的第一步了。
下一步,我會實現一個能進行簡單溝通的聊天室。