<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>网络归档 - 枫阿雨&#039;s blog</title>
	<atom:link href="https://www.crazyfay.com/tag/%E7%BD%91%E7%BB%9C/feed/" rel="self" type="application/rss+xml" />
	<link>https://www.crazyfay.com/tag/网络/</link>
	<description>CrazyFay</description>
	<lastBuildDate>Tue, 04 Apr 2023 02:47:36 +0000</lastBuildDate>
	<language>zh-CN</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	<generator>https://wordpress.org/?v=6.5.2</generator>

<image>
	<url>https://www.crazyfay.com/wp-content/uploads/2023/04/cropped-DockerGopher-32x32.png</url>
	<title>网络归档 - 枫阿雨&#039;s blog</title>
	<link>https://www.crazyfay.com/tag/网络/</link>
	<width>32</width>
	<height>32</height>
</image> 
	<item>
		<title>速通WebSocket协议 &#8211; 建立在HTTP之上的应用层&#8221;TCP&#8221;协议</title>
		<link>https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/</link>
					<comments>https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Tue, 11 Oct 2022 15:09:30 +0000</pubDate>
				<category><![CDATA[学习笔记]]></category>
		<category><![CDATA[WebSocket]]></category>
		<category><![CDATA[网络]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=142</guid>

					<description><![CDATA[<p>由于最近在看一些IM系统相关的内容，学习到了webSocket协议，感觉很有意思，但是感觉感觉网上没有找到很清 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/">速通WebSocket协议 &#8211; 建立在HTTP之上的应用层&#8221;TCP&#8221;协议</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<p>由于最近在看一些IM系统相关的内容，学习到了webSocket协议，感觉很有意思，但是感觉感觉网上没有找到很清晰描述webSocket的文章，故有此篇，文章整理了网络上的一些资料并加上我的个人理解，如有错误，欢迎指出</p>
<h1>WebSocket</h1>
<p><strong>WebSocket是一种网络传输协议</strong>，可在单个TCP连接上进行全双工通信，WebSocket使得客户端和服务器之间的数据交换变得更加简单，允许服务端主动向客户端推送数据。在WebSocket API中，浏览器和服务器只需要完成一次握手，两者之间就可以建立持久性的连接，并进行双向数据传输。WebSocket是为了在浏览器中使用长连接而定义的协议。因此，它是在http超文本传输协议的基础上升级而来，<code>Http和WebSocket协议都是基于TCP协议的</code>。</p>
<p>WebSocket 与 HTTP/2 一样，都是为了解决 HTTP 某方面的缺陷而诞生的。HTTP/2 针对的是“队头阻塞”，而 WebSocket 针对的是“请求 - 应答”通信模式。</p>
<p><strong>那么，“请求 - 应答”有什么不好的地方呢？</strong></p>
<p>“请求 - 应答”是一种“半双工”的通信模式，虽然可以双向收发数据，但同一时刻只能一个方向上有动作，传输效率低。更关键的一点，它是一种“被动”通信模式，服务器只能“被动”响应客户端的请求，无法主动向客户端发送数据。</p>
<p>虽然后来的 HTTP/2、HTTP/3 新增了 Stream、Server Push 等特性，但“请求 - 应答”依然是主要的工作方式。这就导致 HTTP 难以应用在动态页面、即时消息、网络游戏等要求“实时通信”的领域。</p>
<p>在 WebSocket 出现之前，在浏览器环境里用 JavaScript 开发实时 Web 应用很麻烦。因为浏览器是一个“受限的沙盒”，不能用 TCP，只有 HTTP 协议可用，所以就出现了很多“变通”的技术，“轮询”（polling）就是比较常用的的一种。</p>
<p>简单地说，轮询就是不停地向服务器发送 HTTP 请求，问有没有数据，有数据的话服务器就用响应报文回应。如果轮询的频率比较高，那么就可以近似地实现“实时通信”的效果。</p>
<p>但轮询的缺点也很明显，反复发送无效查询请求耗费了大量的带宽和 CPU 资源，非常不经济。</p>
<p>所以，为了克服 HTTP“请求 - 应答”模式的缺点，WebSocket 就“应运而生”了。它原来是 HTML5 的一部分，后来“自立门户”，形成了一个单独的标准，RFC 文档编号是 6455。</p>
<p>WebSocket 采用了二进制帧结构，语法、语义与 HTTP 完全不兼容，但因为它的主要运行环境是浏览器，为了便于推广和应用，就不得不“搭便车”，在使用习惯上尽量向 HTTP 靠拢，这就是它名字里“Web”的含义。</p>
<p>服务发现方面，WebSocket 没有使用 TCP 的“IP 地址 + 端口号”，而是延用了 HTTP 的 URI 格式，但开头的协议名不是“http”，引入的是两个新的名字：“ws”和“wss”，分别表示明文和加密的 WebSocket 协议。</p>
<p>WebSocket 的默认端口也选择了 80 和 443，因为现在互联网上的防火墙屏蔽了绝大多数的端口，只对 HTTP 的 80、443 端口“放行”，所以 WebSocket 就可以“伪装”成 HTTP 协议，比较容易地“穿透”防火墙，与服务器建立连接。</p>
<h2>WebSocket 协议格式</h2>
<pre><code class="language-shell"> 0                   1                   2                   3
 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
+-+-+-+-+-------+-+-------------+-------------------------------+
|F|R|R|R| opcode|M| Payload len |    Extended payload length    |
|I|S|S|S|  (4)  |A|     (7)     |             (16/64)           |
|N|V|V|V|       |S|             |   (if payload len==126/127)   |
| |1|2|3|       |K|             |                               |
+-+-+-+-+-------+-+-------------+ - - - - - - - - - - - - - - - +
|     Extended payload length continued, if payload len == 127  |
+ - - - - - - - - - - - - - - - +-------------------------------+
|                               |Masking-key, if MASK set to 1  |
+-------------------------------+-------------------------------+
| Masking-key (continued)       |          Payload Data         |
+-------------------------------- - - - - - - - - - - - - - - - +
:                     Payload Data continued ...                :
+ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +
|                     Payload Data continued ...                |
+---------------------------------------------------------------+
</code></pre>
<p>WebSocket 和 HTTP/2 的关注点不同，WebSocket 更侧重于“实时通信”，而 HTTP/2 更侧重于提高传输效率，所以两者的帧结构也有很大的区别。</p>
<p>WebSocket 虽然有“帧”，但却没有像 HTTP/2 那样定义“流”，也就不存在“多路复用”“优先级”等复杂的特性，而它自身就是“全双工”的，也就不需要“服务器推送”。所以综合起来，WebSocket 的帧学习起来会简单一些。</p>
<p>上面就是 WebSocket 的帧结构定义，长度不固定，最少 2 个字节，最多 14 字节，看着好像很复杂，实际非常简单。</p>
<p>开头的两个字节是必须的，也是最关键的。</p>
<p>第一个字节的第一位“<strong>FIN</strong>”是消息结束的标志位，相当于 HTTP/2 里的“END_STREAM”，表示数据发送完毕。一个消息可以拆成多个帧，接收方看到“FIN”后，就可以把前面的帧拼起来，组成完整的消息。</p>
<p>“FIN”后面的三个位是保留位，目前没有任何意义，但必须是 0。</p>
<p>第一个字节的后 4 位很重要，叫“Opcode”，操作码，其实就是帧类型，比如 1 表示帧内容是纯文本，2 表示帧内容是二进制数据，8 是关闭连接，9 和 10 分别是连接保活的 PING 和 PONG。</p>
<p>第二个字节第一位是掩码标志位“MASK”，表示帧内容是否使用异或操作（xor）做简单的加密。目前的 WebSocket 标准规定，客户端发送数据必须使用掩码，而服务器发送则必须不使用掩码。</p>
<p>第二个字节后 7 位是“Payload len”，表示帧内容的长度。它是另一种变长编码，最少 7 位，最多是 7+64 位，也就是额外增加 8 个字节，所以一个 WebSocket 帧最大是 2^64。</p>
<p>长度字段后面是“Masking-key”，掩码密钥，它是由上面的标志位“MASK”决定的，如果使用掩码就是 4 个字节的随机数，否则就不存在。</p>
<p>这么分析下来，其实 WebSocket 的帧头就四个部分：“结束标志位 + 操作码 + 帧长度 + 掩码”，只是使用了变长编码的“小花招”，不像 HTTP/2 定长报文头那么简单明了。</p>
<h2>WebSocket 握手</h2>
<p>和 TCP、TLS 一样，WebSocket 也要有一个握手过程，然后才能正式收发数据。</p>
<p>这里它还是搭上了 HTTP 的“便车”，利用了 HTTP 本身的“协议升级”特性，“伪装”成 HTTP，这样就能绕过浏览器沙盒、网络防火墙等等限制，这也是 WebSocket 与 HTTP 的另一个重要关联点。</p>
<p>WebSocket 的握手是一个标准的 HTTP GET 请求，但要带上两个协议升级的专用头字段：</p>
<ul>
<li>“Connection: Upgrade”，表示要求协议“升级”；</li>
<li>“Upgrade: websocket”，表示要“升级”成 WebSocket 协议。</li>
</ul>
<p>另外，为了防止普通的 HTTP 消息被“意外”识别成 WebSocket，握手消息还增加了两个额外的认证用头字段（所谓的“挑战”，Challenge）：</p>
<ul>
<li>Sec-WebSocket-Key：一个 Base64 编码的 16 字节随机数，作为简单的认证密钥；</li>
<li>Sec-WebSocket-Version：协议的版本号，当前必须是 13。</li>
</ul>
<p><strong>来自客户端的握手如下所示</strong>：</p>
<pre><code class="language-http">GET /chat HTTP/1.1
Host: server.example.com
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
Origin: http://example.com
Sec-WebSocket-Protocol: chat, superchat
Sec-WebSocket-Version: 13</code></pre>
<p>服务器收到 HTTP 请求报文，看到上面的四个字段，就知道这不是一个普通的 GET 请求，而是 WebSocket 的升级请求，于是就不走普通的 HTTP 处理流程，而是构造一个特殊的“101 Switching Protocols”响应报文，通知客户端，接下来就不用 HTTP 了，全改用 WebSocket 协议通信。（有点像 TLS 的“Change Cipher Spec”）</p>
<p>WebSocket 的握手响应报文也是有特殊格式的，要用字段“Sec-WebSocket-Accept”验证客户端请求报文，同样也是为了防止误连接。</p>
<p>具体的做法是把请求头里“Sec-WebSocket-Key”的值，加上一个专用的 UUID “258EAFA5-E914-47DA-95CA-C5AB0DC85B11”，再计算 SHA-1 摘要。</p>
<pre><code class="language-shell">encode_base64(sha1(Sec-WebSocket-Key + &#039;258EAFA5-E914-47DA-95CA-C5AB0DC85B11&#039; ))</code></pre>
<p>客户端收到响应报文，就可以用同样的算法，比对值是否相等，如果相等，就说明返回的报文确实是刚才握手时连接的服务器，认证成功。返回握手信息。</p>
<p>握手完成，后续传输的数据就不再是 HTTP 报文，而是 WebSocket 格式的二进制帧了。</p>
<p><strong>来自服务器的握手如下所示</strong>:</p>
<pre><code class="language-http">HTTP/1.1 101 Switching Protocols
Upgrade: websocket
Connection: Upgrade
Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
Sec-WebSocket-Protocol: chat</code></pre>
<p>大致意思就是告诉服务器，我要升级为websocket长连，如果服务器同意，就必须返回101状态，告诉客户端，升级成功。</p>
<blockquote>
<p>服务器不可返回200状态码</p>
</blockquote>
<h2>总结</h2>
<p>浏览器是一个“沙盒”环境，有很多的限制，不允许建立 TCP 连接收发数据，而有了 WebSocket，我们就可以在浏览器里与服务器直接建立“TCP 连接”，获得更多的自由。</p>
<p>不过自由也是有代价的，WebSocket 虽然是在应用层，但使用方式却与“TCP Socket”差不多，过于“原始”，用户必须自己管理连接、缓存、状态，开发上比 HTTP 复杂的多，所以是否要在项目中引入 WebSocket 必须慎重考虑。</p>
<p><strong>WebSocket 特点</strong>：</p>
<ol>
<li>全双工通信，相当于对 TCP 做了一层“薄薄的包装”，让它运行在浏览器环境里。</li>
<li>WebSocket 使用二进制帧，结构比较简单，特殊的地方是有个“掩码”操作，客户端发数据必须掩码，服务器则不用。</li>
<li>Websocket与HTTP和HTTPS使用相同的TCP端口，可以绕过大多数防火墙的限制。</li>
<li>Websocket协议使用80端口；运行在TLS之上时，默认使用443端口。</li>
<li>协议头大小可变，节省空间。</li>
<li>opcode：自带心跳ping/pong协议码。</li>
</ol>
<p><strong>相比TCP缺点</strong>：</p>
<ol>
<li>增加了协议头，对流量有一点影响。</li>
<li>客户端发送数据载体时必须做一个编码(MASK=1)，这会增加cpu负载。</li>
<li>服务端解析HTTP Upgrade头会增加消耗。</li>
</ol>
<blockquote>
<p>tips:</p>
<ol>
<li>WebSocket 标准诞生于2011年，HTTP/2 诞生于2015年</li>
<li>WebSocket 不兼容URI后面的 <code>#</code> 标识，需要编码为 <code>%23</code></li>
<li>WebSocket 强制要求客户端发送数据使用掩码，是为了提供最基本的安全防护，让每次发送的消息都是随机、不可预测的，抵御”缓存中毒“攻击。但如果运行在SSL/TLS上，采用加密通信，那么掩码就没有必要了</li>
<li>WebSocket 协议里的 <code>PING</code>、<code>PONG</code> 帧，对于保持长连接很重要，可以让链路上总有数据在传输，防止被服务器、路由、网关认为是”无效连接“而意外关闭</li>
</ol>
</blockquote>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/">速通WebSocket协议 &#8211; 建立在HTTP之上的应用层&#8221;TCP&#8221;协议</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2022/10/11/%e9%80%9f%e9%80%9awebsocket%e5%8d%8f%e8%ae%ae-%e5%bb%ba%e7%ab%8b%e5%9c%a8http%e4%b9%8b%e4%b8%8a%e7%9a%84%e5%ba%94%e7%94%a8%e5%b1%82tcp%e5%8d%8f%e8%ae%ae/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
		<item>
		<title>Golang实现支持中间件的简易TCP框架</title>
		<link>https://www.crazyfay.com/2022/08/16/golang%e5%ae%9e%e7%8e%b0%e6%94%af%e6%8c%81%e4%b8%ad%e9%97%b4%e4%bb%b6%e7%9a%84%e7%ae%80%e6%98%93tcp%e6%a1%86%e6%9e%b6/</link>
					<comments>https://www.crazyfay.com/2022/08/16/golang%e5%ae%9e%e7%8e%b0%e6%94%af%e6%8c%81%e4%b8%ad%e9%97%b4%e4%bb%b6%e7%9a%84%e7%ae%80%e6%98%93tcp%e6%a1%86%e6%9e%b6/#respond</comments>
		
		<dc:creator><![CDATA[crazyfay]]></dc:creator>
		<pubDate>Tue, 16 Aug 2022 07:48:29 +0000</pubDate>
				<category><![CDATA[代码实战]]></category>
		<category><![CDATA[Golang]]></category>
		<category><![CDATA[TCP]]></category>
		<category><![CDATA[网络]]></category>
		<guid isPermaLink="false">http://net.crazyfay.xyz/?p=138</guid>

					<description><![CDATA[<p>在golang的标准库中没有为tcp直接提供像http那样简单易用的服务框架，我们不妨自己手动实现一个 主体思 [&#8230;]</p>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/08/16/golang%e5%ae%9e%e7%8e%b0%e6%94%af%e6%8c%81%e4%b8%ad%e9%97%b4%e4%bb%b6%e7%9a%84%e7%ae%80%e6%98%93tcp%e6%a1%86%e6%9e%b6/">Golang实现支持中间件的简易TCP框架</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></description>
										<content:encoded><![CDATA[<p>在golang的标准库中没有为tcp直接提供像http那样简单易用的服务框架，我们不妨自己手动实现一个</p>
<h2>主体思路</h2>
<p>我们的实现的主题思路分为以下四个内容</p>
<ol>
<li>监听服务</li>
<li>获取构建新连接对象并设置超时时间及keepalive</li>
<li>设置方法退出时连接关闭</li>
<li>调用回调接口 TcpHandler</li>
</ol>
<h2>主要结构体和接口</h2>
<p>首先是TCPServer的结构体，我们希望用户可以自由构建TcpServer并设置超时时间等自定义选项</p>
<pre><code class="language-go">type TcpServer struct {
   Addr    string
   Handler TCPHandler  &lt;- 对外提供的服务方法接口
   err     error
   BaseCtx context.Context

   WriteTimeout     time.Duration
   ReadTimeout      time.Duration
   KeepAliveTimeout time.Duration

   mu         sync.Mutex
   inShutdown int32
   doneChan   chan struct{}
   l          *onceCloseListener
}</code></pre>
<p>像httpHandler一样，对外提供抽象的ServeTCP方法</p>
<pre><code class="language-go">type TCPHandler interface {
   ServeTCP(ctx context.Context, conn net.Conn)
}</code></pre>
<h2>服务启动方法</h2>
<p>用户可以通过自行构建TcpServer实例再通过ListenAndServe()调用服务，或通过<code>tcp.ListenAndServe(&quot;:8080&quot;, handler)</code> 使用默认的TcpServer实例快速启动服务。</p>
<p>在 <code>ListenAndServe()</code> 方法中，进行参数的校验和初始化操作 </p>
<p><code>Serve(l net.Listener)</code> 方法中，通过 <code>l.Accept()</code> 接收信息，包装接收到的conn并另起一个协程处理服务</p>
<pre><code class="language-go">func ListenAndServe(addr string, handler TCPHandler) error {
    server := &amp;TcpServer{Addr: addr, Handler: handler, doneChan: make(chan struct{})}
    return server.ListenAndServe()
}

func (s *TcpServer) ListenAndServe() error {
   if s.shuttingDown() {
      return ErrServerClosed
   }
   if s.doneChan == nil {
      s.doneChan = make(chan struct{})
   }
   addr := s.Addr
   if addr == &quot;&quot; {
      return errors.New(&quot;need addr&quot;)
   }
   ln, err := net.Listen(&quot;tcp&quot;, addr)
   if err != nil {
      return err
   }
   return s.Serve(tcpKeepAliveListener{
      ln.(*net.TCPListener)})
}

func (s *TcpServer) Serve(l net.Listener) error {
    s.l = &amp;onceCloseListener{Listener: l}
    defer s.l.Close() //执行listener关闭
    if s.BaseCtx == nil {
        s.BaseCtx = context.Background()
    }
    baseCtx := s.BaseCtx
    ctx := context.WithValue(baseCtx, ServerContextKey, s) &lt;- 将TcpServer实例存入context中
    for {
        rw, e := l.Accept()
        if e != nil {
            select {
            case &lt;-s.getDoneChan():
                return ErrServerClosed
            default:
            }
            fmt.Printf(&quot;accept fail, err: %v\n&quot;, e)
            continue
        }
        c := s.newConn(rw)
        go c.serve(ctx)
    }
    return nil
}</code></pre>
<p>包装 <code>net.Conn</code> 为 <code>tcp.conn</code> </p>
<pre><code class="language-go">type conn struct {
    server     *TcpServer   // 反引用TcpServer
    remoteAddr string       // 发送端地址
    rwc        net.Conn
}

func (s *TcpServer) newConn(rwc net.Conn) *conn {
   c := &amp;conn{
      server: s,
      rwc:    rwc,
   }
   // 设置参数
   if d := c.server.ReadTimeout; d != 0 {
      c.rwc.SetReadDeadline(time.Now().Add(d))
   }
   if d := c.server.WriteTimeout; d != 0 {
      c.rwc.SetWriteDeadline(time.Now().Add(d))
   }
   if d := c.server.KeepAliveTimeout; d != 0 {
      if tcpConn, ok := c.rwc.(*net.TCPConn); ok {
         tcpConn.SetKeepAlive(true)
         tcpConn.SetKeepAlivePeriod(d)
      }
   }
   return c
}</code></pre>
<p>由 <code>tcp.conn.Server(ctx)</code> 调用回调函数进行服务处理</p>
<pre><code class="language-go">func (c *conn) serve(ctx context.Context) {
   defer func() {
      if err := recover(); err != nil &amp;&amp; err != ErrAbortHandler {
         const size = 64 &lt;&lt; 10
         buf := make([]byte, size)
         buf = buf[:runtime.Stack(buf, false)]
         fmt.Printf(&quot;tcp: panic serving %v: %v\n%s&quot;, c.remoteAddr, err, buf)
      }
      c.close()
   }()
   c.remoteAddr = c.rwc.RemoteAddr().String()
   ctx = context.WithValue(ctx, LocalAddrContextKey, c.rwc.LocalAddr())
   if c.server.Handler == nil {
      panic(&quot;handler empty&quot;)
   }
   c.server.Handler.ServeTCP(ctx, c.rwc)
}</code></pre>
<p>这样，一个简单易用的TCP服务框架就搭建完成了，其中一些<code>close()</code> 等方法在此处没有展示出来，更多详细代码可在我的代码仓库中查看：<a href="https://github.com/Kirov7/fayUtils/net/tcp">https://github.com/Kirov7/fayUtils/net/tcp</a></p>
<h2>扩展中间件的实现</h2>
<p>扩展中间件功能的实现思路</p>
<ul>
<li>方法构建
<ul>
<li>构建中间件URL路由</li>
<li>构建URL的中间件方法数组</li>
<li>使用Use方法整合路由与方法数组</li>
</ul>
</li>
<li>方法调用
<ul>
<li>构建方法请求逻辑</li>
<li>封装TCPHandler接口与TcpServer整合</li>
</ul>
</li>
</ul>
<p><code>TcpSliceRouter.Group(path)</code> 方法，初始化路由分组（默认只能全局）</p>
<pre><code class="language-go">// 创建 Group
func (g *TcpSliceRouter) Group(path string) *TcpSliceGroup {
   if path != &quot;/&quot; {
      panic(&quot;only accept path=/&quot;)
   }
   return &amp;TcpSliceGroup{
      TcpSliceRouter: g,
      path:           path,
   }
}</code></pre>
<p><code>TcpSliceGroup.Use(middlewares ...TcpHandlerFunc)</code> 构造回调方法</p>
<p>调用 <code>Use</code> 方法传入中间件集合，添加到切片 <code>c.handlers</code> 中</p>
<pre><code class="language-go">// 构造回调方法
func (g *TcpSliceGroup) Use(middlewares ...TcpHandlerFunc) *TcpSliceGroup {
   g.handlers = append(g.handlers, middlewares...)
   existsFlag := false
   for _, oldGroup := range g.TcpSliceRouter.groups {
      if oldGroup == g {
         existsFlag = true
      }
   }
   if !existsFlag {
      g.TcpSliceRouter.groups = append(g.TcpSliceRouter.groups, g)
   }
   return g
}</code></pre>
<p>通过 <code>NewTcpSliceRouterHandler</code> 方法传入最后调用的逻辑方法<code>coreFunc</code>并传入已经 <code>Use</code> 了中间件的， <code>TcpSliceRouter</code> </p>
<pre><code class="language-go">func NewTcpSliceRouterHandler(coreFunc func(*TcpSliceRouterContext) tcp_server.TCPHandler, router *TcpSliceRouter) *TcpSliceRouterHandler {
   return &amp;TcpSliceRouterHandler{
      coreFunc: coreFunc,
      router:   router,
   }
}</code></pre>
<p>最终的回调函数 <code>ServeTCP((ctx context.Context, conn net.Conn)</code>，初始化 <code>context</code> 之后将 <code>coreFunc</code> 追加到 <code>c.handlers</code>，重置执行光标，从第一个 <code>c.handlers</code> 开始执行中间件</p>
<pre><code class="language-go">func (w *TcpSliceRouterHandler) ServeTCP(ctx context.Context, conn net.Conn) {
   c := newTcpSliceRouterContext(conn, w.router, ctx)
   c.handlers = append(c.handlers, func(c *TcpSliceRouterContext) {
      w.coreFunc(c).ServeTCP(ctx, conn)
   })
   c.Reset()
   c.Next()
}</code></pre>
<p>在中间件中自行调用<code>Next()</code>、<code>Abort()</code>等中间件逻辑，最后所有中间件执行完毕之后执行 <code>coreFunc</code>（已经被追加到<code>c.handlers</code>的最后位置）</p>
<pre><code class="language-go">// 从最先加入中间件开始回调
func (c *TcpSliceRouterContext) Next() {
   c.index++
   for c.index &lt; int8(len(c.handlers)) {
      c.handlers[c.index](c)
      c.index++
   }
}

// 跳出中间件方法
func (c *TcpSliceRouterContext) Abort() {
    c.index = abortIndex
}</code></pre>
<p><a rel="nofollow" href="https://www.crazyfay.com/2022/08/16/golang%e5%ae%9e%e7%8e%b0%e6%94%af%e6%8c%81%e4%b8%ad%e9%97%b4%e4%bb%b6%e7%9a%84%e7%ae%80%e6%98%93tcp%e6%a1%86%e6%9e%b6/">Golang实现支持中间件的简易TCP框架</a>最先出现在<a rel="nofollow" href="https://www.crazyfay.com">枫阿雨&#039;s blog</a>。</p>
]]></content:encoded>
					
					<wfw:commentRss>https://www.crazyfay.com/2022/08/16/golang%e5%ae%9e%e7%8e%b0%e6%94%af%e6%8c%81%e4%b8%ad%e9%97%b4%e4%bb%b6%e7%9a%84%e7%ae%80%e6%98%93tcp%e6%a1%86%e6%9e%b6/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		
			</item>
	</channel>
</rss>
