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