跳转至

网络编程知识点随记

信号

  • 信号函数:

  • signal:

    #include <signal.h>
    
    _sighandler_t signal(int sig, _sighandler_t _handler);
    

    返回值是前一次调用signal函数时传入的函数指针(第一次调用是信号sig对应的默认处理函数指针SIG——DEF)

  • sigaction:

    #include <signal.h>
    
    int sigaction(int sig, const struct sigaction* act, struct sigaction* oact);
    

    act:指定新的信号处理方式;oact:输出之前的信号处理方式;

    sigaction结构体:

    struct sigaction
    {
    #ifdef USE POSIX199309
        union
        {
        _sighandler_t sa_handler;   //sa_handler 成员指定倌号处理函数
        void (*sa_sigaction) ( int , siginfo_t  , void ) ;
        }
        _sigaction_handler;
    # define sa_handler __sigaction_handler.sa_handler
    # define sa_sigaction __sigaction_handler.sa_sigaction
    #else
        _sighandler_t sa_handler;
    #endif
        //sa_mask 成员设置进程的信号掩码(确切地说是在进程原有信号掩码的基础上增加信号掩码),
        //以指定哪些信号不能发送给本进程
        _sigset_ t sa_mask; 
        int sa_flags;
        void (* sa_restorer) (void) ;
    };
    
  • SIGCHLD信号:子进程结束运行,其父进程会收到SIGCHLD信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收

SIGCHLD.c
//当options 的取值是WNOHANG 时, waitpid 调用将是非阻塞的:如果pid 指定的目标子进程还没有结束或意外终止, 则waitpid 立即返回0: 如果目标子进程确实正常退出了,则waitpid 返回该子进程的PID 

static void handle_child(int sig){
    pid_t pid;
    int stat;
    while(pid = waitpid(-1, &stat, WNOHANG) > 0) {
        /*对结束的子进程进行善后处理*/
    }
} 

socket

  • accept2返回EMFILE的处理:

  • 调高进程文件描述符的数量

  • 死等

  • 退出程序

  • 关闭监听套接字

  • 如果是epoll模型,可以改用ET模式。问题:漏掉一次accept2,程序再也不会收到新的连接

  • 准备一个空闲的文件描述符。遇到这种情况,先关闭这个空闲的文件描述符,此时获得一个文件描述符名额,再次accept(2)就可以拿到这个socket连接的文件描述符,此时再关闭close文件描述符,就可以做到优雅关闭;最后重新打开这个文件描述符,以备下次再次出现这种情况使用

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

