前言
相信大家在做web项目的时候可能都会遇到某些信息弹窗,提醒之类的功能,最简单粗暴的方式就是轮询,比如每秒浏览器从服务器发起http请求,然后服务器返回最新的结果过来。所以这种一直循环方式有很大的缺陷,浪费带宽资源、被动、单向。因为HTML5定义了WebSocket协议,WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议,简单来说能够达到客户端与服务端的双向,更多的介绍大家可以去网上搜搜,就不多说了,下面开始做一个.net的WebSocket的服务端。
开始写服务端
为了简单的实现我们的效果,这里我是建一个WinForm项目作为我们的服务端
(一)新建一个.net framework4.5的WinForm项目
(二)选择一个适合自己的第三方websocket库
打开万能的开源项目网站https://github.com选择下载一款自己喜欢的websocket开源库,这里我用的是WebSocket-Sharp(里面也有相关的使用说明文档),看个人喜好,至于每种库的好坏对比,就不评论了。
将https://github.com/sta/websocket-sharp下载下来,打开项目,生成编译,然后将生成的dll引用到我们项目里面来。
这里要特别说明下websocket-sharp的核心WebSocketBehavior,他包含了OnOpen,OnMessage,OnClose,OnError四个方法以及一个Sessions对象。熟悉websocket的都知道前四个方法是用来处理客户端链接、发送消息、链接关闭以及出错。sessions就是用来管理所有的回话连接。每产生一个连接,都会有一个新Id,sessions中会新增一个IWebSocketSession对象。当页面关闭或者刷新都会触发OnClose,继而sessions中会移除对应的IwebSocketSession对象。
(三)自定义新增一个继承WebSocketBehavior的处理类
新增一个TestHandler类,继承WebSocketBehavior,TestHandler相当于是一个websocket的服务,你可以创建多个websocketBehavior的实例然后在挂载在websocketServer上。
public class TestHandler : WebSocketBehavior
{
public Label lbUserCount;
public TextBox showText;
private Dictionary<string, string> nameList = new Dictionary<string, string>();
protected override void OnOpen()
{
base.OnOpen();
AppendValue("建立连接" + this.ID + "\r\n");
nameList.Add(this.ID, "游客" + Sessions.Count);
Broadcast(string.Format("{0}上线了,共有{1}人在线", nameList[this.ID], Sessions.Count), "3");
ShowCount();
}
protected override void OnMessage(MessageEventArgs e)
{
base.OnMessage(e);
string strMsg = e.Data;
try
{
var obj = JsonConvert.DeserializeObject<ReceiveMsg>(strMsg);
AppendValue("收到消息:" + obj.Message + " 类型:" + obj.MsgType + "id:" + this.ID + "\r\n");
switch (obj.MsgType)
{
//正常聊天
case "1":
obj.UserName = nameList[this.ID];
Sessions.Broadcast(JsonConvert.SerializeObject(obj));
break;
//修改名称
case "2":
AppendValue(string.Format("{0}修改名称{1} \r\n", nameList[this.ID], obj.Message));
Broadcast(string.Format("{0}修改名称{1}", nameList[this.ID], obj.Message), "3");
nameList[this.ID] = obj.Message;
break;
default:
Sessions.Broadcast(strMsg);
break;
}
}
catch (Exception exception)
{
Console.WriteLine(exception);
}
}
protected override void OnClose(CloseEventArgs e)
{
base.OnClose(e);
AppendValue("连接关闭" + this.ID + "\r\n");
Broadcast(string.Format("{0}下线,共有{1}人在线", nameList[this.ID], Sessions.Count), "3");
nameList.Remove(this.ID);
ShowCount();
}
/// <summary>
/// 广播(群发)
/// </summary>
/// <param name="msg"></param>
/// <param name="type"></param>
private void Broadcast(string msg, string type = "1")
{
var data = new ReceiveMsg() { Message = msg, MsgType = type, UserName = nameList[this.ID] };
Sessions.Broadcast(JsonConvert.SerializeObject(data));
}
/// <summary>
/// 在线人数
/// </summary>
private void ShowCount()
{
//this.lbUserCount.Text = Sessions.Count.ToString();
// 无返回值无参数用MethodInvoker委托,无返回值可有参数用Action委托,有返回值可有参数用Func委托
lbUserCount.BeginInvoke(new Action<string>(msg => lbUserCount.Text = msg),
new object[] { Sessions.Count.ToString() });
}
public void AppendValue(string strValue)
{
// 无返回值无参数用MethodInvoker委托,无返回值可有参数用Action委托,有返回值可有参数用Func委托
showText.BeginInvoke(new Action<string>(msg => showText.Text += msg+DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")),
new object[] { strValue });
}
}
From窗体代码
public partial class Form1 : Form
{
private WebSocketServer wssv;
private bool isStart = false;
public Form1()
{
InitializeComponent();
this.ShowListenStatus();
}
/// <summary>
/// 启动
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
try
{
wssv = new WebSocketServer(Convert.ToInt32(textBox1.Text));
wssv.AddWebSocketService<TestHandler>("/Test/", p =>
{
p.lbUserCount = this.lbUserCount;
p.showText = this.showText;
});
wssv.Start();
isStart = true;
this.showText.Text += "连接成功!" + DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") + "\r\n";
this.ShowListenStatus();
}
catch (Exception ee)
{
MessageBox.Show(ee.Message);
}
}
/// <summary>
/// 停止
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
wssv.Stop();
isStart = false;
this.lbUserCount.Text = "0";//重置为0
this.showText.Text = "";//清空
this.showText.Text += "连接断开!"+DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") +"\r\n";
ShowListenStatus();
}
private void ShowListenStatus()
{
this.button2.Enabled = isStart ? true : false;
this.button1.Enabled = isStart ? false : true;
}
}
运行测试效果
(四)写一个html5的聊天页面
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script type="text/javascript" src="scripts/jquery.js"></script>
<title>测试在线聊天</title>
</head>
<body>
<div id="messages">
</div>
<div>昵称:<input type="text" id="nickName" /> <button id="changebt">修改</button>
</div>
<div>消息内容:<input type="text" id="content" value=""/>
<button id="sendbt">发送</button>
</div>
</body>
<script type="text/javascript">
function initWS() {
ws = new WebSocket("ws://localhost:9600/Test");
ws.onopen = function (e) {
console.log("Openened connection to websocket");
console.log(e);
};
ws.onmessage = function (e) {
console.log("收到",e.data)
var div=$("<div>");
var data=JSON.parse(e.data);
switch(data.MsgType){
case "1":
div.html(data.UserName+":"+data.Message);
break;
case "2":
div.addClass("gray");
div.html("修改名称"+data.Message)
break;
case "3":
div.addClass("gray");
div.html(data.Message)
break;
}
$("#messages").append(div);
};
ws.onerror = function (msg) {
console.log("socket error!");
};
}
initWS();
function sendMsg(msg,type){
ws.send(JSON.stringify({Message:msg,MsgType:type}));
}
$("#sendbt").click(function(){
var text=$("#content").val();
sendMsg(text,"1")
$("#content").val("");
})
$("#changebt").click(function(){
var text=$("#nickName").val();
sendMsg(text,"2")
})
</script>
</html>
测试截图
下面提供本项目相关的下载地址
websocket-sharp.dll:https://download.csdn.net/download/shaojiayong/12903143
项目完全源码(包含页面):https://download.csdn.net/download/shaojiayong/12903215