udp中的connect()&bind()

来源:互联网 发布:饥饿游戏知乎 编辑:程序博客网 时间:2024/05/19 12:39

connect()&bind()的作用

udp

udp connect()

 #include <sys/types.h>          #include <sys/socket.h> int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

udp connect()描述

  1. connect系统调用将sockfd关联的套接字连接到addr指定的地址,如果sockfd是SOCK_DGRAM类型的,那么这个地址就是唯一数据报发送和接收地址
  2. 有链接的协议一般只能成功connect()一次,而无连接的协议可以多次connect()来改变sockfd与addr的联系

返回值

  1. 0,返回成功
  2. EACCES ,EPERM 用户试图链接到一个广播地址,但是相应的socket broadcast flag没有设置,或者是因为有本地防火墙
  3. EADDRINUSE 地址已经被使用
  4. EAFNOSUPPORT 传参的地址中的address family 与sa_family不匹配
  5. EBADF 文件描述符sockfd非法
  6. EINTR connect被signal中断

udp connect()特殊的一些地方

经试验发现,若发送端(客户端)不调用connect(),则发送异步错误时,进程很难知道发送了什么。若是调用connect(),其实际作用也与tcp中有较大不同。

  1. 调用connect()不会发包,内核只是检查是否有立即可知的错误,记录对端的ip&port,于是立即返回。但是这样一个socket就只能与一个ip进行交互(此处包括广播ip)
  2. connect()后,异步错误会被返回,在write()到一个不可达的进程时,对方将发送icmp,我方udp收到这个报文,并在下次准备read()这个套接字时,返回connection refused
  3. 一般而言,无连接的udp在发送数据时,内核中也是会做链接操作的,等数据发送完毕然后断开连接。这样其实效率较低,若是发送进程明确知道自己将于某个进程长时间通信,可以调用connect()来提高效率。【Partridge和Pink 1993】指出,临时链接的UDP套接字会耗费每个UDP传输1/3的开销

广播/多播(组播)

什么是多播

组播通过把224.0.0.0-239.255.255.255的D类地址作为目的地址,有一台源主机发出目的地址是以上范围组播地址的报文,在网络中,如果有其他主机对于这个组的报文有兴趣的,可以申请加入这个组,并可以接受这个组,而其他不是这个组的成员是无法接受到这个组的报文的。

局域网内多播时发生了什么

若局域网中有一个接收进程启动,并通过setsockopt()加入到某多播组。ipv4层内部会保存以上信息,并告知数据链路层接收特定目的地址以太网帧(通过组播ip到以太网地址的映射)。但是接口卡做的是不完备过滤,即接口卡也可能会接收目的地址为其他地址的以太网帧。但是ip层是完备的,它会比较目的ip。

有了以上内容,我们就知道若是某个组播成员(假设是A机)进程关闭了接收,或者他根本就没加入过组播。那么当发送进程再次发生组播时,A机实际上会忽略这个组播数据,既然忽略了,也不会发送icmp了。若是这个A机上有多个进程都加入了这个多播组,那单个套接字成员关系上的抹除,不影响A机继续作为该多播组的成员,直到最后一个套接字也离开该多播组。这种情况下调用connect,应该是connect到一个组播地址。

如何加入到组播

int setsockopt(int sockfd,int level,int optname,void* optval,socklen_t optlen)

optname dataType function IP_ADD_MEMBERSHIP struct ip_mreq enter a group IP_ADD_SOURCE_MEMBERSHIP struct ip_mreq_source enter a SSM IP_BLOCK_SOURCE struct ip_mreq_source block a source

以源特定组播为例

  1. 假设组播IP是GROUP

  2. 按惯例,建立socket,填充好port,sin_family.

  3. inet_aton(GROUP,&serv.sin_addr);inet_aton(GROUP,&mreq.imr_multiaddr);inet_aton(yourInterfaceIP,&(mreq.imr_interface));inet_aton(sourceIP,&(mreq.imr_sourceaddr));setsockopt(sockfd,SOL_IP,IP_ADD_SOURCE_MEMBERSHIP,&mreq,sizeof(mreq))
  4. 第一条语句的意思是接收进程的地址要设置成组播IP,第二至第四条语句的意思是要填充一个 struct ip_mreq_source结构。这个结构三个成员,分别是组播地址,本机一个接口的IP,以及多播发送方的IP

  5. setsockopt第三个参数指明这是加入源特定组播,第四个是上面说的结构体指针,第五个是它的长度

  6. 当optname是IP_ADD_MEMBERSHIP时,填充struct ip_mreq,这个结构体没有imr_sourceaddr成员

connect到多播ip的一个事实

