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常用正则表达式符号浅析
Aug 13 Python
Python多进程机制实例详解
Jul 02 Python
python flask实现分页效果
Jun 27 Python
python虚拟环境的安装配置图文教程
Oct 20 Python
彻底理解Python list切片原理
Oct 27 Python
selenium+python实现1688网站验证码图片的截取功能
Aug 14 Python
python实现浪漫的烟花秀
Jan 30 Python
pandas 对日期类型数据的处理方法详解
Aug 08 Python
pytorch多进程加速及代码优化方法
Aug 19 Python
如何用Python 加密文件
Sep 10 Python
python 合并多个excel中同名的sheet
Jan 22 Python
如何利用pygame实现打飞机小游戏
May 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
JavaScript性能优化 创建文档碎片(document.createDocumentFragment)
2010/07/13 Javascript
javascript函数以及基础写法100多条实用整理
2013/01/13 Javascript
各种页面定时跳转(倒计时跳转)代码总结
2013/10/24 Javascript
jquery和javascript中如何将一元素的内容赋给另一元素
2014/01/09 Javascript
js二维数组定义和初始化的三种方法总结
2014/03/03 Javascript
js中取得变量绝对值的方法
2015/01/03 Javascript
jQuery在页面加载时动态修改图片尺寸的方法
2015/03/20 Javascript
jQuery实现的调整表格行tr上下顺序
2016/01/10 Javascript
jQuery轻松实现表格的隔行变色和点击行变色的实例代码
2016/05/09 Javascript
利用Js的console对象,在控制台打印调式信息测试Js的实现
2016/11/26 Javascript
学习使用Bootstrap输入框、导航、分页等常用组件
2017/05/11 Javascript
浅谈vue实现数据监听的函数 Object.defineProperty
2017/06/08 Javascript
webpack配置sass模块的加载的方法
2017/07/30 Javascript
JS实现读取xml内容并输出到div中的方法示例
2018/04/19 Javascript
微信小程序简单的canvas裁剪图片功能详解
2019/07/12 Javascript
Vue 权限控制的两种方法(路由验证)
2019/08/16 Javascript
linux服务器快速卸载安装node环境(简单上手)
2021/02/22 Javascript
Webpack3+React16代码分割的实现
2021/03/03 Javascript
Python中的默认参数详解
2015/06/24 Python
使用Python内置的模块与函数进行不同进制的数的转换
2016/03/12 Python
从零开始学Python第八周:详解网络编程基础(socket)
2016/12/14 Python
使用apidoc管理RESTful风格Flask项目接口文档方法
2018/02/07 Python
PyQt5每天必学之拖放事件
2020/08/27 Python
Python中对数组集进行按行打乱shuffle的方法
2018/11/08 Python
python绘制散点图并标记序号的方法
2018/12/11 Python
mac系统下Redis安装和使用步骤详解
2019/07/09 Python
使用Python的datetime库处理时间(RPA流程)
2019/11/24 Python
初中地理教学反思
2014/01/11 职场文书
广告业务员岗位职责
2014/02/06 职场文书
清明节网上祭英烈活动总结
2014/04/30 职场文书
房地产资料员岗位职责
2014/07/02 职场文书
2014年四风问题自我剖析材料
2014/09/15 职场文书
四风问题个人对照检查剖析材料
2014/09/27 职场文书
办公用房租赁协议书
2014/11/29 职场文书
机械原理课程设计心得体会
2016/01/15 职场文书
人生哲理妙语30条:淡写流年,笑过人生
2019/09/04 职场文书