golang socket编程

osi参考模型将计算机网络结构分为7个层次,可是在实际的开发应用中,咱们更加承认TCP/IP族协议的五层结构,即应用层(http、ftp、dns),传输层(udp、tcp),网络层(ip),链路层(以太网),物理层。golang

socket编程做为一种基于网络层和传输层的数据io模式主要分为两种,TCP Socket和UDP Socket,也即面向链接的流式Socket和面向无链接的数据报式Socket。编程

今天主要和你们讲讲golang的TCP Socket编程。先来看个简单的例子吧服务器

server.go网络

//模拟server端
func main() {
        tcpServer, _ := net.ResolveTCPAddr("tcp4", ":8080")
        listener, _ := net.ListenTCP("tcp", tcpServer)

        for {
                //当有新的客户端请求来的时候,拿到与客户端的链接
                conn, err := listener.Accept()
                if err != nil {
                        fmt.Println(err)
                        continue
                }   
 
                //处理逻辑
                go handle(conn)
        }   
}

func handle(conn net.Conn) {
        defer conn.Close()                                                                                                                                                                                                                                                    

        //读取客户端传送的消息
        go func() {
                response, _ := ioutil.ReadAll(conn)
                fmt.Println(string(response))
        }() 

        //向客户端发送消息
        time.Sleep(1 * time.Second)
        now := time.Now().String()
        conn.Write([]byte(now))
}

client.go数据结构

//模拟客户端
func main() {
        if len(os.Args) < 2 { 
                fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
        }   
        //获取命令行参数 socket地址
        server := os.Args[1]
        addr, err := net.ResolveTCPAddr("tcp4", server)
        checkError(err)

        //创建tcp链接
        conn, err := net.DialTCP("tcp4", nil, addr)
        checkError(err)

        //向服务端发送数据
        _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
        checkError(err)
        //接收响应
        response, _ := ioutil.ReadAll(conn)
        fmt.Println(string(response))
        os.Exit(0)                                                                                                                                                                                                                                                            
}

func checkError(err error) {
        if err != nil {
                fmt.Println(err)
                os.Exit(1)
        }   
}

总体大概的流程是:socket

    server端先经过tcp协议监听端口8080,以后当有客户端来访问时可以读取信息而且写入响应信息,注意此处 conn, err := listener.Accept() 语句是用来获取下一个调用链接的,若是一直没有链接,则会处于阻塞状态。tcp

    客户端经过DialTCP尝试与服务端创建链接,链接成功后向服务器端发送数据并接收响应,而且以上代码中,只有等待服务器端关闭链接以后,该链接才会失效,不然链接会一直处于ESTABLISHED状态。以下图是笔者将defer conn.Close()注释掉以后,查看的8080端口的链接,会发现链接一直存在:计算机网络

若是使用细心一点的话,在客户端代码执行结束以后,此时的服务器端链接并无释放(注意下图local address是8080端口),而是进入了TIME_WAIT状态:命令行

一开始笔者觉得是链接没有正确释放的缘由,后来查看了下TCP四次挥手有关的内容才弄明白这块的缘由,由于是服务器端主动断开链接,因此在服务器端发送完最后一个ACK以后,会进入TIME_WAIT状态等待一段时间,防止ACK没有发送成功,等待时间一过,系统会自动释放链接。以下是一个简单的状态转变图(不是特别精准,只看断开链接部分),但愿能对读者有所帮助:code

详细的四次挥手的各个状态轮转的变化,能够去谷歌搜索一下。

 

PS:以上是一个简单的socket编程的示例,只包含了一对client和server,那若是一个server有不少个client,此时有一个client但愿可以将消息发送给除它本身以外的全部client fd怎么办?

        其实这个问题能够经过server端来作,首先server端定义一种数据结构,不管是map仍是slice,而后将全部的有效链接都放入其中,若是有client fd发送消息过来,遍历该结构,将消息发送给每个非当前client的fd,由此解决该问题。

相关文章
相关标签/搜索