zoukankan      html  css  js  c++  java
  • [Golang] 从零開始写Socket Server(3): 对长、短连接的处理策略(模拟心跳)

        通过前两章,我们成功是写出了一套凑合能用的Server和Client,并在二者之间实现了通过协议交流。这么一来,一个简易的socket通讯框架已经初具雏形了,那么我们接下来做的。就是想办法让这个框架更加稳定。茁壮~

        作为一个可能会和非常多Client进行通讯交互的Server。首先要保证的就是整个Server执行状态的稳定性,因此在和Client建立连接通讯的时候,确保连接的及时断开非常重要,否则一旦和多个client建立不关闭的长连接,对于server资源的占用是非常可怕的。因此,我们须要针对可能出现的短连接和长连接,设定不同的限制策略。

        针对短连接,我们能够使用golang中的net包自带的timeout函数,一共同拥有三个,各自是:


    func (*IPConn) SetDeadline
    func (c *IPConn) SetDeadline(t time.Time) error
    
    func (*IPConn) SetReadDeadline
    func (c *IPConn) SetReadDeadline(t time.Time) error
    
    func (*IPConn) SetWriteDeadline
    func (c *IPConn) SetWriteDeadline(t time.Time) error


        假设想要给server设置短连接的timeout,我们就能够这么写:

    netListen, err := net.Listen("tcp", Port)
    	Log("Waiting for clients")
    	for {
    		conn, err := netListen.Accept()
    		if err != nil {
    			continue
    		}
    
    		conn.SetReadDeadline(time.Now().Add(time.Duration(10) * time.Second))
        这里的三个函数都是用于设置每次socket连接可以维持的最长时间。一旦超过设置的timeout后,便会在Server端自己主动断开连接。当中SetReadline, SetWriteline设置的是读取和写入的最长持续时间,而SetDeadline则同一时候包括了 SetReadline, SetWriteline两个函数。

        通过这样设定。每一个和Server通讯的Client连接时长最长也不会超过10s了~~


        搞定短连接后,接下来就是针对长连接的处理策略了~~

        作为长连接,因为我们往往非常难确定什么时候会中断连接,因此并不能像处理短连接那样简单粗暴的设定一个timeout就能够搞定,而在Golang的net包中。并没有针对长连接的函数,因此须要我们自己设计并实现针对长连接的处理策略啦~

        针对socke长连接,常见的做法是在Server和Socket之间设计通讯机制。当两者之间没有信息交互时。两方便会定时发送数据包(心跳),以维持连接状态。


        这样的方法是眼下使用相对照较多的做法。可是开销相对也较大。特别是当Server和多个client保持长连接的时候。并发会比較高,考虑到公司的业务需求,我最后选择了逻辑相对简单。开销相对较小的策略:

        当Server每次收到Client发到的信息之后,便会開始心跳计时,假设在心跳计时结束之前没有再次收到Client发来的信息。那么便会断开跟Client的连接。而一旦在设定时间内再次收到Client发来的信息,那么Server便会重置计时器,再次又一次进行心跳计时,直到超时断开连接为止。

    以下就是实现该计时的代码:

    //长连接入口
    func handleConnection(conn net.Conn,timeout int) {
    
    	buffer := make([]byte, 2048)
    	for {
    		n, err := conn.Read(buffer)
    
    		if err != nil {
    			LogErr(conn.RemoteAddr().String(), " connection error: ", err)
    			return
    		}
    		Data :=(buffer[:n])
    		messnager := make(chan byte)
    		//心跳计时
    		go HeartBeating(conn,messnager,timeout)
    		//检測每次Client是否有数据传来
    		go GravelChannel(Data,messnager)
    		Log( "receive data length:",n)
    		Log(conn.RemoteAddr().String(), "receive data string:", string(Data))
    
    	}
    }
    
    //心跳计时,依据GravelChannel推断Client是否在设定时间内发来信息
    func HeartBeating(conn net.Conn, readerChannel chan byte,timeout int) {
    		select {
    		case fk := <-readerChannel:
    			Log(conn.RemoteAddr().String(), "receive data string:", string(fk))
    			conn.SetDeadline(time.Now().Add(time.Duration(timeout) * time.Second))
    			//conn.SetReadDeadline(time.Now().Add(time.Duration(5) * time.Second))
    			break
    		case <-time.After(time.Second*5):
    			Log("It's really weird to get Nothing!!!")
    			conn.Close()
    		}
    
    }
    
    func GravelChannel(n []byte,mess chan byte){
    	for _ , v := range n{
    		mess <- v
    	}
    	close(mess)
    }
    
    
    func Log(v ...interface{}) {
    	log.Println(v...)
    }


        这样。就能够成功实现对于长连接的处理了~~,我们能够这么进行測试:

    func sender(conn net.Conn) {
    	for i := 0; i <5; i++ {
    		words:= strconv.Itoa(i)+"This is a test for long conn" 
    		conn.Write([]byte(words))
    		time.Sleep(2*time.Second)
    
    	}
    	fmt.Println("send over")
    
    }

        能够发现。Sender函数中time.Sleep堵塞的时间设定的比Server中的timeout短的时候,Client端的信息能够自由的发送到循环结束,而当我们设定Sender函数的堵塞时间较长时,就仅仅能发出第一次循环的信息。



    我已经把SocketServer系列的代码整合到了一起。公布到了我个人的github上:点击链接, 希望大家有兴趣的能够学习star一下~


  • 相关阅读:
    HDU 1525
    kmp模板
    hdu 4616 Game(树形DP)
    hdu 4619 Warm up 2(并查集活用)
    hdu 4614 Vases and Flowers(线段树加二分查找)
    Codeforces 400D Dima and Bacteria(并查集最短路)
    poj 2823 Sliding Window (单调队列)
    hdu 2196 Computer(树形dp)
    hdu 4604 Deque
    最短路径
  • 原文地址:https://www.cnblogs.com/blfbuaa/p/7073360.html
Copyright © 2011-2022 走看看