Websocket是一个持久化的协议。
基于.Net服务端:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net;
using System.Net.Sockets;
using System.Text.RegularExpressions;
using System.Security.Cryptography;
namespace WebSocketService
{
/// <summary>
/// webSocket服务端,使用C#Socket实现
/// </summary>
public class WebSocketServer
{
//全局变量集合,字典类<连接字符串,socket客户端>
public static Dictionary<string, ClientConnectSocket> dic = new Dictionary<string, ClientConnectSocket>();
Socket socketServer = null;
/// <summary>
/// 启动服务
/// </summary>
public void StarServer()
{
//实例化socket,TCP流
socketServer = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//绑定ip,端口
socketServer.Bind(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 9000));
//同时监听数量,最大为10个
socketServer.Listen(10);
//异步接受连接
socketServer.BeginAccept(new AsyncCallback(Accept), socketServer);
}
/// <summary>
/// 异步接受回调方法
/// </summary>
/// <param name="ia"></param>
private void Accept(IAsyncResult ia)
{
//连接socket
Socket connectSocket = socketServer.EndAccept(ia);
IPEndPoint clientipe = (IPEndPoint)connectSocket.RemoteEndPoint;
//实例化连接客户端socket类
ClientConnectSocket c = new ClientConnectSocket(connectSocket);
//添加到集合
dic.Add(clientipe.ToString(), c);
//循环异步接受连接
socketServer.BeginAccept(new AsyncCallback(Accept), socketServer);
}
}
/// <summary>
/// 连接客户端socket类
/// </summary>
public class ClientConnectSocket
{
int judge = 0;//判断第一次接受消息,因为与websocket连接上就会给我们会发送一个xml的消息来和我们握手,
Socket socket = null;
byte[] bys = new byte[1000];
public ClientConnectSocket(Socket sok)
{
socket = sok;
//异步接受数据
socket.BeginReceive(bys, 0, bys.Length, SocketFlags.None, new AsyncCallback(Receive), socket);
}
private void Receive(IAsyncResult ia)
{
try
{
int length = socket.EndReceive(ia);
if (judge == 0)
{
//-----------------------------
//如果是websocket刚连接上我们发送的消息,这个消息用于和我们握手,方法解析这个数据,回复一个握手数据
socket.Send(PackHandShakeData(GetSecKeyAccetp(bys, length)));
judge = 1;
}
else
{
//普通接受数据需要用这个方法来解析数据------------------------------
string clientMsg = AnalyticData(bys, length);
foreach (var item in WebSocketServer.dic)
{
item.Value.send(clientMsg);
}
}
socket.BeginReceive(bys, 0, bys.Length, SocketFlags.None, new AsyncCallback(Receive), socket);
}
catch (Exception ex)
{
}
}
//发送消息
public void send(string msg)
{
try
{
//发送消息需要用下边方法来解析----------------
socket.Send(PackData(msg));
}
catch (Exception ex)
{ }
}
#region 和websocket通讯重要的方法
/// <summary>
/// 打包握手信息
/// </summary>
/// <param name="secKeyAccept">Sec-WebSocket-Accept</param>
/// <returns>数据包</returns>
private byte[] PackHandShakeData(string secKeyAccept)
{
var responseBuilder = new StringBuilder();
responseBuilder.Append("HTTP/1.1 101 Switching Protocols" + Environment.NewLine);
responseBuilder.Append("Upgrade: websocket" + Environment.NewLine);
responseBuilder.Append("Connection: Upgrade" + Environment.NewLine);
responseBuilder.Append("Sec-WebSocket-Accept: " + secKeyAccept + Environment.NewLine + Environment.NewLine);
return Encoding.UTF8.GetBytes(responseBuilder.ToString());
}
/// <summary>
/// 生成Sec-WebSocket-Accept
/// </summary>
/// <param name="handShakeText">客户端握手信息</param>
/// <returns>Sec-WebSocket-Accept</returns>
private string GetSecKeyAccetp(byte[] handShakeBytes, int bytesLength)
{
string handShakeText = Encoding.UTF8.GetString(handShakeBytes, 0, bytesLength);
string key = string.Empty;
Regex r = new Regex(@"Sec\-WebSocket\-Key:(.*?)\r\n");
Match m = r.Match(handShakeText);
if (m.Groups.Count != 0)
{
key = Regex.Replace(m.Value, @"Sec\-WebSocket\-Key:(.*?)\r\n", "$1").Trim();
}
byte[] encryptionString = SHA1.Create().ComputeHash(Encoding.ASCII.GetBytes(key + "258EAFA5-E914-47DA-95CA-C5AB0DC85B11"));
return Convert.ToBase64String(encryptionString);
}
/// <summary>
/// 解析客户端数据包
/// </summary>
/// <param name="recBytes">服务器接收的数据包</param>
/// <param name="recByteLength">有效数据长度</param>
/// <returns></returns>
private string AnalyticData(byte[] recBytes, int recByteLength)
{
if (recByteLength < 2) { return string.Empty; }
bool fin = (recBytes[0] & 0x80) == 0x80; // 1bit,1表示最后一帧
if (!fin)
{
return string.Empty;// 超过一帧暂不处理
}
bool mask_flag = (recBytes[1] & 0x80) == 0x80; // 是否包含掩码
if (!mask_flag)
{
return string.Empty;// 不包含掩码的暂不处理
}
int payload_len = recBytes[1] & 0x7F; // 数据长度
byte[] masks = new byte[4];
byte[] payload_data;
if (payload_len == 126)
{
Array.Copy(recBytes, 4, masks, 0, 4);
payload_len = (UInt16)(recBytes[2] << 8 | recBytes[3]);
payload_data = new byte[payload_len];
Array.Copy(recBytes, 8, payload_data, 0, payload_len);
}
else if (payload_len == 127)
{
Array.Copy(recBytes, 10, masks, 0, 4);
byte[] uInt64Bytes = new byte[8];
for (int i = 0; i < 8; i++)
{
uInt64Bytes[i] = recBytes[9 - i];
}
UInt64 len = BitConverter.ToUInt64(uInt64Bytes, 0);
payload_data = new byte[len];
for (UInt64 i = 0; i < len; i++)
{
payload_data[i] = recBytes[i + 14];
}
}
else
{
Array.Copy(recBytes, 2, masks, 0, 4);
payload_data = new byte[payload_len];
Array.Copy(recBytes, 6, payload_data, 0, payload_len);
}
for (var i = 0; i < payload_len; i++)
{
payload_data[i] = (byte)(payload_data[i] ^ masks[i % 4]);
}
return Encoding.UTF8.GetString(payload_data);
}
/// <summary>
/// 打包服务器数据
/// </summary>
/// <param name="message">数据</param>
/// <returns>数据包</returns>
private byte[] PackData(string message)
{
byte[] contentBytes = null;
byte[] temp = Encoding.UTF8.GetBytes(message);
if (temp.Length < 126)
{
contentBytes = new byte[temp.Length + 2];
contentBytes[0] = 0x81;
contentBytes[1] = (byte)temp.Length;
Array.Copy(temp, 0, contentBytes, 2, temp.Length);
}
else if (temp.Length < 0xFFFF)
{
contentBytes = new byte[temp.Length + 4];
contentBytes[0] = 0x81;
contentBytes[1] = 126;
contentBytes[2] = (byte)(temp.Length & 0xFF);
contentBytes[3] = (byte)(temp.Length >> 8 & 0xFF);
Array.Copy(temp, 0, contentBytes, 4, temp.Length);
}
else
{
// 暂不处理超长内容
}
return contentBytes;
}
#endregion
}
}
客户端代码,基于.NETMVC框架
@{
ViewBag.Title = "About";
}
@Scripts.Render("~/bundles/jquery")
<script type="text/javascript">
var ws;
$(
function () {
$("#btnConnect").click(function () {
$("#msg").text("Connection...");
//alert($(document.getElementById("serverIP").value));
//alert($("#ServerIP").text());
//alert($("#serverIP").val());
ws = new WebSocket("ws://" + $("#serverIP").val() + ":" + $("#serverPort").val() +"/");// + "/api/WSChat");
ws.onopen = function () {
$("#msg").text("Connected!");
};
ws.onmessage = function (result) {
$("#msg").text(result.data);
};
ws.onerror = function (error) {
$("#msg").text(error.data);
};
ws.onclose = function () {
$("#msg").text("Disconnected!");
};
});
$("#btnSend").click(function () {
if (ws.readyState == WebSocket.OPEN) {
ws.send($("#txtInput").val());
}
else {
$("#msg").text("Connection is Closed!");
}
});
$("#btnDisconnect").click(function () {
ws.close();
});
}
);
</script>
<div>
@*<div>
<text id="serverIP">127.0.0.1</text>
<text id="serverPort">9000</text>
</div>*@
@*<input type="text" value="127.0.0.1" id="serverIP" placeholder="服务器IP" autofocus="autofocus" />
<input type="text" value="9000" id="serverPort" placeholder="服务器端口" />*@
<div>
@*@Html.TextBox("serverIP", "serverIP", new {Value="127.0.0.1"})
@Html.TextBox("serverPort", "", new {Value="9000" })*@
<div>
<input type="text" id="serverIP" value="127.0.0.1" placeholder="服务器IP" autofocus="autofocus" />
<input type="text" id="serverPort" value="9000" placeholder="服务器端口" />
</div>
<div>
<input type="button" value="Connect" id="btnConnect" />
<input type="button" value="DisConnect" id="btnDisConnect" />
</div>
</div>
<div>
<input type="text" id="txtInput" />
<input type="button" value="Send" id="btnSend" />
</div>
<div>
<textarea id="msg" cols="80" rows="20" readonly="readonly"></textarea>
</div>
</div>