socket 通信關於bind那點事

結論:
1、採用TCP通信時,客戶端不需要bind()他自己的IP和端口號,而服務器必須要bind()自己本機的IP和端口號;
2、若採用UDP通信時(這裏是有客戶端和服務器之分才這麼說的,若是指定特定端口的UDP對等通信則不一樣了),客戶端也可以不需要bind()他自己的IP和端口號,而服務器需要bind自己IP地址和端口號;


原因:

1、
因爲服務器是時時在監聽有沒有客戶端的連接,如果服務器不綁定IP和端口的話,客戶端上線的時候怎麼連到服務器呢,所以服務器要綁定IP和端口,而客戶端就不需要了,客戶端上線是主動向服務器發出請求的,因爲服務器已經綁定了IP和端口,所以客戶端上線的就向這個IP和端口發出請求,這時因爲客戶開始發數據了(發上線請求),系統就給客戶端分配一個隨機端口,這個端口和客戶端的IP會隨着上線請求一起發給服務器,服務收到上線請求後就可以從中獲起發此請求的客戶的IP和端口,接下來服務器就可以利用獲起的IP和端口給客戶端迴應消息了。

2、採用UDP通信
1)若有客戶端和服務器之分的程序,創建sock後即可在該socket上用recvfrom/sendto方法發送接受數據了,因爲客戶端只需要用sendto發送數據到指定的地址,當然若是bind了,程序也沒什麼問題,區別就是系統用默認自動bind()指定你自己的socket參數地址(特別是在指定特定端口的UDP對等通信)只是這種情況沒有這樣用的。
那UDP服務器是怎麼知道客戶端的IP地址和UDP端口?
一般來說有兩種方式:
一種是客戶端發消息顯式地告訴服務器IP地址和端口,消息內容就包括IP地址和UDP端口。
另外一種就是隱式的,服務器從收到的包的頭部中得到包的源IP地址和端口。


2)若是沒有客戶端和服務器之分的程序,即自己指定特定端口的UDP對等通信,則客戶端和服務器都需要bind()IP地址和端口了。
通常udp服務端根本不需要知道客戶端的socket,它直接建立一個socket用於發送即可,udp通信的關鍵只在於IP和端口。
多個客戶端如果需要點到點分發,必須給服務端socket循環設置每個客戶端的IP併發出,但更常用的是廣播分發,服務端socket設定一個X.X.X.255的廣播地址並始終向它發送,每個客戶端建立的socket只需要綁定這個廣播地址便可以收到。

 

 

 

客戶端用不用bind 的區別

無連接的socket的客戶端和服務端以及面向連接socket的服務端通過調用bind函數來配置本地信息。使用bind函數時,通過將my_addr.sin_port置爲0,函數會自動爲你選擇一個未佔用的端口來使用。
  Bind()函數在成功被調用時返回0;出現錯誤時返回"-1"並將errno置爲相應的錯誤號。需要注意的是,在調用bind函數時一般不要將端口號置爲小於1024的值,因爲1到1024是保留端口號,你可以選擇大於1024中的任何一個沒有被佔用的端口號。

 有連接的socket客戶端通過調用Connect函數socket數據結構中保存本地和遠端信息無須調用bind(),因爲這種情況下只需知道目的機器的IP地址,而客戶通過哪個端口與服務器建立連接並不需要關心,socket執行體爲你的程序自動選擇一個未被佔用的端口,並通知你的程序數據什麼時候打開端口。(當然也有特殊情況,linux系統中rlogin命令應當調用bind函數綁定一個未用的保留端口號,還有當客戶端需要用指定的網絡設備接口和端口號進行通信等等)
總之:
1.需要在建連前就知道端口的話,需要 bind 
2.需要通過指定的端口來通訊的話,需要 bind
 

