Go语言基于Socket编写服务器端与客户端通信的实例
在golang中,网络协议已经被封装的非常完好了,想要写一个Socket的Server,我们并不用像其他语言那样需要为socket、bind、listen、receive等一系列操作头疼,只要使用Golang中自带的net包即可很方便的完成连接等操作~ 复制代码 代码如下: package main import ( "fmt" "net" "log" "os" ) func main() { //建立socket,监听端口 netListen,err := net.Listen("tcp","localhost:1024") CheckError(err) defer netListen.Close() Log("Waiting for clients") for { conn,err := netListen.Accept() if err != nil { continue } Log(conn.RemoteAddr().String()," tcp connect success") handleConnection(conn) } } //处理连接 func handleConnection(conn net.Conn) { buffer := make([]byte,2048) for { n,err := conn.Read(buffer) if err != nil { Log(conn.RemoteAddr().String()," connection error: ",err) return } Log(conn.RemoteAddr().String(),"receive data string:n",string(buffer[:n])) } } func Log(v ...interface{}) { log.Println(v...) } func CheckError(err error) { if err != nil { fmt.Fprintf(os.Stderr,"Fatal error: %s",err.Error()) os.Exit(1) } } 唔,抛除Go语言里面10行代码有5行error的蛋疼之处,你可以看到,Server想要建立并接受一个Socket,其核心流程就是 复制代码 代码如下: netListen,"localhost:1024") conn,err := netListen.Accept() n,err := conn.Read(buffer) 这三步,通过Listen、Accept 和Read,我们就成功的绑定了一个端口,并能够读取从该端口传来的内容~ Server写好之后,接下来就是Client方面啦,我手写一个HelloWorld给大家: 复制代码 代码如下: package main import ( "fmt" "net" "os" ) func sender(conn net.Conn) { words := "hello world!" conn.Write([]byte(words)) fmt.Println("send over") } func main() { server := "127.0.0.1:1024" tcpAddr,err := net.ResolveTCPAddr("tcp4",server) if err != nil { fmt.Fprintf(os.Stderr,err.Error()) os.Exit(1) } conn,err := net.DialTCP("tcp",nil,tcpAddr) if err != nil { fmt.Fprintf(os.Stderr,err.Error()) os.Exit(1) } fmt.Println("connect success") sender(conn) } 可以看到,Client这里的关键在于 复制代码 代码如下: tcpAddr,server) conn,tcpAddr) 这两步,主要是负责解析端口和连接~ 写好Server和Client之后,让我们运行一下看看:~~ 成功运行,Console出现Server等待连接的提示: Server端成功的收到了我们的Hello-World啦,至于后面的那行红字,则是断开连接的提示~ 到这里,一个最基础的使用Socket的Server-Client框架就出来啦~ 自定义通讯协议 唔,答案就是这篇文章的主题啦:在Server和Client交互的时候,加入一个通讯协议(protocol),让二者的交互通过这个协议进行封装,从而使Server能够判断收到的信息是否为完整的一段。(也就是解决分包的问题) 下面是协议部分的代码,主要分为数据的封装(Enpack)和解析(Depack)两个部分,其中Enpack用于Client端将传给服务器的数据封装,而Depack是Server用来解析数据,其中Const部分用于定义Headers,HeaderLength则是Headers的长度,用于后面Server端的解析。这里要说一下ConstMLength,这里代表Client传入信息的长度,因为在golang中,int转为byte后会占4长度的空间,因此设定为4。每次Client向Server发送信息的时候,除了将Headers封装进去意以外,还会将传入信息的长度也封装进去,这样可以方便Server进行解析和校验。 复制代码 代码如下: //通讯协议处理 package protocol import ( "bytes" "encoding/binary" ) const ( ConstHeader = "Headers" ConstHeaderLength = 7 ConstMLength = 4 ) //封包 func Enpack(message []byte) []byte { return append(append([]byte(ConstHeader),IntToBytes(len(message))...),message...) } //解包 func Depack(buffer []byte,readerChannel chan []byte) []byte { length := len(buffer) var i int for i = 0; i < length; i = i + 1 { if length < i+ConstHeaderLength+ConstMLength { break } if string(buffer[i:i+ConstHeaderLength]) == ConstHeader { messageLength := BytesToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength]) if length < i+ConstHeaderLength+ConstLength+messageLength { break } data := buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength] readerChannel <- data } } if i == length { return make([]byte,0) } return buffer[i:] } //整形转换成字节 func IntToBytes(n int) []byte { x := int32(n) bytesBuffer := bytes.NewBuffer([]byte{}) binary.Write(bytesBuffer,binary.BigEndian,x) return bytesBuffer.Bytes() } //字节转换成整形 func BytesToInt(b []byte) int { bytesBuffer := bytes.NewBuffer(b) var x int32 binary.Read(bytesBuffer,&x) return int(x) } 协议写好之后,接下来就是在Server和Client的代码中应用协议啦,下面是Server端的代码,主要负责解析Client通过协议发来的信息流: 复制代码 代码如下: package main import ( "protocol" "fmt" "net" "os" ) func main() { netListen,"localhost:6060") CheckError(err) defer netListen.Close() Log("Waiting for clients") for { conn,err := netListen.Accept() if err != nil { continue } //timeouSec :=10 //conn. Log(conn.RemoteAddr().String()," tcp connect success") go handleConnection(conn) } } func handleConnection(conn net.Conn) { // 缓冲区,存储被截断的数据 tmpBuffer := make([]byte,0) //接收解包 readerChannel := make(chan []byte,16) go reader(readerChannel) buffer := make([]byte,1024) for { n,err := conn.Read(buffer) if err != nil { Log(conn.RemoteAddr().String(),err) return } tmpBuffer = protocol.Depack(append(tmpBuffer,buffer[:n]...),readerChannel) } defer conn.Close() } func reader(readerChannel chan []byte) { for { select { case data := <-readerChannel: Log(string(data)) } } } func Log(v ...interface{}) { fmt.Println(v...) } func CheckError(err error) { if err != nil { fmt.Fprintf(os.Stderr,err.Error()) os.Exit(1) } } 然后是Client端的代码,这个简单多了,只要给信息封装一下就可以了~: 复制代码 代码如下: package main import ( "protocol" "fmt" "net" "os" "time" "strconv" ) func send(conn net.Conn) { for i := 0; i < 100; i++ { session:=GetSession() words := "{"ID":"+ strconv.Itoa(i) +"","Session":"+session +"2015073109532345","Meta":"golang","Content":"message"}" conn.Write(protocol.Enpacket([]byte(words))) } fmt.Println("send over") defer conn.Close() } func GetSession() string{ gs1:=time.Now().Unix() gs2:=strconv.FormatInt(gs1,10) return gs2 } func main() { server := "localhost:6060" tcpAddr,server) if err != nil { fmt.Fprintf(os.Stderr,err.Error()) os.Exit(1) } conn,tcpAddr) if err != nil { fmt.Fprintf(os.Stderr,err.Error()) os.Exit(1) } fmt.Println("connect success") send(conn) } 这样我们就成功实现在Server和Client之间建立一套自定义的基础通讯协议啦,让我们运行一下看下效果: 成功识别每一条Client发来的信息啦~~ (编辑:李大同) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |