博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

客户端 用不用 bind 的区别

Posted on 2015-03-25 16:34  bw_0927  阅读(2098)  评论(0)    收藏  举报

http://blog.chinaunix.net/uid-23193900-id-3199173.html

https://www.cnblogs.com/my_life/articles/4935240.html

https://blog.csdn.net/dog250/article/details/5303572

https://blog.csdn.net/dog250/article/details/6896949 

无连接的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函数的,一切都交给内核搞定,YES!
 
  服务端进程bind端口:基本是必须要做的事情,比如一个服务器启动时(比如freebsd),它会一个一个的捆绑众所周知的端口来提供服务,同样,如果bind了一个端口就表示我这个服务器会在这个端口提供一些“特殊服务”
  服务端进程bind IP地址目的是限制了服务端进程创建的socket只接受那些目的地为此IP地址的客户链接,一般一个服务器程序里都有
servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 只是针对IP4,IP6代码不太一样
这样一句话,意思就是:我不指定客户端的目的IP,随便连,来者不拒!
 
总之只要你bind时候没有指定哪一项(置为0),内核会帮你选择。
socklen_t len;
struct sockaddr_in local_addr;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
memset(&local_addr, 0 , sizeof(local_addr));
local_addr.sin_family = AF_INET;
local_addr.sin_port = htons(0);
local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (struct sockaddr*)&local_addr, sizeof(local_addr));
getsockname(sockfd, (struct sockaddr*)&local_addr, &len);

 

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

sendto(fd, buf, size, addr)    :addr 远端地址,入参

recvfrom(fd, buf, size, addr)   :addr 远端地址,出参

 

UDP connect 并没有真正建立一个连接,即没有3次握手过程,只是维护了一种状态,绑定了远程地址因此在调用sendto 时也可以不指定远程地址了,如 sendto(sock, sendbuf, strlen(sendbuf), 0, NULL, 0);  

甚至也可以使用send 函数 send(sock, sendbuf, strlen(sendbuf), 0);

假设现在客户端有多个ip地址,由connect 或 sendto 函数提供的远程地址的参数,系统会选择一个合适的出口,比如远程ip 是192.168.2.10, 而客户端现在的ip 有 192.168.1.32 和 192.168.2.75 那么会自动选择192.168.2.75 这个ip 出去。

 

 

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

https://blog.csdn.net/qq_29344757/article/details/71616748

1. udp客户端使用connect()函数

udp客户端建立了socket后可以直接调用sendto()函数向服务器发送数据,但是需要在sendto()函数的参数中指定目的地址/端口,但是可以调用connect()函数先指明目的地址/端口,然后就可以使用send()函数向目的地址发送数据了,因为此时套接字已经包含目的地址/端口,也就是send()函数已经知道包含目的地址/端口。

