飞飞姐 發表於 2019-6-1 18:50:00

GO语言网络编程

<h2 id="toc_0">socket编程</h2>

<p>Socket是BSD UNIX的进程通信机制,通常也称作”套接字”,用于描述IP地址和端口,是一个通信链的句柄。Socket可以理解为TCP/IP网络的API,它定义了许多函数或例程,程序员可以用它们来开发TCP/IP网络上的应用程序。电脑上运行的应用程序通常通过”套接字”向网络发出请求或者应答网络请求。<br>
Socket是应用层与TCP/IP协议族通信的中间软件抽象层。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket后面,对用户来说只需要调用Socket规定的相关函数,让Socket去组织符合指定的协议数据然后进行通信。</p>

<h2 id="toc_1">GO语言实现TCP通信</h2>

<h3 id="toc_2">TCP协议</h3>

<p>TCP/IP(Transmission Control Protocol/Internet Protocol) 即传输控制协议/网间协议,是一种面向连接(连接导向)的、可靠的、基于字节流的传输层(Transport layer)通信协议,因为是面向连接的协议,数据像水流一样传输,会存在黏包问题。</p>

<h3 id="toc_3">TCP服务端</h3>

<p>一个TCP服务端可以同时连接很多个客户端,Go语言中创建多个goroutine实现并发非常方便和高效,所以可以每建立一次链接就创建一个goroutine去处理。<br>
TCP服务端程序的处理流程:</p>

<ul>
<li>监听端口</li>
<li>接收客户端请求建立链接</li>
<li>创建goroutine处理链接<br>
TCP服务端:</li>
</ul>

<pre><code class="language-go">//TCP server端

func process(conn net.Conn){
    defer conn.Close()//关闭连接
    for {
      reader := bufio.NewReader(conn)
      var buf 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处理连接
    }
}
</code></pre>

<h3 id="toc_4">TCP客户端</h3>

<p>一个TCP客户端进行TCP通信的流程如下:</p>

<ul>
<li>建立与服务端的链接</li>
<li>进行数据收发</li>
<li>关闭链接</li>
</ul>

<p>TCP客户端:</p>

<pre><code class="language-go">//客户端

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 := byte{}
      n,err := conn.Read(buf[:])
      if err != nil{
            fmt.Println("接受失败,错误:",err)
            return
      }
      fmt.Println(string(buf[:n]))
    }
}
</code></pre>

<p>先启动server,后启动client:</p>

<pre><code class="language-go">$go run main.go
我是客户端
我是客户端
$go run main.go
收到客户端信息: 我是客户端
</code></pre>

<h2 id="toc_5">GO语言实现UDP通信</h2>

<h3 id="toc_6">UDp协议</h3>

<p>UDP协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。</p>

<h3 id="toc_7">UDP服务端</h3>

<pre><code class="language-go">//服务端
func main(){
    listen,err := net.ListenUDP("udp",&amp;net.UDPAddr{
      IP:net.IPv4(0,0,0,0),
      Port:8888,
    })
    if err != nil{
      fmt.Println("监听失败,错误:",err)
      return
    }
    defer listen.Close()
    for {
      var data 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
      }
    }
}
</code></pre>

<h3 id="toc_8">UDP客户端</h3>

<pre><code class="language-go">//客户端
func main(){
    socket,err := net.DialUDP("udp",nil,&amp;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)
}
</code></pre>

<p>先启动server,后启动client:</p>

<pre><code class="language-go">$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
</code></pre>

<h2 id="toc_9">HTTP客户端和服务端</h2>

<h3 id="toc_10">HTTP协议</h3>

<p>超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。</p>

<h3 id="toc_11">HTTP服务端</h3>

<p>net/http包是对net包的进一步封装,专门用来处理HTTP协议的数据。</p>

<pre><code class="language-go">// 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
    }
}
</code></pre>

<p><img src="https://img2018.cnblogs.com/blog/830693/201906/830693-20190601185017624-888353573.jpg" alt="" style="width: 828px"></p>

<h3 id="toc_12">HTTP客户端</h3>

<pre><code class="language-go">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))
}
</code></pre>

<p>执行之后就能在终端输出www.baidu.com网站首页的内容了。</p>

