网络编程知识点随记¶
信号¶
-
信号函数:
-
signal:
返回值是前一次调用signal函数时传入的函数指针(第一次调用是信号sig对应的默认处理函数指针SIG——DEF)
-
sigaction:
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信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收
//当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文件描述符,就可以做到优雅关闭;最后重新打开这个文件描述符,以备下次再次出现这种情况使用
基于对象和面向对象¶
-
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的原子操作
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形式:
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节点:
进队列CAS实现方式:
- 把tail指针的next指向要加入的节点
- 把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头指针解决链表中只有一个元素,head
和tail
指向同一个节点的问题;head
和tail
指向同一个节点产生下述问题:
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库的异常实现:
// 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
// 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;
}
//写一个测试程序测试异常功能
#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]