本章以TCP,即Stream Socket寫一個
echo
程序。
其實本章可以參考我的另一篇,就足夠了:Socke編程簡介
至於Socket的API函數可以參考:Linux下C Socket編程基礎API
1. IPv4 TCP Client
向Server發起通信請求,Server是被動等待連接的。
第一個程序,echo客戶端程序:TCPEchoClient4.c。這個程序還引入了另一個工具頭文件:Practical.h,其中使用到的函數實現在DieWithMessage.c。在兩個DieWithXxx
函數中,爲什麼使用fputs()
,而非printf()
,是因爲把從網絡接收的數據作爲第一個參數傳遞給printf()
有安全漏洞,至於是什麼安全漏洞我還不曉得,查了下有關printf格式化漏洞的,請參見*printf()格式化串安全漏洞分析(上)。
客戶端工作步驟:
- 使用
socket()
創建一個TCP Socket; - 使用
connnect()
向Server建立連接; - 使用
send()
和recv()
進行通信; - 使用
close()
關閉連接。
一般在基礎的Socket編程中使用的頭文件如下所列:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <errno.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <netdb.h>
上述所說以及所用的函數原型可以通過Man Pages查到,查詢命令:man <functionName>
。
2. IPv4 TCP Server
C/S結構肯定是一對的,有客戶端和服務器端。很多系統都有一個echo服務器用來調試(debugging)和測試(testing),但是因爲安全的原因,這些服務器經常是不可用的。
這裏給出echo服務器端程序代碼:TCPEchoServer4.c。在這裏它還調用了Practical.h
中另外一個函數HandleTCPClient(),不過該文件不止這一個函數,編譯的話,還需要和另外一個文件AddressUtility.c一起編譯,或者先註釋掉其它函數。
服務器端工作步驟:
- 使用
socket()
創建一個TCP socket; - 使用
bind()
給socket分配一個端口號; - 使用
listen()
告知系統允許使用上述端口的連接; - 重複:
- 對每一個客戶端連接調用
accept()
獲取一個新的socket - 使用
send()
和recv()
和客戶端進行通信; - 使用
close()
關閉客戶端連接。
- 對每一個客戶端連接調用
看服務器端和客戶端的工作步驟就知道了,服務器端比客戶端多了bind()
以及listen()
和accept()
。
2.1 設置socket
其實connect()
也會綁定端口,不過用戶不在乎這個端口是哪個罷了,端口的分類與分配可以參考Service Name and Transport Protocol Port Number Registry。
在bind()
這裏還有一個需要注意的,就是它使用了
servAddr.sin_addr.s_addr = htonl(INADDR_ANY); // Any incoming interface
這個設置使得服務器可以不需要任何一個實質性的IP地址以接受客戶端連接。
bind()
失敗的原因很多,一個主要原因是其它socket正在使用這個端口號以及一些端口號(如系統端口號1~1023)的使用需要權限。
listen()
就是監聽bind()
綁定的端口號,後一個參數表示,最多能夠處理的連接數量(參考listen()
的註釋:N connection requests will be queued before further requests are refused.)
2.2 處理收到的連接
在這裏使用了無限循環。
accept()
是一個阻塞方法,掛起等待直到客戶端發來連接請求,如果有了請求它會返回一個新的socket文件描述符,此時已經和遠端(客戶端)socket建立了連接。這樣就表示可以收發信息了,不過需要使用新的socket文件描述符。
2.3 注意事項
在編譯運行該程序的時候,需要注意,雖然客戶端程序port參數默認給的是7,但是實際上取決於你給服務器端設置的port參數。如果你要是把服務器端的端口設置爲7,就可能會出現在2.1節所說問題:bind() failed: Permission denied
。所以一定要保證二者段口一致且別用系統端口。
Socket編程的一個重要原則是防衛式編程(Defensive Programming):就是說你的代碼不能信任從網絡上收到的任何信息!
作爲自己寫客戶端的替代,你可以使用telnet程序。