基于对象和面向对象

  • C编程风格:注册全局函数到网络库,网络库通过函数指针回调

  • 面向对象风格:用一个具体类继承一个抽象类,实现接口(函数)

  • 基于对象风格:用一个类包含一个具体类对象,在构造函数中使用boost::bind来注册成员函数

  • boost::function

    #include <iostream>
    #include <boost/function.hpp>
    #include <boost/bind.hpp>
    using namespace std;
    
    class Foo
    {
    public:
        void memberFunc(double d, int i, int j) {
            cout<<d<<endl;
            cout<<i<<endl;
            cout<<j<<endl;
        }
    };
    
    int main () {
        Foo foo;
        //_1是占位参数;&foo相当于传入this指针
        boost::function<void(int)> fp = boost::bind(&Foo::memberFunc, &foo, 0.5, _1, 10); 
    
        Foo foo1; //注意,这里实例化出另外在一个对象,防止上面线程和本线程同时调用,造成线程安全问题
        boost::function<void(int, int)> fp1 = boost::bind(&Foo::memberFunc, &foo1, 0.5 ——1 ——2)
    
            Foo foo2;
        boost::function<void(int, int)> fp2 = boost::bind(&Foo::memberFunc, boost::ref(foo)/*传入引用*/, 0.5, _1, _2);
    
        return 0;
    }
    
  • 写一个线程类(基于对象):

    Thread.h
    #ifndef _THREAD_H_
    #define _THREAD_H_
    
    #include <pthread.h>
    #include <boost/function.hpp>
    
    class Thread
    {
    public:
        typedef boost::function<void ()> ThreadFunc; //线程函数(使用boost::function进行绑定)
        explicit Thread(const ThreadFunc& func) //防止隐式调用
    
        void Start(); //线程开始
        void Join(); //线程分离
    
        void SetAutoDelete(bool autoDelete); //自动销毁
    
    private:
        static void* ThreadRoutine(void* arg); //设置为static的原因:保证这个函数传参没有this指针
        void Run(); //线程处理函数
        ThreadFunc func_; //包含线程函数的对象
        pthread_t threadId_;
        bool autoDelete_; //是否自动销毁
    };
    
    #endif // _THREAD_H_
    
    Thread.cpp
    #include "Thread.h"
    #include <iostream>
    using namespace std;
    
    
    //构造函数:初始化线程函数、初始化自动萧徽标志(默认为不自动销毁)
    Thread::Thread(const ThreadFunc& func) : func_(func), autoDelete_(false)
    {
    }
    
    void Thread::Start() //线程开始:创建线程(ThreadRoutine:返回值void*,参数void*,符合pthread)
    {
        pthread_create(&threadId_, NULL, ThreadRoutine, this);
    }
    
    void Thread::Join()
    {
        pthread_join(threadId_, NULL);
    }
    
    void* Thread::ThreadRoutine(void* arg)
    {
        Thread* thread = static_cast<Thread*>(arg); //前后置转换为Thread类,方可调用线程处理函数
        thread->Run();
        if (thread->autoDelete_)
            delete thread;
        return NULL;
    }
    
    void Thread::SetAutoDelete(bool autoDelete)
    {
        autoDelete_ = autoDelete;
    }
    
    void Thread::Run()
    {
        func_();
    }
    
    Thread_test.cpp
    #include "Thread.h"
    #include <boost/bind.hpp>
    #include <unistd.h>
    #include <iostream>
    using namespace std;
    
    class Foo
    {
    public:
        Foo(int count) : count_(count)
        {
        }
    
        void MemberFun()
        {
            while (count_--)
            {
                cout<<"this is a test ..."<<endl;
                sleep(1);
            }
        }
    
        void MemberFun2(int x)
        {
            while (count_--)
            {
                cout<<"x="<<x<<" this is a test2 ..."<<endl;
                sleep(1);
            }
        }
    
        int count_;
    };
    
    void ThreadFunc()
    {
        cout<<"ThreadFunc ..."<<endl;
    }
    
    void ThreadFunc2(int count)
    {
        while (count--)
        {
            cout<<"ThreadFunc2 ..."<<endl;
            sleep(1);
        }
    }
    
    
    int main(void)
    {
        Thread t1(ThreadFunc); //第一个线程:显示调用构造函数,传入线程处理函数
        Thread t2(boost::bind(ThreadFunc2, 3)); //第二个线程:使用boost::bind绑定传参的线程处理函数
                                             //实际类型:void,参见上面的typedef
    
        Foo foo(3);
        Thread t3(boost::bind(&Foo::MemberFun, &foo)); //绑定一个类的成员函数,&foo就是this指针
        Foo foo2(3);
        Thread t4(boost::bind(&Foo::MemberFun2, &foo2, 1000)); //还可以绑定一个带参数的成员函数
    
        t1.Start();
        t2.Start();
        t3.Start();
        t4.Start();
    
        t1.Join();
        t2.Join();
        t3.Join();
        t4.Join();
    
    
        return 0;
    }
    

原子操作和无锁队列

原子操作

CAS(Compare And Swap):几乎所有的CPU指令都支持CAS的原子操作

CAS
int compare_and_swap(int *reg/*寄存器地址*/, int oldval, int newval) {
    int old_reg_val = *reg; //获得原始值
    if(old_reg_val == oldval) {
        *reg = newval; //旧值没有改变,更新新值
    }
    return old_reg_val;
}

bool形式

CAS_bool
bool compare_and_swap(int *addr, int oldval, int newval) {
    if(*addr == oldval) {
        return false;
    }
    *addr = newval;
    return true;
}

各平台下的CAS实现

1)GCC的CAS

GCC4.1+版本中支持CAS的原子操作(完整的原子操作可参看 GCC Atomic Builtins

bool __sync_bool_compare_and_swap (type *ptr, type oldval type newval, ...)

type __sync_val_compare_and_swap (type *ptr, type oldval type newval, ...)

2)Windows的CAS

在Windows下,你可以使用下面的Windows API来完成CAS:(完整的Windows原子操作可参看MSDN的InterLocked Functions

 InterlockedCompareExchange ( __inout LONG volatile *Target,
                                 __in LONG Exchange,
                                 __in LONG Comperand);

3) C++11中的CAS

