WebSocket学习总结
一 、websocket 已解决
但是websocket延伸出来的网络编程还有好多知识点没有清理。主要的流程和实现方式已经大概了解清楚,下面从学习的进度思路来一点点复习。
网络请求第一步,先给目标服务器和端口号进行三次握手,握手成功之后才会发送http请求。
其中有一个概念叫套接字,那么什么是套接字呢?
套接字
套接字,是支持TCP/IP的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
非常非常简单的举例说明下:Socket=Ip address+ TCP/UDP + port。
区分不同应用程序进程间的网络通信和连接,主要有3个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。Socket原意是 “插座”。通过将这3个参数结合起来,与一个“插座”Socket绑定,应用层就可以和传输层通过套接字接口,区分来自不 同应用程序进程或网络连接的通信,实现数据传输的并发服务。
Socket可以看成在两个程序进行通讯连接中的一个端点,是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定与网络驱动建立关系。此后,应用程序送给Socket的数据,由Socket交给网络驱动程序向网络上发送出去。计算机从网络上收到与该Socket绑定IP地址和端口号相关的数据后,由网络驱动程序交给Socket,应用程序便可从该Socket中提取接收到的数据,网络应用程序就是这样通过Socket进行数据的发送与接收的。
对于http程序员来说,套接字api隐藏了tcp和ip的所有细节。套接字API最开始是为Unix系统开发的,但是现在几乎所有的操作系统和语言中都有其变体的存在
WebSocket
websocket协议用于完全双工的双向通讯(双工的意思是服务端给客户端发送消息的同时,客户端也能给服务端发送消息 ps:对讲机不是完全双工的),但仅限于支持websocket的客户端。
个人理解:websocket只是tcp协议上层的一个应用层协议,和http协议是同一级别。这种完全双工的双向通讯早就可以实现了,只是浏览器不可以。而服务器端不同的语言有不同的API实现。
h5的websocket还是要先通过http协议通知服务器进行协议的升级。
这是一个典型的websocket握手的报文,熟悉http报文的能看出来多出来两个东西,
这是告诉服务器把协议升级为ws
第一个是私钥,第二个是用户定义的字符串,用来区分同url下,不同的服务所需要的协议,第三个是告诉服务器用Websocket Draft(协议版本)目前版本是13
秘钥的加密原理是:把客户端上报的key拼上一段GUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11″,拿这个字符串做SHA-1 hash计算,然后再把得到的结果通过base64加密,最后在返回给客户端。
二、编程实现
websocket的端点可以用任意类型的处理程序或模块来创建。我在《C#高级编程》里面用ashx实现了一遍,是一个基于浏览器的简单的聊天室。
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8" /> <script src="Scripts/jquery-1.8.2.min.js"></script> <title></title> <script> $(document).ready(function () { var name = prompt('what is you name?:'); if ("WebSocket" in window) { ws = new WebSocket("ws://localhost/ws.ashx?name=" + name); } //firefox的websocket对象是MozWebSocket,和别的浏览器不一样,不知道现在改了没有 else if ("MozWebSocket" in window) { ws = new MozWebSocket("ws://localhost/ws.ashx?name=" + name); } //请求升级之后,调用open ws.OPEN = function () { $('#messages').prepend('Conneccted</br>'); $('#cmdSend').click(function () { ws.send($('#extMessage').val()); $('#textMessage').val(''); }); }; //收到服务器数据,调用onmessage ws.onmessage = function (e) { $('#chatMessages').prepend(e.data + '<br/>'); }; $('#cmdLeave').click(function () { ws.close(); }); //连接关闭 ws.onclose = function () { $('#chatMessages').prepend('Close<br/>'); }; //出现错误 ws.onerror = function (e) { $('#chatMessage').prepend('Oops something went wrong<br/>'); }; }); </script> </head> <body> <input id="txtMessage" /> <input id="cmdSend" type="button" value="Send" /> <input id="cmdLeave" type="button" value="Leave" /> <br /> <div id="chatMessage"></div> </body> </html>
服务器上的实现要稍微复杂一些
ProcessRequest的主体
public void ProcessRequest(HttpContext context) { if (context.IsWebSocketRequest) { var chatuser = new ChatUser(); chatuser.UserNmae = context.Request["name"]; ChatApp.Add(chatuser); context.AcceptWebSocketRequest(chatuser.HandleWebSocket); } }
chatuser对象,保存用户信息并且实现给用户发消息的方法。
public class ChatUser { public string UserNmae { get; set; } public WebSocketContext _context { get; set; } //调用此函数时,把WebSocketContext传递给ChatUser的_context属性,发广播的时候很方便。 public async Task HandleWebSocket(WebSocketContext wsContext) { _context = wsContext; const int maxMessageSize = 1024; byte[] receiveBuffer = new byte[maxMessageSize]; WebSocket socket = _context.WebSocket; while (socket.State == WebSocketState.Open) { WebSocketReceiveResult receiveResult = await socket.ReceiveAsync(new ArraySegment<byte>(receiveBuffer), CancellationToken.None); if (receiveResult.MessageType == WebSocketMessageType.Close) { await socket.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None); } else if (receiveResult.MessageType == WebSocketMessageType.Binary) { await socket.CloseAsync(WebSocketCloseStatus.InvalidMessageType, "can not accept binary frame", CancellationToken.None); } else { var receivedString = Encoding.UTF8.GetString(receiveBuffer, 0, receiveBuffer.Count()); var edhostring = string.Concat(UserNmae, "said", receivedString); ArraySegment<byte> outputBBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(edhostring)); ChatApp.BroadcastMessage(edhostring); } } } public async Task SendMessage(string msg) { if (_context != null && _context.WebSocket.State == WebSocketState.Open) { var outputBuffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(msg)); await _context.WebSocket.SendAsync(outputBuffer, WebSocketMessageType.Text, true, CancellationToken.None); } } }
用户表对象,主要用来广播消息
public static class ChatApp { public static IList<ChatUser> list = new List<ChatUser>(); public static void Add(ChatUser user) { list.Add(user); } //遍历用户列表发消息 public static void BroadcastMessage(string str) { foreach (var user in list) { user.SendMessage(str); } } }
三、关于代码中动态类型,静态类,静态字段,静态构造函数,下次再写。