通过wireshark抓包来学习TCP HTTP网络协议

发表于:2018-10-30 10:47

字体: | 上一篇 | 下一篇 | 我要投稿

 作者:佚名    来源:51testing采编

  很多招聘需求上都会要求熟悉TCP/IP协议、socket编程之类的,可见这一块是对于web编程是非常重要的。作为一个野生程序员对这块没什么概念,于是便找来一些书籍想来补补。很多关于协议的大部头书都是非常枯燥的,我特意挑了比较友好的《图解TCP/IP》和《图解HTTP》,但看了一遍仍是云里雾里,找不到掌握了知识后的那种自信。所以得换一种思路来学习————通过敲代码来学习,通过抓包工具来分析网络,抓包神器首推wireshark。本文是自己学习TCP过程的记录和总结。
  1、使用TCP socket实现服务端和客户端,模拟http请求
  写一个简单的server和client,模拟最简单的http请求,即client发送get请求,server返回hello。这里是用golang写的,最近在学习golang。
  完成之后可以使用postman充当client测试你的server能不能正常返回响应,或者使用完备的http模块测试你的client。
  client向指定端口发送连接请求,连接后发送一个request并收到response断开连接并退出。server可以和不同的客户端建立多个TCP连接,每来了一个新连接就开一个goruntine去处理。
  TCP是全双工的,所谓全双工就是读写有两个通道,互不影响,我当时还纳闷在conn上又读又写不会出毛病吗-_-
  TCP是流式传输,所以要在for中不断的去读取数据,直到断开。注意没有断开连接的时候是读不到EOF的,代码使用了bufio包中的scanner这个API来逐行读取数据,以\n为结束标志。但数据并不都是以\n结尾的,如果读不到结尾,read就会一直阻塞,所以我们需要通过header中的length判断数据的大小。
  我这里偷懒了,只读了header,读到header下面的空行就返回了。加了个超时,客户端5s不理我就断线,如果有数据过来就保持连接。
  server:
  package main
  import (
  "bufio"
  "bytes"
  "fmt"
  "io"
  "net"
  "time"
  )
  const rn = "\r\n"
  func main() {
  l, err := net.Listen("tcp", ":8888")
  if err != nil {
  panic(err)
  }
  fmt.Println("listen to 8888")
  for {
  conn, err := l.Accept()
  if err != nil {
  fmt.Println("conn err:", err)
  }
  go handleConn(conn)
  }
  }
  func handleConn(conn net.Conn) {
  defer conn.Close()
  defer fmt.Println("关闭")
  fmt.Println("新连接:", conn.RemoteAddr())
  t := time.Now().Unix()
  // 超时
  go func(t *int64) {
  for {
  if time.Now().Unix() - *t >= 5 {
  fmt.Println("超时")
  conn.Close()
  return
  }
  time.Sleep(100 * time.Millisecond)
  }
  }(&t)
  for {
  data, err := readTcp(conn)
  if err != nil {
  if err == io.EOF {
  continue
  } else {
  fmt.Println("read err:", err)
  break
  }
  }
  if (data > 0) {
  writeTcp(conn)
  t = time.Now().Unix()
  } else {
  break
  }
  }
  }
  func readTcp(conn net.Conn) (int, error) {
  var buf bytes.Buffer
  var err error
  rd := bufio.NewScanner(conn)
  total := 0
  for rd.Scan() {
  var n int
  n, err = buf.Write(rd.Bytes())
  if err != nil {
  panic(err)
  }
  buf.Write([]byte(rn))
  total += n
  fmt.Println("读到字节:", n)
  if n == 0 {
  break
  }
  }
  err = rd.Err()
  fmt.Println("总字节数:", total)
  fmt.Println("内容:", rn, buf.String())
  return total, err
  }
  func writeTcp(conn net.Conn) {
  wt := bufio.NewWriter(conn)
  wt.WriteString("HTTP/1.1 200 OK" + rn)
  wt.WriteString("Date: " + time.Now().String() + rn)
  wt.WriteString("Content-Length: 5" + rn)
  wt.WriteString("Content-Type: text/plain" + rn)
  wt.WriteString(rn)
  wt.WriteString("hello")
  err := wt.Flush()
  if err != nil {
  fmt.Println("Flush err: ", err)
  }
  fmt.Println("写入完毕", conn.RemoteAddr())
  }
  client:
  package main
  import (
  "bufio"
  "bytes"
  "fmt"
  "net"
  "time"
  )
  const rn = "\r\n"
  func main() {
  conn, err := net.Dial("tcp", ":8888")
  defer conn.Close()
  defer fmt.Println("断开")
  if err != nil {
  panic(err)
  }
  sendReq(conn)
  for {
  total, err := readResp(conn)
  if err != nil {
  panic(err)
  }
  if total > 0 {
  break
  }
  }
  }
  func sendReq(conn net.Conn) {
  wt := bufio.NewWriter(conn)
  wt.WriteString("GET / HTTP/1.1" + rn)
  wt.WriteString("Date: " + time.Now().String() + rn)
  wt.WriteString(rn)
  err := wt.Flush()
  if err != nil {
  fmt.Println("Flush err: ", err)
  }
  fmt.Println("写入完毕", conn.RemoteAddr())
  }
  func readResp(conn net.Conn) (int, error) {
  var buf bytes.Buffer
  var err error
  rd := bufio.NewScanner(conn)
  total := 0
  for rd.Scan() {
  var n int
  n, err = buf.Write(rd.Bytes())
  if err != nil {
  panic(err)
  }
  buf.Write([]byte(rn))
  if err != nil {
  panic(err)
  }
  total += n
  fmt.Println("读到字节:", n)
  if n == 0 {
  break
  }
  }
  if err = rd.Err(); err != nil {
  fmt.Println("read err:", err)
  }
  if (total > 0) {
  fmt.Println("resp:", rn, buf.String())
  }
  return total, err
  }
  2、通过wireshark监听对应端口抓包分析
  server和client做出来了,下面来使用wireshark抓包来看看TCP链接的真容。当然你也可以现成的http模块来收发抓包,不过还是建议自己写一个最简单的。因为现成的模块里面很多细节被隐藏,比如我开始用postman发一个请求但是会建立两个连接,疑似是先发了个HEAD请求。
   
  打开wireshark,默认设置就行了。选择一个网卡,输入过滤条件开始抓包,因为我们是localhost,所以选择loopback。
   
  抓包开始后,启动之前的server监听8888端口,再启动client发送请求,于是便抓到了一次新鲜的TCP请求。
  从图中我们可以清晰的看到三次握手(1-3)和四次挥手(9-12),还有seq和ack的变化,基于TCP的HTTP请求和响应,还有什么window update(TCP的窗口控制,告诉客户端我这边很空虚,赶紧发射数据)。
  这个时候再结合大部头的协议书籍,理解起来印象更深。还有各种抓包姿势,更多复杂场景,留给大家自己去调教了。

    上文内容不用于商业目的,如涉及知识产权问题,请权利人联系博为峰小编(021-64471599-8017),我们将立即处理。
《2023软件测试行业现状调查报告》独家发布~

关注51Testing

联系我们

快捷面板 站点地图 联系我们 广告服务 关于我们 站长统计 发展历程

法律顾问:上海兰迪律师事务所 项棋律师
版权所有 上海博为峰软件技术股份有限公司 Copyright©51testing.com 2003-2024
投诉及意见反馈:webmaster@51testing.com; 业务联系:service@51testing.com 021-64471599-8017

沪ICP备05003035号

沪公网安备 31010102002173号