热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

这篇文章主要介绍了Go语言基于Socket编写服务器端与客户端通信的实例,包括实现基于自定义通讯协议的Socket通信,需要的朋友可以参考下
在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等待连接的提示:

2016219150512234.png (1016×819)

2016219150553909.png (678×561)

2016219150608465.png (961×778)

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

2016219150627822.jpg (585×484)

唔,答案就是这篇文章的主题啦:在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 if length break
}
if string(buffer[i:i+ConstHeaderLength]) == ConstHeader {
messageLength := BytesToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength])
if length 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之间建立一套自定义的基础通讯协议啦,让我们运行一下看下效果:

2016219150649137.jpg (633×520)

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

推荐阅读
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • 阿,里,云,物,联网,net,core,客户端,czgl,aliiotclient, ... [详细]
  • [译]技术公司十年经验的职场生涯回顾
    本文是一位在技术公司工作十年的职场人士对自己职业生涯的总结回顾。她的职业规划与众不同,令人深思又有趣。其中涉及到的内容有机器学习、创新创业以及引用了女性主义者在TED演讲中的部分讲义。文章表达了对职业生涯的愿望和希望,认为人类有能力不断改善自己。 ... [详细]
  • 本文介绍了高校天文共享平台的开发过程中的思考和规划。该平台旨在为高校学生提供天象预报、科普知识、观测活动、图片分享等功能。文章分析了项目的技术栈选择、网站前端布局、业务流程、数据库结构等方面,并总结了项目存在的问题,如前后端未分离、代码混乱等。作者表示希望通过记录和规划,能够理清思路,进一步完善该平台。 ... [详细]
  • 在Docker中,将主机目录挂载到容器中作为volume使用时,常常会遇到文件权限问题。这是因为容器内外的UID不同所导致的。本文介绍了解决这个问题的方法,包括使用gosu和suexec工具以及在Dockerfile中配置volume的权限。通过这些方法,可以避免在使用Docker时出现无写权限的情况。 ... [详细]
  • t-io 2.0.0发布-法网天眼第一版的回顾和更新说明
    本文回顾了t-io 1.x版本的工程结构和性能数据,并介绍了t-io在码云上的成绩和用户反馈。同时,还提到了@openSeLi同学发布的t-io 30W长连接并发压力测试报告。最后,详细介绍了t-io 2.0.0版本的更新内容,包括更简洁的使用方式和内置的httpsession功能。 ... [详细]
  • 使用在线工具jsonschema2pojo根据json生成java对象
    本文介绍了使用在线工具jsonschema2pojo根据json生成java对象的方法。通过该工具,用户只需将json字符串复制到输入框中,即可自动将其转换成java对象。该工具还能解析列表式的json数据,并将嵌套在内层的对象也解析出来。本文以请求github的api为例,展示了使用该工具的步骤和效果。 ... [详细]
  • 图解redis的持久化存储机制RDB和AOF的原理和优缺点
    本文通过图解的方式介绍了redis的持久化存储机制RDB和AOF的原理和优缺点。RDB是将redis内存中的数据保存为快照文件,恢复速度较快但不支持拉链式快照。AOF是将操作日志保存到磁盘,实时存储数据但恢复速度较慢。文章详细分析了两种机制的优缺点,帮助读者更好地理解redis的持久化存储策略。 ... [详细]
  • http:my.oschina.netleejun2005blog136820刚看到群里又有同学在说HTTP协议下的Get请求参数长度是有大小限制的,最大不能超过XX ... [详细]
  • 本文详细介绍了解决全栈跨域问题的方法及步骤,包括添加权限、设置Access-Control-Allow-Origin、白名单等。通过这些操作,可以实现在不同服务器上的数据访问,并解决后台报错问题。同时,还提供了解决second页面访问数据的方法。 ... [详细]
  • 本文介绍了RPC框架Thrift的安装环境变量配置与第一个实例,讲解了RPC的概念以及如何解决跨语言、c++客户端、web服务端、远程调用等需求。Thrift开发方便上手快,性能和稳定性也不错,适合初学者学习和使用。 ... [详细]
  • 计算机存储系统的层次结构及其优势
    本文介绍了计算机存储系统的层次结构,包括高速缓存、主存储器和辅助存储器三个层次。通过分层存储数据可以提高程序的执行效率。计算机存储系统的层次结构将各种不同存储容量、存取速度和价格的存储器有机组合成整体,形成可寻址存储空间比主存储器空间大得多的存储整体。由于辅助存储器容量大、价格低,使得整体存储系统的平均价格降低。同时,高速缓存的存取速度可以和CPU的工作速度相匹配,进一步提高程序执行效率。 ... [详细]
  • 本文介绍了如何使用iptables添加非对称的NAT规则段,以实现内网穿透和端口转发的功能。通过查阅相关文章,得出了解决方案,即当匹配的端口在映射端口的区间内时,可以成功进行端口转发。详细的操作步骤和命令示例也在文章中给出。 ... [详细]
  • 本文介绍了Web学习历程记录中关于Tomcat的基本概念和配置。首先解释了Web静态Web资源和动态Web资源的概念,以及C/S架构和B/S架构的区别。然后介绍了常见的Web服务器,包括Weblogic、WebSphere和Tomcat。接着详细讲解了Tomcat的虚拟主机、web应用和虚拟路径映射的概念和配置过程。最后简要介绍了http协议的作用。本文内容详实,适合初学者了解Tomcat的基础知识。 ... [详细]
author-avatar
时尚淘宝交__流站
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有