iT邦幫忙

第 12 屆 iThome 鐵人賽

DAY 30
0
自我挑戰組

Golang魔法使 ─ 30天從零開始學習Go語言 | 比Python還簡單 | 理科生一定學得會 | 文科生不好說系列 第 30

#30 UDP ── 用戶資料報協定,嘗試使用 UDP 傳送圖片 | Golang魔法使

  • 分享至 

  • xImage
  •  

究竟什麼是「最後的審判」呢?Golang牌需要一個新主人,但是現在有一大部分的Golang牌在小櫻手上,一小部分在小狼手上

現在只有打倒「審判者月」的人能成為新主人。

那麼審判者月是誰呢?那就是小櫻每天發春的對象「月城雪兔」

那麼如果這些Golang牌沒有新主人呢?那麼這些Golang牌為了不讓自己太過難過。它們一定會想辦法忘記「喜歡」的感覺。所有的人將會忘記喜歡人的感覺。

恭喜看完了 29 天的課程

最後一天的課程,想要來實作一台 UDP 伺服器,前一篇提到 UDP 雖然無法保證資料正確,但是 UDP 延遲低,在不講求資料正確性的情況下可以使用 UDP 來傳輸資料

上次開的 port 1450 可能有人不喜歡,所以改開 689 吔不是,這個數字好像藍綠一起婊吔

簡單的 UDP 示範

伺服器示範
利用 net.ListenPacket() 來建立一個監聽,其中,第一個回傳值的型態為 net.PacketConn 可以到官網進一步了解用法:net.PacketConn - Go

package main

import(
    "fmt"
    "net"
)

func main(){
    // 與 tcp 不同,udp 要改用 net.ListenPacket() 來做監聽
    ln, err := net.ListenPacket("udp", ":689")
    defer ln.Close()
    if err != nil {
        panic("監聽 port 689 失敗")
    }
    fmt.Println("SERVER")
    for {
        buf := make([]byte, 1024)
        // ln.ReadFrom 分別回傳三個值
        // buf_len 代表讀入了幾個 byte
        // addr 代表客戶端的位址
        buf_len, addr, err := ln.ReadFrom(buf)

        if err != nil {
            continue
        }

        go func(ln net.PacketConn, addr net.Addr, buf []byte){
            fmt.Printf("用戶端位址: %s\n收到: %s\n", addr, buf)
            ln.WriteTo([]byte("伺服器端已收到資料!\n"), addr)
        }(ln, addr, buf[:buf_len])
    }
}

用戶端示範

package main

import (
    "fmt"
    "net"
    "time"
)

func main() {
    // 吔,不是還真的開 689 啊
    // 反正 689 沒人用
    res, err := sendUDP("localhost:689", "封印解除!")
    if err != nil {
        fmt.Println(err.Error())
    }else{
        fmt.Println(res)
    }
}

func sendUDP(addr, msg string) (string, error) {
    conn, _ := net.Dial("udp", addr)

    _, err := conn.Write([]byte(msg))

    bs := make([]byte, 1024)

    // 設定 UDP 連線期限
    conn.SetDeadline(time.Now().Add(3 * time.Second))
    len, err := conn.Read(bs)
    if err != nil {
        return "", err
    }else{
        return string(bs[:len]), err
    }
}

嘗試使用 UDP 傳送圖片

UDP 傳送封包時有一定限制,建議最大不要超過 512 bytes (stack overflow 上有人這樣說的)。若是使用 TCP 則不必擔心,TCP 有自己切封包的機制。

server

package main

import(
    "fmt"
    "net"
    "io"
    "os"
)

func photo(path string, buf_channel chan []byte){
    file, err := os.Open(path)
    defer file.Close()
    if err != nil {
        fmt.Println(err)
        close(buf_channel)
    }

    buf := make([]byte, 512)
    for {
        n, err := file.Read(buf)
        if err != nil && err != io.EOF {
            panic(err)
        }
        if n == 0 {
            break
        }

        buf_channel <- buf
    }
    close(buf_channel)
}

func main(){
    ln, err := net.ListenPacket("udp", ":689")
    defer ln.Close()
    if err != nil {
        panic("監聽 port 689 失敗")
    }
    fmt.Println("SERVER")
    for {
        buf := make([]byte, 512)
        buf_len, addr, err := ln.ReadFrom(buf)

        if err != nil {
            continue
        }

        go func(ln net.PacketConn, addr net.Addr, buf []byte){
            fmt.Printf("用戶端位址: %s\n收到: %s\n", addr, buf)
            // 回傳圖片!
            buf_chan := make(chan []byte, 8)
            go photo("demo.jpg", buf_chan)
            for val := range buf_chan{
                ln.WriteTo(val, addr)
            }
        }(ln, addr, buf[:buf_len])
    }
}

client

package main

import (
    "fmt"
    "net"
    "time"
    "os"
)

func main() {
    res, err := sendUDP("localhost:689", "請求傳送圖片")
    if err != nil {
        fmt.Println(err.Error())
    }else{
        fmt.Println(res)
    }
}

func sendUDP(addr, msg string) (res string, err error) {
    err = nil
    conn, err := net.Dial("udp", addr)
    if err != nil{
        return "", err
    }

    _, err = conn.Write([]byte(msg))

    if err != nil{
        return "", err
    }

    buf := make([]byte, 512)
    
    conn.SetDeadline(time.Now().Add(5 * time.Second))
    res = ""
    file, err := os.Create("output.mp3")
    if err != nil{
        return "", err
    }

    for n, err := conn.Read(buf); ; n, err = conn.Read(buf){
        if err != nil{
            fmt.Println(err)
            break
        }
        file.Write(buf[:n])
    }

    file.Close()

    return res, nil
}

運行結果:失敗

UDP 在傳送時,會有傳錯的情況發生,但是沒有想到 jpg, png 都無法容錯。於是我又試了一些格式

圖片

  • bmp 失敗
  • jpg 失敗
  • png 失敗
  • gif 失敗
  • tif 失敗

音樂

  • mp3 成功(雖然會變很ㄎㄧㄤ但是真的能聽)
  • mp2 成功,跟 mp3 差不多
  • wma 失敗
  • ogg 失敗
  • wav 失敗
  • m4a 失敗
  • flac 失敗
  • ac3 失敗
  • arm 失敗

影片

  • mp4 失敗
  • mpg 成功

各位魔法使們能在不使用 TCP 而只用 UDP 的情況下解決這個問題嗎?

期待明年下一季: Golang魔法使 ── 網際網路篇


風啊!快點幫我抓住他(審判者月)

本文圖片來自:庫洛魔法使第二季第十一集

次回也跟小櫻一起 ── 封印解除!


上一篇
#29 TCP ─ 傳輸控制協定 | Golang 魔法使
系列文
Golang魔法使 ─ 30天從零開始學習Go語言 | 比Python還簡單 | 理科生一定學得會 | 文科生不好說30
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言