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()描述
- connect系统调用将sockfd关联的套接字连接到addr指定的地址,如果sockfd是SOCK_DGRAM类型的,那么这个地址就是唯一数据报发送和接收地址
- 有链接的协议一般只能成功connect()一次,而无连接的协议可以多次connect()来改变sockfd与addr的联系
返回值
- 0,返回成功
- EACCES ,EPERM 用户试图链接到一个广播地址,但是相应的socket broadcast flag没有设置,或者是因为有本地防火墙
- EADDRINUSE 地址已经被使用
- EAFNOSUPPORT 传参的地址中的address family 与sa_family不匹配
- EBADF 文件描述符sockfd非法
- EINTR connect被signal中断
udp connect()特殊的一些地方
经试验发现,若发送端(客户端)不调用connect(),则发送异步错误时,进程很难知道发送了什么。若是调用connect(),其实际作用也与tcp中有较大不同。
- 调用connect()不会发包,内核只是检查是否有立即可知的错误,记录对端的ip&port,于是立即返回。但是这样一个socket就只能与一个ip进行交互(此处包括广播ip)
- connect()后,异步错误会被返回,在write()到一个不可达的进程时,对方将发送icmp,我方udp收到这个报文,并在下次准备read()这个套接字时,返回connection refused
- 一般而言,无连接的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 |
以源特定组播为例
假设组播IP是GROUP
按惯例,建立socket,填充好port,sin_family.
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))
第一条语句的意思是接收进程的地址要设置成组播IP,第二至第四条语句的意思是要填充一个 struct ip_mreq_source
结构。这个结构三个成员,分别是组播地址,本机一个接口的IP,以及多播发送方的IP
setsockopt第三个参数指明这是加入源特定组播,第四个是上面说的结构体指针,第五个是它的长度
当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
三路握手
- tcp的稳定连接靠的是三路握手,而三路握手由connect(),accept()完成
- 服务器必须准备好接受外来的连接,这通常通过调用socket,bind,listen来完成
- 客户通过调用connect发起主动连接,这导致客户发送一个syn分节,它告诉服务器客户将发送的数据的初始序列号。
- 服务器则必须确认这个SYN,并发送ACK,同时自己也要发送一个SYN,这个SYn含有服务器发送数据的初始序列号,当这个ACK/SYN到达客户端时,connect()返回
- 客户发送对服务器发来的SYN的ACK,服务器收到这个ack后即从accept返回
- 三路握手完成
TCP连接终止
- TCP终止一个链接需要4个分节,因为终止是双边的,每一边两个分节,也就是说TCP可以存在半关闭状态
- 某进程先调用close(),他是主动关闭方,它将发送一个fin分节,这仅仅表示这个进程告诉对方他的数据已经发送完毕,不代表这个进程不能继续收数据
- 若另外一边也认为他的数据发送完毕,则也会调用close()发送fin分节
- 每个fin分节都需要被ACK
TIME_WAIT
- TIME_WAIT是主动执行关闭的那端的最后一个网络状态。停留在这个状态的时间是2MSL
- 这个状态存在有两个必要的理由,可靠的实现全双工连接的终止,让老的分组在网络中消逝
- 对于第一个理由,假设主动发起关闭的那端在收到被动关闭端来的fin后,发送对这个fin的ACK,然而这个ACK丢失了。则被动关闭方会重传一个FIN,这时,若是没有TIME_WAIT状态,主动关闭方在发完最后一个ACK后就不再维护状态信息,则此时被动关闭方重发的FIN到达,则主动关闭方将对这个FIN分节回应一个RST,这将被被动关闭方解释为一个错误。
- 对于第二个理由,若是一个链接终止后,但是网络上还有这个链接没有到达的分组,此时若是快速的启动一个新的链接,他的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所对应的地址端口对会产生一些我们并不想要的,无法预料的负面影响。但这个问题超过了本文的讨论范围。而且幸运的是这些负面影响在实践中很少见到。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)