G

[Golang基础语法] 网络编程

RoLingG Golang 2024-03-17

网络编程

TCP:传输控制协议,是一种面向连接的、可靠的、基于字节流的传输层通信协议。

TCP保证连接可靠也就是使用:超时重传(我暂时没看)、三次握手、四次挥手。

TCP 三次握手是建立 TCP 连接时使用的一种协议。在进行数据通信之前,客户端和服务器之间需要通过三次握手来确认彼此的可靠性,以确保双方都准备好传输数据。以下是 TCP 三次握手的详细过程:

第一次握手:客户端向服务器发送一个带有 SYN(同步序列编号)标志的数据包,表示请求建立连接。此时客户端进入 SYN_SENT 状态。

第二次握手:服务器收到客户端发送的 SYN 数据包后,会回复一个带有 SYNACK 标志的数据包作为响应。ACK 是确认号,表示对客户端的 SYN 请求做出确认,并且自己也想要建立连接。此时服务器进入 SYN_RECV 状态。

第三次握手:客户端收到服务器的响应后,会再次发送一个带有 ACK 标志的数据包给服务器,表示对服务器的确认。此时客户端和服务器都已经确认了彼此的请求,连接建立完成,双方可以开始进行数据传输。客户端进入 ESTABLISHED 状态,服务器在收到客户端的 ACK 后也进入 ESTABLISHED 状态。

通俗点讲:

客户端想要知道服务端是否具有收、发的能力,所以会传一个SYN标志的数据包给服务端进行确认。

服务端收到后发现有客户端想要连接,就传一个SYNACK标志的数据包回去告诉客户端自己的收、发能力情况(其实能传回去被收到也就明说了服务端的收发能力没事)。

但目前服务端可不知道客户端当前的收、发能力的情况。所以客户端就会返回一个ACK标志数据包表示表示自己收到了没问题,可以开始建立连接。

这也是为什么三次握手就行,因为三次握手是目前最高效方法的同时也最稳方法。

TCP 四次挥手是在数据传输结束后,客户端和服务器断开 TCP 连接时使用的协议。通过四次挥手,双方可以优雅地关闭连接,并确保数据的完整性。以下是 TCP 四次挥手的详细过程:

第一次挥手:客户端发送一个带有 FIN(结束)标志的数据包给服务器,表示客户端已经完成了数据传输并准备关闭连接。客户端进入 FIN_WAIT_1 状态。

第二次挥手:服务器收到客户端发送的 FIN 数据包后,会回复一个 ACK 数据包作为确认。此时服务器进入 CLOSE_WAIT 状态,表示服务器已经知道客户端想要关闭连接。

第三次挥手:一段时间后,服务器也准备好关闭连接时,服务器会发送一个带有 FIN 标志的数据包给客户端,表示服务器也准备关闭连接。服务器进入 LAST_ACK 状态。

第四次挥手:客户端收到服务器的 FIN 数据包后,会回复一个 ACK 数据包作为确认。此时客户端进入 TIME_WAIT 状态,等待一段时间以确保服务器收到了最后的确认。服务器在接收到客户端的 ACK 后就进入 CLOSED 状态,连接完全关闭。

​ 通过这个四次挥手的过程,客户端和服务器都可以完成连接的关闭,释放资源并终止对方的连接。在最后一次挥手完成后,双方的连接就正式关闭,不再传输数据。这种机制可以避免出现半开连接的情况,确保连接的安全关闭。

通过这个三次握手过程,客户端和服务器能够建立起可靠的连接,并且双方都确认了彼此的通信能力。这种机制可以有效地防止因网络延迟或丢包等问题导致的连接异常,从而确保数据传输的可靠性。在数据传输结束后,双方可以通过四次挥手来关闭连接。

下面是两端有关tcp的代码示例:

  • tcp服务端

    package main
    
    import (
        "fmt"
        "io"
        "net"
        "time"
    )
    
    func main() {
        addr, _ := net.ResolveTCPAddr("tcp", "127.0.0.1:8080")
        listen, err := net.ListenTCP("tcp", addr)
        if err != nil { //如果端口占用或者其他错误
            fmt.Println(err)
            return
        }
        fmt.Println("tcpService开始工作:", addr.String())
        for {
            conn, err := listen.Accept() //得到一个连接对象
            if err != nil {
                break
            }
            fmt.Println(conn.RemoteAddr())    //拿去客户端的地址
            conn.Write([]byte("hello world")) //写入,谁连就给谁发这个
            time.Sleep(2 * time.Second)
    
            //接收客户端发送过来的消息
            var buf []byte = make([]byte, 1024)
            n, err := conn.Read(buf)
            if err == io.EOF {
                fmt.Println(conn.RemoteAddr().String() + "出去了")
                break
            }
            fmt.Println(string(buf[0:n]))
    
            conn.Close() 
            //虽然不加conn.Close()对运行没什么影响,但是会导致在每次连接处理完毕后,连接对象不会被关闭,从而可能导致连接资源泄露。
            //这意味着每次有客户端连接到服务端时,会创建一个新的连接对象,而这些连接对象在处理完数据后并不会被关闭,最终可能导致服务器资源耗尽。
            //另外,如果不及时关闭连接,也会使得服务器上的连接对象数量不断增加,最终可能导致服务器无法接受新的连接请求。
        }
    }
  • tcp客户端

    package main
    
    import (
        "fmt"
        "net"
    )
    
    func main() {
        conn, err := net.Dial("tcp", "127.0.0.1:8080")
        if err != nil {
            fmt.Println(err)
            return
        }
        //这里接发送过来的数据可以用io,也可以用循环
        //for {
        //    var byteData = make([]byte, 1024) //长度为1024的byte对象
        //    n, err := conn.Read(byteData)     //获取实际长度n和err,将发过来的数据存放在byteData里
        //    if err == io.EOF {                //如果服务端发完了数据
        //        break
        //    }
        //    fmt.Println(string(byteData[:n])) //没发完就打印发过来的数据
        //}
        
        //这里没用协程,所以测试下面这段代码得把上面的for循环注释掉
        //服务端在循环接收数据,所以客户端可以循环发数据
        for {
            var txt string
            fmt.Printf("输入想要传输的内容:")
            fmt.Scan(&txt)
            if txt == "q" {
                conn.Close()
                break
            }
            conn.Write([]byte(txt))
        }
    }
PREV
[Golang基础语法] 自定义数据类型与类型别名
NEXT
[Golang基础语法] 接口

评论(0)

发布评论