在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ GO/ 8.1 Socket編程
7 文本處理
3 Web基礎(chǔ)
14 擴(kuò)展Web框架
10.4 小結(jié)
2.2 Go基礎(chǔ)
2.8 總結(jié)
6.1 session和cookie
5.5 使用beedb庫(kù)進(jìn)行ORM開(kāi)發(fā)
8.3 REST
13.6 小結(jié)
5.4 使用PostgreSQL數(shù)據(jù)庫(kù)
14.6 pprof支持
14.1 靜態(tài)文件支持
11.2 使用GDB調(diào)試
7.7 小結(jié)
1 GO環(huán)境配置
14.5 多語(yǔ)言支持
7.1 XML處理
1.5 總結(jié)
13 如何設(shè)計(jì)一個(gè)Web框架
14.3 表單及驗(yàn)證支持
12 部署與維護(hù)
10 國(guó)際化和本地化
1.1 Go 安裝
6.2 Go如何使用session
5.6 NOSQL數(shù)據(jù)庫(kù)操作
6.5 小結(jié)
9.4 避免SQL注入
12.1 應(yīng)用日志
4.2 驗(yàn)證表單的輸入
10.1 設(shè)置默認(rèn)地區(qū)
1.3 Go 命令
9.6 加密和解密數(shù)據(jù)
4.1 處理表單的輸入
4.4 防止多次遞交表單
11.3 Go怎么寫測(cè)試用例
8 Web服務(wù)
12.3 應(yīng)用部署
5.7 小結(jié)
12.5 小結(jié)
11 錯(cuò)誤處理,調(diào)試和測(cè)試
9.2 確保輸入過(guò)濾
14.2 Session支持
6.4 預(yù)防session劫持
12.4 備份和恢復(fù)
8.1 Socket編程
13.1 項(xiàng)目規(guī)劃
13.4 日志和配置設(shè)計(jì)
7.6 字符串處理
13.2 自定義路由器設(shè)計(jì)
6.3 session存儲(chǔ)
3.4 Go的http包詳解
8.2 WebSocket
10.3 國(guó)際化站點(diǎn)
7.5 文件操作
7.4 模板處理
9.1 預(yù)防CSRF攻擊
13.3 controller設(shè)計(jì)
2.6 interface
14.4 用戶認(rèn)證
2.3 流程和函數(shù)
附錄A 參考資料
11.1 錯(cuò)誤處理
9.5 存儲(chǔ)密碼
9.3 避免XSS攻擊
12.2 網(wǎng)站錯(cuò)誤處理
6 session和數(shù)據(jù)存儲(chǔ)
2.4 struct類型
3.3 Go如何使得Web工作
2.5 面向?qū)ο?/span>
3.1 Web工作方式
1.2 GOPATH與工作空間
2.1 你好,Go
9.7 小結(jié)
13.5 實(shí)現(xiàn)博客的增刪改
7.2 JSON處理
10.2 本地化資源
7.3 正則處理
2 Go語(yǔ)言基礎(chǔ)
5.1 database/sql接口
4.5 處理文件上傳
8.5 小結(jié)
4.3 預(yù)防跨站腳本
5.3 使用SQLite數(shù)據(jù)庫(kù)
14.7 小結(jié)
3.2 Go搭建一個(gè)Web服務(wù)器
2.7 并發(fā)
5 訪問(wèn)數(shù)據(jù)庫(kù)
4 表單
3.5 小結(jié)
1.4 Go開(kāi)發(fā)工具
11.4 小結(jié)
9 安全與加密
5.2 使用MySQL數(shù)據(jù)庫(kù)
4.6 小結(jié)
8.4 RPC

8.1 Socket編程

在很多底層網(wǎng)絡(luò)應(yīng)用開(kāi)發(fā)者的眼里一切編程都是Socket,話雖然有點(diǎn)夸張,但卻也幾乎如此了,現(xiàn)在的網(wǎng)絡(luò)編程幾乎都是用Socket來(lái)編程。你想過(guò)這些情景么?我們每天打開(kāi)瀏覽器瀏覽網(wǎng)頁(yè)時(shí),瀏覽器進(jìn)程怎么和Web服務(wù)器進(jìn)行通信的呢?當(dāng)你用QQ聊天時(shí),QQ進(jìn)程怎么和服務(wù)器或者是你的好友所在的QQ進(jìn)程進(jìn)行通信的呢?當(dāng)你打開(kāi)PPstream觀看視頻時(shí),PPstream進(jìn)程如何與視頻服務(wù)器進(jìn)行通信的呢? 如此種種,都是靠Socket來(lái)進(jìn)行通信的,以一斑窺全豹,可見(jiàn)Socket編程在現(xiàn)代編程中占據(jù)了多么重要的地位,這一節(jié)我們將介紹Go語(yǔ)言中如何進(jìn)行Socket編程。

