C# 实现WEBSOCKET聊天应用示例

http://blog.163.com/da7_1@126/blog/static/10407267820121016103055506/

2012-11-16 22:30:55|  分类: .Net编程 |  标签:websocket  |举报|字号 订阅

 
 

用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

  

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 voidSendToAll(String msg)
{
foreach(ValueSession session inSessionManager.Sessions)
{
sendTo(session.Socket, msg);
}
}

privateBoolean sendTo(Socket socket,String msg)
{
Byte[] data = valueProtocol.Encode(msg);
return server.Send(socket, data);
}

publicvoidStart()
{
server.Start();
}

publicvoidDispose()
{
sessionManager.Dispose();
server.Dispose();
}
}

SessionManager: 该类就不多说了,集中管理用户类.详情查看源码.

ValueProtocol: 这个类其实只是一个接口类,

真正对数据进行解析的是ProtocolDraft10类(按草案10中介绍的规则对数据进行解析,注:对协议说明有兴趣的同学可以查看这位大牛的文章http://blog.csdn.net/fenglibing/article/details/6852497)

ProtocolDraft10.cs

  

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;
}
privateMessageHeader analyseHead(Byte[] buffer)
{
MessageHeader header =newMessageHeader();
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;
elseif((buffer[0]&0x9)==0x9)
header.Opcode=OperType.Ping;
elseif((buffer[0]&0x8)==0x8)
header.Opcode=OperType.Close;
elseif((buffer[0]&0x2)==0x2)
header.Opcode=OperType.Binary;
elseif((buffer[0]&0x1)==0x1)
header.Opcode=OperType.Text;
elseif((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=newByte[4];
Buffer.BlockCopy(buffer,4, header.Maskey,0,4);
header.PayloadDataStartIndex=8;
}
else
header.PayloadDataStartIndex=4;
}
elseif(len ==127)
{
Byte[] byteLen =newByte[8];
Buffer.BlockCopy(buffer,4, byteLen,0,8);
header.Payloadlen=BitConverter.ToUInt64(byteLen,0);
if(header.MASK == charOne)
{
header.Maskey=newByte[4];
Buffer.BlockCopy(buffer,10, header.Maskey,0,4);
header.PayloadDataStartIndex=14;
}
else
header.PayloadDataStartIndex=10;
}
else
{
if(header.MASK == charOne)
{
header.Maskey=newByte[4];
Buffer.BlockCopy(buffer,2, header.Maskey,0,4);
header.PayloadDataStartIndex=6;
}
else
header.PayloadDataStartIndex=2;
}
return header;
}

#endregion

