在高并发这方面,我对设备的驾驭能力已经达到了个人服务器程序开发的极限,能把一台1核1G的洋垃圾驾驭的如此轻松的,估计也没谁了。我这次实践再次证明,内存和性能时相对立的。
package main
import (
"encoding/binary"
"fmt"
"math"
"net"
"os"
//"reflect"
"bytes"
"container/list"
"time"
)
var isbncc int32 = 0
var sj_car =make([][]byte,600)//
var sj_car_able =make([]bool,600)//
var car_xue [600]int16//
var car_passenger =make([][]int32,600)//
var car_isnpc [600]bool//
var pid_driving [600]int32//以pid为索引
var xuelian = make([]int32,600) //血量数组
var xuelian_log=make([]int32,600)
var area = make([][]list.List, 1003) //各方块区域,根据区域同于储存各sj的指针
var sj = make([][]byte, 600) //各pid的sj
var lep [600]*list.Element //储存pid所在区域(area)内sj内容的指针
var logp = make([][2]int32, 600) //二维数组,切片
var heart = make([]int32, 600) //check heart心跳检测
var heartlog = make([]int32, 600) //check heart
var cn = list.New() //have been leave's isbn离开pid列表
var iplist [600]*net.UDPAddr //储存对应pid的ip
// 限制goroutine数量
var limitChan = make(chan bool, 6)
func bytetofloat(bytes []byte) float32 {
bits := binary.LittleEndian.Uint32(bytes)
return math.Float32frombits(bits)
}
func shorttobyte(isbn int16) []byte {
result := make([]byte, 2)
arcresult := make([]byte, 2)
binary.BigEndian.PutUint16(result, uint16(isbn))
arcresult[0] = result[1]
arcresult[1] = result[0]
return arcresult
}
func inttobyte(x int32) []byte {
bytesBuffer := bytes.NewBuffer([]byte{})
binary.Write(bytesBuffer, binary.BigEndian, x)
s := bytesBuffer.Bytes()
arcs := make([]byte, 4)
arcs[0] = s[3]
arcs[1] = s[2]
arcs[2] = s[1]
arcs[3] = s[0]
return arcs
}
func bytetoint(data []byte) int32 {
arcdata := make([]byte, 4)
arcdata[0] = data[3]
arcdata[1] = data[2]
arcdata[2] = data[1]
arcdata[3] = data[0]
return int32(binary.BigEndian.Uint32(arcdata))
}
func bytetoshort(data []byte) int16 {
arcdata := make([]byte, 4)
arcdata[0] = data[1]
arcdata[1] = data[0]
return int16(binary.BigEndian.Uint16(arcdata))
}
// UDP goroutine 实现并发读取UDP数据
func udpProcess(conn *net.UDPConn) {
sendbuff := make([]byte, 600*21)
data := make([]byte, 64)
for ;1==1; {
n, remoteAddr, err := conn.ReadFromUDP(data)
if err != nil {
fmt.Println("Failed To Read UDP Msg, Error: " + err.Error())
}
//str := string(data[:n]) //n is the end and the front is the began
//fmt.Println("Reveive From Client, Data: " + str)
if n == 2 {//复活的消息
ls_hited:=bytetoshort(data[:2])
if ls_hited>0 {
xuelian[ls_hited]=100
}else{//延迟测试
conn.WriteToUDP(data[:2], remoteAddr)
}
}
if n == 4 {
if cn.Front() == nil {
conn.WriteToUDP(inttobyte(isbncc), remoteAddr) //send
fmt.Println("isbn is null:", bytetoint(inttobyte(isbncc)), "# ", remoteAddr)
iplist[isbncc] = remoteAddr
isbncc++
} else {
lsbbk := cn.Front()
lskisbn := lsbbk.Value.(int32)
conn.WriteToUDP(inttobyte(lskisbn), remoteAddr) //send
fmt.Println("isbn is:", bytetoint(inttobyte(lskisbn)), "# ", remoteAddr)
iplist[lskisbn] = remoteAddr
cn.Remove(cn.Front())
}
}
if n==5 {//返回信息:cid type(-1为失败,成功为位置0为驾驶位) 字符("a"为驾驶或乘坐 "d"为下车) //乘坐、驾驶、下车 数据格式:cid type(0驾驶 1乘坐 2下车) null(len 1)
cid_s:=bytetoshort(data[:2])
cid:=int32(cid_s)
rem_pid:=-1//取远程客户端的pid
for i:=0;i<600;i++{
comp1:=(*iplist[i]).IP
comp2:=(*remoteAddr).IP
if net.IP(comp1).String()==net.IP(comp2).String(){
if (*iplist[i]).Port==(*remoteAddr).Port{
rem_pid=i
}
}
}
if rem_pid<600&&rem_pid>=0{
if sj_car_able[cid]{
d_type:=bytetoshort(data[2:4])
if d_type==0||d_type==1{
if d_type==0{
ls_able_notcon:=true
for i:=0;i<600;i++{
if car_passenger[i][0]==int32(rem_pid){
ls_able_notcon=false
}
}
if ls_able_notcon{
if car_passenger[cid][0]==-1{
car_passenger[cid][0]=int32(rem_pid)
var send_buf=make([]byte,5)
send_buf[0]=data[0]
send_buf[1]=data[1]
copy(send_buf[0:],shorttobyte(int16(0)))
send_buf[4]=byte(97)
pid_driving[rem_pid]=cid
conn.WriteToUDP(send_buf, remoteAddr)
}else{//座位上有人
var send_buf=make([]byte,5)
send_buf[0]=data[0]
send_buf[1]=data[1]
copy(send_buf[0:],shorttobyte(int16(-1)))
send_buf[4]=byte(97)
conn.WriteToUDP(send_buf, remoteAddr)
}
}
}
if d_type==1{
var wz int16
wz=-1
for i:=1;i<4;i++{
if car_passenger[rem_pid][i]==-1{
car_passenger[rem_pid][i]=int32(rem_pid)
wz=int16(i)
}
}
if wz==-1{//乘客位已满
var send_buf=make([]byte,5)
send_buf[0]=data[0]
send_buf[1]=data[1]
copy(send_buf[0:],shorttobyte(int16(-1)))
send_buf[4]=byte(97)
conn.WriteToUDP(send_buf, remoteAddr)
}else{
var send_buf=make([]byte,5)
send_buf[0]=data[0]
send_buf[1]=data[1]
copy(send_buf[0:],shorttobyte(int16(wz)))
send_buf[4]=byte(97)
pid_driving[rem_pid]=cid
car_passenger[cid][int32(wz)]=int32(rem_pid)
conn.WriteToUDP(send_buf, remoteAddr)
}
}
}else{//下车
ls_able_con:=false
ls_psid:=-1
for i:=0;i<4;i++{
if car_passenger[cid][i]==int32(rem_pid){
ls_able_con=true
ls_psid=i
}
}
if ls_able_con{
car_passenger[cid][ls_psid]=-1
var send_buf=make([]byte,5)
send_buf[0]=data[0]
send_buf[1]=data[1]
copy(send_buf[0:],shorttobyte(int16(ls_psid)))
send_buf[4]=byte(100)
pid_driving[rem_pid]=-1
conn.WriteToUDP(send_buf, remoteAddr)
}
}
}
}
}
if n == 7{//攻击人 数据格式:bhit_pid(short) num(int) 填充(byte)
if data[6] ==1 {
ls_short_hited:=bytetoshort(data[:2])
if ls_short_hited>=0 && ls_short_hited<600 {
xuelian[ls_short_hited] = xuelian[ls_short_hited] - bytetoint(data[2:6])
}
}else{//回复收到血量确定 数据格式:bhit_pid(short) xue(int) 填充(byt) 收到或未已发出命令后出现非法操作,即可对其进行违规处理
if data[6]==2{
ls_short_hited2:=bytetoshort(data[:2])
if ls_short_hited2>=0 && ls_short_hited2<600 {
if xuelian[ls_short_hited2] == bytetoint(data[2:6]){
xuelian_log[ls_short_hited2]=xuelian[ls_short_hited2]
}
}
}
}
}
var isbnl int16
if n == 21 {
isbnbytes := data[:2]
isbnl = bytetoshort(isbnbytes)
copy(sj[isbnl], data[:21]) //updata self sj
heart[isbnl]++
if iplist[isbnl] != nil {//if have been lose and no people come,then will send lose 0,but now we do not send back
if (*remoteAddr).Port == (*iplist[isbnl]).Port {//if someone come and comeback,then will not send anything
if xuelian[isbnl] != xuelian_log[isbnl]{//血量同步
conn.WriteToUDP(inttobyte(xuelian[isbnl]), remoteAddr)
}
lsbx := data[2:6]
lsbz := data[10:14]
//sendbuff := make([]byte, 600*21)
icc := 0
var px int32 = int32(bytetofloat(lsbx) / 500)
var pz int32 = int32(bytetofloat(lsbz) / 500)
if lep[isbnl] != nil {
if logp[isbnl][0]!=px + 501 || logp[isbnl][1] != pz + 501{
area[logp[isbnl][0]][logp[isbnl][1]].Remove(lep[isbnl])
lep[isbnl] = area[px+501][pz+501].PushBack(&sj[isbnl]) //updata address
}
}else{
lep[isbnl] = area[px+501][pz+501].PushBack(&sj[isbnl]) //address
}
logp[isbnl][0] = px + 501
logp[isbnl][1] = pz + 501
//putself
//put send
for i := area[px+501][pz+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++
}
for i := area[px+1+501][pz+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++
}
for i := area[px-1+501][pz+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++
}
for i := area[px+501][pz+1+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++
}
for i := area[px+501][pz-1+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++
}
for i := area[px+1+501][pz+1+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++
}
for i := area[px-1+501][pz-1+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++
}
for i := area[px+1+501][pz-1+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++
}
for i := area[px-1+501][pz+1+501].Front(); i != nil; i = i.Next() {
xh := i.Value.(*[]byte)
copy(sendbuff[icc*21:], *xh)
icc++ //icc is the count of all 21
}
conn.WriteToUDP(sendbuff[:icc*21], remoteAddr) //send
}
}else
{
fmt.Println("ex ip", iplist[isbnl], " @ ", remoteAddr)
//conn.WriteToUDP(inttobyte(int32(0)), remoteAddr) //send the cf login respon
}
}
if n == 26 {//创建载具请求
var car_id int16
for car_id=0;car_id<600;car_id++{
if !sj_car_able[car_id]{//!able表示未创建
var ls_car_sj=make([]byte,30)
copy(ls_car_sj[0:],shorttobyte(car_id))
for i:=0;i<26;i++{
ls_car_sj[i+2]=data[i]
}
copy(ls_car_sj[28:],shorttobyte(int16(0)))
copy(sj_car[int32(car_id)][0:],ls_car_sj[:30])
car_xue[car_id]=int16(10000)
sj_car_able[car_id]=true
break;
}
}
}
if n == 30{//载具控制
var isbnfc int16
isbnfc=bytetoshort(data[0:2])
if isbnfc>=0&&isbnfc<600{
if sj_car_able[isbnfc]{
copy(sj_car[isbnfc][0:],data[:30])
}
}
}
}
<-limitChan
}
func udpServer(address string) {
udpAddr, err := net.ResolveUDPAddr("udp", address)
conn, errr := net.ListenUDP("udp", udpAddr)
if conn != nil {
defer conn.Close()
}
if err != nil {
fmt.Println("Read From Connect Failed, Err :" + err.Error())
os.Exit(1)
}
if errr != nil {
fmt.Println("Read From Connect Failed, Err :" + errr.Error())
os.Exit(1)
}
for {
limitChan <- true
go udpProcess(conn)
}
}
func main() {
for i:=0;i<600;i++{
sj_car[i]=make([]byte,30)
}
for i:=0;i<600;i++{
sj_car_able[i]=false
}
for i:=0;i<600;i++{
car_xue[i]=5000
}
for i:=0;i<600;i++{
car_passenger[i]=make([]int32,4)
car_passenger[i][0]=-1
car_passenger[i][1]=-1
car_passenger[i][2]=-1
car_passenger[i][3]=-1
}
for i:=0;i<600;i++{
car_isnpc[i]=false
}
for i:=0;i<600;i++{
pid_driving[i]=-1
}
for i := 0; i < 600; i++ {
xuelian[i] = 100
}
for i := 0; i < 600; i++ {
xuelian_log[i] = 100
}
for i := 0; i < 600; i++ {
iplist[i] = nil
}
for i := 0; i < len(sj); i++ {
sj[i] = make([]byte, 21)//初始化二维数组,[600][21]
}
for i := 0; i < len(area); i++ {
area[i] = make([]list.List, 1003)
}
var ipp string = ""
fmt.Scanln(&ipp)
address := ipp
fmt.Println("success connect" + ipp)
go func() {
udpServer(address)
}()
ticker := time.NewTicker(time.Millisecond * 10)
go func() {
var icount int32 = 0
for _ = range ticker.C {
if heart[icount] == heartlog[icount] {
//is disconnected but it may come back we need ip to tick it out
if iplist[icount] != nil {
//如果存在于载具内,将其踢出车外
if pid_driving[icount]>=0 && pid_driving[icount]<600{
for i:=0;i<4;i++{
if car_passenger[pid_driving[icount]][i]==icount{
car_passenger[pid_driving[icount]][i]=-1
}
}
pid_driving[icount]=-1
}
area[logp[icount][0]][logp[icount][1]].Remove(lep[icount]) //clean the sj
fmt.Println("ip ", iplist[icount], " leave")
iplist[icount] = nil
cn.PushBack(icount)
}
}
heartlog[icount] = heart[icount]
if icount == 599 {
icount = -1
}
icount++
}
}()
fmt.Println("R ")
var t string = ""
fmt.Scanln(&t)
fmt.Println("t ")
os.Exit(1)
}
此程序在初始化基本数据结构的时候就会占用20到30M的内存,启动监听后,第一个玩家以50帧/s进入游戏,内存会扩充至原来的2倍,也就是50多M。随着玩家的数量增加临时变量所产生的Gc会以2M/人渐变增长。和c#对比,go使用的是指针,在索引取数据的时候性能比c#高,但是基本的数据结构会带来更多的内存占用。而且go的Gc比c#的Gc更难清理。这个帧同步脚本支持600人同时在线,脚本仅有位置移动同步功能,其他功能未完善。