什么是Socket?

Socket起源于Unix,而Unix基本哲學(xué)之一就是“一切皆文件”,都可以用“打開(kāi)open –> 讀寫write/read –> 關(guān)閉close”模式來(lái)操作。Socket就是該模式的一個(gè)實(shí)現(xiàn),網(wǎng)絡(luò)的Socket數(shù)據(jù)傳輸是一種特殊的I/O,Socket也是一種文件描述符。Socket也具有一個(gè)類似于打開(kāi)文件的函數(shù)調(diào)用:Socket(),該函數(shù)返回一個(gè)整型的Socket描述符,隨后的連接建立、數(shù)據(jù)傳輸?shù)炔僮鞫际峭ㄟ^(guò)該Socket實(shí)現(xiàn)的。

常用的Socket類型有兩種:流式Socket(SOCK_STREAM)和數(shù)據(jù)報(bào)式Socket(SOCK_DGRAM)。流式是一種面向連接的Socket,針對(duì)于面向連接的TCP服務(wù)應(yīng)用;數(shù)據(jù)報(bào)式Socket是一種無(wú)連接的Socket,對(duì)應(yīng)于無(wú)連接的UDP服務(wù)應(yīng)用。

Socket如何通信

網(wǎng)絡(luò)中的進(jìn)程之間如何通過(guò)Socket通信呢?首要解決的問(wèn)題是如何唯一標(biāo)識(shí)一個(gè)進(jìn)程,否則通信無(wú)從談起!在本地可以通過(guò)進(jìn)程PID來(lái)唯一標(biāo)識(shí)一個(gè)進(jìn)程,但是在網(wǎng)絡(luò)中這是行不通的。其實(shí)TCP/IP協(xié)議族已經(jīng)幫我們解決了這個(gè)問(wèn)題,網(wǎng)絡(luò)層的“ip地址”可以唯一標(biāo)識(shí)網(wǎng)絡(luò)中的主機(jī),而傳輸層的“協(xié)議+端口”可以唯一標(biāo)識(shí)主機(jī)中的應(yīng)用程序(進(jìn)程)。這樣利用三元組(ip地址,協(xié)議,端口)就可以標(biāo)識(shí)網(wǎng)絡(luò)的進(jìn)程了,網(wǎng)絡(luò)中需要互相通信的進(jìn)程,就可以利用這個(gè)標(biāo)志在他們之間進(jìn)行交互。請(qǐng)看下面這個(gè)TCP/IP協(xié)議結(jié)構(gòu)圖

http://wiki.jikexueyuan.com/project/go-web-programming/images/8.1.socket.png" alt="" />

圖8.1 七層網(wǎng)絡(luò)協(xié)議圖

使用TCP/IP協(xié)議的應(yīng)用程序通常采用應(yīng)用編程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已經(jīng)被淘汰),來(lái)實(shí)現(xiàn)網(wǎng)絡(luò)進(jìn)程之間的通信。就目前而言,幾乎所有的應(yīng)用程序都是采用socket,而現(xiàn)在又是網(wǎng)絡(luò)時(shí)代,網(wǎng)絡(luò)中進(jìn)程通信是無(wú)處不在,這就是為什么說(shuō)“一切皆Socket”。

Socket基礎(chǔ)知識(shí)

通過(guò)上面的介紹我們知道Socket有兩種:TCP Socket和UDP Socket,TCP和UDP是協(xié)議,而要確定一個(gè)進(jìn)程的需要三元組,需要IP地址和端口。

IPv4地址

目前的全球因特網(wǎng)所采用的協(xié)議族是TCP/IP協(xié)議。IP是TCP/IP協(xié)議中網(wǎng)絡(luò)層的協(xié)議,是TCP/IP協(xié)議族的核心協(xié)議。目前主要采用的IP協(xié)議的版本號(hào)是4(簡(jiǎn)稱為IPv4),發(fā)展至今已經(jīng)使用了30多年。

