wpf程序网络的影响_谁拔了我的网线?Go 网络异常对程序行为的影响

作者:鸟窝smallnest

原文链接:https://colobu.com/2019/12/28/go-tcp-exceptions/

Go 编写网络程序非常的高效,而且有是那么的简单,寥寥几行代码就可以写一个 ECHO 协议的程序,所以现在很多网络程序都采用 Go 语言开发。但是网络状况是复杂的,会有很多的异常状况,如果不能很好和正确的处理这些异常状况,会导致网络程序出现莫名其妙的现象,或者 hang 住。

本文尝试探讨几种网络异常的情况,研究在这些情况下客户端和服务端的的行为,包括连接断掉的检测能力、half-close 情况下两端的读写能力、丢包的情况等等。

这是我首次采用微课的方式分享技术内容,本文是视频内容的整理版。本来是想录制一个 10 分钟的视频,一不小心录制了半小时。视频在此:https://player.bilibili.com/player.html?aid=80946416&cid=138546780&page=1

TCP 协议介绍

efb30f18830e3ccb2a13b96f89e90dcb.png

tcp 的数据格式包含 header 和 payload, header 中会包含消息的状态,比如我们常见的SYN、ACK、PSH、FIN等。通过 tcpdump 可以根据消息的状态进行筛选。

握手

客户端和服务器端建立连接的时候,需要三路握手。

21d136831f2d3a7de178d9b91bfc8066.png

因为双方都需要和对方同步 seq 号,所以需要来回确认。服务器把 SYN 和 ACK 合并成一条消息,所以最终只需要三次交流就可以了。当然如果你想把 SYN 和 ACK 拆开成两个消息也可以,只不过协议栈一般不这样实现。

比如你参加一次相亲聚会,看到一个漂亮的姑娘,你想去搭讪,首先得先了解一下。

你:姑娘您好,贵庚啊?姑娘:小女子18,请问大哥您贵庚啊?你:我81了......

这样寒暄之后你们双方就可以进一步的深入的交流了。

分手
52d1e40f2b1c8fe644b4f2972c82e928.png

客户端和服务器端都可以主动关闭连接。主动关闭的一方我们称之为发起者,被动关闭接收的那一方我们称之为接受者。

发起者要关闭连接,需要发送FIN,然后接收者发送ACK。这个时候被动者有可能恋恋不舍,还有数据想发送给你,所以接受者这一端它的连接还没有释放,直到它发送FIN,发起者回复ACK,接收端的连接才释放。

姑娘:我要走了你:再见......你依依不舍你:我也要走了姑娘:再见

tcpdump

tcpdump 是分析网络情况的神器,经常用来分析疑难杂症,并且让狡辩者哑口无言。

141f9403c1ba359c2050796e049d8edc.png

打印一张 tcpdump 的小抄放在案头是明智之举。

网络异常状况

视频中,我测试了以下 6 种网络异常情况下的程序响应情况。

e48246ff3fd2f0ef6463e37df9c8e568.png

使用的代码基本上是从下面的代码修改而来。

server.gopackage mainimport ("bufio""flag""fmt""log""net""os")var (addr = flag.String("addr", ":8972", "listened address"))func main() {flag.Parse()ln, err := net.Listen("tcp", *addr)panicOnErr(err)// 接收一个连接conn, err := ln.Accept()panicOnErr(err)clientAddr := conn.RemoteAddr().String()// 读 goroutinego func() {var buf = make([]byte, 1024)for {n, err := conn.Read(buf)if err != nil {log.Printf("read err from client %s: %v", clientAddr, err)return}log.Printf("read %d bytes from client %s", n, clientAddr)}}()// 写id := 0write := func() {msg := fmt.Sprintf("sent id: %d from server", id)id++n, err := conn.Write([]byte(msg))if err != nil {log.Printf("write err to client %s: %v", clientAddr, err)return}log.Printf("write %d bytes to client %s", n, clientAddr)}// 继续监听新的连接go func() {for {_, err := ln.Accept()if err != nil {log.Printf("accept err : %v", err)}}}()scanner := bufio.NewScanner(os.Stdin)for scanner.Scan() {cmd := scanner.Text()switch cmd {case "close_conn":conn.Close()case "close_ln":ln.Close()case "write":write()case "exit", "quit":return}}}func panicOnErr(err error) {if err != nil {panic(err)}}client.gopackage mainimport ("bufio""flag""fmt""log""net""os")var (addr = flag.String("addr", "127.0.0.1:8972", "server address"))func main() {flag.Parse()// 连接服务器conn, err := net.Dial("tcp", *addr)panicOnErr(err)// 读 goroutinego func() {var buf = make([]byte, 1024)for {n, err := conn.Read(buf)if err != nil {log.Printf("read err from server %s: %v", *addr, err)return}log.Printf("read %d bytes from server %s", n, *addr)}}()// 写id := 0write := func() {msg := fmt.Sprintf("sent clientid: %d from client", id)id++n, err := conn.Write([]byte(msg))if err != nil {log.Printf("write err to server %s: %v", *addr, err)return}log.Printf("write %d bytes to server %s", n, *addr)}// 阻塞在这里避免客户端退出scanner := bufio.NewScanner(os.Stdin)for scanner.Scan() {cmd := scanner.Text()switch cmd {case "close_conn":conn.Close()case "write":write()case "exit", "quit":return}}}func panicOnErr(err error) {if err != nil {panic(err)}}
服务器主动关闭连接, 客户端不关闭连接
  • 服务器的 conn.Read会怎样?
  • 服务器继续 conn.Write会怎么样?
  • 客户端的 conn.Read会怎样?
  • 客户端继续 conn.Write会怎么样?
服务器主动关闭连接, 客户端检测到异常后也关闭连接
  • 服务器的 conn.Read会怎样?
  • 服务器继续 conn.Write会怎么样
  • 客户端的 conn.Read会怎样?
  • 客户端继续 conn.Write会怎么样
服务器只关闭Read
  • 服务器的 conn.Read会怎样?
  • 服务器继续 conn.Write会怎么样
  • 客户端的 conn.Read会怎样?
  • 客户端继续 conn.Write会怎么样
服务器只关闭Write
  • 服务器的 conn.Read会怎样?
  • 服务器继续 conn.Write会怎么样
  • 客户端的 conn.Read会怎样?
  • 客户端继续 conn.Write会怎么样
服务器被 kill 掉
  • 服务器的 conn.Read会怎样?
  • 服务器继续 conn.Write会怎么样
  • 客户端的 conn.Read会怎样?
  • 客户端继续 conn.Write会怎么样
把网线、挖光纤、雷暴机房、防火墙始乱终弃

只分析其中一种情况: 包丢了

  • 服务器的 conn.Read会怎样?
  • 服务器继续 conn.Write会怎么样
  • 客户端的 conn.Read会怎样?
  • 客户端继续 conn.Write会怎么样
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值