具體到上面那兩個程序,本來用的是TCP,客戶端就不用綁定端口了,綁定之後只能運行一個client 的程序,是屬於自己程序中人爲設定的障礙,而從服務器那邊得到的客戶機連接端口號(是系統自動分配的)與這邊客戶機綁定的端口號根本是不相關的,所以客戶 綁定也就失去了意義。
注意:
一個端口可以用於多個連接比如多個客戶端連接服務器的同一端口)。但是在同一個操作系統上,即服務器和客戶端都是本機上,多個客戶端去連接服務器,只有第一個客戶端的連接會被接收,第二個客戶端的連接請求不會被接收。
 
 
首先,服務器和客戶都可以bind,bind並不是服務器的專利
客戶端進程bind端口: 由進程選擇一個端口去連服務器,(如果默認情況下,調用bind函數時,內核指定的端口是同一個,那麼運行多個調用了bind 的client 程序,會出現端口被佔用的錯誤)注意這裏的端口是客戶端的端口。如果不分配就表示交給內核去選擇一個可用端口。
客戶進程bind IP地址:相當於爲發送出去的IP數據報分配了源IP地址,但交給進程分配IP地址的時候(就是這樣寫明瞭bind IP地址的時候)這個IP地址必須是主機的一個接口,不能分配一個不存在的IP。如果不分配就表示由內核根據所用的輸出接口來選擇源IP地址。

  一般情況下客戶端是不用調用bind函數的,一切都交給內核搞定!

 服務端進程bind端口:基本是必須要做的事情,比如一個服務器啓動時(比如freebsd),它會一個一個的捆綁衆所周知的端口來提供服務,同樣,如果bind了一個端口就表示我這個服務器會在這個端口提供一些“特殊服務”
 服務端進程bind IP地址目的是限制了服務進程創建的socket只接受那些目的地爲此IP地址的客戶鏈接,一般一個服務器程序裏都有
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 只是針對IP4,IP6代碼不太一樣
這樣一句話,意思就是:我不指定客戶端的IP,隨便連,來者不拒!

總之只要你bind時候沒有指定哪一項(置爲0),內核會幫你選擇。

 

=======================================================

 

客戶端調用bind 的作用及UDP客戶端調用connect 的問題

在水木上看到一個關於在客戶端調用bind的討論,


如果不調用bind,則客戶端在向外發包時,會由系統自己決定使用的接口的源端口,而調用bind則可以指定相應的參數。

另外有個哥們提到“果是udp,使用bind以後,可以不使用sendto/recvform函數,而直接用write/read函數了,少去了寫一個參數。”,這應該是調用connect後的效果。

關於UDP中客戶端調用connect的好處,還有一個作用是能夠捕獲錯誤
由於UDP是無連接的,connect在調用時其實沒有向外發包,只是在協議棧中記錄了該狀態,應該是生成了一個類似TCB的結構。之後如果發生網絡異常,比如對端不可達,客戶端在往對端寫數據後,本機會收到一個ICMP迴應,則回來的ICMP不可達的響應能夠被協議棧處理,通知客戶端進程;當客戶端再次對該fd進行操作時,比如讀數據時,read等調用會返回一個錯誤。而不調用connect時,對於返回的ICMP響應,協議棧不知道該傳遞給上層的哪個應用,所以客戶端進程中捕獲不到相應的錯誤。
在兩種情況下,write或者sendto操作都是把數據放到協議棧的發送隊列之後就返回成功,而相應的ICMP迴應則要等數據到達對端後才能返回,所以通常這種情況叫做“異步錯誤”。

使用下列代碼進行驗證:
  1. 1.#include <sys/socket.h>  
  2.   
  3. 2.#include <unistd.h>  
  4.   
  5. 3.#include <string.h>  
  6.   
  7. 4.#include <stdio.h>  
  8.   
  9. 5.#include <arpa/inet.h>  
  10.   
  11. 6.#include <stdlib.h>  
  12.   
  13. 7.  
  14.   
  15. 8.#define MAXLINE 80  
  16.   
  17. 9.#define SERV_PORT 8888  
  18.   
  19. 10.  
  20.   
  21. 11.struct sockaddr_in servaddr;  
  22.   
  23. 12.  
  24.   
  25. 13.void do_cli(FILE *fp,int sockfd,struct sockaddr *pservaddr,socklen_t servlen)  
  26.   
  27. 14.{  
  28.   
  29. 15.    int n;  
  30.   
  31. 16.    char sendline[MAXLINE],recvline[MAXLINE + 1];  
  32.   
  33. 17.  
  34.   
  35. 18.    #ifdef UDP_CONNECT  
  36.   
  37. 19.    /* connect to server */  
  38.   
  39. 20.    if(connect(sockfd,(struct sockaddr *)pservaddr,servlen) == -1)  
  40.   
  41. 21.    {  
  42.   
  43. 22.        perror("connect error");  
  44.   
  45. 23.        exit(1);  
  46.   
  47. 24.    }  
  48.   
  49. 25.    #endif  
  50.   
  51. 26.  
  52.   
  53. 27.    while(fgets(sendline,MAXLINE,fp) != NULL)  
  54.   
  55. 28.    {  
  56.   
  57. 29.        #ifdef UDP_CONNECT  
  58.   
  59. 30.        /* read a line and send to server */  
  60.   
  61. 31.        write(sockfd,sendline,strlen(sendline));  
  62.   
  63. 32.        #else  
  64.   
  65. 33.        sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));  
  66.   
  67. 34.        #endif  
  68.   
  69. 35.  
  70.   
  71. 36.        printf("write over\n");  
  72.   
  73. 37.        /* receive data from server */  
  74.   
  75. 38.        n = read(sockfd,recvline,MAXLINE);  
  76.   
  77. 39.        if(n == -1)  
  78.   
  79. 40.        {  
  80.   
  81. 41.  
  82.   
  83. 42.            perror("read error");  
  84.   
  85. 43.            exit(1);  
  86.   
  87. 44.        }  
  88.   
  89. 45.        recvline[n] = 0; /* terminate string */  
  90.   
  91. 46.        fputs(recvline,stdout);  
  92.   
  93. 47.    }  
  94.   
  95. 48.}  
  96.   
  97. 49.  
  98.   
  99. 50.int main(int argc,char **argv)  
  100.   
  101. 51.{  
  102.   
  103. 52.    int sockfd;  
  104.   
  105. 53.  
  106.   
  107. 54.    /* check args */  
  108.   
  109. 55.    if(argc != 2)  
  110.   
  111. 56.    {  
  112.   
  113. 57.        printf("usage: udpclient serverip\n");  
  114.   
  115. 58.        exit(1);  
  116.   
  117. 59.    }  
  118.   
  119. 60.  
  120.   
  121. 61.    /* init servaddr */  
  122.   
  123. 62.    bzero(&servaddr,sizeof(servaddr));  
  124.   
  125. 63.    servaddr.sin_family = AF_INET;  
  126.   
  127. 64.    servaddr.sin_port = htons(SERV_PORT);  
  128.   
  129. 65.    if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <= 0)  
  130.   
  131. 66.    {  
  132.   
  133. 67.        printf("[%s] is not a valid IPaddress\n",argv[1]);  
  134.   
  135. 68.        exit(1);  
  136.   
  137. 69.    }  
  138.   
  139. 70.    sockfd = socket(AF_INET,SOCK_DGRAM,0);  
  140.   
  141. 71.    do_cli(stdin,sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));  
  142.   
  143. 72.    return 0;  
  144.   
  145. 73.}  