IPv4的地址位數(shù)為32位,也就是最多有2的32次方的網(wǎng)絡(luò)設(shè)備可以聯(lián)到Internet上。近十年來(lái)由于互聯(lián)網(wǎng)的蓬勃發(fā)展,IP位址的需求量愈來(lái)愈大,使得IP位址的發(fā)放愈趨緊張,前一段時(shí)間,據(jù)報(bào)道IPV4的地址已經(jīng)發(fā)放完畢,我們公司目前很多服務(wù)器的IP都是一個(gè)寶貴的資源。

地址格式類似這樣:127.0.0.1 172.122.121.111

IPv6地址

IPv6是下一版本的互聯(lián)網(wǎng)協(xié)議,也可以說(shuō)是下一代互聯(lián)網(wǎng)的協(xié)議,它是為了解決IPv4在實(shí)施過(guò)程中遇到的各種問(wèn)題而被提出的,IPv6采用128位地址長(zhǎng)度,幾乎可以不受限制地提供地址。按保守方法估算IPv6實(shí)際可分配的地址,整個(gè)地球的每平方米面積上仍可分配1000多個(gè)地址。在IPv6的設(shè)計(jì)過(guò)程中除了一勞永逸地解決了地址短缺問(wèn)題以外,還考慮了在IPv4中解決不好的其它問(wèn)題,主要有端到端IP連接、服務(wù)質(zhì)量(QoS)、安全性、多播、移動(dòng)性、即插即用等。

地址格式類似這樣:2002:c0e8:82e7:0:0:0:c0e8:82e7

Go支持的IP類型

在Go的net包中定義了很多類型、函數(shù)和方法用來(lái)網(wǎng)絡(luò)編程,其中IP的定義如下:

type IP []byte

net包中有很多函數(shù)來(lái)操作IP,但是其中比較有用的也就幾個(gè),其中ParseIP(s string) IP函數(shù)會(huì)把一個(gè)IPv4或者IPv6的地址轉(zhuǎn)化成IP類型,請(qǐng)看下面的例子:

package main
import (
    "net"
    "os"
    "fmt"
)
func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "Usage: %s ip-addr\n", os.Args[0])
        os.Exit(1)
    }
    name := os.Args[1]
    addr := net.ParseIP(name)
    if addr == nil {
        fmt.Println("Invalid address")
    } else {
        fmt.Println("The address is ", addr.String())
    }
    os.Exit(0)
}

執(zhí)行之后你就會(huì)發(fā)現(xiàn)只要你輸入一個(gè)IP地址就會(huì)給出相應(yīng)的IP格式

TCP Socket

當(dāng)我們知道如何通過(guò)網(wǎng)絡(luò)端口訪問(wèn)一個(gè)服務(wù)時(shí),那么我們能夠做什么呢?作為客戶端來(lái)說(shuō),我們可以通過(guò)向遠(yuǎn)端某臺(tái)機(jī)器的的某個(gè)網(wǎng)絡(luò)端口發(fā)送一個(gè)請(qǐng)求,然后得到在機(jī)器的此端口上監(jiān)聽(tīng)的服務(wù)反饋的信息。作為服務(wù)端,我們需要把服務(wù)綁定到某個(gè)指定端口,并且在此端口上監(jiān)聽(tīng),當(dāng)有客戶端來(lái)訪問(wèn)時(shí)能夠讀取信息并且寫入反饋信息。

在Go語(yǔ)言的net包中有一個(gè)類型TCPConn,這個(gè)類型可以用來(lái)作為客戶端和服務(wù)器端交互的通道,他有兩個(gè)主要的函數(shù):

func (c *TCPConn) Write(b []byte) (n int, err os.Error)
func (c *TCPConn) Read(b []byte) (n int, err os.Error)

TCPConn可以用在客戶端和服務(wù)器端來(lái)讀寫數(shù)據(jù)。

還有我們需要知道一個(gè)TCPAddr類型,他表示一個(gè)TCP的地址信息,他的定義如下:

type TCPAddr struct {
    IP IP
    Port int
}

在Go語(yǔ)言中通過(guò)ResolveTCPAddr獲取一個(gè)TCPAddr

func ResolveTCPAddr(net, addr string) (*TCPAddr, os.Error)
  • net參數(shù)是"tcp4"、"tcp6"、"tcp"中的任意一個(gè),分別表示TCP(IPv4-only),TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一個(gè)).
  • addr表示域名或者IP地址,例如"www.google.com:80" 或者"127.0.0.1:22".

