Go语言基于Socket编写服务器端与客户端通信的实例


Posted in Python onFebruary 19, 2016

在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, err := net.Listen("tcp", "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, "Fatal error: %s", err.Error())  

        os.Exit(1)  

    }  

  

    conn, err := net.DialTCP("tcp", nil, tcpAddr)  

    if err != nil {  

        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())  

        os.Exit(1)  

    }  

  

  

    fmt.Println("connect success")  

    sender(conn)  

  

} 

可以看到,Client这里的关键在于
tcpAddr, err := net.ResolveTCPAddr("tcp4", server) 
conn, err := net.DialTCP("tcp", nil, tcpAddr) 

这两步,主要是负责解析端口和连接~
写好Server和Client之后,让我们运行一下看看:~~
成功运行,Console出现Server等待连接的提示:

Go语言基于Socket编写服务器端与客户端通信的实例

Go语言基于Socket编写服务器端与客户端通信的实例

Go语言基于Socket编写服务器端与客户端通信的实例

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端要怎么判断收到的消息是否完整呢?~

Go语言基于Socket编写服务器端与客户端通信的实例

 唔,答案就是这篇文章的主题啦:在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, binary.BigEndian, &x)  

  

    return int(x)  

} 

        协议写好之后,接下来就是在Server和Client的代码中应用协议啦,下面是Server端的代码,主要负责解析Client通过协议发来的信息流:
package main    

    

import (    

    "protocol"    

    "fmt"    

    "net"    

    "os"    

)    

    

func main() {    

    netListen, err := net.Listen("tcp", "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(), " connection error: ", 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, "Fatal error: %s", 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, err := net.ResolveTCPAddr("tcp4", server)    

    if err != nil {    

        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())    

        os.Exit(1)    

    }    

    

    conn, err := net.DialTCP("tcp", nil, tcpAddr)    

    if err != nil {    

        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())    

        os.Exit(1)    

    }    

    

    

    fmt.Println("connect success")    

    send(conn)    

    

    

    

}   

这样我们就成功实现在Server和Client之间建立一套自定义的基础通讯协议啦,让我们运行一下看下效果:

Go语言基于Socket编写服务器端与客户端通信的实例

成功识别每一条Client发来的信息啦~~

Python 相关文章推荐
Python实现的摇骰子猜大小功能小游戏示例
Dec 18 Python
python机器学习理论与实战(五)支持向量机
Jan 19 Python
对python中类的继承与方法重写介绍
Jan 20 Python
Python read函数按字节(字符)读取文件的实现
Jul 03 Python
K最近邻算法(KNN)---sklearn+python实现方式
Feb 24 Python
深入浅析Python 命令行模块 Click
Mar 11 Python
python中导入 train_test_split提示错误的解决
Jun 19 Python
python报错: 'list' object has no attribute 'shape'的解决
Jul 15 Python
Python内置函数property()如何使用
Sep 01 Python
Pytorch实现图像识别之数字识别(附详细注释)
May 11 Python
tensorflow中的梯度求解及梯度裁剪操作
May 26 Python
使用Python开发冰球小游戏
Apr 30 Python
使用C#配合ArcGIS Engine进行地理信息系统开发
Feb 19 #Python
Python中使用OpenCV库来进行简单的气象学遥感影像计算
Feb 19 #Python
Python实现以时间换空间的缓存替换算法
Feb 19 #Python
Python使用爬虫猜密码
Feb 19 #Python
使用Python简单的实现树莓派的WEB控制
Feb 18 #Python
在Ubuntu系统下安装使用Python的GUI工具wxPython
Feb 18 #Python
以一个投票程序的实例来讲解Python的Django框架使用
Feb 18 #Python
You might like
.htaccess文件保护实例讲解
2011/02/06 PHP
php实现购物车产品删除功能(2)
2020/07/23 PHP
学习YUI.Ext 第六天--关于树TreePanel(Part 2异步获取节点)
2007/03/10 Javascript
再谈ie和firefox下的document.all属性
2009/10/21 Javascript
extjs之去除s.gif的影响
2010/12/25 Javascript
javascript 事件处理、鼠标拖动效果实现方法详解
2012/05/11 Javascript
node.js中的http.request.end方法使用说明
2014/12/10 Javascript
js+css实现回到顶部按钮(back to top)
2016/03/02 Javascript
js判断复选框是否选中及选中个数的实现代码
2016/05/30 Javascript
vue.js指令v-model使用方法
2017/03/20 Javascript
jquery dataTable 获取某行数据
2017/05/05 jQuery
详解Vue.js搭建路由报错 router.map is not a function
2017/06/27 Javascript
浅谈Angular2 模块懒加载的方法
2017/10/04 Javascript
form表单数据封装成json格式并提交给服务器的实现方法
2017/12/14 Javascript
Node.js实现mysql连接池使用事务自动回收连接的方法示例
2018/02/03 Javascript
详解vue-cli项目中的proxyTable跨域问题小结
2018/02/09 Javascript
小程序的上传文件接口的注意要点解析
2019/09/17 Javascript
使用zrender.js绘制体温单效果
2019/10/31 Javascript
微信小程序自定义导航栏(模板化)
2019/11/15 Javascript
Vue切换组件实现返回后不重置数据,保留历史设置操作
2020/07/21 Javascript
基于vue实现简易打地鼠游戏
2020/08/21 Javascript
Python内置的字符串处理函数详细整理(覆盖日常所用)
2014/08/19 Python
Python批量重命名同一文件夹下文件的方法
2015/05/25 Python
python 遍历目录(包括子目录)下所有文件的实例
2018/07/11 Python
python中bs4.BeautifulSoup的基本用法
2019/07/27 Python
python实现按键精灵找色点击功能教程,使用pywin32和Pillow库
2020/06/04 Python
表单button的outline在firefox浏览器下的问题
2012/12/24 HTML / CSS
Quiksilver荷兰官方网站:冲浪和滑雪板
2019/11/16 全球购物
介绍JAVA 中的Collection FrameWork(及如何写自己的数据结构)
2014/10/31 面试题
园林设计师自荐信
2013/11/18 职场文书
平安建设汇报材料
2014/12/29 职场文书
五星级酒店前台接待岗位职责
2015/04/02 职场文书
反邪教警示教育活动总结
2015/05/09 职场文书
六年级情感作文之500字
2019/10/23 职场文书
导游词之京东大峡谷旅游区
2019/10/29 职场文书
祝福语集锦:给妹妹结婚的祝福语
2019/12/18 职场文书