分享
  1. 首页
  2. 文章

golang初体验

sniperHW · · 6411 次点击 · · 开始浏览
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

学习golang的时间断断续续加起来也有将近一个月了,这期间都是偶看翻几页书,没有写过实际的代码.最近做一个app项目,是一个展示类 的软件,当客户要看某个图片时首先向服务器发出一个请求,比对图片的版本,如果版本与本地一致,则直接显示,如果版本落后了则由服务 器将最新的版本发送给客户端.

对服务器的需求就是一个简单的版本比对和文件传输,于是打算用go去实现,正好也可以练练手.

在设计上,受到以往框架设计的影响,还是使用了wpacket,rpacket和共享buffer这个方案,不同的地方是go的网络API不支持 gather io,所以buffer没有被实现为buffer list,而是一整块的连续内存,当buffer空间不足时需要手动的去扩展内存.

下面说点与以往用C设计网络框架不同的地方,对于已经习惯了使用异步回调方式来使用网络的我,刚开始用go来做一个网络框架还是稍有不适. go的网络API都是同步的,也不存在select,epoll,poll这类东西,这也就意味着需要使用goroutine来实现并发.

我的做法是这样的,首先定义了一个类型Tcpconnection

type Tcpsession struct{
 Conn net.Conn
 Packet_que chan interface{}
 Send_que chan *packet.Wpacket
 raw bool
 send_close bool
}

其中Conn成员是连接对象,Packet_que是一个管道,用于接收收到的网络包和连接事件.Send_que是另一个管道,用来将需要发送 的数据包传给sender。

func NewTcpSession(conn net.Conn,raw bool)(*Tcpsession){
 session := &Tcpsession{Conn:conn,Packet_que:make(chan interface{},1024),Send_que:make(chan *packet.Wpacket,1024),raw:raw,send_close:false}
 if raw{
 go dorecv_raw(session)
 }else{
 go dorecv(session)
 }
 go dosend(session)
 return session
}

在建立一个新连接之后,启动两个goroutine分别用于执行recv和send.

func dorecv_raw(session *Tcpsession){
 for{
 recvbuf := make([]byte,packet.Max_bufsize)
 _,err := session.Conn.Read(recvbuf)
 if err != nil {
 session.Packet_que <- "rclose"
 return
 }
 rpk := packet.NewRpacket(packet.NewBufferByBytes(recvbuf),true)
 session.Packet_que <- rpk
 }
}
func dosend(session *Tcpsession){
 for{
 wpk,ok := <-session.Send_que
 if !ok {
 return
 }
 _,err := session.Conn.Write(wpk.Buffer().Bytes())
 if err != nil {
 session.send_close = true
 return
 }
 if wpk.Fn_sendfinish != nil{
 wpk.Fn_sendfinish(session,wpk)
 }
 }
}

dorecv的工就只是简单的接收数据,将收到的原始数据打包成rpacket,然后写到管道Packet_que中。 dosend则是从'Send_que'中提取出要待发送的wpacket,然后send出去.

然后来看下主过程:

func main(){
 service := ":8010"
 tcpAddr,err := net.ResolveTCPAddr("tcp4", service)
 if err != nil{
 fmt.Printf("ResolveTCPAddr")
 }
 listener, err := net.ListenTCP("tcp", tcpAddr)
 if err != nil{
 fmt.Printf("ListenTCP")
 }
 for {
 conn, err := listener.Accept()
 if err != nil {
 continue
 }
 session := tcpsession.NewTcpSession(conn,true)
 fmt.Printf("a client comming\n")
 go tcpsession.ProcessSession(session,process_client,session_close)
 }
}

主过程等待在listen上,每当接受一个新的连接就用这个连接作为参数创建一个Tcpsession,然后启动一个 goroutine运行ProcessSession

func ProcessSession(tcpsession *Tcpsession,process_packet func (*Tcpsession,*packet.Rpacket),session_close func (*Tcpsession)){
 for{
 msg,ok := <- tcpsession.Packet_que
 if !ok {
 fmt.Printf("client disconnect\n")
 return
 }
 switch msg.(type){
 case * packet.Rpacket:
 rpk := msg.(*packet.Rpacket)
 process_packet(tcpsession,rpk)
 case string:
 str := msg.(string)
 if str == "rclose"{
 session_close(tcpsession)
 close(tcpsession.Packet_que)
 close(tcpsession.Send_que)
 tcpsession.Conn.Close()
 return
 }
 }
 }
}

ProcessSession的工作就是不断的从Packet_que中取出消息,如果消息是一个rpacket就回调使用者传进来的process_packet函数。 如果是一个网络连接断开的事件则清理这个Tcpsession.

对每个连接,使用3个goroutine,2个channel,一个goroutine用于发送,一个用于接收和拆包,一个用于处理数据包.这个模式基本上就是标准的 go模式了.至于性能,在测试程序中我没有启用goroutine在多核心上运行的特性,也就说整个进程就是一个单线程的程序.其效率并不比实现同样功能 的C程序差.

项目地址


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

本文来自:博客园

感谢作者:sniperHW

查看原文:golang初体验

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

关注微信
6411 次点击
暂无回复
添加一条新回复 (您需要 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传

用户登录

没有账号?注册
(追記) (追記ここまで)

今日阅读排行

    加载中
(追記) (追記ここまで)

一周阅读排行

    加载中

关注我

  • 扫码关注领全套学习资料 关注微信公众号
  • 加入 QQ 群:
    • 192706294(已满)
    • 731990104(已满)
    • 798786647(已满)
    • 729884609(已满)
    • 977810755(已满)
    • 815126783(已满)
    • 812540095(已满)
    • 1006366459(已满)
    • 692541889

  • 关注微信公众号
  • 加入微信群:liuxiaoyan-s,备注入群
  • 也欢迎加入知识星球 Go粉丝们(免费)

给该专栏投稿 写篇新文章

每篇文章有总共有 5 次投稿机会

收入到我管理的专栏 新建专栏