学习golang的时间断断续续加起来也有将近一个月了,这期间都是偶看翻几页书,没有写过实际的代码.最近做一个app项目,是一个展示类的软件,当客户要看某个图片时首先向服务器发出一个请求,比对图片的版本,如果版本与本地一致,则直接显示,如果版本落后了则由服务器将最新的版本发送给客户端.
对服务器的需求就是一个简单的版本比对和文件传输,于是打算用go去实现,正好也可以练练手.
在设计上,受到以往框架设计的影响,还是使用了wpacket,rpacket和共享buffer这个方案,不同的地方是go的网络API不支持gatherio,所以buffer没有被实现为bufferlist,而是一整块的连续内存,当buffer空间不足时需要手动的去扩展内存.
下面说点与以往用C设计网络框架不同的地方,对于已经习惯了使用异步回调方式来使用网络的我,刚开始用go来做一个网络框架还是稍有不适.go的网络API都是同步的,也不存在select,epoll,poll这类东西,这也就意味着需要使用goroutine来实现并发.
我的做法是这样的,首先定义了一个类型Tcpconnection
typeTcpsessionstruct{ Connnet.Conn Packet_quechaninterface{} Send_quechan*packet.Wpacket rawbool send_closebool }其中Conn成员是连接对象,Packet_que是一个管道,用于接收收到的网络包和连接事件.Send_que是另一个管道,用来将需要发送的数据包传给sender。
funcNewTcpSession(connnet.Conn,rawbool)(*Tcpsession){ session:=&Tcpsession{Conn:conn,Packet_que:make(chaninterface{},1024),Send_que:make(chan*packet.Wpacket,1024),raw:raw,send_close:false} ifraw{ godorecv_raw(session) }else{ godorecv(session) } godosend(session) returnsession }在建立一个新连接之后,启动两个goroutine分别用于执行recv和send.
funcdorecv_raw(session*Tcpsession){ for{ recvbuf:=make([]byte,packet.Max_bufsize) _,err:=session.Conn.Read(recvbuf) iferr!=nil{ session.Packet_que<-"rclose" return } rpk:=packet.NewRpacket(packet.NewBufferByBytes(recvbuf),true) session.Packet_que<-rpk } } funcdosend(session*Tcpsession){ for{ wpk,ok:=<-session.Send_que if!ok{ return } _,err:=session.Conn.Write(wpk.Buffer().Bytes()) iferr!=nil{ session.send_close=true return } ifwpk.Fn_sendfinish!=nil{ wpk.Fn_sendfinish(session,wpk) } } }dorecv的工就只是简单的接收数据,将收到的原始数据打包成rpacket,然后写到管道Packet_que中。dosend则是从Send_que中提取出要待发送的wpacket,然后send出去.
然后来看下主过程:
funcmain(){ service:=":8010" tcpAddr,err:=net.ResolveTCPAddr("tcp4",service) iferr!=nil{ fmt.Printf("ResolveTCPAddr") } listener,err:=net.ListenTCP("tcp",tcpAddr) iferr!=nil{ fmt.Printf("ListenTCP") } for{ conn,err:=listener.Accept() iferr!=nil{ continue } session:=tcpsession.NewTcpSession(conn,true) fmt.Printf("aclientcomming\n") gotcpsession.ProcessSession(session,process_client,session_close) } }主过程等待在listen上,每当接受一个新的连接就用这个连接作为参数创建一个Tcpsession,然后启动一个goroutine运行ProcessSession。
funcProcessSession(tcpsession*Tcpsession,process_packetfunc(*Tcpsession,*packet.Rpacket),session_closefunc(*Tcpsession)){ for{ msg,ok:=<-tcpsession.Packet_que if!ok{ fmt.Printf("clientdisconnect\n") return } switchmsg.(type){ case*packet.Rpacket: rpk:=msg.(*packet.Rpacket) process_packet(tcpsession,rpk) casestring: str:=msg.(string) ifstr=="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程序差.
项目地址
本文内容总结:
原文链接:https://www.cnblogs.com/sniperHW/p/3581249.html