.NETCore基于Websocket的室实现
什么是Websocket
我们在传统的客户端程序要实现实时双⼯通讯第⼀想到的技术就是socket通讯,但是在web体系是⽤不了socket通讯技术的,因为http被设计成⽆状态,每次跟服务器通讯完成后就会断开连接。
在没有websocket之前web系统如果要做双⼯通讯往往使⽤http long polling技术。http long polling 每次往服务器发送请求后,服务端不会⽴刻返回信息来结束请求,⽽是⼀直挂着直到有数据需要返回,或者等待超时了才会返回。客户端在结束上⼀次请求后⽴刻再发送⼀次请求,如此反复。http long polling虽然能实现web系统的双⼯通讯,但是有个很⼤的问题,就是基于http 协议客户端每次发送请求都需要携带巨⼤的头部。在并发交互少量数据的时候⾮常不划算,对服务器资源的消耗也是巨⼤的。
websocket很好的改善了以上问题。它基于tcp重新设计了⼀套协议,同时⼜兼容http,默认跟http⼀样使⽤80/443端⼝。
websocket链接建⽴本质上就是⼀次http请求,直接使⽤http协议的upgrade头来标识这是⼀次websocket请求,服务端回复101状态码表⽰“握⼿”成功。
//客户端请求
GET / HTTP/1.1
Upgrade: websocket
Connection: Upgrade
Host: example
Origin: example
Sec-WebSocket-Key: sN9cRrP/n9NdMgdcy2VJFQ==
Sec-WebSocket-Version: 13
//服务端响应
HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: fFBooB7FAkLlXgRSz0BT3v4hq5s=
Sec-WebSocket-Location: ws://example/
socket编程聊天室基本流程使⽤asp core来处理websocket
上⾯我们简单的了解了websocket,那么如何来使⽤asp core处理websocket呢?因为websocket的握⼿就是⼀次http请求,那么我们就可以使⽤⼀个middleware来拦截websocket的请求,把建⽴的链接统⼀进⾏管理,其实微软已经帮我们简单的封装过了。
新建⼀个asp core⽹站
新建WebsocketHandlerMiddleware中间件
这个中间件就是我们管理websocket链接的⼊⼝,我们调⽤context.WebSockets.AcceptWebSocketAsync()⽅法把请求转换为websocket链接。
在Invoke⽅法内接收websocket链接
public async Task Invoke(HttpContext context)
{
if (context.Request.Path == "/ws")
{
if (context.WebSockets.IsWebSocketRequest)
{
WebSocket webSocket = await context.WebSockets.AcceptWebSocketAsync();
string clientId = Guid.NewGuid().ToString(); ;
var wsClient = new WebsocketClient
{
Id = clientId,
WebSocket = webSocket
};
}
catch (Exception ex)
{
_logger.LogError(ex, "Echo websocket client {0} err .", clientId);
await context.Response.WriteAsync("closed");
}
}
else
{
context.Response.StatusCode = 404;
}
}
else
{
await _next(context);
}
}
在Hanle⽅法等待客户端的消息
private async Task Handle(WebsocketClient webSocket)
{
WebsocketClientCollection.Add(webSocket);
_logger.LogInformation($"Websocket client added.");
WebSocketReceiveResult result = null;
do
{
var buffer = new byte[1024 * 1];
result = await webSocket.WebSocket.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
if (result.MessageType == WebSocketMessageType.Text && !result.CloseStatus.HasValue)
{
var msgString = Encoding.UTF8.GetString(buffer);
_logger.LogInformation($"Websocket client ReceiveAsync message {msgString}.");
var message = JsonConvert.DeserializeObject<Message>(msgString);
message.SendClientId = webSocket.Id;
MessageRoute(message);
}
}
while (!result.CloseStatus.HasValue);
WebsocketClientCollection.Remove(webSocket);
_logger.LogInformation($"Websocket client closed.");
}
在MessageRoute⽅法内对客户端的消息进⾏转发
对客户端的消息定义⼏个标准的action,对不同的action进⾏特定的处理,⽐如加⼊房间、离开房间、在房间内⼴播消息等。private void MessageRoute(Message message)
{
var client = WebsocketClientCollection.Get(message.SendClientId);
switch (message.action)
{
case "join":
client.RoomNo = message.msg;
client.SendMessageAsync($"{message.nick} join room {client.RoomNo} success .");
_logger.LogInformation($"Websocket client {message.SendClientId} join room {client.RoomNo}.");
break;
case "send_to_room":
if (string.IsNullOrEmpty(client.RoomNo))
{
break;
}
var clients = WebsocketClientCollection.GetRoomClients(client.RoomNo);
clients.ForEach(c =>
{
c.SendMessageAsync(message.nick + " : " + message.msg);
});
_logger.LogInformation($"Websocket client {message.SendClientId} send message {message.msg} to room {client.RoomNo}");
break;
case "leave":
var roomNo = client.RoomNo;
client.RoomNo = "";
client.SendMessageAsync($"{message.nick} leave room {roomNo} success .");
_logger.LogInformation($"Websocket client {message.SendClientId} leave room {roomNo}");
break;
default:
break;
}
}
新建WebsocketClientCollection管理类
这个类是个容器,⽤来存放所有的websocket链接,便于统⼀管理。
public class WebsocketClientCollection
{
private static List<WebsocketClient> _clients = new List<WebsocketClient>();
public static void Add(WebsocketClient client)
{
_clients.Add(client);
}
public static void Remove(WebsocketClient client)
{
_clients.Remove(client);
}
public static WebsocketClient Get(string clientId)
{
var client = _clients.FirstOrDefault(c=>c.Id == clientId);
return client;
}
public static List<WebsocketClient> GetRoomClients(string roomNo)
{
var client = _clients.Where(c => c.RoomNo == roomNo);
return client.ToList();
有了上⾯的中间件,我们需要use⼀下。
app.UseWebSockets(new WebSocketOptions
{
KeepAliveInterval = TimeSpan.FromSeconds(60),
ReceiveBufferSize = 1* 1024
});
app.UseMiddleware<WebsocketHandlerMiddleware>();
到此我们的服务端基本完成了,下⾯进⾏客户端html跟JavaScript的编写。
编写客户端界⾯
修改index.cshtml来实现⼀个简单的聊天室ui。
<div >
room no: <input type="text" id="txtRoomNo" value="8888"/> <button id="btnJoin">join room</button> <button id="btnLeave">leave room</button>
</div>
<div >
nick name: <input type="text" id="txtNickName" value="batman" />
</div>
<div >
<textarea id="msgList"></textarea>
<div >
<input type="text" id="txtMsg" value="" /> <button id="btnSend">send</button>
</div>
</div>
使⽤JavaScript来处理websocket链接及消息
现代浏览器已经都⽀持websocket协议,JavaScript运⾏时内置了WebSocket类,我们仅仅需要new⼀个Websocket对象出来就可以对websocket进⾏操作。var server = 'ws://localhost:5000'; //如果开启了https则这⾥是wss
var WEB_SOCKET = new WebSocket(server + '/ws');
pen = function (evt) {
console.log('Connection open ...');
$('#msgList').val('websocket connection opened .');
};
ssage = function (evt) {
console.log('Received Message: ' + evt.data);
if (evt.data) {
var content = $('#msgList').val();
content = content + '\r\n' + evt.data;
$('#msgList').val(content);
}
};
lose = function (evt) {
console.log('Connection closed.');
};
$('#btnJoin').on('click', function () {
var roomNo = $('#txtRoomNo').val();
var nick = $('#txtNickName').val();
if (roomNo) {
var msg = {
action: 'join',
msg: roomNo,
nick: nick
};
WEB_SOCKET.send(JSON.stringify(msg));
}
});
$('#btnSend').on('click', function () {
var message = $('#txtMsg').val();
var nick = $('#txtNickName').val();
if (message) {
WEB_SOCKET.send(JSON.stringify({
action: 'send_to_room',
msg: message,
nick: nick
}));
}
});
$('#btnLeave').on('click', function () {
var nick = $('#txtNickName').val();
var msg = {
action: 'leave',
msg: '',
nick: nick
};
WEB_SOCKET.send(JSON.stringify(msg));
});
运⾏
⾄此我们的聊天室已经搭建完成了,运⾏⼀下看看效果。我们启动两个页⾯,进⾏聊天。
可以看到我们的消息被实时的转发出去了,good job !
源码
源码已上传github
以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论