<h2 id="toc_13">TCP粘包</h2>

<h3 id="toc_14">粘包服务端</h3>

<pre><code class="language-go">//粘包
func process(conn net.Conn){
    defer conn.Close()
    reader := bufio.NewReader(conn)
    var buf 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)
    }
}
</code></pre>

<h3 id="toc_15">粘包客户端</h3>

<pre><code class="language-go">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&lt;20;i++{
      msg := "Ares is a bird!"
      conn.Write([]byte(msg))
    }
}
</code></pre>

<p>先启动服务端再启动客户端,可以看到服务端输出结果如下:</p>

<pre><code class="language-go">$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!
</code></pre>

<p>客户端分10次发送的数据,在服务端并没有成功的输出10次,而是多条数据“粘”到了一起。</p>

<h3 id="toc_16">TCP为什么会出现粘包</h3>

<p>在socket网络程序中,TCP和UDP分别是面向连接和非面向连接的。因此TCP的socket编程,收发两端(客户端和服务器端)都要有成对的socket,因此,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小、数据量小的数据,合并成一个大的数据块,然后进行封包。这样,接收端,就难于分辨出来了,必须提供科学的拆包机制。</p>

<p>对于UDP,不会使用块的合并优化算法,这样,实际上目前认为,是由于UDP支持的是一对多的模式,所以接收端的skbuff(套接字缓冲区)采用了链式结构来记录每一个到达的UDP包,在每个UDP包中就有了消息头(消息来源地址,端口等信息),这样,对于接收端来说,就容易进行区分处理了。所以UDP不会出现粘包问题。</p>

<h3 id="toc_17">粘包产生原因</h3>

<p>1发送端需要等缓冲区满才发送出去,造成粘包<br>
2接收方不及时接收缓冲区的包,造成多个包接收<br>
具体点:<br>
(1)发送方引起的粘包是由TCP协议本身造成的,TCP为提高传输效率,发送方往往要收集到足够多的数据后才发送一包数据。若连续几次发送的数据都很少,通常TCP会根据优化算法把这些数据合成一包后一次发送出去,这样接收方就收到了粘包数据。</p>

<p>(2)接收方引起的粘包是由于接收方用户进程不及时接收数据,从而导致粘包现象。这是因为接收方先把收到的数据放在系统接收缓冲区,用户进程从该缓冲区取数据,若下一包数据到达时前一包数据尚未被用户进程取走,则下一包数据放到系统接收缓冲区时就接到前一包数据之后,而用户进程根据预先设定的缓冲区大小从系统接收缓冲区取数据,这样就一次取到了多包数据。<br>
参考:TCP通信粘包问题分析和解决</p>

<h3 id="toc_18">解决办法</h3>

<p>出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。<br>
自定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。</p>

<pre><code class="language-go">// 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,&amp;length)
    if err != nil{
      return "",err
    }
    // Buffered返回缓冲中现有的可读取的字节数。
    if int32(reader.Buffered()) &lt; length+4{
      return "",err
    }
    //读取真正的消息数据
    pack := make([]byte,int(4+length))
    _,err = reader.Read(pack)
    if err != nil{
      return "",err
    }
    return string(pack),nil
}
</code></pre>

<p>server端:</p>

<pre><code class="language-go">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)
    }
}
</code></pre>

<p>client端:</p>

<pre><code class="language-go">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&lt;20;i++{
      msg := "Hello Ares!"
      data,err := proto.Encode(msg)
      if err != nil{
            fmt.Println("encode失败,err",err)
            return
      }
      conn.Write(data)
    }
}
</code></pre>

<p>先启动服务端再启动客户端,可以看到服务端输出结果如下:</p>

<pre><code class="language-go">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!
</code></pre>

</div>
<div id="MySignature" role="contentinfo">
    <div>作者:Mr.Ares</div>
<div>出处:http://www.cnblogs.com/aresxin/</div>
<div>个性签名:许多人的付出都是浅尝辄止!</div><br><br>
来源:https://www.cnblogs.com/aresxin/p/GO-yu-yan-wang-luo-bian-cheng.html
頁: [1]
查看完整版本: GO语言网络编程