C++11中的STL中的atomic类的函数可以让你跨平台。(完整的C++11的原子操作可参看 Atomic Operation Library

template< class T >
bool atomic_compare_exchange_weak( std::atomic* obj,
                                   T* expected, T desired );
template< class T >
bool atomic_compare_exchange_weak( volatile std::atomic* obj,
                                   T* expected, T desired );

无锁队列

初始化一个dummy节点:

InitQueue(Q)
{
    node = new node()
    node->next = NULL;
    Q->head = Q->tail = node;
}

进队列CAS实现方式:

  1. 把tail指针的next指向要加入的节点
  2. 把tail指针移到队尾
EnQueue(Q, data) //进队列
{
    //准备新加入的结点数据
    n = new node();
    n->value = data;
    n->next = NULL;

    do {
        p = Q->tail; //取链表尾指针的快照
    } while( CAS(p->next, NULL, n) != TRUE); 
    //while条件注释:如果没有把结点链在尾指针上,再试

    CAS(Q->tail, p, n); //置尾结点 tail = n;
}
  • 很有可能我在准备在队列尾加入结点时,别的线程已经加成功了,于是tail指针就变了,于是我的CAS返回了false,于是程序再试,直到试成功为止

  • 思考:最后一行为何不判断成功呢?

  • 如果有一个线程T1,它的while中的CAS如果成功的话,那么其它所有的 随后线程的CAS都会失败,然后就会再循环,

  • 此时,如果T1 线程还没有更新tail指针,其它的线程继续失败,因为tail->next不是NULL了。
  • 直到T1线程更新完 tail 指针,于是其它的线程中的某个线程就可以得到新的 tail 指针,继续往下走了。
  • 所以,只要线程能从 while 循环中退出来,意味着,它已经“独占”了,tail 指针必然可以被更新。

  • 问题:如果T1线程在用CAS更新tail指针的之前,线程停掉或是挂掉了,那么其它线程就进入死循环了

改良:

EnQueue(Q, data) //进队列改良版 v1
{
    n = new node();
    n->value = data;
    n->next = NULL;

    p = Q->tail;
    oldp = p
    do {
        while (p->next != NULL)
            p = p->next;
    } while( CAS(p.next, NULL, n) != TRUE); //如果没有把结点链在尾上,再试

    CAS(Q->tail, oldp, n); //置尾结点
}
  • 问题:每个线程都遍历指针p到末尾,性能差

    直接取尾节点,所有线程都可以取到,也就是所有线程都是共享Q->tail一旦有线程改动了它,其他的线程就会在下次循环直接更新,这样就避免了所有线程都需要遍历到尾端的情况

    EnQueue(Q, data) //进队列改良版 v2 
    {
        n = new node();
        n->value = data;
        n->next = NULL;
    
        while(TRUE) {
            //先取一下尾指针和尾指针的next
            tail = Q->tail;
            next = tail->next;
    
            //如果尾指针已经被移动了,则重新开始
            if ( tail != Q->tail ) continue;
    
            //如果尾指针的 next 不为NULL,则 fetch 全局尾指针到next
            if ( next != NULL ) {
                CAS(Q->tail, tail, next);
                continue;
            }
    
            //如果加入结点成功,则退出
            if ( CAS(tail->next, next, n) == TRUE ) break;
        }
        CAS(Q->tail, tail, n); //置尾结点
    }
    

出队列的CAS操作:

DeQueue(Q) //出队列
{
    do{
        p = Q->head;
        if (p->next == NULL){
            return ERR_EMPTY_QUEUE;
        }
    while( CAS(Q->head, p, p->next) != TRUE );
    return p->next->value;
}

问题:DeQueue的操作时head->next,不是head本身,则需要一个dummy头指针解决链表中只有一个元素,headtail指向同一个节点的问题;headtail指向同一个节点产生下述问题:

DeQueue(Q) //出队列,改进版
{
    while(TRUE) {
        //取出头指针,尾指针,和第一个元素的指针
        head = Q->head;
        tail = Q->tail;
        next = head->next;

        // Q->head 指针已移动,重新取 head指针
        if ( head != Q->head ) continue;

        // 如果是空队列
        if ( head == tail && next == NULL ) {
            return ERR_EMPTY_QUEUE;
        }

        //如果 tail 指针落后了
        if ( head == tail && next == NULL ) {
            CAS(Q->tail, tail, next);
            continue;
        }

        //移动 head 指针成功后,取出数据
        if ( CAS( Q->head, head, next) == TRUE){
            value = next->value;
            break;
        }
    }
    free(head); //释放老的dummy结点
    return value;
}

参考文章

异常

muduo库的异常实现:

Exception.h
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)

#ifndef MUDUO_BASE_EXCEPTION_H
#define MUDUO_BASE_EXCEPTION_H

#include <muduo/base/Types.h>
#include <exception>

namespace muduo
{

class Exception : public std::exception
{
 public:
  explicit Exception(const char* what);
  explicit Exception(const string& what);
  virtual ~Exception() throw();
  virtual const char* what() const throw();
  const char* stackTrace() const throw(); //函数调用帧栈 栈回溯 

 private:
  void fillStackTrace();
  string demangle(const char* symbol);

  string message_; //抛出异常的信息
  string stack_; //帧栈的信息
};

}

#endif  // MUDUO_BASE_EXCEPTION_H
Exception.cc
// Use of this source code is governed by a BSD-style license
// that can be found in the License file.
//
// Author: Shuo Chen (chenshuo at chenshuo dot com)

#include <muduo/base/Exception.h>

#include <cxxabi.h>
#include <execinfo.h>
#include <stdlib.h>
#include <stdio.h>

using namespace muduo;

Exception::Exception(const char* msg)  
  : message_(msg) //构造函数初始化传入的异常信息
{
  fillStackTrace(); //调用填充帧栈信息 
}

//string重载 
Exception::Exception(const string& msg)
  : message_(msg)
{
  fillStackTrace();
}

Exception::~Exception() throw ()
{
}

const char* Exception::what() const throw()
{
  return message_.c_str(); //异常信息转换成 C风格字符串 
}

const char* Exception::stackTrace() const throw()
{
  return stack_.c_str(); // 帧栈信息转换成 C风格字符串 
}

void Exception::fillStackTrace()
{
  const int len = 200;
  void* buffer[len];
  int nptrs = ::backtrace(buffer, len); //栈回溯 
  char** strings = ::backtrace_symbols(buffer, nptrs); //根据地址转换成相应的符号 
  //再把转换后的符号追加到 成员变量stack_ --> 栈帧信息 中 
  if (strings)
  {
    for (int i = 0; i < nptrs; ++i)
    {
      // TODO demangle funcion name with abi::__cxa_demangle
      //stack_.append(strings[i]);
      stack_.append(demangle(strings[i]));
      stack_.push_back('\n');
    }
    free(strings);
  }
}

//将编译的函数名还原回程序员规定的函数名,更加直观 
string Exception::demangle(const char* symbol)
{
  size_t size;
  int status;
  char temp[128];
  char* demangled;
  //first, try to demangle a c++ name
  if (1 == sscanf(symbol, "%*[^(]%*[^_]%127[^)+]", temp)) {
    if (NULL != (demangled = abi::__cxa_demangle(temp, NULL, &size, &status))) {
      string result(demangled);
      free(demangled);
      return result;
    }
  }
  //if that didn't work, try to get a regular c symbol
  if (1 == sscanf(symbol, "%127s", temp)) {
    return temp;
  }

  //if all else fails, just return the symbol
  return symbol;
}
Exception_test.cc
//写一个测试程序测试异常功能
#include <muduo/base/Exception.h>
#include <stdio.h>

class Bar
{
 public:
  void test()
  {
    throw muduo::Exception("oops");
  }
};

//全局函数foo调用类对象的成员函数test,抛出名为“oop”的异常 
void foo()
{
  Bar b;
  b.test();
}

int main()
{
  try
  {
    foo();
  }
  catch (const muduo::Exception& ex) //接受这个异常 
  {
    printf("reason: %s\n", ex.what()); //打印异常信息 
    printf("stack trace: %s\n", ex.stackTrace()); //打印异常的调用帧栈信息 
  }
}

编译之后的结果为:

reason: oops
stack trace: ./exception_test(_ZN5muduo9ExceptionC1ENSt7__cxx1112basic_stringIcSt11char_traitsIcESaIcEEE+0x50) [0x401920]
./exception_test(_ZN3Bar4testEv+0x4c) [0x4017fc]
./exception_test(_Z3foov+0x14) [0x4016da]
./exception_test(main+0xe) [0x4016eb]
/lib64/libc.so.6(__libc_start_main+0xf3) [0x7f9c5fb9c493]
./exception_test(_start+0x2e) [0x40160e]