国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Python > 正文

Go語言基于Socket編寫服務器端與客戶端通信的實例

2019-11-25 16:55:35
字體:
來源:轉載
供稿:網友

在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 < length; i = i + 1 { 
        if length < i+ConstHeaderLength+ConstMLength { 
            break 
        } 
        if string(buffer[i:i+ConstHeaderLength]) == ConstHeader { 
            messageLength := BytesToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength]) 
            if length < i+ConstHeaderLength+ConstLength+messageLength { 
                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發來的信息啦~~

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 阳曲县| 武鸣县| 宁海县| 苏尼特右旗| 穆棱市| 泸定县| 贵州省| 曲靖市| 霍山县| 梧州市| 友谊县| 正安县| 喀什市| 扶绥县| 云和县| 天峨县| 高要市| 嵩明县| 汽车| 太仆寺旗| 陆丰市| 安图县| 肥东县| 从江县| 罗定市| 聊城市| 兴和县| 龙胜| 普安县| 东港市| 安岳县| 龙游县| 宁陕县| 常山县| 崇阳县| 门头沟区| 曲沃县| 靖宇县| 大方县| 江北区| 东丰县|