HTTP SSE (Server-Sent Events)流式服务器推送技术

打印 上一主题 下一主题

主题 510|帖子 510|积分 1530

HTTP SSE (Server-Sent Events)流式服务器推送技术

SSE(Server-Sent Events)是一种基于HTTP协议的服务器推送技术,用于实现服务器向客户端实时发送变乱的功能。在SSE中,服务器可以发送不同类型的消息给客户端。
以下是SSE大概出现的消息类型:

变乱消息(Event Message):服务器发送的一般变乱消息,可以包含自定义的变乱类型和数据。
注释消息(Comment Message):服务器发送的注释消息,用于向客户端传递一些额外的信息,但不会触发任何变乱处理。
重连消息(Reconnect Message):服务器发送的重连消息,用于告知客户端重新连接服务器。
下面是一个完备的HTTP请求返回结果示例,展示了SSE的利用:

  1. HTTP/1.1 200 OK
  2. Content-Type: text/event-stream
  3. Cache-Control: no-cache
  4. Connection: keep-alive
  5. id: msgid
  6. event: myEvent
  7. data: {"message": "Hello, SSE!"}
  8. id: msgid
  9. event: myEvent
  10. data: {"message": "Another event"}
  11. : This is a comment
  12. id: msgid
  13. event: reconnect
  14. retry: 5000
