백엔드 개발 파이썬 튜토리얼 Go语言基于Socket编写服务器端与客户端通信的实例

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

Jun 10, 2016 pm 03:05 PM
go golang 언어로 가다 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 기반 앱

AI Clothes Remover

AI Clothes Remover

사진에서 옷을 제거하는 온라인 AI 도구입니다.

Undress AI Tool

Undress AI Tool

무료로 이미지를 벗다

Clothoff.io

Clothoff.io

AI 옷 제거제

AI Hentai Generator

AI Hentai Generator

AI Hentai를 무료로 생성하십시오.

인기 기사

R.E.P.O. 에너지 결정과 그들이하는 일 (노란색 크리스탈)
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 최고의 그래픽 설정
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
R.E.P.O. 아무도들을 수없는 경우 오디오를 수정하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌
WWE 2K25 : Myrise에서 모든 것을 잠금 해제하는 방법
3 몇 주 전 By 尊渡假赌尊渡假赌尊渡假赌

뜨거운 도구

메모장++7.3.1

메모장++7.3.1

사용하기 쉬운 무료 코드 편집기

SublimeText3 중국어 버전

SublimeText3 중국어 버전

중국어 버전, 사용하기 매우 쉽습니다.

스튜디오 13.0.1 보내기

스튜디오 13.0.1 보내기

강력한 PHP 통합 개발 환경

드림위버 CS6

드림위버 CS6

시각적 웹 개발 도구

SublimeText3 Mac 버전

SublimeText3 Mac 버전

신 수준의 코드 편집 소프트웨어(SublimeText3)

GO에서 플로팅 포인트 번호 작업에 어떤 라이브러리가 사용됩니까? GO에서 플로팅 포인트 번호 작업에 어떤 라이브러리가 사용됩니까? Apr 02, 2025 pm 02:06 PM

Go Language의 부동 소수점 번호 작동에 사용되는 라이브러리는 정확도를 보장하는 방법을 소개합니다.

Go 's Crawler Colly의 큐 스레드의 문제는 무엇입니까? Go 's Crawler Colly의 큐 스레드의 문제는 무엇입니까? Apr 02, 2025 pm 02:09 PM

Go Crawler Colly의 대기열 스레딩 문제는 Colly Crawler 라이브러리를 GO 언어로 사용하는 문제를 탐구합니다. � ...

휴대 전화에서 XML을 PDF로 변환하는 방법은 무엇입니까? 휴대 전화에서 XML을 PDF로 변환하는 방법은 무엇입니까? Apr 02, 2025 pm 10:18 PM

휴대 전화에서 XML을 PDF로 직접 변환하는 것은 쉽지 않지만 클라우드 서비스를 통해 달성 할 수 있습니다. 가벼운 모바일 앱을 사용하여 XML 파일을 업로드하고 생성 된 PDF를 수신하고 클라우드 API로 변환하는 것이 좋습니다. Cloud API는 Serverless Computing Services를 사용하고 올바른 플랫폼을 선택하는 것이 중요합니다. XML 구문 분석 및 PDF 생성을 처리 할 때 복잡성, 오류 처리, 보안 및 최적화 전략을 고려해야합니다. 전체 프로세스에는 프론트 엔드 앱과 백엔드 API가 함께 작동해야하며 다양한 기술에 대한 이해가 필요합니다.

GO의 어떤 라이브러리가 대기업에서 개발하거나 잘 알려진 오픈 소스 프로젝트에서 제공합니까? GO의 어떤 라이브러리가 대기업에서 개발하거나 잘 알려진 오픈 소스 프로젝트에서 제공합니까? Apr 02, 2025 pm 04:12 PM

GO의 어떤 라이브러리가 대기업이나 잘 알려진 오픈 소스 프로젝트에서 개발 했습니까? GO에 프로그래밍 할 때 개발자는 종종 몇 가지 일반적인 요구를 만납니다.

GO 언어에서 'var'와 'type` 키워드 정의 구조의 차이점은 무엇입니까? GO 언어에서 'var'와 'type` 키워드 정의 구조의 차이점은 무엇입니까? Apr 02, 2025 pm 12:57 PM

GO 언어에서 구조를 정의하는 두 가지 방법 : VAR과 유형 키워드의 차이. 구조를 정의 할 때 Go Language는 종종 두 가지 다른 글쓰기 방법을 본다 : 첫째 ...

Redis Stream을 사용하여 GO Language에서 메시지 대기열을 구현할 때 User_ID 유형 변환 문제를 해결하는 방법은 무엇입니까? Redis Stream을 사용하여 GO Language에서 메시지 대기열을 구현할 때 User_ID 유형 변환 문제를 해결하는 방법은 무엇입니까? Apr 02, 2025 pm 04:54 PM

Go Language에서 메시지 대기열을 구현하기 위해 Redisstream을 사용하는 문제는 Go Language와 Redis를 사용하는 것입니다 ...

이동 중에 왜 println 및 string () 함수로 문자열이 다른 효과를 갖는 이유는 무엇입니까? 이동 중에 왜 println 및 string () 함수로 문자열이 다른 효과를 갖는 이유는 무엇입니까? Apr 02, 2025 pm 02:03 PM

Go Language의 문자열 인쇄의 차이 : println 및 String () 함수 사용 효과의 차이가 진행 중입니다 ...

다중 프로세스 로그를 작성할 때 동시성이 안전하고 효율적인지 확인하는 방법은 무엇입니까? 다중 프로세스 로그를 작성할 때 동시성이 안전하고 효율적인지 확인하는 방법은 무엇입니까? Apr 02, 2025 pm 03:51 PM

다중 프로세스 로그 쓰기에서 동시성 보안 문제를 효율적으로 처리합니다. 여러 프로세스는 동시에 동일한 로그 파일을 작성합니다. 동시성을 안전하고 효율적으로 보장하는 방법은 무엇입니까? 이것은 ...

See all articles