//用sendto()函数发送数据的udp客户端程序
int main(int argc, char *argv[])
{
    int sd;
    struct sockaddr_in svr_addr;
    int ret;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[BUFSZ] = {};

    if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    //sendto()函数需要指定目的端口/地址
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_port = htons(PORT);
    svr_addr.sin_addr.s_addr = inet_addr("192.168.1.166");

    while (1)
    {   
        memset(buf, 0, BUFSZ);
        printf("ple input: ");
        fgets(buf, BUFSZ, stdin);
        sendto(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, addrlen);

        ret = recvfrom(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, &addrlen);
        printf("client: IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(svr_addr.sin_addr), ntohs(svr_addr.sin_port), buf);  
    }

    close(sd);  
    return 0;
}

  



send()函数发送数据的udp客户端程序:

//用send()函数发送数据的udp客户端程序
int main(int argc, char *argv[])
{
    int sd;
    struct sockaddr_in svr_addr;
    int ret;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[BUFSZ] = {};

    if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    //先调用connect()函数,为套接字指定目的地址/端口
    svr_addr.sin_family = AF_INET;
    svr_addr.sin_port = htons(PORT);
    svr_addr.sin_addr.s_addr = inet_addr("192.168.1.166");
    connect(sd, (struct sockaddr* )&svr_addr, addrlen);

    while (1)
    {   
        memset(buf, 0, BUFSZ);
        printf("ple input: ");
        fgets(buf, BUFSZ, stdin);
        //sendto(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, addrlen);
        send(sd, buf, BUFSZ, 0);

        ret = recvfrom(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, &addrlen);
        printf("client: IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(svr_addr.sin_addr), ntohs(svr_addr.sin_port), buf);  
    }

    close(sd);  
    return 0;
}

  

 


2. udp客户端程序使用bind()函数

 

//为客户端绑定端口和地址信息
int main(int argc, char *argv[])
{
    int sd;
    struct sockaddr_in svr_addr, cli_addr;
    int ret;
    socklen_t addrlen = sizeof(struct sockaddr_in);
    char buf[BUFSZ] = {};

    if ((sd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
    {
        perror("socket");
        exit(EXIT_FAILURE);
    }

    //绑定地址信息
    cli_addr.sin_family = AF_INET;
    cli_addr.sin_port = htons(9693);
    cli_addr.sin_addr.s_addr = 0;
    if ((ret = bind(sd, (struct sockaddr* )&cli_addr, addrlen)) < 0)
    {
        perror("bind");
        exit(EXIT_FAILURE);
    }

    svr_addr.sin_family = AF_INET;
    svr_addr.sin_port = htons(PORT);
    svr_addr.sin_addr.s_addr = inet_addr("192.168.1.166");

    while (1)
    {   
        memset(buf, 0, BUFSZ);
        printf("ple input: ");
        fgets(buf, BUFSZ, stdin);
        sendto(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, addrlen);

        ret = recvfrom(sd, buf, BUFSZ, 0, (struct sockaddr* )&svr_addr, &addrlen);
        printf("client: IPAddr = %s, Port = %d, buf = %s\n", inet_ntoa(svr_addr.sin_addr), ntohs(svr_addr.sin_port), buf);  
    }

    close(sd);

    return 0;
}

  


3. udp服务器程序使用connect()函数

如上所述,connect()函数可以用来指明套接字的目的地址/端口号,那么若udp服务器可以使用connect,将导致服务器只接受这特定一个主机的请求
---------------------

http://blog.chinaunix.net/uid-20401941-id-3178235.html

在水木上看到一个关于在客户端调用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. #include <sys/socket.h>
  2. #include <unistd.h>
  3. #include <string.h>
  4. #include <stdio.h>
  5. #include <arpa/inet.h>
  6. #include <stdlib.h>
  7. #define MAXLINE 80
  8. #define SERV_PORT 8888
  9. struct sockaddr_in servaddr;
  10. void do_cli(FILE *fp,int sockfd,struct sockaddr *pservaddr,socklen_t servlen)
  11. {
  12.     int n;
  13.     char sendline[MAXLINE],recvline[MAXLINE + 1];
  14.     #ifdef UDP_CONNECT
  15.     /* connect to server */
  16.     if(connect(sockfd,(struct sockaddr *)pservaddr,servlen) == -1)
  17.     {
  18.         perror("connect error");
  19.         exit(1);
  20.     }
  21.     #endif
  22.     while(fgets(sendline,MAXLINE,fp) != NULL)
  23.     {
  24.         #ifdef UDP_CONNECT
  25.         /* read a line and send to server */
  26.         write(sockfd,sendline,strlen(sendline));
  27.         #else
  28.         sendto(sockfd, sendline, strlen(sendline), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
  29.         #endif
  30.         printf("write over\n");
  31.         /* receive data from server */
  32.         n = read(sockfd,recvline,MAXLINE);
  33.         if(n == -1)
  34.         {
  35.             perror("read error");
  36.             exit(1);
  37.         }
  38.         recvline[n] = 0; /* terminate string */
  39.         fputs(recvline,stdout);
  40.     }
  41. }
  42. int main(int argc,char **argv)
  43. {
  44.     int sockfd;
  45.     /* check args */
  46.     if(argc != 2)
  47.     {
  48.         printf("usage: udpclient serverip\n");
  49.         exit(1);
  50.     }
  51.     /* init servaddr */
  52.     bzero(&servaddr,sizeof(servaddr));
  53.     servaddr.sin_family = AF_INET;
  54.     servaddr.sin_port = htons(SERV_PORT);
  55.     if(inet_pton(AF_INET,argv[1],&servaddr.sin_addr) <= 0)
  56.     {
  57.         printf("[%s] is not a valid IPaddress\n",argv[1]);
  58.         exit(1);
  59.     }
  60.     sockfd = socket(AF_INET,SOCK_DGRAM,0);
  61.     do_cli(stdin,sockfd,(struct sockaddr *)&servaddr,sizeof(servaddr));
  62.     return 0;
  63. }
 
########################实验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()操作返回了错误【是read的时候返回错误,不是sendto的时候返回错误】,通过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端口发送数据时,它不会做出响应。
 
多同一个socket既做了connect,又做了bind;bind不会响应外部的链接。应该可以理解,TCP也是啊。 
 
 
参考:
 [3]www.cs.rpi.edu/~hollingd/netprog/notes/udp/udp.pdf