########################實驗1,客戶端進行connect######################
客戶端執行:
mt@ubuntu:~/code$ gcc -o udpclient_connect -DUDP_CONNECT udpclient.c
mt@ubuntu:~/code$ ./udpclient_connect 192.168.0.1
abcd
write over
read error: Connection refused

在另一窗口抓包:
mt@ubuntu:~$ sudo tcpdump -i eth0 port 8888 or icmp -v
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
21:49:40.300735 IP (tos 0x0, ttl 64, id 20973, offset 0, flags [DF], proto UDP (17), length 33)
    localhost.42774 > localhost.8888: UDP, length 5
21:49:40.303965 IP (tos 0x0, ttl 64, id 22696, offset 0, flags [none], proto ICMP (1), length 56)
    localhost > localhost: ICMP localhost udp port 8888 unreachable, length 36
        IP (tos 0x0, ttl 64, id 20973, offset 0, flags [DF], proto UDP (17), length 33)
    localhost.42774 > localhost.8888: UDP, length 5

可以看到,客戶端發送數據之後,收到了ICMP不可達的迴應,此時客戶端進程的read()操作返回了錯誤,通過perror打印出來的錯誤信息爲:Connection refused
##########################實驗1 結束###################################

###########################實驗2,客戶端不進行connect###############
mt@ubuntu:~/code$ gcc -o udpclient udpclient.c
mt@ubuntu:~/code$ ./udpclient 192.168.0.1
abcd
write over

在另一窗口抓包:
mt@ubuntu:~$ sudo tcpdump -i eth0 port 8888 or icmp -v
tcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes
22:14:23.863178 IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto UDP (17), length 33)
    localhost.46642 > localhost.8888: UDP, length 5
22:14:23.864000 IP (tos 0x0, ttl 64, id 22730, offset 0, flags [none], proto ICMP (1), length 56)
    localhost > localhost: ICMP localhost udp port 8888 unreachable, length 36
        IP (tos 0x0, ttl 64, id 0, offset 0, flags [DF], proto UDP (17), length 33)
    localhost.46642 > localhost.8888: UDP, length 5

儘管有ICMP迴應返回,但客戶端沒有捕獲到該錯誤,此時阻塞在了read調用上。
##############################實驗2 結束############################

另外,調用connect之後,會發現應用程序只會對調用了connect的fd進行相應的操作,如果它同時監聽在某fd上,則不會響應該監聽fd上的數據。比如參考資料2中提到一個程序先用UDP監聽在機器B的9000上,同時用udp connect到另一臺機器A的8000端口,結果發現使用其他機器往機器B的9000端口發送數據時,它不會做出響應。



參考:
[3]www.cs.rpi.edu/~hollingd/netprog/notes/udp/udp.pdf
發佈了7 篇原創文章 · 獲贊 14 · 訪問量 19萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章