用C# ASP.NET MVC 實現WebSocket ,對於WebSocket想必都很了解了,不多說.
東西做的很粗糙 只能實現基本的聊天功能,不過基本的通信實現了,那么后序的擴展應該也不難(個人這么認為...)
先看下效果
可同時支持群聊和私聊 源碼下載地址
http://download.csdn.net/detail/formularz/4668280
首先介紹下ValueWebSocket.cs 這個文件 主要是對與客戶端的通信進行集中控制
1.ValueServer: Socket服務端
2.ValueProtocol:對WebSocket通信的數據加以解析
3.SessionManager: 集中管理在線用戶
ValueWebSocket.cs
public class ValueWebSocket
{
// WebSocket服務端
private ValueServer server;
// 解析協議
private ValueProtocol valueProtocol;
// 管理在線用戶
private SessionManager sessionManager;
public ValueWebSocket(String ipAddress, Int32 port)
{
valueProtocol = new ValueProtocol();
sessionManager = new SessionManager();
server = new ValueServer(ipAddress, port, Encoding.UTF8);
server.OnReceive += new ValueHelper.ValueSocket.Infrastructure.ReceiveHandler(server_OnReceive);
}
private void server_OnReceive(ValueHelper.ValueSocket.SocketEvents.ReceiveEventArgs e)
{
// 分析用戶是否已存在
if (sessionManager.CheckSessionExist(e.Socket))
{
Message message = valueProtocol.Decode(e.Data);
if (message.header.Opcode == OperType.Close)
{
removeUser(e.Socket);
}
if (message.header.Opcode == OperType.Text)
{
String msg = message.Data.ToString();
execMsg(e.Socket, msg);
}
}
else
{
// 用戶不存在則添加用戶
// 並發送握手信息與客戶端建立連接
String request = Encoding.UTF8.GetString(e.Data);
Byte[] response = valueProtocol.GetResponse(request);
server.Send(e.Socket, response);
sessionManager.AddSession(e.Socket, request);
}
}
// 對消息進行的處理
private void execMsg(Socket socket, String message)
{
String name = String.Empty;
foreach (ValueSession session in SessionManager.Sessions)
{
Socket sk = session.Socket;
if (sk.Connected)
{
if (sk.RemoteEndPoint == socket.RemoteEndPoint)
{
name = session.Cookies["name"];
break;
}
}
}
// 判斷私聊還是公共
String[] separator = message.Split(new String[] { "<separator>" }, StringSplitOptions.None);
String msg = String.Concat(name, ": ", separator[1]);
if (separator[0] == "All")
SendToAll(msg);
else
{
foreach (ValueSession session in SessionManager.Sessions)
{
if (session.Cookies["name"] == separator[0])
{
sendTo(session.Socket, msg);
break;
}
}
}
}
private void removeUser(Socket socket)
{
sessionManager.RemoveSession(socket);
}
private void SendToAll(String msg)
{
foreach (ValueSession session in SessionManager.Sessions)
{
sendTo(session.Socket, msg);
}
}
private Boolean sendTo(Socket socket, String msg)
{
Byte[] data = valueProtocol.Encode(msg);
return server.Send(socket, data);
}
public void Start()
{
server.Start();
}
public void Dispose()
{
sessionManager.Dispose();
server.Dispose();
}
}
SessionManager: 該類就不多說了,集中管理用戶類.詳情查看源碼.
ValueProtocol: 這個類其實只是一個接口類,
真正對數據進行解析的是ProtocolDraft10類(按草案10中介紹的規則對數據進行解析,注:對協議說明有興趣的同學可以查看這位大牛的文章http://blog.csdn.net/fenglibing/article/details/6852497)
ProtocolDraft10.cs
public class ProtocolDraft10 : IProtocol
{
private const String WebSocketKeyPattern = @"Sec\-WebSocket\-Key:\s+(?<key>.*)\r\n";
private const String MagicKey = "258EAFA5-E914-47DA-95CA-C5AB0DC85B11";
private const Char charOne = '1';
private const Char charZero = '0';
#region Handshake
// 發送回復信息完成握手
public Byte[] ProduceResponse(string request)
{
String webSocketKey = Common.GetRegexValue(request, WebSocketKeyPattern)[0].Groups["key"].Value;
String acceptKey = produceAcceptKey(webSocketKey);
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.Append(String.Concat("HTTP/1.1 101 Web Socket Protocol Handshake", Environment.NewLine));
stringBuilder.Append(String.Concat("Upgrade: WebSocket", Environment.NewLine));
stringBuilder.Append(String.Concat("Connection: Upgrade", Environment.NewLine));
stringBuilder.Append(String.Concat("Sec-WebSocket-Accept: ", acceptKey, Environment.NewLine, Environment.NewLine));
String asd = stringBuilder.ToString();
return Encoding.UTF8.GetBytes(stringBuilder.ToString());
}
// 根據Sec-WebSocket-Key和MagicKey生成AcceptKey
private String produceAcceptKey(String webSocketKey)
{
Byte[] acceptKey = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(webSocketKey + MagicKey));
return Convert.ToBase64String(acceptKey);
}
#endregion
#region Decode
// 對客戶端發來的數據進行解析
public Message Decode(Byte[] data)
{
Byte[] buffer = new Byte[14];
if (data.Length >= 14)
Buffer.BlockCopy(data, 0, buffer, 0, 14);
else
Buffer.BlockCopy(data, 0, buffer, 0, data.Length);
MessageHeader header = analyseHead(buffer);
Message msg = new Message();
msg.header = header;
Byte[] payload;
if (header != null)
{
payload = new Byte[data.Length - header.PayloadDataStartIndex];
Buffer.BlockCopy(data, header.PayloadDataStartIndex, payload, 0, payload.Length);
if (header.MASK == charOne)
{
for (int i = 0; i < payload.Length; i++)
{
payload[i] = (Byte)(payload[i] ^ header.Maskey[i % 4]);
}
}
}
else
{
msg.Data = Encoding.UTF8.GetString(data);
return msg;
}
if (header.Opcode == OperType.Text)
msg.Data = Encoding.UTF8.GetString(payload);
return msg;
}
private MessageHeader analyseHead(Byte[] buffer)
{
MessageHeader header = new MessageHeader();
header.FIN = (buffer[0] & 0x80) == 0x80 ? charOne : charZero;
header.RSV1 = (buffer[0] & 0x40) == 0x40 ? charOne : charZero;
header.RSV2 = (buffer[0] & 0x20) == 0x20 ? charOne : charZero;
header.RSV3 = (buffer[0] & 0x10) == 0x10 ? charOne : charZero;
if ((buffer[0] & 0xA) == 0xA)
header.Opcode = OperType.Pong;
else if ((buffer[0] & 0x9) == 0x9)
header.Opcode = OperType.Ping;
else if ((buffer[0] & 0x8) == 0x8)
header.Opcode = OperType.Close;
else if ((buffer[0] & 0x2) == 0x2)
header.Opcode = OperType.Binary;
else if ((buffer[0] & 0x1) == 0x1)
header.Opcode = OperType.Text;
else if ((buffer[0] & 0x0) == 0x0)
header.Opcode = OperType.Row;
header.MASK = (buffer[1] & 0x80) == 0x80 ? charOne : charZero;
Int32 len = buffer[1] & 0x7F;
if (len == 126)
{
header.Payloadlen = (UInt16)(buffer[2] << 8 | buffer[3]);
if (header.MASK == charOne)
{
header.Maskey = new Byte[4];
Buffer.BlockCopy(buffer, 4, header.Maskey, 0, 4);
header.PayloadDataStartIndex = 8;
}
else
header.PayloadDataStartIndex = 4;
}
else if (len == 127)
{
Byte[] byteLen = new Byte[8];
Buffer.BlockCopy(buffer, 4, byteLen, 0, 8);
header.Payloadlen = BitConverter.ToUInt64(byteLen, 0);
if (header.MASK == charOne)
{
header.Maskey = new Byte[4];
Buffer.BlockCopy(buffer, 10, header.Maskey, 0, 4);
header.PayloadDataStartIndex = 14;
}
else
header.PayloadDataStartIndex = 10;
}
else
{
if (header.MASK == charOne)
{
header.Maskey = new Byte[4];
Buffer.BlockCopy(buffer, 2, header.Maskey, 0, 4);
header.PayloadDataStartIndex = 6;
}
else
header.PayloadDataStartIndex = 2;
}
return header;
}
#endregion
#region Encode
// 對要發送的數據進行編碼一符合草案10的規則
public Byte[] Encode(String msg)
{
Byte[] data = Encoding.UTF8.GetBytes(msg);
Int32 dataLength = data.Length;
Byte[] head = packetHeader(OperType.Text, dataLength);
for (int i = 0; i < data.Length; i++)
{
data[i] = (Byte)(data[i] ^ maskKey[i % 4]);
}
Byte[] result = new Byte[head.Length + dataLength];
Buffer.BlockCopy(head, 0, result, 0, head.Length);
Buffer.BlockCopy(data, 0, result, head.Length, dataLength);
return result;
}
private const Byte byte80 = 0x80;
private Byte[] maskKey = new Byte[] { 113, 105, 97, 110 };
private Byte[] packetHeader(OperType operType, Int32 length)
{
Byte byteHead = (Byte)(byte80 | (Byte)operType);
Byte[] byteLen;
if (length < 126)
{
byteLen = new Byte[1];
byteLen[0] = (Byte)(byte80 | (Byte)length);
}
else if (length < 65535)
{
byteLen = new Byte[3];
byteLen[0] = (Byte)(byte80 | (Byte)126);
for (int i = 1; i < 3; i++)
byteLen[i] = (Byte)(length >> (8 * (2 - i)));
}
else
{
byteLen = new Byte[9];
byteLen[0] = (Byte)(byte80 | (Byte)127);
for (int i = 1; i < 9; i++)
byteLen[i] = (Byte)(length >> (8 * (8 - i)));
}
Byte[] packet = new Byte[1 + byteLen.Length + maskKey.Length];
packet[0] = byteHead;
Buffer.BlockCopy(byteLen, 0, packet, 1, byteLen.Length);
Buffer.BlockCopy(maskKey, 0, packet, 1 + byteLen.Length, maskKey.Length);
return packet;
}
#endregion
}
改類主要實現與客戶端的握手級數據的解析,至於為什么這么解析,可訪問上面那位大牛的文章,有詳細說明值得一提的是有掩碼的話接
通信時發送的數據都是真實數據與掩碼按按異或處理過的.
其他類其實都是一些基礎部件, 詳情查看源碼.
ValueServer: 該類其實就是用套接口編寫了一個服務端實現Accept,Receive,Send等相關事件
至於Socket服務端的具體實現請查看這位大牛的文章,有詳細說明http://www.cnblogs.com/tianzhiliang/archive/2010/09/08/1821623.html
這里就不贅述了
客戶端的實現就比較簡單了 只要調用WebSocket的API就行了
具體如下:
WebSocket.js
<script type="text/javascript">
var noSupportMessage = "您的瀏覽器不支持WebSocket!";
var ws;
function connectSocketServer() {
var messageBoard = $("#messageBoard");
var support = "MozWebSocket" in window ? 'MozWebSocket' : ('WebSocket' in window ? 'WebSocket' : null);
if (support == null) {
alert(noSupportMessage);
messageBoard.append('*' + noSupportMessage + "<br />");
return;
}
messageBoard.append("* Connecting to server..<br />");
try {
ws = new WebSocket('ws://localhost:3000');
} catch (e) {
alert(e.Message);
}
ws.onmessage = function (event) {
messageBoard.append(event.data + "<br />");
}
ws.onopen = function () {
messageBoard.append('* Connection open<br />');
}
ws.onclose = function () {
messageBoard.append('* Connection closed<br />');
}
}
function sendMessage() {
if (ws) {
var mssageBox = document.getElementById("messageBox");
var user = document.getElementById("users");
var msg = user.value + "<separator>" + mssageBox.value;
ws.send(msg);
mssageBox.value = "";
} else {
alert(noSupportMessage);
}
}
window.onload = function () {
connectSocketServer();
}
</script>