golang初体验
本文内容纲要:
学习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