TCP client

Go語(yǔ)言中通過(guò)net包中的DialTCP函數(shù)來(lái)建立一個(gè)TCP連接,并返回一個(gè)TCPConn類型的對(duì)象,當(dāng)連接建立時(shí)服務(wù)器端也創(chuàng)建一個(gè)同類型的對(duì)象,此時(shí)客戶端和服務(wù)器段通過(guò)各自擁有的TCPConn對(duì)象來(lái)進(jìn)行數(shù)據(jù)交換。一般而言,客戶端通過(guò)TCPConn對(duì)象將請(qǐng)求信息發(fā)送到服務(wù)器端,讀取服務(wù)器端響應(yīng)的信息。服務(wù)器端讀取并解析來(lái)自客戶端的請(qǐng)求,并返回應(yīng)答信息,這個(gè)連接只有當(dāng)任一端關(guān)閉了連接之后才失效,不然這連接可以一直在使用。建立連接的函數(shù)定義如下:

func DialTCP(net string, laddr, raddr *TCPAddr) (c *TCPConn, err os.Error)
  • net參數(shù)是"tcp4"、"tcp6"、"tcp"中的任意一個(gè),分別表示TCP(IPv4-only)、TCP(IPv6-only)或者TCP(IPv4,IPv6的任意一個(gè))
  • laddr表示本機(jī)地址,一般設(shè)置為nil
  • raddr表示遠(yuǎn)程的服務(wù)地址

接下來(lái)我們寫一個(gè)簡(jiǎn)單的例子,模擬一個(gè)基于HTTP協(xié)議的客戶端請(qǐng)求去連接一個(gè)Web服務(wù)端。我們要寫一個(gè)簡(jiǎn)單的http請(qǐng)求頭,格式類似如下:

"HEAD / HTTP/1.0\r\n\r\n"

從服務(wù)端接收到的響應(yīng)信息格式可能如下:

HTTP/1.0 200 OK
ETag: "-9985996"
Last-Modified: Thu, 25 Mar 2010 17:51:10 GMT
Content-Length: 18074
Connection: close
Date: Sat, 28 Aug 2010 00:43:48 GMT
Server: lighttpd/1.4.23

我們的客戶端代碼如下所示:

package main

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

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "Usage: %s host:port ", os.Args[0])
        os.Exit(1)
    }
    service := os.Args[1]
    tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
    checkError(err)
    conn, err := net.DialTCP("tcp", nil, tcpAddr)
    checkError(err)
    _, err = conn.Write([]byte("HEAD / HTTP/1.0\r\n\r\n"))
    checkError(err)
    result, err := ioutil.ReadAll(conn)
    checkError(err)
    fmt.Println(string(result))
    os.Exit(0)
}
func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

通過(guò)上面的代碼我們可以看出:首先程序?qū)⒂脩舻妮斎胱鳛閰?shù)service傳入net.ResolveTCPAddr獲取一個(gè)tcpAddr,然后把tcpAddr傳入DialTCP后創(chuàng)建了一個(gè)TCP連接conn,通過(guò)conn來(lái)發(fā)送請(qǐng)求信息,最后通過(guò)ioutil.ReadAllconn中讀取全部的文本,也就是服務(wù)端響應(yīng)反饋的信息。

TCP server

上面我們編寫了一個(gè)TCP的客戶端程序,也可以通過(guò)net包來(lái)創(chuàng)建一個(gè)服務(wù)器端程序,在服務(wù)器端我們需要綁定服務(wù)到指定的非激活端口,并監(jiān)聽(tīng)此端口,當(dāng)有客戶端請(qǐng)求到達(dá)的時(shí)候可以接收到來(lái)自客戶端連接的請(qǐng)求。net包中有相應(yīng)功能的函數(shù),函數(shù)定義如下:

func ListenTCP(net string, laddr *TCPAddr) (l *TCPListener, err os.Error)
func (l *TCPListener) Accept() (c Conn, err os.Error)

參數(shù)說(shuō)明同DialTCP的參數(shù)一樣。下面我們實(shí)現(xiàn)一個(gè)簡(jiǎn)單的時(shí)間同步服務(wù),監(jiān)聽(tīng)7777端口

package main

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

