socket中关闭套接字的函数close和shutdown区别

socket关闭close和shutdown

socket关闭有2个close,shutdown

他们之间的区别:

close-----关闭本进程的socket id,但链接还是开着的,用这个socket id的其它进程还能用这个链接,能读或写这个socket id

shutdown--则破坏了socket 链接,读的时候可能侦探到EOF结束符,写的时候可能会收到一个SIGPIPE信号,这个信号可能直到

socket buffer被填充了才收到,shutdown还有一个关闭方式的参数,0 不能再读,1不能再写,2 读写都不能。

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

socket 多进程中的shutdown, close使用
当所有的数据操作结束以后,你可以调用close()函数来释放该socket,从而停止在该socket上的任何数据操作:
close(sockfd);


你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输,而一个方向上的数据传输继

续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据,直至读入所有数据。
int shutdown(int sockfd,int how);
Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式:
    SHUT_RD:关闭连接的读端。也就是该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该

套接字发出任何读操作。对TCP套接字该调用之后接受到的任何数据将被确认然后无声的丢弃掉。
    SHUT_WR:关闭连接的写端,进程不能在对此套接字发出写操作
    SHUT_RDWR:相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR


使用close中止一个连接,但它只是减少描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭连接。
shutdown可直接关闭描述符,不考虑描述符的参考数,可选择中止一个方向的连接。

注意:
    1>. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套

接字将被释放。
    2>. 在多进程中如果一个进程中shutdown(sfd, SHUT_RDWR)后其它的进程将无法进行通信. 如果一个进程close(sfd)将不会

影响到其它进程. 得自己理解引用计数的用法了. 有Kernel编程知识的更好理解了.

同事发现的该问题, 认为很重要就记下了!

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/cwmbecoming/archive/2008/11/10/3265343.aspx

 

再解释:

来自:http://huyuguang1976.spaces.live.com/blog/cns!2A9E272E3C33AFF1!183.entry 

 

closesocket, shutdown, tcp::socket.close()

tcp关闭连接有2种方式,一种是关闭端发送FIN,对方回应FINACK,关闭端再回ACK,这是优雅的关闭连接。双方可以保证所有数据都发送接收完成了。另一种是硬关闭,关闭方直接发送RSET。对方收到后立刻断开连接。

首先应该了解win32 api closesocket,这里只说msdn文档中叙述不够清晰的地方。

首先说缺省情况:l_onoff为0,closesocket立刻返回,但底层依然在持续发包,并且试图优雅关闭连接。这种情况下对于应用程序来说,该连接已经关闭,但底层socket的相关资源还没有释放,且不知道要等待多长时间。该方法对于一般程序来说没有问题,对于服务器来说,因为有可能有非常多的socket(超过100k),这样处理可能会导致系统可用的socket资源不足。

其次说l_onoff不为0的情况,此时取决于l_linger的值,系统会尽可能的优雅的关闭连接,如果在l_linger的时间内还无法关闭连接,则硬关闭。也就是说,此时如果l_linger为0,则肯定硬关闭,且closesocket立刻返回,同时释放所有资源。如果l_linger不为0,则尽可能优雅关闭并返回,释放所有资源,如果超时,则硬关闭,closesocket返回,释放所有资源。注意此种情况下closesocket可能是同步的。

shutdown有2个作用。首先禁止后续的send或者recv,但注意它不会影响底层,也就是说,此前发出的异步send/recv不会返回。其次,在所有发送的包被对方确认后,会发送FIN包给对方,试图优雅的关闭连接。但shutdown本身并不影响任何底层的东西,因此,shutdown并且优雅关闭了连接之后,该socket以及其所关联的资源依然存在,必须调用closesocket才能释放。

以上是msdn文档的说明。但实际过程中发现并非完全如此。测试程序使用asio实现了一个tcp server,在handle_send,也即一个包已经异步返回表明发送成功(成功发送到了系统缓冲区),这时候调用socket_.close,结果发现该包并没有真正发送(也可能发送了),系统发出了一个RSET而不是FIN。如果在socket_.close之前调用一次socket_.shutdown(both),则发出FIN包。此处颇有些奇怪,实验平台是windows 20008和windows xp。实验时用socket_.get_option检查了l_onoff和l_linger的值,的确为缺省值。

因为许多时候都存在这样的需求,也即client或者server在发出最后一个包之后就关闭连接,一般来说是在发出最后一个包之后就调用socket_.close,或者不再发出任何异步调用让socket_自动进入析构函数。这样很可能有问题。在发完最后一个包之后先调用一次shutdown在网速快的情况下可能有一定帮助。但有2个缺点,一方面是网速未必快,那个包未必很快就发出,由于shutdown根本不影响前面已经发出的包,所以FIN未必能发出,另一个缺点是随后的closesocket并不真正意味着资源已释放。

