1. 程式人生 > >網路程式設計之:TCP伺服器的簡單實現

網路程式設計之:TCP伺服器的簡單實現

說到TCP伺服器,就不得不提socket程式設計,我們知道,在TCP/IP協議中,“IP地址+TCP或UDP埠號”唯一標識⽹絡通訊中的唯一一個程序,“IP地址+埠號”就稱為socket 在TCP協議中,建立連線的兩個程序各自有一個socket來標識,那麼這兩個socket組成 的socket pair就唯一標識一個連線。socket本身有“插座”的意思,因此⽤來描述網路連線的一 對⼀的關 系。  TCP/IP協議最早在BSD UNIX上實現,為TCP/IP協議設計的應用層程式設計介面稱為socket APITCP/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伺服器/客戶端啦,希望各種小夥伴們指出問題,共同學習~