func main() {
    service := ":7777"
    tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
    checkError(err)
    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        daytime := time.Now().String()
        conn.Write([]byte(daytime)) // don't care about return value
        conn.Close()                // we're finished with this client
    }
}
func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

上面的服務(wù)跑起來(lái)之后,它將會(huì)一直在那里等待,直到有新的客戶端請(qǐng)求到達(dá)。當(dāng)有新的客戶端請(qǐng)求到達(dá)并同意接受Accept該請(qǐng)求的時(shí)候他會(huì)反饋當(dāng)前的時(shí)間信息。值得注意的是,在代碼中for循環(huán)里,當(dāng)有錯(cuò)誤發(fā)生時(shí),直接continue而不是退出,是因?yàn)樵诜?wù)器端跑代碼的時(shí)候,當(dāng)有錯(cuò)誤發(fā)生的情況下最好是由服務(wù)端記錄錯(cuò)誤,然后當(dāng)前連接的客戶端直接報(bào)錯(cuò)而退出,從而不會(huì)影響到當(dāng)前服務(wù)端運(yùn)行的整個(gè)服務(wù)。

上面的代碼有個(gè)缺點(diǎn),執(zhí)行的時(shí)候是單任務(wù)的,不能同時(shí)接收多個(gè)請(qǐng)求,那么該如何改造以使它支持多并發(fā)呢?Go里面有一個(gè)goroutine機(jī)制,請(qǐng)看下面改造后的代碼

package main

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

func main() {
    service := ":1200"
    tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
    checkError(err)
    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        go handleClient(conn)
    }
}

func handleClient(conn net.Conn) {
    defer conn.Close()
    daytime := time.Now().String()
    conn.Write([]byte(daytime)) // don't care about return value
    // we're finished with this client
}
func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

通過(guò)把業(yè)務(wù)處理分離到函數(shù)handleClient,我們就可以進(jìn)一步地實(shí)現(xiàn)多并發(fā)執(zhí)行了??瓷先ナ遣皇呛軒?,增加go關(guān)鍵詞就實(shí)現(xiàn)了服務(wù)端的多并發(fā),從這個(gè)小例子也可以看出goroutine的強(qiáng)大之處。

有的朋友可能要問(wèn):這個(gè)服務(wù)端沒(méi)有處理客戶端實(shí)際請(qǐng)求的內(nèi)容。如果我們需要通過(guò)從客戶端發(fā)送不同的請(qǐng)求來(lái)獲取不同的時(shí)間格式,而且需要一個(gè)長(zhǎng)連接,該怎么做呢?請(qǐng)看:

package main

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

func main() {
    service := ":1200"
    tcpAddr, err := net.ResolveTCPAddr("tcp4", service)
    checkError(err)
    listener, err := net.ListenTCP("tcp", tcpAddr)
    checkError(err)
    for {
        conn, err := listener.Accept()
        if err != nil {
            continue
        }
        go handleClient(conn)
    }
}

func handleClient(conn net.Conn) {
    conn.SetReadDeadline(time.Now().Add(2 * time.Minute)) // set 2 minutes timeout
    request := make([]byte, 128) // set maxium request length to 128KB to prevent flood attack
    defer conn.Close()  // close connection before exit
    for {
        read_len, err := conn.Read(request)

        if err != nil {
            fmt.Println(err)
            break
        }

        if read_len == 0 {
            break // connection already closed by client
        } else if string(request) == "timestamp" {
            daytime := strconv.FormatInt(time.Now().Unix(), 10)
            conn.Write([]byte(daytime))
        } else {
            daytime := time.Now().String()
            conn.Write([]byte(daytime)) 
        }

        request = make([]byte, 128) // clear last read content
    }
}

func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error: %s", err.Error())
        os.Exit(1)
    }
}

在上面這個(gè)例子中,我們使用conn.Read()不斷讀取客戶端發(fā)來(lái)的請(qǐng)求。由于我們需要保持與客戶端的長(zhǎng)連接,所以不能在讀取完一次請(qǐng)求后就關(guān)閉連接。由于conn.SetReadDeadline()設(shè)置了超時(shí),當(dāng)一定時(shí)間內(nèi)客戶端無(wú)請(qǐng)求發(fā)送,conn便會(huì)自動(dòng)關(guān)閉,下面的for循環(huán)即會(huì)因?yàn)檫B接已關(guān)閉而跳出。需要注意的是,request在創(chuàng)建時(shí)需要指定一個(gè)最大長(zhǎng)度以防止flood attack;每次讀取到請(qǐng)求處理完畢后,需要清理request,因?yàn)?code>conn.Read()會(huì)將新讀取到的內(nèi)容append到原內(nèi)容之后。

