成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Go BIO/NIO探討:基于系統調用實現Tcp Echo Server

開發 前端
這篇文章中,我們利用面向過程編程的思路,只依賴Syscall庫而不是Net庫,實現一個簡單的 Echo Server,以更好地理解 Tcp Server 的工作原理。

Go net庫對 tcp server 的支持非常完善,其中最核心的部分依賴系統調用 socket/bind/listen/accept。這些系統調用被完好地封裝在syscall庫里, 而且這層封裝屏一定程度上蔽掉了底層操作系統的差異性。

如果你讀過前一篇文章,會發現net庫應用了面向對象編程的思路,對系統調用做了很多層封裝。這篇文章中,我們利用面向過程編程的思路,只依賴syscall庫而不是net庫,實現一個簡單的 echo server,以更好地理解 tcp server 的工作原理。

服務流程

  1. 創建套接字: syscall.Socket()。
  2. 綁定套接字和ip:port: syscall.Bind()。
  3. 監聽套接字: syscall.Listen()。
  4. for循環:接收tcp connection: syscall.Accept()處理tcp connection: go echo(clientSocketFd)。

通用的一些變量有:

var (
// IPV4協議
family = syscall.AF_INET
// 基于TCP, 提供有序、可靠、雙向、基于連接的字節流,不限制消息長度,支持消息的優先級傳輸
sotype = syscall.SOCK_STREAM
// protocol = tcp
_ = "tcp"
// ESTABLISHED狀態的tcp conn隊列的最大長度
listenBacklog = syscall.SOMAXCONN
// server ip:port
serverip = net.IPv4(0, 0, 0, 0)
serverport = 8080
)

為了方便代碼跳轉到linux的實現,可以修改Goland上的GOOS選項:

Goland配置

創建套接字

// 創建套接字
sockfd, err := syscall.Socket(family, sotype, 0)
if err != nil {
panic(fmt.Errorf("fails to create socket: %s", err))
}
syscall.CloseOnExec(sockfd)

net庫將套接字設置為 SOCK_NONBLOCK,非阻塞模式下 accept/read/write 有時候會返回 EWOULDBLOCK 或 EAGAIN 錯誤,需要利用 wait 機制去實現goroutine的阻塞,增加了編程的復雜度。這里我們使用默認的阻塞模式。如果想要實驗非阻塞模式,可以參考下面這段代碼:

// Nonblock 處理起來太復雜了,先注釋掉這一段
if err := syscall.SetNonblock(sockfd, true); err != nil {
syscall.Close(sockfd)
log.Printf("setnonblock error=%v\n", err)
os.Exit(-1)
}

綁定套接字和ip:port

// ipToSockaddrInet4 是從 net/tcpsock_posix.go 抄的
addr, err := ipToSockaddrInet4(serverip, serverport)
if err != nil {
panic(fmt.Sprintf("fails to convert address %s:%d to socket addr, err=%s",
serverip, serverport,
err))
}

if err := syscall.Bind(sockfd, &addr); err != nil {
panic(fmt.Sprintf("fails to bind socket %d to address %s:%d, err=%s",
sockfd,
serverip, serverport,
err))
}

監聽套接字

syscall.Listen函數修改sockfd的狀態為 LISTEN,內核開始監聽套接字。

if err := syscall.Listen(sockfd, listenBacklog); err != nil {
log.Printf("listen sockfd %d to addr error=%v\n", sockfd, err)
panic(fmt.Sprintf("fails to listen socket %d", sockfd))
} else {
log.Printf("Started listening on %s:%d", serverip, serverport)
}

for循環 accept

這里 syscall.Accept 仍然采用了阻塞模式。如果要采用非阻塞模式,則需要改成 syscall.Accept4 并傳入 SOCK_NONBLOCK 和 SOCK_CLOEXEC flag。

for {
clientSockfd, clientSockAddr, err := syscall.Accept(sockfd)
if err != nil {
log.Printf("accept sockfd %d error=%v\n", sockfd, err)
continue
}
clientSockAddrInet4 := clientSockAddr.(*syscall.SockaddrInet4)
log.Printf("Connected with new client, sock addr = %v:%d\n", clientSockAddrInet4.Addr, clientSockAddrInet4.Port)
go echo(clientSockfd)
}

一個 ESTABLISHED 套接字代表一個client端的連接,我們將這個字段傳給echo函數,實現復讀機功能。echo 會持續從套接字讀取數據到 byte buffer 結構中,然后再寫回到套接字。如果client端關閉連接,Read/Write 就會失敗,導致函數退出。

