细说socket函数

来源:互联网 发布:ftp 21端口 编辑:程序博客网 时间:2024/06/06 10:48


                    函数connect原型:

  int connect(int sockfd,const struct sockaddr*servaddr,socklen_t addrlen   该函数是在客户端创建套接字后调用的,         我们可以从TCP状态状换图中看到,当int sockfd=socket(...),创建后,这个sockfd是处于    colsed的状态的,当调用connect后,它就处于SYS_SENT的状态。    这个函数激发TCP的三次握手过程(如果是TCP套接字).     它如果成功,则返回0,否则返回1,   成功是个什么情况呢?就是说 ,三次握手已经完成了,即(syn),(syn+ack),(ack),这个三个包都已经完成了   但这不表示连接以及建立,因为握手完成,还只是把它放入了服务器的等带队列,还需要服务器调用accept,这时候成功后,才能算可以发送数据了。   那身边吗情况下会失败了,失败后情况怎样呢?   失败:  1, 若TCP客户端没有收到(syn+ack)包,那么他会返回ETIMEOUT的错误,当然,connect会尝试几次发送syn包,过了一定的时间还是没受到,就放弃返回了。   这中情况一般是我们指定的ip地址有误(不存在的主机)或服务器故意忽视。  2,若收到的是RST包,表示复位,表明服务器在我们这段指定的端口上没有进程运行或者没有进程等待与之链接,这种情况服务器会马上发包RST,故也会马上返回  ECONNREFUSED错误。  3, 特殊错误,引发了路由器发送目的地不可达,ICMP错误。这里不详细解释。  故代码一般写为:         if((connect(sockfd,(struct sockaddr*)&servaddr,sizeof(servaddr))<0)         {               perror("connect error");               exit(0);         }   当conncet成功返回0后,套接字sockfd则进入ESTABLISHED状态。 当然,处于ESTABLISHED状态也不是完成了连接,还要accepte,   当时,若失败,则这个套接字就不再可用了,必须关闭close(sockfd),重新创建才可以socket().

bind 函数:

          int bind(int sockfd,const struct sockaddr*myaddr,socklen_t addrlen);


     对应TCP,调用bind函数可以指定一个端口号,或者指定一个IP地址,或者两者都指定,或者两者都不指定。

    如果客户端没有bind,那么当connect函数调用时,内核会自动选择一个临时端口,

  如果 服务器端没有bind,那么调用listen时候,内核会自动选择一个临时端口,但这通常不常见,因为是服务器。


    如果让内核来为套接字选择一个临时端口,那么如果我们要得到这个临时端口的值,必须调用getsockname函数来返回协议地址,以得到端口值。


   绑定成功,返回0,否则返回-1(一般都是端口已有绑定)。调用这个函数,不会使得socket的状态发生任何变化。



listen函数 。


            int listen(int sockfd,int backlog)


    这是由服务器端调用的,它做两件事情;

1, 把这个套接字转换为被动套接字,(每个刚刚创建的套接字都是假定为主动套接字的,它们都期待这调用connect发起链接)。

2,给这个套接字指定相应排队列的大小..


  这会使得套接字状态由        closed---->listen

 

   在listen函数中,有很多小细节,其中参数backlog就是一个可以详细讨论的。

  为了理解backlog,必须认识到内核为任何一个给定的监听套接字为何了两个队列。

1,未完成连接队列,每个这样的syn包对应着队列中的一项:即在connect函数中,客户端发出的syn分节若达到了服务器端,但三次握手还没有完全完成。

这时候的服务器端的套接字是处于SYN_RCVD状态。

2,已完成链接队列,每个已完成TCP三路握手过程的客户端对应其中一项。这时候服务器端的套接字是处于ESTABLISHED状态的。

从队列1到队列2,是一个怎样的过程呢?首先,处于队列1中的项,都发送了syn+ack分节,等待这客户的ack分节到达,如果客户的ack分节到达了,

那么,就会把这一项移到队列2的尾端。即在队列1中所处的时间是:从发送接受客户的syn到接受客户的ack这段时间。

当进程调用accept(下面)已完成队列中的队头项将被返回给进程(这句话要好好理解)。。。。

如果队列2为空,那么accept将会挂起进程。直到tcp在该队列中放入一项才唤醒它。


即从队列1到队列2 是接受到了相应的ack分节,

也即从SYN_RCVD状态到ESTABLISED状态,是接受到了ack分节(客户回应的)



还考虑的一个问题是,如果队列1已经满了,那么若新客户的syn分节到了后,会怎么处理呢?答案是 服务器将不理睬。

因为,如果不理睬,那么客户会过段时间再次发送syn分节,然而,这段时间可能队列1中已有空位了。故可以受理。

服务器不会发送RST,否则,客户端connect会马上失败返回-1,使得客户不知道到底是由于服务器未完成队列已满还是这个端口没有开放。



accept

           int  accept(int sockfd,struct sockaddr*cliaddr,socklen_t *addrlen)


这个函数由服务器调用,用于从已完成队列头部返回下一个已完成的链接,如果已完成队列为空,那么进程被投入睡眠阻塞。

                                                若成功返回,则返回值是由内核自动生成的一个全新的描述符。

      故成功,返回非负描述符,否则返回-1  , 若对客户端不感兴趣,则后两个参数可设置为NULL,。


这个没有在套接字状态变化图中体现出来。



close

            int close(int sockfd).

这个函数是表示”关闭套接字“,但是这个关闭是可以讨论的。

它的默认行为是在调用该函数的进程中,把该套接字标记为已关闭,然后理解返回到调用进程。该套接字不能再由该进程调用,即不能作为像

write或read的第一关参数。需要注意的是,该函数是否会引起四次握手结束呢?


这涉及到描述符计数的问题。close只是把sockfd的计数减少1,若它的计数还不是0的话,那么不会引发四分组的终止,若已经为0了,则为引发四分组的终止。


如果我们确实想要关闭一个套接字,即引发四分组终止,那么可以调用shutdown,它会发送FIN给对端。有关这个函数的详情,以后介绍。














0 0
原创粉丝点击