复制代码
在这个示例中,服务器返回的HTTP相应头中指定了Content-Type为text/event-stream,表示这是一个SSE的相应。Cache-Control设置为no-cache,确保每次请求都能从服务器获取最新的数据。Connection设置为keep-alive,保持长连接。
接下来,服务器通过多个变乱消息和注释消息向客户端发送数据。每个消息以event字段指定变乱类型,以data字段包含消息的数据。在示例中,服务器发送了两个变乱消息,分别是myEvent类型的消息,包含不同的数据。还发送了一个注释消息,以冒号开头,用于传递额外的信息。
最后,服务器发送了一个重连消息,以event字段指定为reconnect类型,同时通过retry字段指定了重连的时间间隔为5000毫秒。
客户端可以通过监听SSE变乱,实时吸取服务器发送的消息,并进行相应的处理。
下面是一个利用C#解析SSE示例,展示了SSE的解析过程:

  1. namespace System.Net
  2. {
  3.     using System;
  4.     using System.Collections.Generic;
  5.     using System.IO;
  6.     using System.Text;
  7.     /// <summary>
  8.     /// 解析SSE 。
  9.     /// </summary>
  10.     public class EventStreamHandler
  11.     {
  12.         /// <summary>
  13.         /// 新的换行符。
  14.         /// </summary>
  15.         private readonly string newLine;
  16.         /// <summary>
  17.         /// 消息体与消息体之间的空白行的数量。
  18.         /// </summary>
  19.         private const int emptySplitLineCount = 1;
  20.         /// <summary>
  21.         /// 最后1个消息体与流结束符的空白行的数量。
  22.         /// </summary>
  23.         private readonly static int emptySplitLineCountEnd = 1;
  24.         private string idSource;
  25.         private string eventSource;
  26.         private string dataSource;
  27.         // 是不是可能有未知项(消息体与消息体之间的未知项)。
  28.         private bool possibleUnknownItem;
  29.         /// <summary>
  30.         /// 当前行。
  31.         /// </summary>
  32.         private string line;
  33.         /// <summary>
  34.         /// 是不是处理了当前行 <see cref="line"/>。
  35.         /// </summary>
  36.         private bool isHandledLine;
  37.         /// <summary>
  38.         /// 消息体和消息体中间的未知行。
  39.         /// </summary>
  40.         private readonly List<string> unknownLines = new List<string>(16);
  41.         /// <summary>
  42.         /// 消息体列表。
  43.         /// </summary>
  44.         private readonly List<EventStreamEventArgs> addedItems = new List<EventStreamEventArgs>(64);
  45.         public EventStreamHandler(string newLine)
  46.         {
  47.             this.newLine = newLine;
  48.         }
  49.         /// <summary>
  50.         /// 消息体列表。
  51.         /// </summary>
  52.         public List<EventStreamEventArgs> AddedItems
  53.         {
  54.             get { return addedItems; }
  55.         }
  56.         protected string DataSource { get { return dataSource; } }
  57.         protected bool IsHandledLine { get { return isHandledLine; } }
  58.         /// <summary>
  59.         /// 测试代码。
  60.         /// </summary>
  61.         [System.Diagnostics.Conditional("DEBUG")]
  62.         public static void Test()
  63.         {
  64.             var builder = new StringBuilder(64);
  65.             builder.AppendLine("id: msgid");
  66.             builder.AppendLine("event: myEvent");
  67.             builder.AppendLine("data: Hello, SSE!");
  68.             builder.AppendLine();
  69.             builder.AppendLine("id: msgid");
  70.             builder.AppendLine("event: myEvent");
  71.             builder.AppendLine("data: Another event");
  72.             builder.AppendLine();
  73.             builder.AppendLine(": This is a comment");
  74.             builder.AppendLine();
  75.             builder.AppendLine("id: msgid");
  76.             builder.AppendLine("event: reconnect");
  77.             builder.AppendLine("retry: 5000");
  78.             builder.AppendLine();
  79.             builder.AppendLine("id: msgid end");
  80.             builder.AppendLine("event: myEvent end");
  81.             builder.AppendLine("data: Another event end");
  82.             builder.AppendLine();
  83.             var bytes = Encoding.UTF8.GetBytes(builder.ToString());
  84.             var stream = new MemoryStream(bytes);
  85.             var reader = new StreamReader(stream, Encoding.UTF8);
  86.             var handler = new EventStreamHandler("\r\n");
  87.             handler.HandleStreamReader(reader);
  88.             var messageItems = handler.AddedItems;
  89.             System.Diagnostics.Trace.WriteLine($"Message items count {messageItems.Count}");
  90.         }
  91.         /// <summary>
  92.         /// 解析SSE 。
  93.         /// </summary>
  94.         /// <param name="streamReader"></param>
  95.         public void HandleStreamReader(StreamReader streamReader)
  96.         {
  97.             while (!streamReader.EndOfStream)
  98.             {
  99.                 line = streamReader.ReadLine();
  100.                 isHandledLine = false;
  101.                 if (!string.IsNullOrEmpty(line))
  102.                 {
  103.                     if (line.StartsWith(EventStreamEventArgs.IdBegin))
  104.                     {
  105.                         HandleForIdBegin();
  106.                     }
  107.                     else if (line.StartsWith(EventStreamEventArgs.EventBegin))
  108.                     {
  109.                         HandleForEventBegin();
  110.                     }
  111.                     else if (line.StartsWith(EventStreamEventArgs.DataBegin))
  112.                     {
  113.                         HandleForDataBegin();
  114.                     }
  115.                     else if (line.StartsWith(EventStreamEventArgs.RetryBegin))
  116.                     {
  117.                         HandleForRetryBegin();
  118.                     }
  119.                 }
  120.                 if (!isHandledLine)
  121.                 {
  122.                     unknownLines.Add(line);
  123.                 }
  124.             }
  125.             HandleUnknownItemWhenEnd();
  126.         }
  127.         #region 解析各种消息。
  128.         private void HandleForIdBegin()
  129.         {
  130.             // 当前行是不是用来传输 id: 信息的行。
  131.             var isIdLine = HandleCheckIdLine();
  132.             if (isIdLine)
  133.             {
  134.                 idSource = line;
  135.                 isHandledLine = true;
  136.             }
  137.         }
  138.         private bool HandleCheckIdLine()
  139.         {
  140.             // 当前行是不是用来传输 id: 信息的行。
  141.             bool isIdLine = false;
  142.             // 如果是第1次遇到 idSource 。移除前面遇到的未知行。
  143.             if (addedItems.Count < 1)
  144.             {
  145.                 ClearIfNotEmpty(unknownLines);
  146.                 isIdLine = true;
  147.             }
  148.             // 如果遇到了,上1个消息体结束的特征。
  149.             else if (MatchEmptySplitLine(unknownLines, emptySplitLineCount))
  150.             {
  151.                 HandleUnknownItemWhenMiddle();
  152.                 isIdLine = true;
  153.             }
  154.             return isIdLine;
  155.         }
  156.         private void HandleForEventBegin()
  157.         {
  158.             ClearIfNotEmpty(unknownLines);
  159.             eventSource = line;
  160.             isHandledLine = true;
  161.         }
  162.         private void HandleForDataBegin()
  163.         {
  164.             ClearIfNotEmpty(unknownLines);
  165.             dataSource = line;
  166.             isHandledLine = true;
  167.             AddItem(addedItems, idSource, eventSource, dataSource);
  168.             possibleUnknownItem = true;
  169.         }
  170.         private void HandleForRetryBegin()
  171.         {
  172.             ClearIfNotEmpty(unknownLines);
  173.             isHandledLine = true;
  174.             possibleUnknownItem = true;
  175.         }
  176.         private void HandleUnknownItemWhenMiddle()
  177.         {
  178.             if (possibleUnknownItem)
  179.             {
  180.                 // 如果遇到消息体与消息体之间,插入了,额外的未知内容时。
  181.                 AddUnknownItem(addedItems, emptySplitLineCount, unknownLines);
  182.                 possibleUnknownItem = false;
  183.                 ClearIfNotEmpty(unknownLines);
  184.             }
  185.         }
  186.         private void HandleUnknownItemWhenEnd()
  187.         {
  188.             if (possibleUnknownItem)
  189.             {
  190.                 var emptySplitLineCountEndNew = (emptySplitLineCountEnd == 0) ? 0 :
  191.                     Math.Min(emptySplitLineCountEnd, GetEndEmptyLineCount(unknownLines));
  192.                 AddUnknownItem(addedItems, emptySplitLineCountEndNew, unknownLines);
  193.                 possibleUnknownItem = false;
  194.                 ClearIfNotEmpty(unknownLines);
  195.             }
  196.         }
  197.         #endregion
  198.         #region 帮助函数。
  199.         private static void ClearIfNotEmpty(List<string> unknownLines)
  200.         {
  201.             if (unknownLines.Count > 0)
  202.             {
  203.                 unknownLines.Clear();
  204.             }
  205.         }
  206.         /// <summary>
  207.         /// 是不是匹配了,消息体与消息体之间的分割特征。
  208.         /// </summary>
  209.         /// <param name="lines"></param>
  210.         /// <param name="emptySplitLineCount"></param>
  211.         /// <returns></returns>
  212.         private static bool MatchEmptySplitLine(List<string> lines, int emptySplitLineCount)
  213.         {
  214.             bool isMatch = lines.Count >= emptySplitLineCount;
  215.             if (isMatch && emptySplitLineCount > 0)
  216.             {
  217.                 int minIndex = lines.Count - emptySplitLineCount;
  218.                 for (int index = lines.Count - 1; index >= minIndex; index--)
  219.                 {
  220.                     if (!string.IsNullOrEmpty(lines[index]))
  221.                     {
  222.                         isMatch = false;
  223.                         break;
  224.                     }
  225.                 }
  226.             }
  227.             return isMatch;
  228.         }
  229.         /// <summary>
  230.         /// 末尾连续的空白行数。
  231.         /// </summary>
  232.         /// <param name="lines"></param>
  233.         /// <returns></returns>
  234.         private static int GetEndEmptyLineCount(List<string> lines)
  235.         {
  236.             int count = 0;
  237.             for (int index = lines.Count - 1; index >= 0 && string.IsNullOrEmpty(lines[index]); index--)
  238.             {
  239.                 count++;
  240.             }
  241.             return count;
  242.         }
  243.         private string GetUnknownValue(int splitLineCount, List<string> unknownLines)
  244.         {
  245.                 string followingValue = string.Empty;
  246.                 if (unknownLines.Count > splitLineCount)
  247.                 {
  248.                     var addLine = unknownLines.Count - splitLineCount;
  249.                     StringBuilder builder = new StringBuilder(64);
  250.                     for (int index = 0; index < addLine; index++)
  251.                     {
  252.                         builder.Append(newLine);
  253.                         var unknownLine = unknownLines[index];
  254.                         if (!string.IsNullOrEmpty(unknownLine))
  255.                         {
  256.                             builder.Append(unknownLine);
  257.                         }
  258.                     }
  259.                     followingValue = builder.ToString();
  260.                 }
  261.                 return followingValue;
  262.         }
  263.         private void AddUnknownItem(List<EventStreamEventArgs> addedItems, string unknownValue)
  264.         {
  265.             var item = new EventStreamEventArgs(unknownValue);
  266.             AddItem(addedItems, item);
  267.         }
  268.         private void AddUnknownItem(List<EventStreamEventArgs> addedItems, int splitLineCount, List<string> unknownLines)
  269.         {
  270.             var unknownValue = GetUnknownValue(splitLineCount, unknownLines);
  271.             if (!string.IsNullOrEmpty(unknownValue))
  272.             {
  273.                 AddUnknownItem(addedItems, unknownValue);
  274.             }
  275.         }
  276.         private void AddItem(List<EventStreamEventArgs> addedItems, string idSource, string eventSource, string dataSource)
  277.         {
  278.             var item = new EventStreamEventArgs(idSource, eventSource, dataSource);
  279.             AddItem(addedItems, item);
  280.         }
  281.         private void AddItem(List<EventStreamEventArgs> addedItems, EventStreamEventArgs item)
  282.         {
  283.             addedItems.Add(item);
  284.         }
  285.         #endregion
  286.     }
  287.     /// <summary>
  288.     /// 一个消息体。
  289.     /// </summary>
  290.     public class EventStreamEventArgs : EventArgs
  291.     {
  292.         internal const string IdBegin = "id: ";
  293.         internal const string EventBegin = "event: ";
  294.         internal const string DataBegin = "data: ";
  295.         /// <summary>
  296.         /// 重连消息(Reconnect Message)
  297.         /// 服务器发送的重连消息,用于告知客户端重新连接服务器。
  298.         /// 示例:
  299.         /// event: reconnect
  300.         /// retry: 5000
  301.         /// </summary>
  302.         internal const string RetryBegin = "retry: ";
  303.         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
  304.         private readonly string idSource;
  305.         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
  306.         private readonly string eventSource;
  307.         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
  308.         private readonly string dataSource;
  309.         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
  310.         private string idValue;
  311.         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
  312.         private string eventValue;
  313.         [System.Diagnostics.DebuggerBrowsable(System.Diagnostics.DebuggerBrowsableState.Never)]
  314.         private string dataValue;
  315.         public EventStreamEventArgs(string idSource, string eventSource, string dataSource)
  316.         {
  317.             this.idSource = idSource;
  318.             this.eventSource = eventSource;
  319.             this.dataSource = dataSource;
  320.         }
  321.         public EventStreamEventArgs(string unknownValue)
  322.         {
  323.             this.UnknownValue = unknownValue;
  324.         }
  325.         public string IdValue
  326.         {
  327.             get
  328.             {
  329.                 if (idValue == null) idValue = GetValue(idSource, IdBegin);
  330.                 return idValue;
  331.             }
  332.         }
  333.         public string EventValue
  334.         {
  335.             get
  336.             {
  337.                 if (eventValue == null) eventValue = GetValue(eventSource, EventBegin);
  338.                 return eventValue;
  339.             }
  340.         }
  341.         public string DataValue
  342.         {
  343.             get
  344.             {
  345.                 if (dataValue == null) dataValue = GetValue(dataSource, DataBegin);
  346.                 return dataValue;
  347.             }
  348.         }
  349.         /// <summary>
  350.         /// 上1个 <see cref="EventStreamEventArgs"/> 中的 <see cref="DataValue"/> 后面跟随的未知内容。
  351.         /// </summary>
  352.         public string UnknownValue
  353.         {
  354.             get;
  355.             private set;
  356.         }
  357.         private string GetValue(string source, string begin)
  358.         {
  359.             string value;
  360.             if (source != null && source.Length > begin.Length)
  361.             {
  362.                 value = source.Substring(begin.Length, source.Length - begin.Length);
  363.             }
  364.             else
  365.             {
  366.                 value = string.Empty;
  367.             }
  368.             return value;
  369.         }
  370.     }
  371. }
复制代码
免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

花瓣小跑

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表