02.丟掉nc,自己實現echo客戶端
引言
上一篇文章『要瘋了,到底什麼是網路程式設計?』,我們用
Go
實現了自己的echo伺服器
,並且使用nc
偽裝echo客戶端
和我們自己寫的echo伺服器
進行了收發資料互動,並對這一過程進行了詳細的講解。這一節我們將用Go
實現自己的echo客戶端
,Let's go
。
目錄
設計思路
- 使用Go語言開發我們的
echo客戶端
,最小使用Go語言的原生net
網路庫,從而直擊網路程式設計
的本質。 - 從標準輸入讀取資料,發往伺服器,讀取伺服器返回的資料,列印到標準輸出。
- 注意讀寫資料細節問題。
echo客戶端程式碼
/** * File: echoClient.go * Author: 蛇叔 * 公眾號: 蛇叔程式設計心法 */ package main import ( "bufio" "fmt" "net" "os" "syscall" ) const ( PORT = 8888 ADDR = "127.0.0.1" SIZE = 100 ) func main() { // 1. 建立socket socketFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0) if err != nil || socketFd < 0 { fmt.Println("socket create err: ", err) os.Exit(-1) } ip4 := net.ParseIP(ADDR).To4() if ip4 == nil { fmt.Println("net.ParseIP err") os.Exit(-1) } sa := &syscall.SockaddrInet4{Port:PORT} copy(sa.Addr[:], ip4) // 2. 發起主動連線 err = syscall.Connect(socketFd, sa) if err != nil { fmt.Println("socket connect err: ", err) os.Exit(-1) } var ( bufReader = bufio.NewReader(os.Stdin) buf = make([]byte, SIZE) writen int readn int err2 error ) for { // 3. 從標準輸入讀取資料 line, _, err := bufReader.ReadLine() if err != nil { fmt.Println("bufReader.ReadLine err: ", err) break } buf = line[:] // 4. 向socket對端寫入資料 writen, err2 = syscall.Write(socketFd, buf) if writen > 0 { readn, err2 =syscall.Read(socketFd, buf) if readn > 0 { fmt.Println("read from socket: ") fmt.Println(string(buf[:readn])) } else { break } } else if writen <= 0 && err2 != nil { fmt.Printf("socket write; writen:%d, err: %s\n", writen, err2) break } } // 5. close socketFd _ = syscall.Close(socketFd) }
# 編譯
go build -o echoClient echoCLient.go
# 啟動上一節的`echoServer`
./echoServer
# 執行echoClient
./echoClient
# 傳送字串,列印返回
hello-echo
read from socket:
hello-echo
互動詳解
上一篇文章,我們畫了echo伺服器
和echo客戶端
詳細的互動過程。
echo客戶端
並不需要bind(), listen(),想一想這是為什麼呢?
其實bind會將當前socket
和一個埠相繫結,這樣就限制了客戶端的自由性。假如你要在一臺機器啟動多個echoClient
bind
了一個埠,那麼第二個echoClient
就啟動不了了。至於listen只有在被動連線的時候才需要監聽套接字,echoClient
客戶端無疑是需要主動發起連線的。在C/S
架構中,也一定是客戶端主動發起連線。
建立
socket
核心資料結構
// 1. 建立socket socketFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0) if err != nil || socketFd < 0 { fmt.Println("socket create err: ", err) os.Exit(-1) }
echoClient
第一步就是建立socket
核心資料結構,並繫結一個file
。都說Linux一切皆檔案
。那如何才能觀察到這個檔案呢?
首先我們把之後的程式碼都忽略掉。當新建了一個socket
核心資料結構後,給我們返回一個socketFd
。我們在後邊加一行for {}
。如下:
// 1. 建立socket
socketFd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, 0)
if err != nil || socketFd < 0 {
fmt.Println("socket create err: ", err)
os.Exit(-1)
}
for {
}
這時候,我們編譯go build -o echoClient echoClient.go
, 並執行 ./echoClient
。
[root@VM-16-9-centos ~]# ps -ef |head -1 ; ps -ef|grep echo |grep -v grep
UID PID PPID C STIME TTY TIME CMD
root 5662 3085 0 22:06 pts/0 00:00:00 ./echoServer
root 5755 5689 0 22:06 pts/1 00:00:00 ./echoClient
我們首先通過ps
和grep
命令,找到了echo客戶端
的程序號為5755。
[root@VM-16-9-centos v1]# lsof -nP -p 5755
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
echoClien 5755 root cwd DIR 253,1 4096 1180315 /root/wx/v1
echoClien 5755 root rtd DIR 253,1 4096 2 /
echoClien 5755 root txt REG 253,1 2035084 1051843 /root/wx/v1/echoClient
echoClien 5755 root 0u CHR 136,3 0t0 6 /dev/pts/3
echoClien 5755 root 1u CHR 136,3 0t0 6 /dev/pts/3
echoClien 5755 root 2u CHR 136,3 0t0 6 /dev/pts/3
echoClien 5755 root 3u sock 0,7 0t0 24732864 protocol: TCP
之後,我們通過lsof命令檢視echoClient
程序開啟的檔案,這裡我們著重關注最後一列
Linux中一切皆檔案。socket
套接字也不例外。3u
中3
表示的是這個socket
檔案描述符,u
表示這是一個讀寫方式開啟的檔案。TYPE列中的sock
表示這是一個socket
檔案型別。最後的TCP
說明該sock
是基於Tcp協議
的。
發起主動握手
去掉之前的for{}
程式碼,我們正常編譯執行一下。echoClient呼叫Connect發起3次握手的主動連線,也就是會給對端傳送一個SYN
同步原語,等待echoServer
收到後,發來ACK,SYN
,之後echoClient
傳送ACK
給echoServer
。此時Connect返回,Connect
認為三次握手已經完成,echoClient
端的TCP
狀態變為ESTABLISHED
。我們通過命令列工具lsof再檢視一下。
[root@VM-16-9-centos ~]# lsof -nP -p 5755
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
echoClien 5755 root cwd DIR 253,1 4096 1180315 /root/wx/v1
echoClien 5755 root rtd DIR 253,1 4096 2 /
echoClien 5755 root txt REG 253,1 2197037 1051837 /root/wx/v1/echoClient
echoClien 5755 root mem REG 253,1 2156240 265623 /usr/lib64/libc-2.17.so
echoClien 5755 root mem REG 253,1 142144 265649 /usr/lib64/libpthread-2.17.so
echoClien 5755 root mem REG 253,1 163312 265614 /usr/lib64/ld-2.17.so
echoClien 5755 root 0u CHR 136,1 0t0 4 /dev/pts/1
echoClien 5755 root 1u CHR 136,1 0t0 4 /dev/pts/1
echoClien 5755 root 2u CHR 136,1 0t0 4 /dev/pts/1
echoClien 5755 root 3u IPv4 24694342 0t0 TCP 127.0.0.1:52230->127.0.0.1:8888 (ESTABLISHED)
這裡我們仍然著重關注最後一列:
3u
中3
表示的是這個socket
檔案描述符,u
表示這是一個讀寫方式開啟的檔案。TYPE列中的IPv4
和NODE的TCP
表示這是一個基於ipv4
的tcp
型別的socket
。最後的Name也唯一確定了一個socket
的檔名。52230
表示是echoClient
客戶端隨機選擇的一個埠號,目的地址是127.0.0.1:8888
也正是我們的echoServer
伺服器地址。最後ESTABLISHED
表示這個TCP連線是一個ESTABLISHED
狀態的連線,和我們預期的一樣。在這裡,我們通過lsof
真正做到了看的見的TCP
。平時說的Linux一切皆檔案
思想,也得到了真實的印證。
收發資料
writen, err2 = syscall.Write(socketFd, buf)
當3次握手成功後,echoClient
向echoServer
寫入資料,這裡如果寫入成功,writen
一定是大於0,並且等於len(buf)
的。因為這裡用的是阻塞模式(非阻塞模式,以後的文章會講,所以記得關注哦~~),如果socket
傳送緩衝區空閒空間不夠,則syscall.Write
會一隻阻塞,直到傳送緩衝區可以完全寫入資料。如果writen
返回小於等於0則發生了錯誤。需要關閉連線。值得一提的是,如果對端echoServer
程式奔潰,echoServer
端核心協議棧會往客戶端傳送FIN
,這時候echoClient
read的時候,會返回0
,也就是EOF
。這種情況,通常需要客戶端關閉連線。
關閉
socket
最後呼叫Close()
,關閉連線,這裡echoClient
發起主動關閉。也就是會給echoServer
傳送FIN
。等待對端確認後,併發送過來FIN
,我們回覆一個ACK
, 客戶端會進入TIME_WAIT
狀態。
那麼在close()
之後,我們的echoClient
客戶端程序內的套機字是啥樣的呢?我們在程式末尾加上for{}
,像之前一樣,編譯執行,等待一會,我們再通過lsof
觀察一下echoClent
客戶端。
[root@VM-16-9-centos ~]# lsof -nP -p 5755
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
echoClien 5755 root cwd DIR 253,1 4096 1180315 /root/wx/v1
echoClien 5755 root rtd DIR 253,1 4096 2 /
echoClien 5755 root txt REG 253,1 2197037 1051837 /root/wx/v1/echoClient
echoClien 5755 root mem REG 253,1 2156240 265623 /usr/lib64/libc-2.17.so
echoClien 5755 root mem REG 253,1 142144 265649 /usr/lib64/libpthread-2.17.so
echoClien 5755 root mem REG 253,1 163312 265614 /usr/lib64/ld-2.17.so
echoClien 5755 root 0u CHR 136,1 0t0 4 /dev/pts/1
echoClien 5755 root 1u CHR 136,1 0t0 4 /dev/pts/1
echoClien 5755 root 2u CHR 136,1 0t0 4 /dev/pts/1
這時候,我們會看到,之前的3u
已經不存在了,標明這個socket
檔案已經關閉了。
至此,我們的echoClient
也分析完了。在CS架構
中,客戶端和伺服器都是必不可少。比如我們的安卓
或者IOS
應用就是客戶端,每時每刻都在和我們的服務端在做網路互動。可以說網路程式設計
是我們網際網路的基石。
上一篇和這一篇文章我們講解了正常的網路互動程式,下一篇我們將通過Wireshark和tcpdump一步步來分析下我們的echo客戶端/伺服器程式
,並對一些可能的異常情況進行分析,希望大家多多關注,我們下期再見。
參考文獻
- 《TCP/IP詳解 卷1》
- 《Unix網路程式設計 卷1》
- 《計算機網路》
希望大家喜歡,原創文章不易,麻煩大家關注
,在看
,轉發
一鍵三連,謝謝大家。希望通過程式碼+圖片的方式,教大家學看得見的網路程式設計。做不了火影主角,做個掌握核心科技的“蛇叔”也不錯哈