func echo(sockfd int) {
defer func() {
if err := syscall.Close(sockfd); err != nil {
log.Printf("[echo] close sock %v fails, err=%v\n", sockfd, err)
}
}()
var buf [32 * 1024]byte
for {
nRead, err := syscall.Read(sockfd, buf[:])
if err != nil {
log.Printf("fails to read data from sockfd %d, err=%v\n", sockfd, err)
return
}

if _, err := syscall.Write(sockfd, buf[:nRead]); err != nil {
log.Printf("fails to write data %s into sockfd %d, err=%v\n", buf[:nRead], sockfd, err)
return
}
}
}

關閉套接字

作為一個 Server,我們通常會要求 Graceful Shutdown (不過Gin框架沒有實現這一點)。做法也比較簡單。

  1. 創建一個容量為0的channel。
  2. 注冊監聽哪些操作系統信號。
  3. 在 goroutine 里從channel讀取信號,并做出相應的反應。
// 接收到Ctrl+C信號后,關閉socket
c := make(chan os.Signal)
signal.Notify(c, os.Interrupt, syscall.SIGTERM)
go func() {
<-c
log.Println("\r- Ctrl+C pressed in Terminal")

if err := syscall.Close(sockfd); err != nil {
log.Printf("Close sockfd %d fails, err=%v\n", sockfd, err)
} else {
log.Printf("Server stopped successfully!!!")
}
// 收到信號后需要處理, 否則程序會永久hang住, 需要kill -9 <pid>
// os.Exit 會導致所有goroutine都會立即停止執行
os.Exit(0)
}()

我們這里的處理比較簡單,沒有判斷具體是什么信號,只是關閉套接字,然后退出程序。

這段代碼放在 syscall.Socket 和 syscall.Bind 之間即可。

實現 echo client

echo client的功能是:

  1. 通過 socket, connect 系統調用建立與tcp server的連接。
  2. 創建 bufio.Reader,從os.Stdin讀取輸入。
  3. for循環: 從stdin讀取輸入,寫入套接字;遇到Ctrl+D退出。

代碼如下:

func main() {
var (
family = syscall.AF_INET
sotype = syscall.SOCK_STREAM
_ = "tcp"
serverip = net.IPv4(0, 0, 0, 0)
serverport = 8080
)

// 創建套接字
sockfd, err := syscall.Socket(family, sotype, 0)
if err != nil {
panic(fmt.Errorf("fails to create socket: %s", err))
}

defer syscall.Close(sockfd)

serverAddr, err := ipToSockaddrInet4(serverip, serverport)
if err != nil {
panic(fmt.Sprintf("fails to convert address %s:%d to socket addr, err=%v", serverip, serverport, err))
}

if err := syscall.Connect(sockfd, &serverAddr); err != nil {
panic(fmt.Errorf("fails to connect sockfd %d to server, err=%v\n", sockfd, err))
}

reader := bufio.NewReader(os.Stdin)
readBuf := make([]byte, 1024)

for {
dataBytes, err := reader.ReadBytes('\n')

if err == io.EOF { // keyboard signal: CTRL-D
log.Printf("Client exits gracefully!!!\n")
return
} else if err != nil {
log.Printf("read error %v, shall exit\n", err)
return
} else {
nWrite, err := syscall.Write(sockfd, dataBytes)
if err != nil {
log.Printf("write sockfd %d fails, error=%#v\n", sockfd, err)
return
} else {
log.Printf("write %d bytes\n", nWrite)
}

nRead, err := syscall.Read(sockfd, readBuf[:])
if err != nil {
log.Printf("read sockfd %d fails, error=%#v\n", sockfd, err)
return
} else {
log.Printf("read %d bytes, data=%s\n", nRead, readBuf[:nRead])
}
}
}
}

測試

為了能夠在Linux下運行代碼,可以在機器上安裝docker,在容器里跑。docker官方提供了 golang:1.19 鏡像,GOPATH 是 /go,我們直接用這個,并把本機的目錄映射進去:

# 刪除之前的容器,如果有
docker rm -f go_app

# 啟動容器
docker run -d \
--mount type=bind,source=$HOME/go/src,target=/go/src \
--workdir /go/src/github.com/ \
--name go_app \
--restart always \
golang:1.19 \
sleep infinity

# 進入容器的命令行
docker exec -it go_app bash

# cd echo_server directory
go run main.go

Golang鏡像不提供 netstat vim 等命令,需要手動在容器里安裝 net-tools 和 vim:

# 查看linux發行版
cat /etc/issue