综上述,好的做法是在handle_send里面,如果是最后一个包,那么设置一个标志,shutdown,然后启动一个1s的deadline_timer:

socket_.shutdown(boost::asio::ip::tcp::socket::shutdown_both, boost::system::error_code());           

if(!graceful_close_)
{
    handle_hard_close(boost::system::error_code());
}
else
{
    timer_hard_close_.expires_from_now(boost::posix_time::seconds(1));
    timer_hard_close_.async_wait(
        strand_.wrap(boost::bind(
        &tcp_client::handle_hard_close,
        shared_from_this(),
        boost::asio::placeholders::error)));
}

然后

void handle_hard_close(const boost::system::error_code& error)
{
    if(!error)
    {
        boost::asio::socket_base::linger option(true, 0);
        socket_.set_option(option);
        socket_.close();
    }
}

 

附录:

asio的部分源代码:

tcp::socket的析构函数:

win_iocp_socket_service.hpp

  void close_for_destruction(implementation_type& impl)
  {
    if (is_open(impl))
    {
      // Check if the reactor was created, in which case we need to close the
      // socket on the reactor as well to cancel any operations that might be
      // running there.
      reactor_type* reactor = static_cast<reactor_type*>(
            interlocked_compare_exchange_pointer(
              reinterpret_cast<void**>(&reactor_), 0, 0));
      if (reactor)
        reactor->close_descriptor(impl.socket_, impl.reactor_data_);

      // The socket destructor must not block. If the user has changed the
      // linger option to block in the foreground, we will change it back to the
      // default so that the closure is performed in the background.
      if (impl.flags_ & implementation_type::close_might_block)
      {
        ::linger opt;
        opt.l_onoff = 0;
        opt.l_linger = 0;
        boost::system::error_code ignored_ec;
        socket_ops::setsockopt(impl.socket_,
            SOL_SOCKET, SO_LINGER, &opt, sizeof(opt), ignored_ec);
      }

      boost::system::error_code ignored_ec;
      socket_ops::close(impl.socket_, ignored_ec);
      impl.socket_ = invalid_socket;
      impl.flags_ = 0;
      impl.cancel_token_.reset();
#if defined(BOOST_ASIO_ENABLE_CANCELIO)
      impl.safe_cancellation_thread_id_ = 0;
#endif // defined(BOOST_ASIO_ENABLE_CANCELIO)
    }
  }

tcp::socket.close

win_iocp_socket_service.hpp

  boost::system::error_code close(implementation_type& impl,
      boost::system::error_code& ec)
  {
    if (is_open(impl))
    {
      // Check if the reactor was created, in which case we need to close the
      // socket on the reactor as well to cancel any operations that might be
      // running there.
      reactor_type* reactor = static_cast<reactor_type*>(
            interlocked_compare_exchange_pointer(
              reinterpret_cast<void**>(&reactor_), 0, 0));
      if (reactor)
        reactor->close_descriptor(impl.socket_, impl.reactor_data_);

      if (socket_ops::close(impl.socket_, ec) == socket_error_retval)
        return ec;

      impl.socket_ = invalid_socket;
      impl.flags_ = 0;
      impl.cancel_token_.reset();
#if defined(BOOST_ASIO_ENABLE_CANCELIO)
      impl.safe_cancellation_thread_id_ = 0;
#endif // defined(BOOST_ASIO_ENABLE_CANCELIO)
    }

    ec = boost::system::error_code();
    return ec;
  }

以上代码很清晰的说明了asio的实现。

首先如果是析构,那么不管此前是否设置过setsockopt,asio都把socket设置回缺省状态,也即不可预测时间的优雅关闭连接。如果socket.close,那么直接调用closesocket。

另一个值得注意的地方是,这两个函数都把socket设置为了invalid_socket,从而避免了多次关闭的问题。

多次关闭的问题是这样:首先假定socket a = 0x1234,调用closesocket关闭了这个socket,然后再次createsocket得到一个socket b也为0x1234,而代码(可能是另一个thread或者iocp得到一个错误返回)试图再此关闭socket a,结果把socket b关闭了。这种情况在windows平台下很常见,也即socket b很可能也为0x1234。 

 

posted @ 2010-11-23 12:34  vily_雷  阅读(6170)  评论(0编辑  收藏  举报