
在golang中,网络协议已经被封装的非常完好了,想要写一个Socket的Server,我们并不用像其他语言那样需要为socket、bind、Listen、receive等一系列 *** 作头疼,只要使用Golang中自带的net包即可很方便的完成连接等 *** 作~
在这里,给出一个最最基础的基于Socket的Server的写法:
复制代码 代码如下:
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的请求,我们只要在Server端的代码的main入口中,
在 handleConnection(conn net.Conn) 这句代码的前面加上一个 go,就可以让服务器并发处理不同的ClIEnt发来的请求啦
自定义通讯协议
在上面我们做出来一个最基础的demo后,已经可以初步实现Server和ClIEnt之间的信息交流了~ 这一章我会介绍一下怎么在Server和ClIEnt之间实现一个简单的通讯协议,从而增强整个信息交流过程的稳定性。
在Server和clIEnt的交互过程中,有时候很难避免出现网络波动,而在通讯质量较差的时候,ClIEnt有可能无法将信息流一次性完整发送,最终传到Server上的信息很可能变为很多段。
如下图所示,本来应该是分条传输的Json,结果因为一些原因连接在了一起,这时候就会出现问题啦,Server端要怎么判断收到的消息是否完整呢?~
唔,答案就是这篇文章的主题啦:在Server和ClIEnt交互的时候,加入一个通讯协议(protocol),让二者的交互通过这个协议进行封装,从而使Server能够判断收到的信息是否为完整的一段。(也就是解决分包的问题)
因为主要目的是为了让Server能判断客户端发来的信息是否完整,因此整个协议的核心思路并不是很复杂:
协议的核心就是设计一个头部(headers),在ClIEnt每次发送信息的时候将header封装进去,再让Server在每次收到信息的时候按照预定格式将消息进行解析,这样根据ClIEnt传来的数据中是否包含headers,就可以很轻松的判断收到的信息是否完整了~
如果信息完整,那么就将该信息发送给下一个逻辑进行处理,如果信息不完整(缺少headers),那么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发来的信息啦~~
总结以上是内存溢出为你收集整理的Go语言基于Socket编写服务器端与客户端通信的实例全部内容,希望文章能够帮你解决Go语言基于Socket编写服务器端与客户端通信的实例所遇到的程序开发问题。
如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。
欢迎分享,转载请注明来源:内存溢出
微信扫一扫
支付宝扫一扫
评论列表(0条)