SO_REUSEADDR
我们都知道,对于服务端,启动后,通过setsocketopt设置SO_REUSEADDR选项,功能是当服务器突然挂掉,可以马上重启
,而不用等到time_wait
之后,先复习一下time_wait状态吧!为什么会存在这一状态?
time_wait状态存在的主要意义就是等待之前建立的连接断开后,套接字缓冲区要是存在一些未发送或者接受的数据,通过time_wait这段时间等其消逝.以免再次建立使用相同连接和端口建立的新连接接收到之前的旧数据.次要意义就是完成断开连接的过程.实现套接字全双工的终止!
再说下正常情况下断开连接四次挥手的各个阶段的状态吧!
- 服务端主动断开连接的话,会发送fin包到客户端,然后服务端进入
fin_wait1
状态;- 客户端接到fin包以后,发送ack包,服务端进入fin_wait2状态,客户端进入close_wait状态,准备关闭本端套接字.
- 客户端发送自己的fin包后,服务端接收到后,发送一个ack,并进入time_wait状态,客户端接收到last ack后,就彻底断开连接.(过程如下图)
(左边为server|右边为Client)
SO_REUSEADDR允许启动一个监听服务器并捆绑其众所周知的端口,即使以前建立的将该端口用作他们的本地端口的连接仍存在。
彻底断开连接,当服务器未设置SO_REUSEADDR选项的话,服务器会进入到time_wait状态持续两分钟左右.也就是说,在这段时间内,服务器端是不可以使用之前的IP和端口重新建立会话的.非要建立会话就设置新端口才可以.
解决这一问题的方法就是设置SO_REUSEADDR进行地址和端口复用,该选项的作用就是忽略time_wait状态,就可以使用原端口和IP重新建立会话,并且继续使用原来的IP和端口.
以下是一个设置了SO_REUSEADDR的测试程序:
//创建套接字的类
//此处头文件略了
#pragma once
class sockfd{
private :
static sockfd* sock ;
public :
struct sockaddr_in serv ;
int fd ;
sockfd() ;
~sockfd() ;
static sockfd* getsockfd( char* ip, char* port ) ;
//设置非阻塞
void s_bind() ;
void s_listen() ;
};
sockfd* sockfd:: sock = NULL ;
sockfd :: sockfd()
{
sock = NULL ;
}
sockfd* sockfd :: getsockfd( char* ip, char* port )
{
if(sock == NULL )
{
sock = new sockfd() ;
sock->fd = socket( AF_INET, SOCK_STREAM, 0) ;
//
// struct linger tmp = { 0,0}
// setsockopt( sock->fd, SOL_SOCKET, SO_LINGER, &tmp, sizeof( tmp ) );
cout << sock->fd <<endl ;
if( sock->fd < 0 )
{
free(sock) ;
cout << "build socket error!" << endl ;
exit( 1 ) ;
}
( sock->serv ).sin_family = AF_INET ;
( sock->serv ).sin_port = htons( atoi(port) ) ;
( sock->serv ).sin_addr.s_addr = inet_addr( ip );
memset(sock->serv.sin_zero,'\0',8);
//可取消
int flag = 1 ;
int ret = setsockopt( sock->fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag) ) ;
if( ret < 0 )
{
free(sock) ;
cout << "build socketopt error!" << endl ;
exit( 1 ) ;
}
}
return sock ;
}
void sockfd :: s_bind()
{
int ret = bind( sock->fd, ( struct sockaddr* )&serv, sizeof( serv ) ) ;
if( ret < 0 )
{
free( sock ) ;
cout << "bind socket error!" << endl ;
exit( 1 ) ;
}
cout << "binded......" << endl ;
}
void sockfd :: s_listen(){
int ret = listen( sock->fd , MAXLINE ) ;
if( ret < 0 )
{
free(sock) ;
cout << "listen socket error!" << endl ;
exit( 1 ) ;
}
cout << "listen......" << endl ;
}
sockfd :: ~sockfd()
{
close( fd ) ;
free( sock ) ;
sock = NULL ;
}
//程序server
///////////////////////////////////////////////////////////////////////////////////////////////
#include <iostream>
#include"socketfd.h"
typedef void (*sighandler_t)(int) ;
sockfd* sock = NULL ;
void sig_handle( int signo )
{
if( signo == SIGINT )
{
delete sock ;
exit(0) ;
}
}
int main(int argc,char** argv)
{
if( argc!=3 )
{
cout <<"NAME:" << argv[0] << "IP:"<<argv[1]<<"PORT:"<< argv[2] <<endl ;
exit(1);
}
sock = sockfd::getsockfd(argv[1], argv[2]) ;
sock->s_bind() ;
sock->s_listen() ;
int connfd ;
if((connfd = accept(sock->fd, NULL, NULL)) < 0)
{
cout << connfd << "--------------" << endl ;
cout << "accept error!" << endl ;
exit(1) ;
}
char buf[20] = "hello client!" ;
send(connfd, buf, 20, 0) ;
//接收信号,创建子进程重启服务器
while(1)
{
signal( SIGINT, sig_handle ) ;
}
}
运行结果
可见重新监听SO_REUSEADDR并没有影响之前连接的套接字time_wait状态!
SO_LINGER
在APUE中,作用是当还有未发报文而套接字要close时,可以延迟close返回.
在网上查相关资料解释如下:
此选项指定函数close对面向连接的协议如何操作(如TCP)。
内核缺省close操作是立即返回,如果有数据残留在套接口缓冲区中则系统将试着将这些数据发送给对方。
SO_LINGER选项用来改变此缺省设置。使用如下结构:
struct linger {
int l_onoff; //就是一个延时开关,为0或者非0
int l_linger; //延时长度,以秒为单位
};
选项的用法:
对于服务器与浏览器将的对话的过程,是不断建立-断开连接的过程。当发送一个.html文件的话,里面包含许多例如图片等请求,当发送完html文件后。两端会重新建立连接,浏览器又请求html文件中的图片等文件。当然对于各种请求服务端可以通过线程池进行并发处理。有时候客户端会收不到html文件里面的图片信息,尽管服务器端已经显示发送成功。为了使数据能尽量完整的到达浏览器,为两端套接字设置SO_LINGER选项,结构体中两属性设置为0,0,实现每次在关闭套接字前,要是缓冲区有数据,尽量将数据发送了再关闭的效果。
为什么将其和地址复用一块说呢?由上表可以看出当结构体中的开关为非0时,延时长度为0的话,服务端套接字不会进入到time_wait状态,套接字设置这一选项的话,和地址复用是比较像的。下面是设置了该选项,并将程序中地址复用的选项取消掉后,系统显示的结果:
显示结果表示:按照上表中的第二条选项设置的话,关闭服务器,套接字确实没有了2msl的time_wait状态了,并且重启的话,没有什么问题!
当按照表中第三条设置结构体中的两个值为非0时,相当指定了延迟关闭连接的时间!