控制TCP連接

TCP有很多連接控制函數(shù),我們平常用到比較多的有如下幾個(gè)函數(shù):

func DialTimeout(net, addr string, timeout time.Duration) (Conn, error)

設(shè)置建立連接的超時(shí)時(shí)間,客戶端和服務(wù)器端都適用,當(dāng)超過(guò)設(shè)置時(shí)間時(shí),連接自動(dòng)關(guān)閉。

func (c *TCPConn) SetReadDeadline(t time.Time) error
func (c *TCPConn) SetWriteDeadline(t time.Time) error

用來(lái)設(shè)置寫入/讀取一個(gè)連接的超時(shí)時(shí)間。當(dāng)超過(guò)設(shè)置時(shí)間時(shí),連接自動(dòng)關(guān)閉。

func (c *TCPConn) SetKeepAlive(keepalive bool) os.Error

設(shè)置客戶端是否和服務(wù)器端保持長(zhǎng)連接,可以降低建立TCP連接時(shí)的握手開(kāi)銷,對(duì)于一些需要頻繁交換數(shù)據(jù)的應(yīng)用場(chǎng)景比較適用。

更多的內(nèi)容請(qǐng)查看net包的文檔。

UDP Socket

Go語(yǔ)言包中處理UDP Socket和TCP Socket不同的地方就是在服務(wù)器端處理多個(gè)客戶端請(qǐng)求數(shù)據(jù)包的方式不同,UDP缺少了對(duì)客戶端連接請(qǐng)求的Accept函數(shù)。其他基本幾乎一模一樣,只有TCP換成了UDP而已。UDP的幾個(gè)主要函數(shù)如下所示:

func ResolveUDPAddr(net, addr string) (*UDPAddr, os.Error)
func DialUDP(net string, laddr, raddr *UDPAddr) (c *UDPConn, err os.Error)
func ListenUDP(net string, laddr *UDPAddr) (c *UDPConn, err os.Error)
func (c *UDPConn) ReadFromUDP(b []byte) (n int, addr *UDPAddr, err os.Error
func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (n int, err os.Error)

一個(gè)UDP的客戶端代碼如下所示,我們可以看到不同的就是TCP換成了UDP而已:

package main

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

func main() {
    if len(os.Args) != 2 {
        fmt.Fprintf(os.Stderr, "Usage: %s host:port", os.Args[0])
        os.Exit(1)
    }
    service := os.Args[1]
    udpAddr, err := net.ResolveUDPAddr("udp4", service)
    checkError(err)
    conn, err := net.DialUDP("udp", nil, udpAddr)
    checkError(err)
    _, err = conn.Write([]byte("anything"))
    checkError(err)
    var buf [512]byte
    n, err := conn.Read(buf[0:])
    checkError(err)
    fmt.Println(string(buf[0:n]))
    os.Exit(0)
}
func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
        os.Exit(1)
    }
}

我們來(lái)看一下UDP服務(wù)器端如何來(lái)處理:

package main

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

func main() {
    service := ":1200"
    udpAddr, err := net.ResolveUDPAddr("udp4", service)
    checkError(err)
    conn, err := net.ListenUDP("udp", udpAddr)
    checkError(err)
    for {
        handleClient(conn)
    }
}
func handleClient(conn *net.UDPConn) {
    var buf [512]byte
    _, addr, err := conn.ReadFromUDP(buf[0:])
    if err != nil {
        return
    }
    daytime := time.Now().String()
    conn.WriteToUDP([]byte(daytime), addr)
}
func checkError(err error) {
    if err != nil {
        fmt.Fprintf(os.Stderr, "Fatal error ", err.Error())
        os.Exit(1)
    }
}

總結(jié)

通過(guò)對(duì)TCP和UDP Socket編程的描述和實(shí)現(xiàn),可見(jiàn)Go已經(jīng)完備地支持了Socket編程,而且使用起來(lái)相當(dāng)?shù)姆奖?,Go提供了很多函數(shù),通過(guò)這些函數(shù)可以很容易就編寫出高性能的Socket應(yīng)用。