讓我們考慮乙個具體例子,引入將本書中遇到的許多概念和說法。實現乙個tcp當前時間查詢客戶程式的實現。該客戶程式與其伺服器建立乙個tcp連線後,伺服器直觀可讀格式簡單地送回當前時間和日期。
獲取時間的伺服器端**
#include
#include
#include
#include
#include
#include
#include
#include
#define max 1024
int main(
int argc, char **ar**)
bzero(
&servaddr, sizeof(servaddr));
servaddr.sin_family = af_inet;
servaddr.sin_addr.s_addr = htonl(inaddr_any)
; servaddr.sin_port = htons(13)
;/* daytime server */
bind(listenfd,
(struct sockaddr *
)&servaddr, sizeof(servaddr));
listen(listenfd,10)
;while(1
)return0;
}
客戶端的**
#include
#include
#include
#include
#include
#define port 8888
#define maxline 1024
int main(
int argc, char **ar**)
if(n <0)
perror(
"read error");
exit(0)
;}
執行客戶端的可以執行檔案的時候執行方式是
.
/a.out 127.0
.0.1
建立tcp套接字
socket函式建立乙個網際(af_inet)位元組流(sock_stream)套接字,它是tcp套接字的花哨名字。該函式返回乙個小整數描述符,以後的所有函式呼叫(如隨後的connect和read)就用該描述符來標識這個套接字。
指定伺服器的ip位址和埠
我們把伺服器的ip位址和埠號填入乙個網際套接字位址結構(乙個名為servaddr的sockaddr_in結構變數)。使用bzero把整個結構清零後置位址族為af_inet,埠號為8888,ip位址為第乙個命令列引數的值(ar**[1])。網際套接字位址結構中ip位址和埠號這兩個成員必須使用特定格式,為此我們呼叫函式htons(「主機到網路短整型」)去轉換二進位制埠號,又呼叫庫函式inet_pton(「呈現形式到數值」)去把ascii命令列引數轉換成合適的形式
建立與伺服器的連線
connect函式應用於乙個tcp套接字時,將與由它的第二個引數指向的套接字位址結構指定的伺服器建立乙個tcp連線。該套接字位址結構的長度也必須作為函式的第三個引數指定,對於網際套接字位址結構,我們總是使用c語言的sizeof操作符由編譯器來計算長度
讀入並輸出伺服器的應答
我們使用read函式讀取伺服器應答,並使用標準的i/o函式fputs輸出結果。使用tcp時必須小心,因為tcp是乙個沒有記錄邊界的位元組流協議。伺服器的應答通常如下格式的26位元組字串
mon may 26 20::58:40 2003\r\n
其中,\r時ascii回車符,\n時ascii換行符。使用位元組流協議的情況下,這26個位元組可以有多種返回方式:既可以時包含所有26個位元組的單個tcp分節,也可以是每個分節只包含1個位元組的26個tcp分節,還可以時總共26個位元組的任何其他組合。通常伺服器返回包含所有26個位元組的單個分節,但是如果資料量很大,我們就不能確保乙個read呼叫能返回伺服器的整個應答。因此從tcp套接字讀取資料時,我們總是需要把read編寫在某個迴圈中,當read返回0(表明對端關閉連線)或負值(表明發生錯誤)時終止迴圈
本例中伺服器關閉表徵記錄的結束。http超文字傳送協議也採用這個技術。還可以用其他的技術標記和記錄結束。例如,smtp(簡單郵件傳送協議)使用由ascii回車符後跟換行符構成的2位元組序列標記記錄的結束 ;sun遠端過程呼叫以及網域名稱系統在使用tcp承載應用資料時,在每個要傳送的記錄之前放置乙個二進位制的計數值,給出這個記錄的長度。這裡的重要概念時tcp本身並不提供記錄的結束標誌:如果應用程式需要確定記錄的邊界,它就要自己去實現已有的一些常用的方法可供選擇
終止程式
exit終止程式。unix在乙個程序終止時總是關閉該程序所有開啟的描述符,我們的tcp套接字 就此被關閉
伺服器端**解析:
建立tcp套接字
tcp套接字的建立和客戶端相同
把伺服器的眾所周知的埠和套接字**
通過填寫乙個網際套接字位址結構並呼叫bind函式,伺服器的眾所周知的埠(8888)被**到所建立的套接字。我們指定ip位址為inaddr_any,這樣要時伺服器主機有多個網路介面,伺服器程序就可以在任意網路介面上接受客戶端的連線。以後我們將了解怎麼樣限定伺服器程序只在單個網路介面上接受客戶端連線
把套接字轉換成監聽套接字
呼叫listen函式把該套接字轉換成監聽套接字,這樣來自客戶端的外來連線就可在該套接字上由核心接受。socket、bind、listen這3個呼叫步驟是任何tcp伺服器準備所謂的監聽描述符的正常步驟
接受客戶連線,傳送應答
通常情況下,伺服器程序在accept呼叫中被投入睡眠,等待某個客戶連線到達並被核心接受。tcp連線使用所謂的三路握手來建立。握手完成時accept返回,其返回值是乙個稱為已連線描述符的新描述符。該描述符用於與新的那個客戶通訊。accept為每個連線到本伺服器的客戶返回乙個新描述符
終止連線
伺服器通過呼叫close關閉與客戶的連線。該呼叫引發正常的tcp連線終止序列:每個方向上傳送乙個fin,每個fin又由各自對端的確認
本伺服器一次只能處理乙個客戶。如果多個客戶連線差不多同時到達,系統核心在某個最大數目的限制下把它們排入佇列,然後每次返回乙個給accept函式。本伺服器只需呼叫time和ctime函式,執行速度很快。然而如果伺服器需要較多的時間(譬如說幾秒鐘或者幾分鐘)服務每個客戶,那麼我們必須以某種方式重疊對各個客戶的伺服器。迭代服務,因為對於每個客戶都迭代執行一次。同時能處理多個客戶的併發伺服器有多種編寫技術。最簡單的技術就是fork,或在伺服器啟動時預先fork一定數量的子程序
如果從shell命令列啟動本例這樣的伺服器,我們也許想要它執行很長時間,因為伺服器往往在系統工作期間一直執行。這要求我們往往伺服器程式中新增**,以便它能夠作為乙個unix守護程序–能在後台執行且不跟任何終端關聯的程序執行
當大家出現這種錯誤的時候
connect error: connection refused
read error: transport endpoint is
not connected
可以先除錯一下埠和ping一下位址是否通,注意埠不能太小,因為小的會被系統保留
ping 位址
例如:ping 127.0
.0.1
telnet 位址 埠
例如:telnet 127.0
.0.1
8888
乙個簡單的時間獲取程式
客戶程式 include unp.h include myerror.h int main int argc,char argv 建立乙個tcp套接字,返回sockfd作為套接字描述符 int sockfd if sockfd socket af inet,sock stream,0 0 指定伺服器...
乙個簡單的時間獲取客戶端 伺服器程式
tcp時間獲取客戶程式 include unp.h intmain int argc,char argv exit 0 注釋 1.首先用socket建立套接字。引數af inet,sock stream是網際位元組流的意思 2.指定伺服器ip位址和埠號。首先用將儲存位址清零 然後設定位址族為af i...
乙個獲取時間的Python小程式
import ntplib from time import ctime c ntplib.ntpclient response c.request time.pool.aliyun.com print ctime response.tx time ntp是一種提高同步時間精度的網路協議,使用特定的...