server 编程

来源:互联网 发布:女王网络聊天记录 编辑:程序博客网 时间:2024/05/22 12:29

1.signal(SIGPIPE,SIG_IGN) 忽略SIGPIPE信号
如果客户端关闭套接字,而服务器调用了一次write,服务器会接收一个RST segment(Tcp传输层)如果服务器再次调用了write,这个时候会产生SIGPIPE信号。linux默认的处理方式是退出进程。

2.TIME_WAIT 状态 对大并发服务器的影响
应尽可能在服务器避免出现 TIME_WAIT状态.
如果服务器端 主动断开连接(先于client调用close),服务端就会进入TIME_WAIT协议设计上,应该让客户端主动断开连接,这样就把TIME_WAIT状态分散到大量的客户端。如果客户端不活跃了,一些客户端不断开连接,这样子就会占用服务器的连接资源。服务器需有个机制来踢掉不活跃的连接。

3.signeal(SIGCHLD,SIG_IGN)
子进程在终止时会给父进程发SIGCHLD信号,该信号的默认处理动作是忽略,父进程可以自定义SIGCHLD信号的处理函数,这样父进程只需专心处理自己的工作,不必关心子进程了,子进程终止时会通知父进程,父进程在信号处理函数中调用wait清理子进程即可。事实上,由于UNIX的历史原因,要想不产生僵尸进程还有另外一种办法:父进程用sigaction将SIGCHLD的处理动作置为SIG_IGN,这样fork出来的子进程在终止时会自动清理掉,不会产生僵尸进程,也不会通知父进程。系统默认的忽略动作和用户sigaction函数自定义的忽略通常是没有区别的,但这是一个特例。此方法对于Linux可用,但不保证在其它UNIX系统上都可用。请编写程序验证这样做不会产生僵尸进程。

4.socket(PF_INET,SOCK_STREAM|SOCK_NONBLOCK|SOCK_CLOEXEC,IPPROTO_TCP)
大部分这种问题都能够解决,在文章的最后,提到了一种特殊情况,就是父子进程中的端口占用情况。父进程监听一个端口后,fork出一个子进程,然后kill掉父进程,再重启父进程,这个时候提示端口占用,用netstat查看,子进程占用了父进程监听的端口。
原理其实很简单,子进程在fork出来的时候,使用了写时复制(COW,Copy-On-Write)方式获得父进程的数据空间、 堆和栈副本,这其中也包括文件描述符。刚刚fork成功时,父子进程中相同的文件描述符指向系统文件表中的同一项(这也意味着他们共享同一文件偏移量)。这其中当然也包含父进程创建的socket。
接着,一般我们会调用exec执行另一个程序,此时会用全新的程序替换子进程的正文,数据,堆和栈等。此时保存文件描述符的变量当然也不存在了,我们就无法关闭无用的文件描述符了。所以通常我们会fork子进程后在子进程中直接执行close关掉无用的文件描述符,然后再执行exec。但是在复杂系统中,有时我们fork子进程时已经不知道开了多少个文件描述符(包括socket句柄等),这此时进行逐一清理确实有很大难度。我们期望的是能在fork子进程前打开某个文件句柄时就指定好:“这个句柄我在fork子进程后执行exec时就关闭”。其实时有这样的方法的:即所谓 的 close-on-exec。回到我们的应用场景中来,只要我们在创建socket的时候加上 SOCK_CLOEXEC标志,就能够达到我们要求的效果,在fork子进程中执行exec的时候,会清理掉父进程创建的socket。
当然,其他的文件描述符也有类似的功能,例如文件,可以在打开的时候使用O_CLOEXEC标识( linux 2.6.23才开始支持此标记 ),达到和上面一样的效果。或者使用系统的fcntl函数设置FD_CLOEXEC即可。

5.connfd = accept4(listenfd, (struct sockaddr*)&peeraddr,&peerlen, SOCK_NONBLOCK | SOCK_CLOEXEC);
accept4可以给接收的套接字设置标志

6.EMFILE
当套接字资源使用完,后accept将进入忙等待状态,一直处于活跃。此时可采用方法:
1.创建一个无用套接字

int idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);

2.当出现EMFILE时,将无用套接字关闭。再接收新连接,后立即关闭。最后重新建立无用套接字

if (connfd == -1){if (errno == EMFILE){close(idlefd);idlefd = accept(listenfd, NULL, NULL);close(idlefd);idlefd = open("/dev/null", O_RDONLY | O_CLOEXEC);continue;}elseERR_EXIT("accept4");}

7.写缓冲
当写缓冲满时,可以监听其OUT事件。直到数据发送完,再取消关注OUT(可写)时间

8.读缓冲

socket_ret = read(m_hostClientFd, buf, 1500);if (socket_ret > 0){    std::string tstr(buf, socket_ret);    m_msgbuf.append(tstr);    while (m_msgbuf.size() > 0)    {        // we have already some data in the message buffer        std::string::size_type len = m_msgbuf.find_first_of('\004');        if (std::string::npos != len)        {            // Complete message in the buffer            std::cout<<"--read complete message--"<<std::endl;            //process(m_msgbuf.substr(0, len));//不包括 '\004'            m_msgbuf.erase(0, len+1);            std::cout<<"--process message complete--"<<std::endl;        }        else        {            break;        }    }}

9.epool的 边沿触发与电平触发
边沿触发:缓存区非满:高电平; 满:低电平 当缓冲区数据满时,电平有高到低,触发EPOOLOUT事件当缓冲区数据由无到有,电平由低到高,触发EPOOLIN时间所以读数据时,需读到其返回EAGAIN错误,此时缓冲区为空;否则,当有数据时其不会出现边沿触发,一直处于高电平;同理写数据时,也需返回EAGAIN错误。解决EMFILE,存在问题。虽然不会出现busy-loop,但后续套接字也无法处理。因为一直处于高电平。

电平触发:
缓冲区为空 低电平
缓冲区不为空 高电平 触发EPOOLIN事件

10.是不是使用epool效率就高呢?
因为epool内核中实现是根据每个fd上的callback函数来实现的,只有活跃的socket才会主动调用callback,所以在活跃socket较少的情况下,使用epool没有select及pool两者的线性下降的性能问题;
但是如果已连接套接字数量不多,且套接字非常活跃。此时epool在内核态会不断调用fd的callback,其相对select及poll效率可能更低。

0 0