GO语言网络编程
socket编程
Socket是BSD UNIX的进程通信机制,通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。Socket可以理解为TCP/IP网络的API,它定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。电脑上运行的应用程序通常通过”套接字”向网络发出请求或者应答网络请求。
Socket是应用层与TCP/IP协议族通信的中间软件抽象层。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket后面,对用户来说只需要调用Socket规定的相关函数,让Socket去组织符合指定的协议数据然后进行通信。
GO语言实现TCP通信
TCP协议
TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议,数据像水流一样传输,会存在黏包问题。
TCP服务端
一个TCP服务端可以同时连接很多个客户端,Go语言中创建多个goroutine实现并发非常方便和高效,所以可以每建立一次链接就创建一个goroutine去处理。
TCP服务端程序的处理流程:
- 监听端口
- 接收客户端请求建立链接
- 创建goroutine处理链接
TCP服务端:
//TCP server端
func process(conn net.Conn) {
defer conn.Close() //关闭连接
for {
reader := bufio.NewReader(conn)
var buf [128]byte
n,err := reader.Read(buf[:]) //读取数据
if err != nil{
fmt.Println("连接客户端失败,错误信息:",err)
}
recvStr := string(buf[:n])
fmt.Println("收到客户端信息:",recvStr)
conn.Write([]byte(recvStr)) //发送数据
}
}
func main() {
listen,err := net.Listen("tcp","127.0.0.1:8888")
if err != nil{
fmt.Println("监听失败,错误:",err)
return
}
for {
conn,err := listen.Accept() //建立连接
if err!= nil{
fmt.Println("建立连接失败,错误:",err)
continue
}
go process(conn) //启动一个goroutine处理连接
}
}
TCP客户端
一个TCP客户端进行TCP通信的流程如下:
- 建立与服务端的链接
- 进行数据收发
- 关闭链接
TCP客户端:
//客户端
func main() {
conn ,err := net.Dial("tcp","127.0.0.1:8888")
if err != nil {
fmt.Println("连接失败,错误:",err)
return
}
defer conn.Close()
inputReader := bufio.NewReader(os.Stdout)
for {
input, _ := inputReader.ReadString('\n') //读取用户输入
inputInfo := strings.Trim(input,"\r\n")
if strings.ToUpper(inputInfo) == "q"{
return //如果输入q就退出
}
_,err = conn.Write([]byte(inputInfo)) //发送数据
if err != nil{
return
}
buf := [512]byte{}
n,err := conn.Read(buf[:])
if err != nil{
fmt.Println("接受失败,错误:",err)
return
}
fmt.Println(string(buf[:n]))
}
}
先启动server,后启动client:
$go run main.go
我是客户端
我是客户端
$go run main.go
收到客户端信息: 我是客户端
GO语言实现UDP通信
UDp协议
UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。
UDP服务端
//服务端
func main() {
listen,err := net.ListenUDP("udp",&net.UDPAddr{
IP:net.IPv4(0,0,0,0),
Port:8888,
})
if err != nil{
fmt.Println("监听失败,错误:",err)
return
}
defer listen.Close()
for {
var data [1024]byte
n,addr,err := listen.ReadFromUDP(data[:])
if err != nil{
fmt.Println("接收udp数据失败,错误:",err)
continue
}
fmt.Printf("data:%v addr:%v count:%v\n", string(data[:n]), addr, n)
_ ,err = listen.WriteToUDP(data[:n],addr) //发送数据
if err != nil{
fmt.Println("发送数据失败,错误:",err)
continue
}
}
}
UDP客户端
//客户端
func main() {
socket,err := net.DialUDP("udp",nil,&net.UDPAddr{
IP:net.IPv4(0,0,0,0),
Port:8888,
})
if err != nil{
fmt.Println("连接服务器失败,错误:",err)
return
}
defer socket.Close()
sendData := []byte("hello world!")
_,err = socket.Write(sendData)
if err != nil{
fmt.Println("发送数据失败,错误:",err)
return
}
data := make([]byte,4096)
n,remoteAddr,err := socket.ReadFromUDP(data)
if err != nil{
fmt.Println("接受数据失败,错误:",err)
return
}
fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n)
}
先启动server,后启动client:
$go run main.go
recv:hello world! addr:127.0.0.1:8888 count:12
$go run main.go
data:hello world! addr:127.0.0.1:51222 count:12
HTTP客户端和服务端
HTTP协议
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。
HTTP服务端
net/http包是对net包的进一步封装,专门用来处理HTTP协议的数据。
// http server
func sayHi(w http.ResponseWriter,r *http.Request) {
fmt.Fprintln(w,"你好,ares!")
}
func main() {
http.HandleFunc("/",sayHi)
err := http.ListenAndServe(":8888",nil)
if err != nil{
fmt.Println("Http 服务建立失败,err:",err)
return
}
}

