温故知新: TCP 服务与 HTTP 服务

1,901 阅读4分钟

作者:林冠宏 / 指尖下的幽灵。转载者,请: 务必标明出处。

掘金:juejin.im/user/178526…

博客:www.cnblogs.com/linguanh/

GitHub : github.com/af913337456…

出版的书籍:


随着年龄增长,越发觉得,计算机网络的基础知识,很重要。


  • 初始的思维
  • 面向自定义的 TCP 服务
  • HTTP 服务本质
  • 服务的约束

初始的思维

相信很多做后端开发的同学,在 github 或者其他地方阅读搜索一些开源代码,阅读它们的时候,都有见到过服务编写的多种形式,并意识到怎么不都是 HTTP 形式的服务。

比如看到 A 项目的代码,里面的请求登录获取用户信息,从入口函数到返回输出都没见到熟悉的RouterPath 以及 HttpMethod

这很正常,我们一开始接触后端服务的时候,99%的开发者都是从 HTTP 服务开始,思维习惯于 HTTP 服务的代码阅读风格。比如下面的,就很熟悉。


router = router.append("POST","/login",func(){}).append("POST","/getUserInfo",func(){})
server.listen(":9000",router)

这种服务格式对我们来说,一目了然。这就是使用最广的 HTTP 服务。而对于 TCP 服务的直接实现就看得云里雾里。

面向自定义的 TCP 服务

然而,当一些项目做得很大的时候,它们的服务所采用的实现方式绝大多数是自定义的形式。尽管如此,自定义的层数也多截止到传输层的 TCP。

基于 TCP 协议做服务的定制化

比如下面的 TCP 服务端伪代码。

func main() {
    listen = net.Listen("tcp", ":20000")
    for {
       conn = listen.Accept() // 建立连接
       go process(conn)
    }
}

func process(conn net.Conn) {
   defer conn.close()
   for {
      var buf [128]byte
      n = conn.Read(buf[:])
      // if err return
      recvStr := string(buf[:n])
      fmt.Println("服务端收到客户端发来的数据:", recvStr)
      conn.Write([]byte("Hello World"))
   }
}

客户端请求的时候,怎么请求?直接就是建立 TCP 链接,然后写字节数据。

这样的一来一往,完全没有了 HTTP 服务常见的 GETPOSTmethodPath 也不见了。这样也能实现 Client 与 Server 的通讯。

我一般称这类服务为:TCP 直连服务

HTTP 服务本质

HTTP 全称是超文本传输协议。属于 OSI 模型中的应用层。而 TCP 是传输层,属于更低层的协议,HTTP 所接收的服务请求最终都会走到 TCP 中来。

既可以理解为 HTTP 是在 TCP 的基础上增加的一套通用且便捷的数据解析规则后形式的应用层协议。这套规则就是 HTTP 报文规则

比如一个 HTTP 请求报文 是下面的格式:

[Method] [Url] [Version][\r\n]
[Key:] [Value][\r\n]
...
[Key:] [Value][\r\n]
[\r\n]
[Body]
----------对应于-------------
GET /userInfo HTTP/1.1
Content-Type: text/html
Content-Length: 44

Content Here ...

又由于,几乎 HTTP 服务的网络框架都支持了这么一套规则,所以这么一个报文,在传输到 TCP 端的时候,数据读取就会变成下面这个样子,我们改装下上面的的 process 函数,使得它实现 HTTP 的回复形式:


func process(conn net.Conn) {
   defer conn.close()
   for {
      var buf [128]byte
      total = conn.Read(buf[:])
      // if err return
      obj = httpParse(buf[:]) // 按照 Http 报文协议解析请求的数据流
      if obj.metohd == "GET" && obj.url == "/userInfo" & ... {
          // 以 http 报文格式回复
          resp = "Hello World"
          conn.Write([]byte("HTTP/1.1 200 OK\r\nContent-Length: {respLen}\r\n\r\n"+resp))
          contine
      }
      conn.Write([]byte("Hello World"))
   }
}

上面伪代码展示了一个简单的例子。现在的 HTTP 服务框架都是基于这种形式来实现封装后给到开发者使用的。如果你想试试,你也可以封装自己的轮子,比如 HTT_Q 协议。

业界还有 FTPTELNET 这些应用层协议,它们和 HTTP 同级。都是基于 TCP 开发出来的。

服务的约束

如果基于 TCP 服务来实现一套自己的应用层协议。这意味着,请求的客户端也要跟随实现自己的请求框架。同时在网络中传输数据我们还要考虑安全性,整体去看就是开发成本比较高。

所以基于下面的条件限制,我们现在绝大多数服务采用 HTTP 服务,而不是 TCP 数据裸传服务。

  1. 协议规范定制;
  2. 基于规范的代码框架开发;
  3. 规范中安全算法的稳定及其可靠性;
  4. 数据的包装及其传输的效率。

对于安全这个,基于 HTTP 协议家族中的 HTTPS 就是专门解决这个问题而生的。

推荐

介绍个写代码查 bug 的小程序 helper: