其他知识点

1.服务器TIME_WAIT和CLOSE_WAIT分析和解决办法

 

  1. TIME_WAIT:主动关闭

  2. CLOSE_WAIT:被动关闭

  3. FIN:关闭连接

  4. SYN:建立连接

  5. ACK:响应

  6. RST:连接重置

  7. PSH:有DATA数据传输

  8. 2MSL:报文最大生存时间 MSL是一个数据报在网络中单向发出 到认定丢失的时间

  9. TTL:生存时间

  10. 服务出现异常最常出现的状况

    1. 服务器保持大量的TIME_WAIT状态

    2. 服务器保持大量的CLOSE_WAIT状态

  1. 保持大量的time_wait状态

 背景:爬虫服务器和web服务器器
 解决问题:保证最后一个ACK能够正常传输
  网络上仍然有可能有残余的数据包能够正常处理
 TIME_WAIT原因:
  假设最后的一个ACK丢失,那么被动关闭一方收不到这最后一个ACK则会重发FIN。此时主动关闭一方必须保持一个有效的(time_wait状态下维持)状态信息,以便可以重发ACK。如果主动关闭的socket不维持这种状态而是进入close状态,那么主动关闭的一方在收到被动关闭方重新发送的FIN时则响应给被动方一个RST。被动方收到这个RST后会认为此次回话出错了。所以如果TCP想要完成必要的操作而终止双方的数据流传输,就必须完全正确的传输四次握手的四步,不能有任何的丢失。这就是为什么在socket在关闭后,任然处于time_wait状态的第一个原因。因为他要等待可能出现的错误(被动关闭端没有接收到最后一个ACK),以便重发ACK。
  假设目前连接的通信双方都调用了close(),双方同时进入closed的终结状态,而没有走 time_wait状态。则会出现如下问题:假如现在有一个新的连接建立起来,使用的IP地址与之前的端口完全相同,现在建立的一个连接是之前连接的完全复用,我们还假定之前连接中有数据报残存在网络之中,这样的话现在的连接收到的数据有可能是之前连接的报文。为了防止这一点。TCP不允许新的连接复用time_wait状态下的socket。处于time_wait状态的socket在等待2MSL时间后(之所以是两倍的MSL,是由于MSL是一个数据报在网络中单向发出 到认定丢失的时间,即(Maximum Segment Lifetime)报文最长存活时间,一个数据报有可能在发送途中或是其响应过程中成为残余数据报,确认一个数据报及其响应的丢弃需要两倍的MSL),将会转为closed状态。这就意味着,一个成功建立的连接,必须使得之前网络中残余的数据报都丢失了。
 

2.服务器保持了大量的close_wait()状态

   time_wait问题可以通过调整内核参数和适当的设置web服务器的keep-Alive值来解决。因为time_wait是自己可控的,要么就是对方连接的异常,要么就是自己没有快速的回收资源,总之不是由于自己程序错误引起的。但是close_wait就不一样了,从上图中我们可以看到服务器保持大量的close_wait只有一种情况,那就是对方发送一个FIN后,程序自己这边没有进一步发送ACK以确认。换句话说就是在对方关闭连接后,程序里没有检测到,或者程序里本身就已经忘了这个时候需要关闭连接,于是这个资源就一直被程序占用着。这个时候快速的解决方法是:
 
  关闭正在运行的程序,这个需要视业务情况而定。
  尽快的修改程序里的bug,然后测试提交到线上服务器。
  1. cpu密集型:线程数=cpu核数*2

  2. IO密集型:线程数=cpu核心数/(1-阻塞系数)

  3. mutex:递归(可重入)和非递归(不可重入)

  4. RAII:利用对象生命周期管理程序资源(包括内存、文件句柄、锁等)的技术.

    使用 RAII 时,一般在资源获得的同时构造对象, 在对象生存期间,资源一直保持有效;对象析构时,资源被释放。

    关键:要保证资源的释放顺序与获取顺序严格相反

     /*************************************************************************
     @FileName: test.cc
     @Author:   zkm
     @Email:   zachary9121@gmail.com
     @Time:   Thu 04 Jul 2019 09:16:58 AM CST
     ************************************************************************/
     #include <iostream>
     using namespace std;
     
     class SafeFile
     {
     public:
     SafeFile(const char *filename)
     :fileHander_(fopen(filename,"w+"))
     {
        if(fileHander_==NULL){
            throw runtime_error("Open Error!");
        }
     }
     ~SafeFile(){ fclose(fileHander_); }
     
     void write(const char *str)
     {
        if(fputs(str,fileHander_)==EOF){
            throw runtime_error("Write Error!");
        }
     }
     
     void write(const char *buffer,size_t num)
     {
        if(num!=0 && fwrite(buffer,num,1,fileHander_)==0){
            throw runtime_error("Write Error!");
        }
     
     }
     
     private:
     SafeFile(const SafeFile&);
     SafeFile & operator=(const SafeFile&);
     private:
     FILE * fileHander_;
     };
     
     int main()
     {
     SafeFile testVar("test");
     testVar.write("Hello RAII");
     
     return 0;
     }
     

     

  5. pthread_onece

 /*************************************************************************
 @FileName: pthread_onece.cc
 @Author:   zkm
 @Email:   zachary9121@gmail.com
 @Time:   Wed 03 Jul 2019 06:00:17 PM CST
 ************************************************************************/
 #include <pthread.h>
 #include <iostream>
 using namespace std;
 template<typename T>
 class Singleton
 {
 public:
 static T& instance()
 {
    pthread_once(&_ponce,&Singleton::init);
 }
 static void init()
 {
    _value = new T();
 }
 private:
 static pthread_once_t _ponce;
 static T* _value;
 };
 
 template<typename T>
 pthread_once_t Singleton<T>::_ponce=PTHREAD_ONCE_INIT;
 
 template<typename T>
 T*Singleton<T>::_value=NULL;
 

 

  1. 多线程服务端开发

    1. 进程间通信:TCP sockets

    2. 线程同步四项原则

    3. 使用互斥锁的条件变量,关键RAII技术

  1. 空悬指针:指向已经销毁的对象或已经回收的地址

  2. 野指针:没有初始化的指针

  3. SIGPIPE信号处理

    1. SIGPIPE信号详解

     当服务器close一个连接时,若client端接着发数据。根据TCP协议的规定,会收到一个RST响应,client再往这个服务器发送数据时,系统会发出一个SIGPIPE信号给进程,告诉进程这个连接已经断开了,不要再写了。
     
     解决方案:
     TCP是全双工的信道, 可以看作两条单工信道, TCP连接两端的两个端点各负责一条. 当对端调用close时, 虽然本意是关闭整个两条信道, 但本端只是收到FIN包. 按照TCP协议的语义, 表示对端只是关闭了其所负责的那一条单工信道, 仍然可以继续接收数据. 也就是说, 因为TCP协议的限制, 一个端点无法获知对端的socket是调用了close还是shutdown.
     
     对一个已经收到FIN包的socket调用read方法, 如果接收缓冲已空, 则返回0, 这就是常说的表示连接关闭. 但第一次对其调用write方法时, 如果发送缓冲没问题, 会返回正确写入(发送). 但发送的报文会导致对端发送RST报文, 因为对端的socket已经调用了close, 完全关闭, 既不发送, 也不接收数据. 所以, 第二次调用write方法(假设在收到RST之后), 会生成SIGPIPE信号, 导致进程退出.
     
     为了避免进程退出, 可以捕获SIGPIPE信号, 或者忽略它, 给它设置SIG_IGN信号处理函数:

     


  1. close与shutdown的区别

     10. shutdown()函数可以选择关闭全双工连接的读通道或者写通道,如果两个通道同时关闭,则这个连接不能再继续通信。close()函数会同时关闭全双工连接的读写通道,除了关闭连接外,还会释放套接字占用的文件描述符。而shutdown()只会关闭连接,但是不会释放占用的文件描述符。所以即使使用了SHUT_RDWR类型调用shutdown()关闭连接,也仍然要调用close()来释放连接占用的文件描述符。

  1. 多线程服务器模型-one loop per thread

  此种模型下,程序的每个IO线程有一个event loop, 用于处理读写和定时事件。
 
  *eventloop 代表线程的主循环,需要让哪个线程干活,就把timer或者IO channel注册到那个线程的loop里。
 
  *对实时性有要求的connection可以独占一个线程
 
  *对于数据量的可以独占一个线程,并把数据处理任务分到另几个计算线程中(用线程池)
 
  优点:
 
    * 服务器中线程数目基本固定,可以在程序启动时设置,不会频繁创建与销毁。
 
  *可以方便在线程间调配负载。

  1. accept和accpet4的区别

     #include <sys/types.h>          /* See NOTES */
     #include <sys/socket.h>
     
     int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
     
     #define _GNU_SOURCE             /* See feature_test_macros(7) */
     #include <sys/socket.h>
     
     int accept4(int sockfd, struct sockaddr *addr,socklen_t *addrlen, int flags);

    可以看到两者的区别仅仅在于accept4()有第四个参数flags,这个参数如果为0,就跟accept()一样;下面的两个参数可以用按位OR来获取不同的行为:

    SOCK_NONBLOCK:为新打开的文件描述符设置O_NONBLOCK标志位,这跟用fcntl()设置的效果是一样的,区别就是用fcntl()的话需要多调用个函数。

    SOCK_CLOEXEC: 为新打开的文件描述符设置FD_CLOEXEC标志位,该标志位的作用是在进程使用fork()加上execve()的时候自动关闭打开的文件描述符。其实使用fcntl()设置FD_CLOEXEC标志位(也就是用open()的时候设置的O_CLOEXEC标志位)也能达到同样的效果,但跟fcntl()有什么不同呢?在多线程环境中,如果使用fcntl()会多出一步操作,这样就可能形成竞争;而使用accept4()就可以直接在打开的文件描述符上设置,可以消除竞争的问题。(原则上该竞争在那些新建文件描述符的调用中都存在,所以很多linux的系统调用都做了类似的处理。)

13.recv中的MSG_PEEK作用

 

posted @ 2019-07-04 23:14  2km2  阅读(97)  评论(0)    收藏  举报