#region Encode
// 对要发送的数据进行编码一符合草案10的规则
publicByte[]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 =newByte[head.Length+ dataLength];
Buffer.BlockCopy(head,0, result,0, head.Length);
Buffer.BlockCopy(data,0, result, head.Length, dataLength);
return result;
}
privateconstByte byte80 =0x80;
privateByte[] maskKey =newByte[]{113,105,97,110};
privateByte[] packetHeader(OperType operType,Int32 length)
{
Byte byteHead =(Byte)(byte80 |(Byte)operType);
Byte[] byteLen;
if(length <126)
{
byteLen =newByte[1];
byteLen[0]=(Byte)(byte80 |(Byte)length);
}
elseif(length <65535)
{
byteLen =newByte[3];
byteLen[0]=(Byte)(byte80 |(Byte)126);
for(int i =1; i <3; i++)
byteLen[i]=(Byte)(length >>(8*(2- i)));
}
else
{
byteLen =newByte[9];
byteLen[0]=(Byte)(byte80 |(Byte)127);
for(int i =1; i <9; i++)
byteLen[i]=(Byte)(length >>(8*(8- i)));
}

Byte[] packet =newByte[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>

 
 
 
 
阅读(38849)| 评论(8)

C# 实现WEBSOCKET聊天应用示例的更多相关文章

  1. swoole webSocket 聊天室示例

    swoole1.7.9增加了内置的WebSocket服务器支持,通过几行PHP代码就可以写出一个异步非阻塞多进程的WebSocket服务器. 基于swoole websocket的用户上下线通知,在线 ...

  2. 使用.NET Core和Vue搭建WebSocket聊天室

    博客地址是:https://qinyuanpei.github.io.  WebSocket是HTML5标准中的一部分,从Socket这个字眼我们就可以知道,这是一种网络通信协议.WebSocket是 ...

  3. Netty 实现 WebSocket 聊天功能

    上一次我们用Netty快速实现了一个 Java 聊天程序(见http://www.waylau.com/netty-chat/).现在,我们要做下修改,加入 WebSocket 的支持,使它可以在浏览 ...

  4. Win7搭建nginx+php+mysql开发环境以及websocket聊天实例测试

    Win7搭建nginx+php+mysql开发环境以及websocket聊天实例测试一.下载相关安装包 1.下载nginx最新版本(nginx1.3.13版之后才支持websocket协议) 下载地址 ...

  5. WebSocket聊天室demo

    根据Socket异步聊天室修改成WebSocket聊天室 WebSocket特别的地方是 握手和消息内容的编码.解码(添加了ServerHelper协助处理) ServerHelper: using ...

  6. h2engine游戏服务器设计之聊天室示例

    游戏服务器设计之聊天室示例 简介 h2engine引擎建群以后,有热心网友向我反馈,想尝试h2engine但是没有服务器开发经验觉得无从入手,希望我能提供一个简单明了的示例.由于前一段时间工作实在忙碌 ...

  7. Netty入门(一)之webSocket聊天室

    一:简介 Netty 是一个提供 asynchronous event-driven (异步事件驱动)的网络应用框架,是一个用以快速开发高性能.高可靠性协议的服务器和客户端. 换句话说,Netty 是 ...

  8. 用Java构建一个简单的WebSocket聊天项目之新增HTTP接口调度

    采用框架 我们整个Demo基本不需要大家花费太多时间,就可以实现以下的功能. 用户token登录校验 自我聊天 点对点聊天 群聊 获取在线用户数与用户标签列表 发送系统通知 首先,我们需要介绍一下我们 ...

  9. 用Java构建一个简单的WebSocket聊天室

    前言 首先对于一个简单的聊天室,大家应该都有一定的概念了,这里我们省略用户模块的讲解,而是单纯的先说说聊天室的几个功能:自我对话.好友交流.群聊.离线消息等. 今天我们要做的demo就能帮我们做到这一 ...

随机推荐

  1. CAD参数绘制图案填充(网页版)

    绘制工程图,常常需要将某种图案填充到某一区域,例如剖面线的绘制.MxCAD提供了丰富的填充图案,可以利用这些图案进行快速填充. js中实现代码说明: function DrawPathToHatch2 ...

  2. 指定PHP编码

    有时候我们写好的PHP页面在网页中打开是乱码的,就需要指定编码,即加入代码: header("content-type:text/html;charset=utf-8"); 位置图 ...

  3. DIV可编辑后,与限制输入及光标偏移的纠葛

    前言 最近在弄个人的网站,偶然间发现DIV可以设置编辑模式,之前设计的方案在此功能上需要限制输入的长度.网上搜索了一波,综合搜索的结果,考虑使用的监听事件有:keydown .textInput .i ...

  4. Jdk动态代理和CGLIB动态代理大比拼

    前言: 这2种动态代理算是老生常谈的吧,面试还是会经常问到的,下面做下分析: jdk动态代理: import java.lang.reflect.InvocationHandler; import j ...

  5. Java设计模式之策略模式(Strategy Pattern)

    简介 策略模式定义了算法族,分别封装起来,让它们之间可以互相替换,此模式让算法的变化独立于使用算法的客户. 组成 1.抽象策略角色(Strategy): 策略类,通常由一个接口或者抽象类实现. 2.具 ...

  6. (转载)C++ string中find() ,rfind() 等函数 用法总结及示例

    string中 find()的应用  (rfind() 类似,只是从反向查找) 原型如下: (1)size_t find (const string& str, size_t pos = 0) ...

  7. 图论算法——最短路径Dijkstra,Floyd,Bellman Ford

    算法名称 适用范围 算法过程 Dijkstra 无负权 从s开始,选择尚未完成的点中,distance最小的点,对其所有边进行松弛:直到所有结点都已完成 Bellman-Ford 可用有负权 依次对所 ...

  8. Leetcode 166.分数到小数

    分数到小数 给定两个整数,分别表示分数的分子 numerator 和分母 denominator,以字符串形式返回小数. 如果小数部分为循环小数,则将循环的部分括在括号内. 示例 1: 输入: num ...

  9. Online IDE & Public URLs & turbo

    Online IDE powered by Visual Studio Code https://stackblitz.com/ https://www.polymer-project.org/3.0 ...

  10. python用模块zlib压缩与解压字符串和文件的方法

    摘自:http://www.jb51.net/article/100218.htm Python标准模块中,有多个模块用于数据的压缩与解压缩,如zipfile,gzip, bz2等等. python中 ...