UDP bind 端口和IP

14 篇文章 0 订阅
结论: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的讨论,http://www.newsmth.net/bbstcon.php?board=NetPRG&gid=40783如果不调用bind,则客户端在向外发包时,会由系统自己决定使用的接口的源端口,而调用bind则可以指定相应的参数。另外有个哥们提到“果是udp,使用bind以后,可以不使用sendto/recvform函数,而直接用write/read函数了,少去了写一个参数。”,这应该是调用connect后的效果。关于UDP中客户端调用connect的好处,还有一个作用是能够捕获错误。由于UDP是无连接的,connect在调用时其实没有向外发包,只是在协议栈中记录了该状态,应该是生成了一个类似TCB的结构。之后如果发生网络异常,比如对端不可达,客户端在往对端写数据后,本机会收到一个ICMP回应,则回来的ICMP不可达的响应能够被协议栈处理,通知客户端进程;当客户端再次对该fd进行操作时,比如读数据时,read等调用会返回一个错误。而不调用connect时,对于返回的ICMP响应,协议栈不知道该传递给上层的哪个应用,所以客户端进程中捕获不到相应的错误。在两种情况下,write或者sendto操作都是把数据放到协议栈的发送队列之后就返回成功,而相应的ICMP回应则要等数据到达对端后才能返回,所以通常这种情况叫做“异步错误”。使用下列代码进行验证:[cpp] view plaincopy 1.#include 2.#include 3.#include 4.#include 5.#include 6.#include 7. 8.#define MAXLINE 80 9.#define SERV_PORT 8888 10. 11.struct sockaddr_in servaddr; 12. 13.void do_cli(FILE *fp,int sockfd,struct sockaddr *pservaddr,socklen_t servlen) 14.{ 15. int n; 16. char sendline[MAXLINE],recvline[MAXLINE + 1]; 17. 18. #ifdef UDP_CONNECT 19. /* connect to server */ 20. if(connect(sockfd,(struct sockaddr *)pservaddr,servlen) == -1) 21. { 22. perror("connect error"); 23. exit(1); 24. } 25. #endif 26. 27. while(fgets(sendline,MAXLINE,fp) != NULL) 28. { 29. #ifdef UDP_CONNECT 30. /* read a line and send to server */ 31. write(sockfd,sendline,strlen(sendline)); 32. #else 33. sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); 34. #endif 35. 36. printf("write over\n"); 37. /* receive data from server */ 38. n = read(sockfd,recvline,MAXLINE); 39. if(n == -1) 40. { 41. 42. perror("read error"); 43. exit(1); 44. } 45. recvline[n] = 0; /* terminate string */ 46. fputs(recvline,stdout); 47. } 48.} 49. 50.int main(int argc,char **argv) 51.{ 52. int sockfd; 53. 54. /* check args */ 55. if(argc != 2) 56. { 57. printf("usage: udpclient serverip\n"); 58. exit(1); 59. } 60. 61. /* init servaddr */ 62. bzero(&servaddr,sizeof(servaddr)); 63. servaddr.sin_family = AF_INET; 64. servaddr.sin_port = htons(SERV_PORT); 65. if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <= 0) 66. { 67. printf("[%s] is not a valid IPaddress\n",argv[1]); 68. exit(1); 69. } 70. sockfd = socket(AF_INET,SOCK_DGRAM,0); 71. do_cli(stdin,sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); 72. return 0; 73.} [cpp] view plain copy 1.#include 2.#include 3.#include 4.#include 5.#include 6.#include 7. 8.#define MAXLINE 80 9.#define SERV_PORT 8888 10. 11.struct sockaddr_in servaddr; 12. 13.void do_cli(FILE *fp,int sockfd,struct sockaddr *pservaddr,socklen_t servlen) 14.{ 15. int n; 16. char sendline[MAXLINE],recvline[MAXLINE + 1]; 17. 18. #ifdef UDP_CONNECT 19. /* connect to server */ 20. if(connect(sockfd,(struct sockaddr *)pservaddr,servlen) == -1) 21. { 22. perror("connect error"); 23. exit(1); 24. } 25. #endif 26. 27. while(fgets(sendline,MAXLINE,fp) != NULL) 28. { 29. #ifdef UDP_CONNECT 30. /* read a line and send to server */ 31. write(sockfd,sendline,strlen(sendline)); 32. #else 33. sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *)&servaddr, sizeof(servaddr)); 34. #endif 35. 36. printf("write over\n"); 37. /* receive data from server */ 38. n = read(sockfd,recvline,MAXLINE); 39. if(n == -1) 40. { 41. 42. perror("read error"); 43. exit(1); 44. } 45. recvline[n] = 0; /* terminate string */ 46. fputs(recvline,stdout); 47. } 48.} 49. 50.int main(int argc,char **argv) 51.{ 52. int sockfd; 53. 54. /* check args */ 55. if(argc != 2) 56. { 57. printf("usage: udpclient serverip\n"); 58. exit(1); 59. } 60. 61. /* init servaddr */ 62. bzero(&servaddr,sizeof(servaddr)); 63. servaddr.sin_family = AF_INET; 64. servaddr.sin_port = htons(SERV_PORT); 65. if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <= 0) 66. { 67. printf("[%s] is not a valid IPaddress\n",argv[1]); 68. exit(1); 69. } 70. sockfd = socket(AF_INET,SOCK_DGRAM,0); 71. do_cli(stdin,sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr)); 72. return 0; 73.} ########################实验1,客户端进行connect######################客户端执行:mt@ubuntu:~/code$ gcc -o udpclient_connect -DUDP_CONNECT udpclient.cmt@ubuntu:~/code$ ./udpclient_connect 192.168.0.1abcdwrite overread error: Connection refused在另一窗口抓包:mt@ubuntu:~$ sudo tcpdump -i eth0 port 8888 or icmp -vtcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes21: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 521: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.cmt@ubuntu:~/code$ ./udpclient 192.168.0.1abcdwrite over在另一窗口抓包:mt@ubuntu:~$ sudo tcpdump -i eth0 port 8888 or icmp -vtcpdump: listening on eth0, link-type EN10MB (Ethernet), capture size 65535 bytes22: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 522: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端口发送数据时,它不会做出响应。
  • 4
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值