C# - Websocket - 将消息发送回客户端
Posted
技术标签:
【中文标题】C# - Websocket - 将消息发送回客户端【英文标题】:C# - Websocket - Sending Message Back To Client 【发布时间】:2015-01-17 06:08:19 【问题描述】:我已经在 C# Web Socket 服务器上工作了大约 24 小时。
我目前已经弄清楚如何完成握手并初始化连接。
我还想出了如何获取byte[]
数据并将其解码为原始字符串。
但现在我陷入困境并寻求帮助。
我似乎无法弄清楚如何组合正确的数据结构并将其发送回客户端。 如果您发送原始数据,您在客户端收到的 WebSocket 会告诉您数据不能被屏蔽(这就是它需要解码的原因)。
所以基本上,我要问的是如何构造响应数据以发送回 WebSocket 客户端?
我一直使用https://www.rfc-editor.org/rfc/rfc6455 作为我的研究资源。
请记住,我只是为此使用常规套接字。
这是我的解码代码:
if (dataBuffer.Length > 0)
if (dataBuffer[0] == 129)
int msg_length = dataBuffer[1] - 128;
if (msg_length <= 125)
// Msg ready to decode.
Log.info("Message Length: " + msg_length);
Byte[] decoded = new Byte[dataBuffer.Length];
Byte[] encoded = new Byte[dataBuffer.Length - 6];
Array.Copy(dataBuffer, 6, encoded, 0, msg_length);
Byte[] key = new Byte[4] dataBuffer[2], dataBuffer[3], dataBuffer[4], dataBuffer[5] ;
for (int i = 0; i < encoded.Length; i++)
decoded[i] = (Byte)(encoded[i] ^ key[i % 4]);
Log.info("MSG: " + Encoding.UTF8.GetString(decoded));
byte[] return_msg = new byte[decoded.Length + 8];
return_msg[0] = 1;
return_msg[1] = 0;
return_msg[2] = 0;
return_msg[3] = 0;
// OP Code
return_msg[4] = 0x1;
return_msg[5] = 0x0;
return_msg[6] = 0x0;
return_msg[7] = 0x0;
Array.Copy(decoded, 0, return_msg, 8, decoded.Length);
socket.Send(return_msg);
else if (msg_length == 126)
// Longer Message
msg_length = dataBuffer[2] + dataBuffer[3];
Log.info("Message Length: " + msg_length);
Byte[] key = new Byte[4] dataBuffer[4], dataBuffer[5], dataBuffer[6], dataBuffer[7] ;
Byte[] decoded = new Byte[dataBuffer.Length];
Byte[] encoded = new Byte[dataBuffer.Length - 8];
Array.Copy(dataBuffer, 8, encoded, 0, msg_length);
for (int i = 0; i < encoded.Length; i++)
decoded[i] = (Byte)(encoded[i] ^ key[i % 4]);
Log.info("MSG: " + Encoding.UTF8.GetString(decoded));
byte[] return_msg = new byte[decoded.Length + 4];
return_msg[0] = 129;
return_msg[1] = 0;
return_msg[2] = 0;
return_msg[3] = 0;
Array.Copy(decoded,0,return_msg,4,decoded.Length);
socket.Send(return_msg);
else if (msg_length == 127)
// Huge Message:
Log.info("BIG MESSAGE");
【问题讨论】:
您学习 Socket 是为了好玩还是为了完成工作?如果是后者,你检查过 TcpClient 吗?它为你做了很多工作。 我认为手动操作是一种更好的学习体验。 【参考方案1】:@vtortola 感谢您发布链接和解释 - 我花了很多时间研究它和一个开源代码库(基本上是我自己编写的),我将它提炼成这个,以便从服务器发送消息到客户。
对我来说,关键是要意识到几件事:
首先,了解标题。 GetHeader() 负责它是否是最后一帧以及操作码是否设置为连续帧的文本。 @vtortola 发布的链接对此进行了解释,但在我看到这些位之前,我必须真正盯着它:
This 的帖子实际上解释得很好,但你必须花时间研究它——注意 FIN 和操作码位如何匹配 GetHeader() 的工作:
客户端:FIN=1, opcode=0x1, msg="hello" 服务器:(立即处理完成消息)嗨。 客户端:FIN=0,opcode=0x1,msg="and a" 125 服务器:(正在侦听,包含文本的新消息已启动) 客户端:FIN=0,opcode=0x0,msg="happy new" 服务器:(侦听,负载连接到上一条消息) 客户端:FIN=1,opcode=0x0,msg="year!" 服务器:(处理完成消息)也祝你新年快乐!接下来,了解您在调用 stream.Write() 时发送的内容 - bytes[], index, LENGTH OF Bytes You're SENDING ;)
注意:我的意图是向 Web 客户端发送 JSON 格式的字符串和从 Web 客户端发送 JSON 格式的字符串,因此我的操作码设置为文本(我的示例基于 假设您要发送字符串数据),但您可以 也发送其他类型。
SendMessageToClient()
基本上将消息分成 125 个块并创建一个要从中提取的队列。根据我们在队列中的位置,使用适当的 FIN 和操作码标志创建标头。最后,准备好标题后,用字符串块的实际长度(
此时,您的标头已正确创建(FIN、rsv1、2、3 设置正确,操作码和掩码以及有效负载的大小)。现在发送它。
public void SendMessageToClient(TcpClient client, string msg)
NetworkStream stream = client.GetStream();
Queue<string> que = new Queue<string>(msg.SplitInGroups(125));
int len = que.Count;
while (que.Count > 0)
var header = GetHeader(
que.Count > 1 ? false : true,
que.Count == len ? false : true
);
byte[] list = Encoding.UTF8.GetBytes(que.Dequeue());
header = (header << 7) + list.Length;
stream.Write(IntToByteArray((ushort)header), 0, 2);
stream.Write(list, 0, list.Length);
protected int GetHeader(bool finalFrame, bool contFrame)
int header = finalFrame ? 1 : 0;//fin: 0 = more frames, 1 = final frame
header = (header << 1) + 0;//rsv1
header = (header << 1) + 0;//rsv2
header = (header << 1) + 0;//rsv3
header = (header << 4) + (contFrame ? 0 : 1);//opcode : 0 = continuation frame, 1 = text
header = (header << 1) + 0;//mask: server -> client = no mask
return header;
protected byte[] IntToByteArray(ushort value)
var ary = BitConverter.GetBytes(value);
if (BitConverter.IsLittleEndian)
Array.Reverse(ary);
return ary;
/// ================= [ extension class ]==============>
public static class XLExtensions
public static IEnumerable<string> SplitInGroups(this string original, int size)
var p = 0;
var l = original.Length;
while (l - p > size)
yield return original.Substring(p, size);
p += size;
yield return original.Substring(p);
除了上面的帖子,我还研究了websocket-sharp's 代码库。我真的很想学习如何做到这一点并编写自己的服务器/客户端,并且能够学习该代码库以及创建基本 c# WebSocket 服务器的一个很好的起点here。
最后,我感谢上帝耐心地阅读所有这些;)
【讨论】:
我真的不认为我自己能解决这个问题。我抓住了你的代码并成功了。 该代码非常完美,做得很好,感谢您节省了大量时间!!【参考方案2】:看看这两篇关于编写 C# WebSocket 服务器的文章:
https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_servers
https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_server
似乎它们都是同一篇文章,但它们不是!在第一个链接的this part 中有关于如何构建框架的说明。
更新
第一个字节包含几个信息:
FIN :表示该帧包含完整的消息。 RSV1:选项 1 RSV2:选项 2 RSV3:选项 3 OptCode:表示帧的类型。如果您想发送小于 125 个字节的文本消息,假设您的消息有 90 个字节,在第一个字节中,您会将位 0 设置为 1(更重要),接下来的 3 设置为 0,除非您想要启用选项,接下来的 4 将是 0001,表示文本框。所以你的第一个再见将是 10000001 或 129。
现在在第二个字节中,第一位指示帧是否被屏蔽。您不屏蔽从服务器到客户端的帧,因此设置为 0。接下来的 7 位表示长度或帧长度的类型。因为您正在发送一个小帧,所以您可以在这 7 位中指示最多 125 的任何值。因此,由于帧长度为 90 字节,因此标头的第二个字节将为 01011010,即 90。
因此,当从服务器向客户端发送 90 个字节的文本帧时,符合标头的前两个字节将是 129 和 90。消息的其余部分将是 90 个字节的 UTF8 编码字节。
如果帧长于 125 字节,也是标头的长度,请检查规范。如果需要对帧进行屏蔽(例如从客户端获取的帧),则正文的前 4 个字节包含屏蔽键。如您所见,有一些花絮需要处理,所以我建议您阅读规范:https://www.rfc-editor.org/rfc/rfc6455
【讨论】:
好的,我已经看过几十次了,但我不太明白如何在byte[]
中构建框架。我知道第一个字节是 1,因为它是一个文本数据包,但在那之后我该怎么办?
不是真的,检查我的更新。它解释了如何构建小文本框架的标题。
很烦人,你不只是包含一个代码示例。当然,我现在有时间进行位移位横冲直撞,如果您问我,这应该包含在 C# 中。这是一个糟糕的答案。如果我决定编写此代码,我会将其发布为不同的答案,但我很可能会选择与 websockets 不同的解决方案。 “这就是 WebSockets 的魔力。然而,从这些所谓的数据“帧”中提取信息并不是那么神奇的体验。” => 确实以上是关于C# - Websocket - 将消息发送回客户端的主要内容,如果未能解决你的问题,请参考以下文章