網路程式設計之:TCP伺服器的簡單實現
阿新 • • 發佈:2019-01-03
說到TCP伺服器,就不得不提socket程式設計,我們知道,在TCP/IP協議中,“IP地址+TCP或UDP埠號”唯一標識⽹絡通訊中的唯一一個程序,“IP地址+埠號”就稱為socket。
在TCP協議中,建立連線的兩個程序各自有一個socket來標識,那麼這兩個socket組成 的socket pair就唯一標識一個連線。socket本身有“插座”的意思,因此⽤來描述網路連線的一 對⼀的關 系。
TCP/IP協議最早在BSD UNIX上實現,為TCP/IP協議設計的應用層程式設計介面稱為socket API。
TCP/IP協議規定:網路資料流規定應採用大端位元組序,即低地址儲存高位資料,因此網路資料流的地址是由低到高的,此處應包括收取哈傳送資料兩個過程。
1.socket資料型別及其相關函式
socket API是一層抽象的⽹網路程式設計介面,適⽤用於各種底層⽹網路協議,如IPv4、IPv6,以及UNIX Domain Socket。然⽽,各種網路協議的地址格式並不相同,如下圖所示:
sockaddr資料結構:
可以看到,各種sockaddr的地址結構前16位都是一樣的,都表示整個結構體的長度,IPv4、IPv6和UNIX Domain Socket的地 址型別分別定義為常數AF_INET、AF_INET6、AF_UNI,這樣,只要取 得某種sockaddr結構體的 ⾸首地址,不需要知道具體是哪種型別的sockaddr結構體,就可以根據地 址型別欄位確定結構體中的內容。因此,socket API可以接受各種型別的sockaddr結構體指標做引數,例如bind、accept、connect等函式,這些函式的引數應該設計成void
*型別以便接受各 種類型的指 針,但是sock API的實現早於ANSI C標準化,那時還沒有void *型別,因此這些函式的引數都用struct sockaddr *型別表示,在傳遞引數之前要強制型別轉換⼀下。
舉個例子:bind(listen_sock,(struct sockaddr*)&local,sizeof(local))
下面是基於TCP協議伺服器/客戶端的一般流程:
伺服器呼叫socket()---建立套接字、bind()---繫結、listen()---監聽, 完成初始化後,調⽤accept()阻塞等待,處於監聽埠的狀態,客戶端呼叫socket()初始化後,呼叫connect()發出SYN段並阻塞等待伺服器應答,伺服器應答一個SYN-ACK段,客戶端收到後從connect()返回,同時應答一個ACK段,伺服器收到後從accept()返回。
資料傳輸的過程: 建立連線後,TCP協議提供全雙工 的通訊服務,但是一般的客戶端/伺服器程式的流程是由客戶端主動發起請求,伺服器被動處理請求,一問一答的⽅方式。因此,伺服器accept()返回後立刻呼叫read(),讀socket就像讀管道一樣,如果沒有資料到達就阻塞等待,這時客戶端呼叫write()傳送請求給伺服器,伺服器收到後從read()返回,對客戶端的請求進行處理,在此
期間客戶端呼叫read()阻塞等待伺服器的應答,伺服器呼叫write()將處理結果發回給客戶端,再次調⽤用read()阻塞 等待下一條請求,客戶端收到後從read()返回,傳送下一條請求,如此迴圈下去。
如果客戶端沒有更多的請求了,就呼叫close() 關閉連線,就像寫端關閉的管道⼀樣,伺服器 的 read()返回0,這樣伺服器就知道客戶端關閉了連線,也調⽤用close()關閉連線。注意,任何⼀方close()後,連線的兩個傳輸⽅方向都關閉,不能再發送資料了。如果⼀方呼叫shutdown() 則連線處於半關閉狀態,仍可接收對⽅方發來的資料。
下面就來看一下這個伺服器和客戶端是如何實現的:
首先來到目錄下建立server.c和client.c兩個檔案,在編寫一下Makefile檔案,然後正式編寫server.c:
以上就是server的編寫,下面是client 的編寫:
好了,現在伺服器和客戶端已將編寫好了,我們來測試一下:
我們在另一個終端進入到目標目錄裡執行client,並輸入文字:
然後來看看客戶端是否可以收到client發過來的訊息:
server已經成功收到了訊息,並且TCP伺服器只能被動的接受處理client發起的請求,所以只能是server接受client發來的訊息,server並不能給client發訊息。
好啦,這就是我編寫的簡單的TCP伺服器/客戶端啦,希望各種小夥伴們指出問題,共同學習~