標籤:blog http io os ar 使用 strong sp 檔案
socket
socket是在應用程式層和傳輸層之間的一個抽象層,它把TCP/IP層複雜的操作抽象為幾個簡單的介面供應用程式層調用已實現進程在網路中通訊。
socket起源於UNIX,在Unix一切皆檔案哲學的思想下,socket是一種"開啟—讀/寫—關閉"模式的實現,伺服器和用戶端各自維護一個"檔案",在建立串連開啟後,可以向自己檔案寫入內容供對方讀取或者讀取對方內容,通訊結束時關閉檔案。
socket 類型
常見的socket有3種類型如下。 (1)流式socket(SOCK_STREAM ) 流式通訊端提供可靠的、連線導向的通訊流;它使用TCP 協議,從而保證了資料轉送的正確性和順序性。 (2)資料報socket(SOCK_DGRAM ) 資料通訊端定義了一種不需連線的服 ,資料通過相互獨立的報文進行傳輸,是無序的,並且不保證是可靠、無差錯的。它使用資料報協議UDP。 (3)原始socket(SOCK_RAW) 原始通訊端允許對底層協議如IP或ICMP進行直接存取,功能強大但使用較為不便,主要用於一些協議的開發。
socket建立和串連
電腦資料存放區有兩種位元組優先順序:高位位元組優先和低位位元組優先。Internet上資料以高位位元組優先順序在網路上傳輸,所以對於在內部是以低位位元組優先方式儲存資料的機器,在Internet上傳輸資料時就需要進行轉換。
幾個位元組順序轉換函式: htons()--"Host to Network Short" ; htonl()--"Host to Network Long" ntohs()--"Network to Host Short" ; ntohl()--"Network to Host Long" 在這裡, h表示"host" ,n表示"network",s 表示"short",l表示 "long"。
int socket(int family, int type, int protocol); family指定協議族;type參數指定socket的類型:SOCK_STREAM、SOCK_DGRAM、SOCK_RAW;protocol通常賦值"0"。
socket()調用返回一個整型socket描述符,你可以在後面的調用使用它。 一旦通過socket調用返回一個socket描述符,你應該將該socket與你本機上的一個連接埠相關聯(往往當你在設計伺服器端程式時需要調用該函數。隨後你就可以在該連接埠監聽服務要求;而用戶端一般無須調用該函數)。
int bind(int sockfd, struct sockaddr *my_addr, int addrlen); sockfd是一個socket描述符,my_addr是一個指向包含有本機IP地址及連接埠號碼等資訊的sockaddr類型的針; addrlen常被設定為sizeof(struct sockaddr)。 最後,對於bind 函數要說明的一點是,你可以用下面的賦值實現自動獲得本機IP地址和隨機擷取一個沒有被佔用的連接埠號碼: my_addr.sin_port = 0; /* 系統隨機播放一個未被使用的連接埠號碼 */ my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本機IP地址 */ 通過將my_addr.sin_port置為0,函數會自動為你選擇一個未佔用的連接埠來使用。同樣,通過將 my_addr.sin_addr.s_addr置為INADDR_ANY,系統會自動填入本機IP地址。bind()函數在成功被調用時返回0;遇到錯 誤時返回"-1"並將errno置為相應的錯誤號碼。另外要注意的是,當調用函數時,一般不要將連接埠號碼置為小於1024的值,因為1~1024是保留連接埠 號,你可以使用大於1024中任何一個沒有被佔用的連接埠號碼。
當對TCP/IP協議族的通訊端進行綁定時,我們通常使用另一個地址結構: struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; 其中sin_family置AF_INET;sin_port指明連接埠號碼;sin_addr結構體中只有一個唯一的欄位s_addr,表示IP地址,該字 段是一個整數,一般用函數inet_addr()把字串形式的IP地址轉換成unsigned long型的整數值後再置給s_addr。有的伺服器是多重主機機,至少有兩個網卡,那麼運行在這樣的伺服器上的服務程式在為其socket綁定IP地址時 可以把htonl(INADDR_ANY)置給s_addr,這樣做的好處是不論哪個網段上的客戶程式都能與該服務程式通訊;如果只給運行在多重主機機上的 服務程式的socket綁定一個固定的IP地址,那麼就只有與該IP地址處於同一個網段上的客戶程式才能與該服務程式通訊。我們用0來填充 sin_zero數組,目的是讓sockaddr_in結構的大小與sockaddr結構的大小一致。下面是一個bind函數調用的例子: struct sockaddr_in saddr; memset((void *)&saddr,0,sizeof(saddr)); saddr.sin_family = AF_INET; saddr.sin_port = htons(8888); saddr.sin_addr.s_addr = htonl(INADDR_ANY); //saddr.sin_addr.s_addr = inet_addr("192.168.22.5"); 綁定固定IP bind(ListenSocket,(struct sockaddr *)&saddr,sizeof(saddr)); int listen(int sockfd, int backlog); sockfd是socket系統調用返回的伺服器端socket描述符;backlog指定在請求隊列中允許的最大請求數,進入的串連請求將在隊列中等待 accept()它們(參考下文)。backlog對隊列中等待服務的請求的數目進行了限制,大多數系統預設值為20。當listen遇到錯誤時返回 -1,errno被置為相應的錯誤碼。
int accept(int sockfd, struct sockaddr *addr, int *addrlen); sockfd是被監聽的伺服器socket描述符,addr通常是一個指向sockaddr_in變數的指標,該變數用來存放提出串連請求的用戶端地 址;addrten通常為一個指向值為sizeof(struct sockaddr_in)的整型指標變數。錯誤發生時返回一個-1並且設定相應的errno值。accept()函數將返回一個新的socket描述符, 來供這個新串連來使用,在新的socket描述符上進行資料send()和recv()操作。
故伺服器端程式通常按下列順序進行函數調用: socket(); bind(); listen(); /* accept() goes here */
connect()函數用來與遠端伺服器建立一個TCP串連其函數原型為: int connect(int sockfd, struct sockaddr *serv_addr, int addrlen); sockfd是目的伺服器的sockt描述符;serv_addr是伺服器端的IP地址和連接埠號碼的地址。遇到錯誤時返回-1,並且errno中包含相應的 錯誤碼。進行用戶端程式設計無須調用bind(),因為這種情況下只需知道目的機器的IP地址,而客戶通過哪個連接埠與伺服器建立串連並不需要關心,核心會 自動選擇一個未被佔用的連接埠供用戶端來使用。
UDP的“connect”: 1、程式可以使用connect實現UDP串連通訊端,作用是在UDP通訊端中記住目的地址和目的連接埠。 2、UDP通訊端使用connect後,如果資料報不是connect中指定的地址和連接埠,將被丟棄。沒有調用connect的UDP通訊端,將接收所有到達這個連接埠的UDP資料報,而不區分源連接埠和地址。
關於“bind”: 1、client端的socket不需要bind,核心會自動選擇一個未被佔用的port供client來使用,如果有多個可用的串連(多個IP),核心會根據優先順序選擇一個IP作為源IP使用。 2、如果socket使用bind綁定到特定的IP和port,則無論是TCP還是UDP,都會從指定的IP和port發送資料。
socket發送與接收資料
send()和recv()——資料轉送,用於連線導向的socket(SOCK_STREAM)上進行資料轉送
int send(int sockfd, const void *msg, int len, int flags); sockfd是你想用來傳輸資料的socket描述符,msg是一個指向要發送資料的指標。 len是以位元組為單位的資料的長度。flags一般情況下置為0(關於該參數的用法可參照man手冊)。 send()函數返回實際上發送出的位元組數,可能會少於你希望發送的資料。所以需要對send()的傳回值進行測量。當send()傳回值與len不匹配時,應該對這種情況進行處理。
int recv(int sockfd,void *buf,int len,unsigned int flags); sockfd是接受資料的socket描述符;buf 是存放接收資料的緩衝區;len是緩衝的長度。flags也被置為0。recv()返回實際上接收的位元組數,或當出現錯誤時,返回-1共置相應的errno值。
帶外資料:在資料流通道之外的通道上傳輸的資料,常用於對遠端進程的同步和控制。在TCP中一次只能發送1位元組的帶外資料。 發送:send(sock_fd,‘f‘,1,MSG_OOB); 接收:recv(sock_fd,&out_data,1,MSG_OOB); 帶外資料存放區在out_data中。
sendto()和recvfrom()——資料轉送,用於面向非串連的socket(SOCK_DGRAM/SOCK_RAW)上進行資料轉送
在不需連線的資料報socket方式下,由於本地socket並沒有與遠端機器建立串連,所以在發送資料時應指明目的地址,sendto()函數原型為: int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to,int tolen); 該函數比send()函數多了兩個參數,to表示目地機的IP地址和連接埠號碼資訊,而tolen常常被賦值為sizeof (struct sockaddr)。sendto 函數也返回實際發送的資料位元組長度或在出現發送錯誤時返回-1。
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); from是一個struct sockaddr類型的變數,該變數儲存源機的IP地址及連接埠號碼。fromlen常置為sizeof(struct sockaddr)。當recvfrom()返回時,fromlen包含實際存入from中的資料位元組數。Recvfrom()函數返回接收到的位元組數或 當出現錯誤時返回-1,共置相應的errno。 應注意的一點是,當你對於資料報socket調用了connect()函數時,你也可以利用 send()和recv()進行資料轉送,但該socket仍然是資料報socket,並且利用傳輸層的UDP服務。但在發送或接收資料報時,核心會自動 為之加上目地和源地址資訊。
關閉socket
close()和shutdown()——結束資料轉送 當所有的資料操作結束以後,你可以調用close()函數來釋放該socket,從而停止在該socket上的任何資料操作:
close(sockfd); close()是對通訊端的操作,關閉後進程不能在訪問這個通訊端。 你也可以調用shutdown()函數來關閉該socket。該函數允許你只停止在某個方向上的資料轉送,而一個方向上的資料轉送繼續進行。如你可以關閉 某socket的寫操作而允許繼續在該socket上接受資料,直至讀入所有資料。shutdown是對TCP串連的操作。
int shutdown(int sockfd,int how); sockfd的含義是顯而易見的,而參數 how可以設為下列值: ·0-------不允許繼續接收資料 ·1-------不允許繼續發送資料 ·2-------不允許繼續發送和接收資料,均為允許則調用close() shutdown在操作成功時返回0,在出現錯誤時返回-1(共置相應errno)。
IP DNS 等相關函數
in_addr_t inet_addr(const char * strptr); 將字串IP地址轉換為IPv4地址結構in_addr值
char * inet_ntoa(struct in_addr * addrptr); 將IPv4地址結構in_addr值轉換為字串IP
網域名稱和IP地址的轉換: struct hostent *gethostbyname(const char *name); 函數返回一種名為hostent的結構類型,它的定義如下: struct hostent { char *h_name; /* 主機的官方網域名稱 */ char **h_aliases; /* 一個以NULL結尾的主機別名數組 */ int h_addrtype; /* 返回的地址類型,在Internet環境下為AF-INET */ int h_length; /*地址的位元組長度 */ char **h_addr_list; /* 一個以0結尾的數組,包含該主機的所有地址*/ }; #define h_addr h_addr_list[0] /*在h-addr-list中的第一個地址*/
注意:以上三個函數都是不可重新進入的,如果你寫下如下代碼: if(strcmp( inet_ntoa(ip1), inet_ntoa(ip2) ) == 0 ) //判斷2個IP地址是否相同 { .... .... } 上面if條件判斷永遠為真!在使用上面三個函數時,函數返回後,要馬上取出結果儲存傳回值,否則會被下次調用覆蓋。因為struct in_addr 和 struct hostent 在儲存時使用了static類型。
參考:http://www.ibm.com/developerworks/cn/education/linux/l-sock/index.html
網路編程socket基本API詳解(轉)