HTTP客户端
func main() {
resp, err := http.Get("https://www.baidu.com/")
if err != nil {
fmt.Println("get failed, err:", err)
return
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
fmt.Printf("%T\n",body)
fmt.Println(string(body))
}
执行之后就能在终端输出www.baidu.com网站首页的内容了。
TCP粘包
粘包服务端
//粘包
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
var buf [1024]byte
for {
n,err := reader.Read(buf[:])
if err == io.EOF{
break
}
if err != nil{
fmt.Println("读取客户端失败,err",err)
break
}
recvStr := string(buf[:n])
fmt.Println("收到client发来的数据:",recvStr)
}
}
func main() {
listen,err := net.Listen("tcp","127.0.0.1:8888")
if err != nil{
fmt.Println("监听失败,err",err)
return
}
defer listen.Close()
for {
conn,err := listen.Accept()
if err != nil{
fmt.Println("接受失败,err",err)
continue
}
go process(conn)
}
}
粘包客户端
func main() {
conn,err := net.Dial("tcp","127.0.0.1:8888")
if err != nil{
fmt.Println("连接失败,err",err)
return
}
defer conn.Close()
for i:=0;i<20;i++{
msg := "Ares is a bird!"
conn.Write([]byte(msg))
}
}
先启动服务端再启动客户端,可以看到服务端输出结果如下:
$go run main.go
收到client发来的数据: Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!
收到client发来的数据: Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!Ares is a bird!
客户端分10次发送的数据,在服务端并没有成功的输出10次,而是多条数据“粘”到了一起。
TCP为什么会出现粘包
在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小、数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。
对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。所以UDP不会出现粘包问题。
粘包产生原因
1发送端需要等缓冲区满才发送出去,造成粘包
2接收方不及时接收缓冲区的包,造成多个包接收
具体点:
(1)发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。
(2)接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。
参考:TCP通信粘包问题分析和解决
解决办法
出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。
自定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。
// Encode 将消息编码
func Encode(message string)([]byte ,error) {
// 读取消息的长度,转换成int32类型(占4个字节)
var length = int32(len(message))
var pkg = new(bytes.Buffer)
//写入消息头
err := binary.Write(pkg,binary.LittleEndian,length)
if err != nil{
return nil,err
}
//写入消息实体
err = binary.Write(pkg,binary.LittleEndian,[]byte(message))
if err != nil{
return nil,err
}
return pkg.Bytes(),nil
}
// Decode 消息解码
func Decode(reader *bufio.Reader)(string,error) {
//读取消息长度
lengthByte,_ := reader.Peek(4) //读取前4个字节数据
lengthBuff := bytes.NewBuffer(lengthByte)
var length int32
err := binary.Read(lengthBuff,binary.LittleEndian,&length)
if err != nil{
return "",err
}
// Buffered返回缓冲中现有的可读取的字节数。
if int32(reader.Buffered()) < length+4{
return "",err
}
//读取真正的消息数据
pack := make([]byte,int(4+length))
_,err = reader.Read(pack)
if err != nil{
return "",err
}
return string(pack[4:]),nil
}
server端:
func process(conn net.Conn) {
defer conn.Close()
reader := bufio.NewReader(conn)
for {
msg,err := proto.Decode(reader)
if err == io.EOF{
return
}
if err != nil{
fmt.Println("decode 失败,err",err)
return
}
fmt.Println("收到client数据:",msg)
}
}
func main() {
listen,err := net.Listen("tcp","127.0.0.1:8888")
if err != nil{
fmt.Println("监听失败,err",err)
return
}
defer listen.Close()
for {
conn,err := listen.Accept()
if err != nil{
fmt.Println("接受失败,err",err)
continue
}
go process(conn)
}
}
client端:
func main() {
conn,err := net.Dial("tcp","127.0.0.1:8888")
if err != nil{
fmt.Println("dial失败,err",err)
return
}
defer conn.Close()
for i:=0;i<20;i++{
msg := "Hello Ares!"
data,err := proto.Encode(msg)
if err != nil{
fmt.Println("encode失败,err",err)
return
}
conn.Write(data)
}
}
先启动服务端再启动客户端,可以看到服务端输出结果如下:
go run main.go
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
收到client数据: Hello Ares!
GO语言网络编程的更多相关文章
- 11. Go 语言网络编程
Go 语言网络编程 Go语言在编写 web 应用方面非常得力.因为目前它还没有 GUI(Graphic User Interface 图形化用户界面)的框架,通过文本或者模板展现的 html 界面是目 ...
- 《Go语言网络编程》第一章:体系
原书地址:http://tumregels.github.io/Network-Programming-with-Go 如果不知道想要构建什么,是不可能创建一个系统的.而且如果不知道它工作的环境,也同 ...
- C语言网络编程(Linux && Windows)(1)
和朋友一起做课程设计,同时学习C语言的网络编程,以前写的都是python网络编程,但python很多的库都是封装好的,大部分人在使用的时候不会去了解底层的实现,这样对长远的学习不太好,也改正自己这方面 ...
- Linux C语言 网络编程(二) server模型
前面介绍了关于连接linux服务端方式,可是服务端的资源是有限的,所以我们通常须要又一次思考,设计一套server模型来处理相应的client的请求. 第一种:并发server.通过主进程统一处理cl ...
- GO语言练习:网络编程 ICMP 示例
1.代码 2.编译及运行 1.Go语言网络编程:ICMP示例代码 icmptest.go package main import ( "fmt" "net" & ...
- 黑马程序员_Java基础:网络编程总结
------- android培训.java培训.期待与您交流! ---------- Java语言是在网络环境下诞生的,它是第一个完全融入网络的语言,虽然不能说它是对支持网络编程做得最好的语言,但是 ...
- JAVA的网络编程
网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...
- JAVA网络编程【转】出处不详
网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者可能觉得网络编 ...
- 【转】JAVA之网络编程
转自:火之光 网络编程 网络编程对于很多的初学者来说,都是很向往的一种编程技能,但是很多的初学者却因为很长一段时间无法进入网络编程的大门而放弃了对于该部分技术的学习. 在 学习网络编程以前,很多初学者 ...
随机推荐
- javascript创建一个基于对象的栈结构
上篇博客介绍了基于数组创建一个栈,这是用对象创建一个栈 s1.声明一个Stack类 class Stack { constructor() { this.count = 0; this.items = ...
- js 算法,判断一个数组中的数字出现多少次
let arr = [11, 11, 2, 2, 5, 5, 5, 5, 3]; //创建一个map,把每个数字和其个数相对应 let countObj = {}; for (i = 0; i < ...
- 那些年伴我一起成长的SAP装备
今天这篇文章无关技术,我们来聊聊SAP装备,即打上了SAP logo的那些物品. 但凡在SAP圈子工作过一段时间的从业者们,手上或多或少都拥有一些此类装备.Jerry当然也不例外,这些装备无论物品本身 ...
- Python七大原则,24种设计模式
七大设计原则:1.单一职责原则[SINGLE RESPONSIBILITY PRINCIPLE]:一个类负责一项职责.2.里氏替换原则[LISKOV SUBSTITUTION PRINCIPLE]:继 ...
- centos 7.6 安装php70
1.首先查看是否有老版本 yum list installed | grep php 2.如果安装的有,清除老版本 yum remove php.x86_64 php-cli.x86_64 php-c ...
- layui 单选框、复选框、下拉菜单 不显示问题 记录
1. 如果是 ajax嵌套了 页面, 请确保 只有最外层的页面引入了 layui.css 和 layui.js 内层页面 切记不要再次引入 2. layui.use(['form', 'upload ...
- linux网络编程之socket编程(四)
经过两周的等待,终于可以回归我正常的学习之旅了,表哥来北京了在我这暂住,晚上回家了基本在和他聊天,周末带他在北京城到处乱转,几乎剥夺了我自由学习的时间了,不过,亲人之情还是很难得的,工作学习并不是生活 ...
- Makefile学习一
上次随着信号学习告一段落,也标志着linux系统编程相关的知识学完了,而学了这么多知识点,是需要用一个综合的项目来将其进行串起来的,这样学习的技术才会不那么空洞,所以接下来会以一个实际例子来综合运用下 ...
- Python3中BeautifulSoup的使用方法
BeautifulSoup的使用 我们学习了正则表达式的相关用法,但是一旦正则写的有问题,可能得到的就不是我们想要的结果了,而且对于一个网页来说,都有一定的特殊的结构和层级关系,而且很多标签都有id或 ...
- windows 使用SVN命令
在不用安装TortoiseSVN客户端的情况,大家可以再http://subversion.apache.org/packages.html#windows 找到windows下的svn客户端工具.选 ...