2

golang使用tcp设计一个有读写交互的长连接服务

 1 year ago
source link: https://studygolang.com/articles/25977?fr=sidebar
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

golang使用tcp设计一个有读写交互的长连接服务

tianxia0079 · 2020-01-10 13:32:39 · 3524 次点击 · 预计阅读时间 5 分钟 · 大约8小时之前 开始浏览    
这是一个创建于 2020-01-10 13:32:39 的文章,其中的信息可能已经有所发展或是发生改变。

golang新手参考网络文章,改造了一下代码,加了一些自己的理解和注释,有标注不准的地方请大佬指点出来,感谢!
服务器端:

package main  
  
import (  
   "fmt"  
 "log" "net" "time")  
  
/\*\*  
来自远程ip: 127.0.0.1:10568 的消息: hello!i am client !  
来自远程ip: 127.0.0.1:10568 的消息:  
来自远程ip: 127.0.0.1:10568 的消息:  
  
conn关闭后有两次空白输出,是因为三次握手机制 accept到空白内容了  
\*/  
func main7() {  
   addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:93")  
   dealErrorWithExist(err)  
   tcpListen, err := net.ListenTCP("tcp", addr)  
   dealErrorWithExist(err)  
   fmt.Println("tcp服务器端启动:  ", addr.String())  
   for {  
      conn, err := tcpListen.Accept()  
      if err != nil {  
         continue  
  }  
      handlerConn(conn)  
      conn.Close() //正常链接情况下,handlerConn不会释放出来到这里 当客户端强制断开,才会return到这里,吧当前conn关闭  
  fmt.Println("当前用户 ", conn.RemoteAddr(), " 主动断开链接!")  
   }  
}  
  
func handlerConn(conn net.Conn) {  
   //获取客户端信息 info,并返回 info+服务器时间  
  var buf \[1024\]byte  
 for {  
      readSize, err := conn.Read(buf\[0:\])  
      dealErrorWithReturn(err)  
      remoteAddr := conn.RemoteAddr()  
      fmt.Println("来自远程ip:", remoteAddr, " 的消息:", string(buf\[0:readSize\]))  
  
      \_, err2 := conn.Write(\[\]byte(string(buf\[0:readSize\]) + " " \+ time.Now().String()))  
      //一定要执行下面的return 才能监听到客户端主动断开,服务器端对本次conn进行close处理 dealErrorWithReturn不能达到这个效果。  
  if err2 != nil {  
         return  
  }  
   }  
}  
  
func dealErrorWithExist(err error) {  
   //有异常会停止进程  
  if err != nil {  
      log.Fatal("运行异常", err)  
   }  
}  
func dealErrorWithReturn(err error) {  
   //有异常会停止进程  
  if err != nil {  
      return  
  }  
}
package main  
  
import (  
   "fmt"  
 "log" "net" "os" "strconv" "time")  
  
func main8() {  
   clientStop := false  
 var buf \[1024\]byte  
  //关于buf大小设置 参考:https://segmentfault.com/q/1010000021544230  
  
  addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:93")  
   if err != nil {  
      log.Fatal("运行异常", err)  
   }  
   dialTCP, err := net.DialTCP("tcp", nil, addr)  
   if err != nil {  
      log.Fatal("运行异常", err)  
   }  
  
   var i int64 \= 1  
  for {  
      clientStop \= false //实时更新系统是否接到关闭信号 可能是通过界面传递到某个配置文件或表字段里  
  //到这里说明和服务器链接成功  
  \_, err2 := dialTCP.Write(\[\]byte("hello!i am client msg!" \+ strconv.FormatInt(i, 10)))  
      //此时不会结束for 一次写失败可以继续,看具体业务对异常信息的处理机制  
  if err2 != nil {  
         dialTCP.Close()  
         log.Fatal(err2)  
      }  
      //获取服务器端返回的信息  
  read, err3 := dialTCP.Read(buf\[0:\])  
      //从服务器端获取信息失败,就要及时打断进程  
  if err3 != nil {  
         dialTCP.Close()  
         log.Fatal(err3)  
      }  
  
      if clientStop {  
         // 程序通过 信号量 clientStop 控制客户端链接是否关闭  
  dialTCP.Close()  
         os.Exit(0)  
      }  
      fmt.Println("客户端主动从服务器第", i, "次获取到的信息:", string(buf\[0:read\]))  
  
      time.Sleep(time.Duration(2) \* time.Second)  
      i++  
   }  
  
}

通过测试,一个服务端,多个客户端时候,只能一个客户端链接,改造成高性能并发服务器端:

package main  
  
import (  
   "bytes"  
 "fmt" "log" "net" "runtime" "strconv" "time")  
  
/\*\*  
来自远程ip: 127.0.0.1:10568 的消息: hello!i am client !  
来自远程ip: 127.0.0.1:10568 的消息:  
来自远程ip: 127.0.0.1:10568 的消息:  
  
conn关闭后有两次空白输出,是因为三次握手机制 accept到空白内容了  
\*/  
func main7() {  
   addr, err := net.ResolveTCPAddr("tcp", "127.0.0.1:93")  
   dealErrorWithExist(err)  
   tcpListen, err := net.ListenTCP("tcp", addr)  
   dealErrorWithExist(err)  
   fmt.Println("tcp服务器端启动:  ", addr.String())  
   for {  
      conn, err := tcpListen.Accept()  
      if err != nil {  
         continue  
  }  
      //加go 编程高性能并发服务器端  
  go handlerConn(conn)  
   }  
}  
  
func handlerConn(conn net.Conn) {  
   //\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*  
  defer fmt.Println("当前用户 ", conn.RemoteAddr(), " 主动断开链接!")  
   defer conn.Close() //正常链接情况下,handlerConn不会释放出来到这里 当客户端强制断开,才会return到这里,吧当前conn关闭  
  fmt.Println("新链接了一个客户端", conn.RemoteAddr())  
   //\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*\*  
 //获取客户端信息 info,并返回 info+服务器时间  
  var buf \[1024\]byte  
 for {  
      readSize, err := conn.Read(buf\[0:\])  
      dealErrorWithReturn(err)  
      remoteAddr := conn.RemoteAddr()  
      gorid := GetGoroutineID()  
  
      fmt.Println("协程id: "+strconv.FormatUint(gorid, 10)+" 来自远程ip:", remoteAddr, " 的消息:", string(buf\[0:readSize\]))  
  
      \_, err2 := conn.Write(\[\]byte(string(buf\[0:readSize\]) + " " \+ time.Now().String()))  
      //一定要执行下面的return 才能监听到客户端主动断开,服务器端对本次conn进行close处理 dealErrorWithReturn不能达到这个效果。  
  if err2 != nil {  
         return  
  }  
   }  
}  
func GetGoroutineID() uint64 {  
   b := make(\[\]byte, 64)  
   runtime.Stack(b, false)  
   b \= bytes.TrimPrefix(b, \[\]byte("goroutine "))  
   b \= b\[:bytes.IndexByte(b, ' ')\]  
   n, \_ := strconv.ParseUint(string(b), 10, 64)  
   return n  
}  
func dealErrorWithExist(err error) {  
   //有异常会停止进程  
  if err != nil {  
      log.Fatal("运行异常", err)  
   }  
}  
func dealErrorWithReturn(err error) {  
   //有异常会停止进程  
  if err != nil {  
      return  
  }  
}

通过gorountine,每个客户端分配一个独立协程。

功能运行良好,请大佬指出不足和错误,谢谢!
另附一张goland编译linux程序的配置图:


有疑问加站长微信联系(非本文作者)

280

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK