首頁 後端開發 Python教學 Go语言基于Socket编写服务器端与客户端通信的实例

Go语言基于Socket编写服务器端与客户端通信的实例

Jun 10, 2016 pm 03:05 PM
go golang go語言 socket 網路程式設計

在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         if length             break 
        } 
        if string(buffer[i:i+ConstHeaderLength]) == ConstHeader { 
            messageLength := BytesToInt(buffer[i+ConstHeaderLength : i+ConstHeaderLength+ConstMLength]) 
            if length                 break 
            } 
            data := buffer[i+ConstHeaderLength+ConstMLength : i+ConstHeaderLength+ConstMLength+messageLength] 
            readerChannel  
        } 
    } 
 
    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 :=             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         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发来的信息啦~~

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1664
14
CakePHP 教程
1423
52
Laravel 教程
1321
25
PHP教程
1269
29
C# 教程
1249
24
手機上如何將XML轉換成PDF? 手機上如何將XML轉換成PDF? Apr 02, 2025 pm 10:18 PM

直接在手機上將XML轉換為PDF並不容易,但可以藉助雲端服務實現。推薦使用輕量級手機App上傳XML文件並接收生成的PDF,配合雲端API進行轉換。雲端API使用無服務器計算服務,選擇合適的平台至關重要。處理XML解析和PDF生成時需要考慮複雜性、錯誤處理、安全性和優化策略。整個過程需要前端App與後端API協同工作,需要對多種技術有所了解。

在Go語言中使用Redis Stream實現消息隊列時,如何解決user_id類型轉換問題? 在Go語言中使用Redis Stream實現消息隊列時,如何解決user_id類型轉換問題? Apr 02, 2025 pm 04:54 PM

Go語言中使用RedisStream實現消息隊列時類型轉換問題在使用Go語言與Redis...

GoLand中自定義結構體標籤不顯示怎麼辦? GoLand中自定義結構體標籤不顯示怎麼辦? Apr 02, 2025 pm 05:09 PM

GoLand中自定義結構體標籤不顯示怎麼辦?在使用GoLand進行Go語言開發時,很多開發者會遇到自定義結構體標籤在�...

Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Go語言中哪些庫是由大公司開發或知名的開源項目提供的? Apr 02, 2025 pm 04:12 PM

Go語言中哪些庫是大公司開發或知名開源項目?在使用Go語言進行編程時,開發者常常會遇到一些常見的需求,�...

Golang的目的:建立高效且可擴展的系統 Golang的目的:建立高效且可擴展的系統 Apr 09, 2025 pm 05:17 PM

Go語言在構建高效且可擴展的系統中表現出色,其優勢包括:1.高性能:編譯成機器碼,運行速度快;2.並發編程:通過goroutines和channels簡化多任務處理;3.簡潔性:語法簡潔,降低學習和維護成本;4.跨平台:支持跨平台編譯,方便部署。

在Go編程中,如何正確管理Mysql和Redis的連接與釋放資源? 在Go編程中,如何正確管理Mysql和Redis的連接與釋放資源? Apr 02, 2025 pm 05:03 PM

Go編程中的資源管理:Mysql和Redis的連接與釋放在學習Go編程過程中,如何正確管理資源,特別是與數據庫和緩存�...

Golang vs. Python:性能和可伸縮性 Golang vs. Python:性能和可伸縮性 Apr 19, 2025 am 12:18 AM

Golang在性能和可擴展性方面優於Python。 1)Golang的編譯型特性和高效並發模型使其在高並發場景下表現出色。 2)Python作為解釋型語言,執行速度較慢,但通過工具如Cython可優化性能。

c#多線程編程是什麼  c#多線程編程用處 c#多線程編程是什麼 c#多線程編程用處 Apr 03, 2025 pm 02:45 PM

C# 多線程編程是一種讓程序同時執行多項任務的技術,它可以通過提升性能、提高響應能力和實現並行處理來提高程序效率。雖然 Thread 類提供了直接創建線程的方法,但 Task 和 async/await 等高級工具可以提供更安全的異步操作和更簡潔的代碼結構。多線程編程中常見的難題包括死鎖、競態條件和資源洩漏,需要仔細設計線程模型和使用適當的同步機制來避免這些問題。

See all articles