websocket是HTML5中的比较有特色一块,它使得以往在客户端软件中常用的socket在web程序中也能轻松的使用,较大的提高了效率。废话不多说,直接进入题。
网页聊天室包括2个部分,后端服务器+前端页面。
1、后端服务部分:.net4.0 + windows服务。相比寄宿在iis中,寄宿在进程中的windows服务更加的稳定可靠(文章中的例子用windows控制台程序演示,后面给出完整的windows服务的代码)。
2、前端部分:html5 + jQuery + bootstrap。基本的前端快速开发利器。
一、分析一下聊天室的场景需求,以便构建合适的数据结构。
1、在线用户类 OnlineUser 用户的基本特征为姓名Name(性别年龄啥的先忽略),当然在系统设计中,姓名并不能很好的区分不同用户,所以得需要一个唯一标识符Id。另外,由于可能存在多个聊天室,因此聊天室的编号RoomId也是用户的特征之一。综上,可得在线用户类OnlineUser的结构为:
///
/// 在线用户信息 /// public class OnlineUser { public int Id { get; set; } public string Name { get; set; } public string RoomId { get; set; } public string SessionId { get; set; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
PS:SessionId是后续加入的属性,此处可先忽略。
2、消息类 Message 消息是聊天室最核心的部分,我最简单的消息机构应包含如下成员,消息发送者FromUserId,消息接受者ToUserId,消息类型Type,消息内容Content,消息时间Time。
public class Message
{
public int FromUserId { get; set; } public int ToUserId { get; set; } public int Type { get; set; } public string Time { get; set; } public string Content { get; set; } }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
PS:这里Type用的是int类型表示的,是出于后续消息传递时,以便于将其包裹成Json格式传递。但在判断时,会将其转换成MessageType格式的枚举型,MessageType暂定包含以下几种状态,
public enum MessageType
{
/// /// 新用户进入 /// NewUserIn = 1, /// /// 用户离开 /// UserExit = 2, /// /// 新用户提供自身信息 /// ReprotUserInfo = 3, /// /// 新文字消息 /// NewTextMessage = 4, /// /// 广播基本信息 /// BroadcastBasicInfo = 5 }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
二、项目结构
这是一个windows控制台程序,Program.cs是入口,ChatWebSocket.cs是核心代码快,外部引用的dll包括Json操作类库和Socket操作类库。
其中,Model文件夹下的是上面提到的一些基本数据结构,这里看一下核心的代码ChatWebSocket。这当中用到的SuperSocket已经将socket的主要操作封装的很完备了,使用方法如下:
/* 侦听地址(注意,此处的地址一定要和前端js中的地址一致!!) */
const string IP = "127.0.0.1";
/* 侦听端口 */ const int PORT = 2016; /* SuperWebSocket中的WebSocketServer对象 */ WebSocketServer wsServer = null; /* 当前在线用户列表 */ List olUserList = new List(); /* 定时通知客户端线程 */ BackgroundWorker bkWork = null;
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
这里,WebSocketServer是SuperSocket中封装好的Socket服务端类,olUserList 是在线用户列表,bkWork 是后台线程,负责定时向客户端发送一些系统信息。
构造函数中:
public ChatWebSocket()
{
/* 初始化 以及 相关事件注册 */
wsServer = new WebSocketServer();
/* 有新会话握手并连接成功 */ wsServer.NewSessionConnected += WsServer_NewSessionConnected; /* 有会话被关闭 可能是服务端关闭 也可能是客户端关闭 */ wsServer.SessionClosed += WsServer_SessionClosed; /* 有新文本消息被接收 */ wsServer.NewMessageReceived += WsServer_NewMessageReceived; }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
WebSocketServer有几个比较重要的事件,
1、NewSessionConnected 有新会话握手并连接成功
2、SessionClosed 有会话被关闭 可能是服务端关闭 也可能是客户端关闭
3、NewMessageReceived 有新文本消息被接收
其中NewMessageReceived事件是消息传递的重要事件,也是我们重点处理的事件,下面将3个事件的方法体列出:
// 有新会话握手并连接成功
private void WsServer_NewSessionConnected(WebSocketSession session)
{
LogHelper.Write(session.SessionID.ToString() + " Connect!"); } // 有新文本消息被接收 private void WsServer_NewMessageReceived(WebSocketSession session, string value) { LogHelper.Write("Receive Message:" + value); var msg = JsonConvert.DeserializeObject(value); MessageType mt = (MessageType)msg.Type; switch (mt) { /* 用户报告自己信息,将UserId与SessionId关联 */ case MessageType.ReprotUserInfo: olUserList.Add(new OnlineUser { SessionId = session.SessionID, Id = msg.FromUserId, RealName = msg.FromUserName, RoomId = msg.RoomId }); /* 通知其他用户 */ SendMessage(session, new Message { FromUserId = msg.FromUserId, FromUserName = msg.FromUserName, ToUserId = 0,// 同一房间的人 Type = (int)MessageType.NewUserIn, Content = "", Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"), RoomId = msg.RoomId }); break; /* 用户文字(图片)消息,服务器进行转发 */ case MessageType.NewTextMessage: /* 通知其他用户 */ msg.Time = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss"); SendMessage(session, msg); break; default: break; } } // 有会话被关闭 可能是服务端关闭 也可能是客户端关闭 private void WsServer_SessionClosed(WebSocketSession session, CloseReason value) { LogHelper.Write(session.SessionID.ToString() + " Exit!"); var u = olUserList.Find(m => m.SessionId == session.SessionID); if (u == null) { return; } olUserList.Remove(u); // 通知其他用户 SendMessage(session, new Message { FromUserId = u.Id, FromUserName = u.RealName, ToUserId = 0,// 同一房间的人 Type = (int)MessageType.UserExit, Content = "", RoomId = u.RoomId }); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 14
- 15
- 16
- 17
- 18
- 19
- 20
- 21
- 22
- 23
- 24
- 25
- 26
- 27
- 28
- 29
- 30
- 31
- 32
- 33
- 34
- 35
- 36
- 37
- 38
- 39
- 40
- 41
- 42
- 43
- 44
- 45
- 46
- 47
- 48
- 49
- 50
- 51
- 52
- 53
- 54
- 55
- 56
- 57
- 58
- 59
- 60
- 61
- 62
- 63
- 64
- 65
- 66
- 67
- 68
- 69
- 70
- 71
其中,SendMessage(WebSocketSession session, Message msg)
方法如下:
private void SendMessage(WebSocketSession session, Message msg)
{
// -1:全体;0:同一房间;剩下:特定的用户
var users = msg.ToUserId == -1 ? olUserList : msg.ToUserId == 0 ? olUserList.Where(m => m.RoomId == msg.RoomId).ToList() : olUserList.Where(m => m.Id == msg.ToUserId).ToList(); users.ForEach(u => { var ss = session.AppServer.GetAppSessionByID(u.SessionId); if (ss != null) { ss.Send(JsonConvert.SerializeObject(msg)); } }); }
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
- 1
- 2
- 3
- 4
- 5
- 6
- 7
- 8
- 9
- 10
- 11
- 12
- 13
另外,WebSocketServer的启动和停止也非常的简单:
public void Start()
{
if (!wsServer.Setup(IP, PORT))
{
throw new Exception("设置WebSocket服务侦听地址失败!"); } if (!wsServer.Start()) { throw new Exception("启动WebSocket服务侦听失败!"); } } public void Stop() { if (wsServer != null) { wsServer.Stop(); } }