# 替換成阿里云 debian 11 的源
# https://developer.aliyun.com/mirror/debian/
cat > /etc/apt/sources.list << EOF \
deb https://mirrors.aliyun.com/debian/ bullseye main non-free contrib \
deb-src https://mirrors.aliyun.com/debian/ bullseye main non-free contrib \
deb https://mirrors.aliyun.com/debian-security/ bullseye-security main \
deb-src https://mirrors.aliyun.com/debian-security/ bullseye-security main \
deb https://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib \
deb-src https://mirrors.aliyun.com/debian/ bullseye-updates main non-free contrib \
deb https://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib \
deb-src https://mirrors.aliyun.com/debian/ bullseye-backports main non-free contrib \
EOF

apt update
apt install -y net-tools vim man

一個觀察

關于四次揮手的一些觀察:有client連接時,server關閉后,需要等待一段時間才能釋放端口。這里挖個坑,后續可能不會填了。

下面是一個server和一個client的情況:

# netstat -anlop |grep 8080
Proto Recv-Q Send-Q Local Address Foreign Address State PID/Program name Timer
tcp 0 0 0.0.0.0:8080 0.0.0.0:* LISTEN 1784/main off (0.00/0/0)
tcp 0 0 127.0.0.1:34132 127.0.0.1:8080 ESTABLISHED 1856/main off (0.00/0/0)
tcp 0 0 127.0.0.1:8080 127.0.0.1:34132 ESTABLISHED 1784/main off (0.00/0/0)

Ctrl+C 關掉server,netstat 返回這樣的結果:

# netstat -anlop |grep 8080
tcp 1 0 127.0.0.1:34132 127.0.0.1:8080 CLOSE_WAIT 1856/main off (0.00/0/0)
tcp 0 0 127.0.0.1:8080 127.0.0.1:34132 FIN_WAIT2 - timewait (33.07/0/0)

短時間內再次啟動server, bind時會報錯 address already in use。再等一段時間,client端自動斷開,server才能啟動:

# netstat -anlop |grep 8080
tcp 1 0 127.0.0.1:34132 127.0.0.1:8080 CLOSE_WAIT 1856/main off (0.00/0/0)
責任編輯:姜華 來源: 今日頭條
相關推薦

2023-03-07 08:00:12

netpollGo

2023-03-06 08:37:58

JavaNIO

2023-03-09 08:22:57

Go net庫Socket

2020-04-16 15:20:43

PHP前端BIO

2011-12-15 10:56:55

JavaNIO

2011-03-31 10:41:49

BIONIOIO

2022-04-16 16:52:24

Netty網絡服務器客戶端程序

2011-12-15 09:40:06

Javanio

2018-09-19 14:53:02

NIOBIO運行

2020-10-10 19:37:27

BIO 、NIO 、A

2023-07-11 08:40:02

IO模型后臺

2019-10-18 08:22:43

BIONIOAIO

2021-06-21 11:25:54

GoTLS語言

2021-08-12 18:48:31

響應式編程Bio

2024-05-08 16:44:40

TCPRST網絡協議

2010-01-19 14:42:43

VB.NET調用過程重

2021-11-02 12:19:18

Go函數結構

2024-06-11 00:05:00

CasaOS云存儲管理

2011-07-22 17:48:29

IOS Socket TCP

2022-09-04 23:24:45

Go語言監控
點贊
收藏

51CTO技術棧公眾號

主站蜘蛛池模板: 亚洲精品在线免费 | 欧美日韩在线免费观看 | 欧美一二三 | 久久久青草婷婷精品综合日韩 | 色婷婷激情 | 国产福利小视频 | 无吗视频 | 国产欧美一区二区三区久久手机版 | 一区在线观看视频 | 农村真人裸体丰满少妇毛片 | 精品久久久久一区二区国产 | 亚洲一区二区中文字幕 | 日本三级网站在线观看 | 91国产精品 | 国产九九精品 | 欧美精品片 | av中文字幕在线 | 久久免费观看视频 | 成人综合在线视频 | 精品一区二区三区中文字幕 | 国产一区二区视频在线 | 日韩三级在线 | 无码日韩精品一区二区免费 | av在线播放免费 | 在线播放亚洲 | 亚洲免费人成在线视频观看 | av片免费观看 | 中文二区 | 日韩成人一区 | 国产精品永久 | 欧美黄色小视频 | 午夜电影在线播放 | 久久99久久98精品免观看软件 | 暖暖成人免费视频 | 黄免费看 | 日日天天 | 91精品久久久久久久久中文字幕 | 精品国产精品国产偷麻豆 | 亚洲免费网| 久久久av | 国产在线区 |