UNP1上说可以connect到一个多播ip,我在把cli发送进程做了这样的设置。但是令人奇怪的是,我的发送程序再也收不到来自接收ser的任何信息。后来仔细翻UNP,发现上面说“目的地为为这个已连接UDP套接字的本地协议地址,发源地却不是该套接字早先connect到的协议地址的数据报,不会投递到该套接字,这样就限制了已连接套接字能且仅能与一个对端交换数据”。这个事实印证了这个说法。udp调用connect()需要付出这样的代价。

bind()以及REUSE_ADDR、REUSE_PORT功能

见下,非常详细

http://blog.csdn.net/yaokai_assultmaster/article/details/68951150

有待解决

此次试验在一台机子上进行,多播试验,发送端未做特殊设置,接收端设置IP_ADD_MEMBERSHIP。一开始观察到发送进程未bind()时,接收可以收到数据,但是却显示源地址是0.0.0.0。后发送端加上bind(),接收端依旧显示源地址是0.0.0.0。

后改变接收端设置,变为IP_ADD_SOURCE_MEMBERSHIP,并填上多播的源地址192.168.1.136。此时再测试,发现接收端依旧显示源地址是0.0.0.0。后发现当第二次启动发送端程序发送多播时,接收端显示源地址是192.168.1.136。于是我修改发送端程序,让他不再recv 接收端的回复,而是只发送数据。我发现第一次发生数据时,接收端总是不能得到发送端的源地址,但是第二次发送数据后,接收端就能知道源地址了。

TCP

三路握手

  1. tcp的稳定连接靠的是三路握手,而三路握手由connect(),accept()完成
  2. 服务器必须准备好接受外来的连接,这通常通过调用socket,bind,listen来完成
  3. 客户通过调用connect发起主动连接,这导致客户发送一个syn分节,它告诉服务器客户将发送的数据的初始序列号。
  4. 服务器则必须确认这个SYN,并发送ACK,同时自己也要发送一个SYN,这个SYn含有服务器发送数据的初始序列号,当这个ACK/SYN到达客户端时,connect()返回
  5. 客户发送对服务器发来的SYN的ACK,服务器收到这个ack后即从accept返回
  6. 三路握手完成

TCP连接终止

  1. TCP终止一个链接需要4个分节,因为终止是双边的,每一边两个分节,也就是说TCP可以存在半关闭状态
  2. 某进程先调用close(),他是主动关闭方,它将发送一个fin分节,这仅仅表示这个进程告诉对方他的数据已经发送完毕,不代表这个进程不能继续收数据
  3. 若另外一边也认为他的数据发送完毕,则也会调用close()发送fin分节
  4. 每个fin分节都需要被ACK

TIME_WAIT

  1. TIME_WAIT是主动执行关闭的那端的最后一个网络状态。停留在这个状态的时间是2MSL
  2. 这个状态存在有两个必要的理由,可靠的实现全双工连接的终止,让老的分组在网络中消逝
  3. 对于第一个理由,假设主动发起关闭的那端在收到被动关闭端来的fin后,发送对这个fin的ACK,然而这个ACK丢失了。则被动关闭方会重传一个FIN,这时,若是没有TIME_WAIT状态,主动关闭方在发完最后一个ACK后就不再维护状态信息,则此时被动关闭方重发的FIN到达,则主动关闭方将对这个FIN分节回应一个RST,这将被被动关闭方解释为一个错误。
  4. 对于第二个理由,若是一个链接终止后,但是网络上还有这个链接没有到达的分组,此时若是快速的启动一个新的链接,他的ip和port都和旧的一样,那万一这个旧的分组在此时到达,则会被新的连接误收。所以为了让旧的分组消逝,有必要等待一个较长的时间。

bind()

如果SO_REUSEADDR选项没有被设置,处于TIME_WAIT阶段的socket任然被认为是绑定在原来那个地址和端口上的。直到该socket被完全关闭之前(结束TIME_WAIT阶段),任何其他企图将一个新socket绑定该该地址端口对的操作都无法成功。这一等待的过程可能和延迟等待的时间一样长。所以我们并不能马上将一个新的socket绑定到一个刚刚被关闭的socket对应的地址端口对上。在大多数情况下这种操作都会失败。

然而,如果我们在新的socket上设置了SO_REUSEADDR选项,如果此时有另一个socket绑定在当前的地址端口对且处于TIME_WAIT阶段,那么这个已存在的绑定关系将会被忽略。事实上处于TIME_WAIT阶段的socket已经是半关闭的状态,将一个新的socket绑定在这个地址端口对上不会有任何问题。这样的话原来绑定在这个端口上的socket一般不会对新的socket产生影响。但需要注意的是,在某些时候,将一个新的socket绑定在一个处于TIME_WAIT阶段但仍在工作的socket所对应的地址端口对会产生一些我们并不想要的,无法预料的负面影响。但这个问题超过了本文的讨论范围。而且幸运的是这些负面影响在实践中很少见到。

原创粉丝点击