c++笔记

一.socket
1.listen(int fd,int backlog).backlog表示已经完成3次握手而等待accept的最大个数。
listen调用后,内核会建立2个队列,一个是syn队列,表示接受的请求但还未完成3次握手的连接,一个是accept队列,表示完成3次握手的队列。
2.accept(int fd,struct addr,socklen_t *len).会从accept队列中拿到一个连接,返回一个new_fd
new_fd和listen_fd区别在于new_fd所指向的so_pcb的请求地址和端口会被初始化,listen_fd指向的so_pcb请求地址和端口没被初始化。
3.send(fd,buf,len,flag),
flag为下列:MSG_DONTWAIT 不阻塞
MSG_DONTROUTE 数据包不允许通过网关
MSG_OOB 带外数据
4.recv(fd,buf,len)


socket常见错误
1.accept句柄耗尽,每个进程默认可使用1024个句柄,除去stdin,out,err,则最多有1021个句柄,如果耗尽,则accept返回-1,errno=24(EMFILE).如果采用poll,select,epoll lt,系统会不断通知,但就是获取不了fd,cpu会跑在100%
解决:方法1.使用/dev/null 方法
2.限制最大允许连接数
2.未能有效检测和处理socket关闭事件,导致socket资源耗尽,不能连入新连接
解决:采用keepalive,并加入应用层心跳,定时关闭不活动连接。
3.发送缓冲使用不当,数据包乱序
解决:不直接调用send发送数据,应将数据发放入发送缓冲区。
4.网络串包:将应该给a的消息给了b,原因:serv接受a的连接,处理中a掉线,服务close fda,这时b上线,系统会将当前最小可用的fd给b,于是b会使用fda,服务器就会将应该给a的消息发给b
解决:socket错误处理不能直接关闭close了事,处理应答,应该建立某种机制确认socket句柄是你想要发送的那个
5.服务端太多time_wait,服务器使用close主动关闭连接时,该连接会进入time_wait.状态会维持1-4分钟。消耗服务器资源
解决:使用shutdown(fd,SHUT_WR),由客户端执行close,将time_wait留给客户端
6.未处理sigpipe引起服务器崩溃:当一个进程向一个已收到RST的socket写数据,内核就会发一个SIGPIPE,默认是终止进程
解决:忽略该信号
7.阻塞IO未处理EINTR,非阻塞IO未处理EAGIN

二、高性能网络编程
1.服务器端的accept处理。
1.阻塞情况下,accept会一直等到内核accept队列有连接,才返回。
2.非阻塞下,accept如果发现accept独立中没有连接,就立即返回并将errno设置为EAGAIN。
3.如果accept队列满了,server会根据/proc.sys/net/ipv4/tcp_about_on_overflow来决定如何返回,返回0表示直接丢弃该ack,1表示发送RST通知client。这样,在syn队列中就不会存在完成了3次握手的连接。

tomcat是单独用一个线程来对accept处理。防止出现accept队列得不到及时处理而堆满的情况。
nginx等会将该套接口设置为非阻塞,然后在一个线程中即使用accept获取连接,又继续在该连接上进行读写。
2.数据发送 send 
1.mss,为了避免ip层分片,在握手的时候告知对方期望接受的数据长度。
但是通过mss还是不能避免被分片,因为中间经过的路由器mtu可能小于mss.
怎么避免中间网络可能出现的分片呢?ip头部有个df标志,会告诉途径的所有ip层不要分片,如果路由器觉得报文太大,必须要分片,
则返回icmp错误,带有路由器的mtu值,发送方就可以重新确定mss.
2.无论是阻塞还是非阻塞模式io,send返回后,并不代表对端收到数据,也不代表本端把数据发送到网络上,只是说明内核将会试图保证将消息发给对端。
如何看数据是否已经发送到网络上?/proc/net/tcp/tx_queue大小就是已使用发送缓冲区的大小。可以通过获取已占用发送缓冲区大小来识别,有两种办法
1.通过ioctl(tcp_socket,SIOCOUTO,&value)
2.netlink接口,socket(AF_NETLINK,SOCK_RAW,NETLINK_TCPDIAG);获取stuct tcpdiagmsg结构体,其中,tcpdiag_wqueuw就是已占用缓冲区大小。
如果已占用缓冲区大小为0,说明已经发送成功。

3.滑动窗口 :握手时会告诉对方的本端缓冲区大小。慢启动算法:发送数据时,先慢慢发,确保网络非常通畅后,在按照通告窗口大小发。
阻塞窗口:是为了配合慢启动,刚开始是一个mss,每收到一个ack,就会扩大一个mss,最大不能超过接收窗口
nagle算法:慢启动可能造成很多小tcp报文,会增加网络拥塞可能性。会将相邻的小tcp报文合并。但会要求一个连接上,最多只存在一个没被确认的分组,并且确认到达前,不发送其他分组。当对请求时延比较严格并且网络环境非常好的时候,比如同一个机房,就可以关闭nagle算法。使用setsockopt函数。
3.消息接收 recv(fd,buf,len,flag)
1.内核队列,receive,out_of_order,prequeue  ,backlog. 
recei
ve队列存放的是去除tcp头,排好序的报文。out_of_order存放乱序报文。每次向receive队列添加报文时,都会检查out_of_order队列。
backlog队列,如果进程正在拷数据,网卡收到的报文会放到这个队列中。
prequeue队列,当tcp_low_latency为0时,进程正在拷数据,网卡收到的报文也会放到该队列,不会只放到backlog队列,可以提高系统吞吐量。
2.多个进程同时使用socket,内核会对socket加锁,确保只有一个进程能拿到,其他进程会处于休眠状态。
3.recv flag为MSG_PEEK,读取后,receive队列不会删除该报文。此标志主要用于多进程读取同一套接字的情形。
4.如果已经拷贝完s1报文,拷贝s2前会检查buf还是否能放下s2,如果不能就直接返回已拷贝字节数。
5.SO_RCVLOWAT:接收缓冲区(receive队列)低水位。默认为1个字节.进程读取receive缓冲区时,只有读取大于SO_RCVLOWAT的字节数,才返回,否则会休眠。
6.使用sysctl将tcp_low_latency设置为1,可以更及时的接收到tcp消息,
4.关闭
1.多线程和多进程程序中关闭连接有什么区别
在多进程中,没多一个进程,socket的引用计数就会加1.多线程不会。多进程正常关闭连接,会等到socket引用计数为0时才开始关闭。
2.close和shutdown
shutdown不考虑引用计数,与多进程,多线程无关,直接关闭,close考虑。shundown关闭半连接。
3.对监听socket关闭和对处于establish的socket关闭,有何区别?
关闭监听端口: 1.会向所有处于尚未完成三次握手的半连接发RST关闭。
2.关闭keepalive定时器,那些处于establish的,但在keepalive_time时间内没有通讯的都会被关闭
关闭普通establisth连接(没有设置so_linger):
1.检查连接上是否有未读取的消息,有的话,丢弃,发rst关闭
2.没有未读取消息的话,检查是否有未发送的消息,有的话,就在最后一个包上架FIN标志,没有的话,就新建一个FIN包。
然后取消angle算法,一次性发送所有消息。然后关闭连接

2.全关闭和半关闭:只关闭读或只关闭写的叫半关闭,是有shutdown实现,全关闭有close实现。
由于tcp是全双工的可靠连接,即要收到ack(确认),还要发送ack(确认)。所有分手要分4次
5.使用so_linger连接,用于强可靠性需求。会阻塞close等待对方ack。如果对方代码有问题收到fin,但没有close,就不会发fin,但这时仍会发ack给我方,此时我方的close就会直接返回。

二、资源获取及释放对象
原则: 1.获取资源后,立即放入资源管理器对象
2.管理对象运用析构函数释放资源
class: class Inve{};
factory: Inve* createInve(){Inve *pInve=new Inve();return pInve}
use: void f(){Inve* pInve=createInve();...... delete pInve;}
资源管理器对象:
一、对于动态内存资源:
1.auto_ptr auto_ptr被销毁时会自动删除它所指对象
1.使用:std::auto_ptr<Inve> p1(createInve());//参数为指针
2.注意 如果p2=p1,则p1变为NULL.


2.share_ptr 采用引用计数的方法追踪资源,当无人指向它时删除资源,行为类似垃圾回收机制,但无法打破环状引用。
1.使用 std:trl:shared_ptr<Inve> pInv(createInve());//第一个参数为指针
2.share_ptr第二个参数可以自定义删除函数:
重新定义factory接口,直接在其中返回智能指针: 
std::trl::shared_ptr<Inve> createInve()
{

std::trl::shared_ptr<Inve> retVal(static_cast<Inve*>(0),getRidOfInve);//getRidOfInve是自定义删除函数
retVal=....;
return retVal;
}
这种接口设计可以避免用户忘记使用智能指针
1.在多线程中同时访问同一个share_ptr,要用mutex保护
2.避免循环引用,如ower有指向child的shared_ptr,child有指向ower的shared_ptr
3.unique_ptr 不能允许第二个unique_ptr指向同一个资源
3.使用pInv.get(),可获取指向资源的指针。
注意:以上两个资源管理对象在其析构函数中调用的是delete而不是delete[],所以无法管理动态分配的array.不能使用如形式:std::auto_ptr<std::string> ps(new std::string[10]),但编译器不会报错。对动态数组可以使用vector或string 管理。
4.weak_ptr 做为shared_ptr指针的观察者配合shared_ptr共同使用,可以判断当前当前资源是否存在,成员函数:
1.初始化 share_ptr<Inve> sp(new Inve());  weak_ptr<Inve> wp(sp);
1.lock(),返回一个被观察的shared_ptr对象. share_ptr<Inve> sp1=wp.lock(),此时如果sp1是空,说明资源以及不存在了
2.use_count() 返回被观察资源的引用计数  wp.use_count()
3.expired() 判断被观察资源是否存在,等价于 use_count()==0
5.scoped_ptr 和auto_ptr功能类似,但不允许拷贝和赋值,并且可以使用*和->操作符
二、对于其他资源,如锁等,需要自定义资源管理类。
1.同样要在构造函数中获取资源,析构函数中释放。
class : class Lock{
public:
explicit Lock(Mutext* pm):mutexPtr(pm){lock(mutexPtr);}
~Lock(){unlock(mutexPtr);}
private:
Mutext* mutexPtr;
}
2.对于不能拷贝的对象。要使其拷贝构造和赋值运算为private
class Uncopyable{
protected:
Uncopyable(){}
~Uncopyable(){}
private:
Uncopyable(const Uncopyable&);
Uncopyable& operator=(const Uncopyable&);
}
Uncopyable的析构函数没有virtual。这是因为 Uncopyable不是做为多态基类被使用的。
给基类虚构函数加virtual,只适用于带有多态性质的基类上,这类基类的设计目的是用来通过基类接口处理派生类对象。所以如果基类中只要有一个virtual函数,就需要定义virtual析构函数。
class Lock:private Uncopyable{}
3.可以使用shared_ptr来管理此类资源,因为它可以自定义删除。
class Lock{
public:
explicit Lock(Mutext* pm):mutexPtr(pm,unlock){lock(mutexPtr.get());}
private:std::trl::share_ptr<Mutext> mutexPtr;
}
不需要再声明析构函数,因为mutexPtr是non-static类型,Lock默认析构函数会自动调用non-static类型成员变量的析构函数。

三、在资源管理类中要提供对原始资源的访问,如shared_ptr的get()方法
四、智能指针赋值要以独立语句进行,例如 processWget(std::trl::share_ptr<Wget>(new Wget),priority());编译器先执行哪个参数是不确定的时候,如果在参数执行中出了异常,就可能会造成泄露,应该分开:
std::trl::share_ptr<Wget> pw(new Wget);
processWget(pw,priority());

三、设计与声明
1.函数参数尽量以const refenrenc替代值传递
2.一个必须返回新对象的函数,如要做为返回右值。就应以新对象形式返回。
const A operator*(const A& a,const A& b){return A(a.n*b.n);}
1.不能返回临时对象的引用
如const A& operator*(const A& a,const A& b){A c(a.n*b.n),return c;}//c在函数结束时就销毁了,该返回引用已经无定义。
2.如果是临时变量,就不要在堆上申请,如
const A& operator*(const A& a,const A& b){A* c=new A(a.n*b.n),return *c;}//如果使用w=x*y*z;即operator*(operator*(x,y),z).那么就会造成内存泄露
3.如果是临时变量,不能将其定义为静态变量。
const A& operator*(const A& a,const A& b){static A c,c=.... return c;}//1.多线程下不安全。2.如果使用if((a*b)==(c*d)),那么永远都是true,因为使用的是静态对象,返回又是引用,所以结果都是true.
3.将成员变量声明为private,目的是为了封装。因为不封装意味着不可改变。如声明为public int a,客户可使用A.a,如果a被删除,客户代码就需要修改。如果声明为protected,则派生类中可以使用a,如果a被取消,则所有派生类需要修改。
4.数据尽量被封装
四、慎用inline函数:inline函数是指对函数的调用以函数本体替换之,定义在头文件中
1.构造析构函数不能inline
2.库内的inline函数无法随库升级而升级,客户端代码要重新编译
3.不好调试
五、8020原则
六、降低文件约束:由于对象构造时,编译器要知道对象的大小,如果对象成员修改了,那么所有使用该对象的代码都要重新编译,可采用下列方法避免
1.将接口和实现分离,使用handle class(为声明和定义提供两个不同的头文件)或者interface
handle class例子:

#include <string>
#include "date.h"
#include "adress.h"
class person{
public:
person(const std::string& a,const Date& b,const Adress& c);
string name();
private:
Date a;
Adress b;
std:string c;
}
改为
person.h
#include <string>
#include <memory>
class Date;//声明
class Adress;
class personImpl;
class person{
public:
person(const std::string& a,const Date& b,const Adress& c);
string name();
private:
std::trl::share_ptr<personImpl> pImpl;//改为智能指针,指向实现类 personImpl
}
person.cpp
#include "person.h"
#include "personImpl.h"
person::person(const std::string& a,const Date& b,const Adress& c)::pImpl(new personImpl(a,b,c)){}
string person::name(){pImpl->name();}
客户使用只需#include "person.h" 而person成员修改,只需要修改personImpl类,person文件不需要重新编译,客户代码不需要重新编译

2.库的头文件应该完全是声明而非定义,如上修改后的person.h只包含声明

七、epoll
1.边沿触发模式,只有在监控的文件描述符状态发生变化的时候,才会触发事件。仅当对方的动作(发出数据,关闭连接等)造成的事件才能导致状态发生变化.状态变化一定是对方造成的.
建议是:将文件描述符置为非阻塞;需要循环抱住所有的事件处理,一直处理到数据为空,否则会收不到接下来的事件:
//先打开/dev/null,防止accept句柄耗尽
int idlefd=open("/dev/null",O_RDONLY|O_CLOEXEC)
int nfds = epoll_wait(epollfd,events,MAXEVENTS,-1)
for(i=0,i<nfds,i++)
{
if(events[i].events&EPOLLERR || !events[i].events&EPOLLHUP || !events[i].events&EPOLLIN)
{//error 处理
closd(events[i].data.fd);
continue;
}
else if(listenfd=events[i].data.fd)
{
while(1)//循环处理所有accept
{
infd=accept();
if(infd<0)//错误处理
{
switch(errno)
{
case EAGIN:case ECONNABORTED:case EINTR:case EPORTO:case EPERM:
{//忽略
break;

}
case EMFILE: //accept句柄耗尽
{
close(idlefd);
idlefd=accept();
log();//记录客户端地址
close(idlefd);
idlefd=open("/dev/null",O_RDONLY|O_CLOEXEC);//重新占用这个句柄
break;
}
default:
//错误处理,有可能是accept期间,对方断开连接
break;
}
}
if(EAGAIN==errno || EWOULDBLOK==errno)//说明该循环已经处理完所有的connection事件
break;
//加入接受新链接,并加入epoll处理
.......
}
}
else if(events[i].event&EPOLLIN)//读事件
{
int done=0;
//一直读到errno==EAGAIN为止
while(1)
{
int count = read();
if(-1==count)
{
if(errno != EAGAIN)//读取错误
done=1;
break;
}else if(0==count)//对端关闭
{
done=1;break;
}
//读取数据处理
.....

}
if(1 == done)
{
close(fd);//对端关闭,或读取出错
break;
}
}
else if(events[i].events&EPOLLOUT)
{
//一定要发完,或者发到eno==EAGAIN为止
}
}
八、mutable修饰的成员变量,即使对象是const,该对象的mutable成员也是可修改的。
九、指针
1.空悬指针:这个指针所指的内存已经被释放了
野指针:没有被正确初始化,指向随机地址
十、pthread_once 每个进程只会执行一次该函数
1.用于单例模式
template<typename T>
class singletion::public nocoypyable
{
private:
static pthread_once_t ponce;
static T* value;
static init(){value = new T();}
public:
static &T instance(){
pthread_once(ponce,singletion::init);
return *value;
}
}
template<typename T>
pthread_once_t singletion<T>::ponce=PTHREAD_ONCE_INIT;
template<typename T> T* singletion<T>::value = NULL;
十一、进程
1.进程结束后,系统会回收进程创建的文件描述符,但跨进程的mutex不会回收
多线程服务器端编程
线程竞争条件(race condition):
在即将析构一个对象的时候,如何保证此刻不会有一个线程正在使用该对象的一个成员函数
如何保证执行一个对象成员函数期间,对象不会在另一个线程中被析构
线程同步
一、锁
1.c++库中大多数class都不是线程安全的,如std::string,std::vector,std::map等,需要加锁,
2.构造函数实现多线程:避免暴漏this指针。
1.不能在构造函数中将this指针传递给跨线程对象,自身创建的子对象除外
2.不在构造函数中注册任何回调
原因:其他线程有可能访问这个尚未构造成功的对象,后果难以预料
3.做为成员变量的锁,只能用于同步本类成员变量的读写
4.如果一个函数要加多个锁,应该根据锁的地址大小来定顺序,如swap(obj a,obj b){lock(a.mutex),lock(b.mutex)}.如果一个线程调swap(a,b),另一个线程调swap(b,a)就出问题了
5.影响程序性能的并非锁,而是锁争用
在一个多线程中多读少写的结构?
用volatile修饰该结构体,写的时候,tmp=ptr,new=copy(tmp),change(new),ptr=new
二、条件变量condition 
1.wait()必须用在while循环中
while(queue.empty())
cond.wait();
2.可用于倒计时机制,实现主线程要子线程初始化后再执行时采用
class countdownlatch::nocoypyable
{
public:
explicit countdownlatch()
countwait();
countdown();
private:
pthread_cond cond;
mutex lock;
int count;
}
countwait(){mutexlock(lock);while(count >0) cond.wait()}
countdown(){mutexlock(lock);--count;if(count==0)cond.notify();}
三、非线程安全的库函数:strtok,ctime
四、one loop pre thread+threadpool
1.线程数目固定
2.方便在线程间调配负载
3.每个io连接存在于一个固定线程中,不用考虑事件并发
4.threadpool用来做计算,具体可以使任务队列或生产者消费者队列
5.处理io,定时器事件用eventloop
五.用于计算(非io连接,定时器)的线程池,可使用由阻塞队列实现的任务队列
六.连接重试
七.由于tcp是字节流,要对收到的数据进行整理,会有时间开销,可使用goole protocal buffers
八.分布式系统中心跳协议设计
1.应用层心跳必要性,1.由于一些异常可能导致服务器关闭连接没法发送fin包,比如系统故障重启,网络故障等,可通过心跳超时解决
2.tcp keepalive是系统自动发的,无法感知应用层异常,如死锁,阻塞等。
3.发送间隔,在故障延迟敏感情况下,为t=1s,否则为t=10s,如果接收端最近接收的心跳时间小于now-2t就表示心跳失效
4.要在工作线程中发送心跳,不要单独起心跳线程。要与业务消息使用同一个连接,不要单独起个心跳连接。这样可以防止伪心跳
1.对于单线程服务器,在eventloop中注册一个周期定时器,定时器回调函数中发心跳
2.对于多线程服务器,在eventloop中注册一个周期定时器,定时器回调函数中向线程池post一个任务,由任务发心跳
九、使用可扩展的消息格式,原因是由于系统升级及系统组件可能是不同语言开发
1.消息格式避免加入版本号,否则会出现一堆难以维护的switch/case
十、tcp使用长链接,原因:
可以用netstat -tn看到进程发送队列和接受队列中的字节数,正常情况下二者都是在0附近徘徊,如果一个recv-q比较大,说明该进程可能阻塞或死锁,导致无法处理消息,
如果send-q大说明服务器太忙,或网络出现故障导致丢包
十一.多核多cpu,效率最高的线程数量是cpu个数*核数。可以避开操作系统线程调度开销。
十二.多线程方案
1.简单处理方式:一个线程负责accept,其他线程负责send/reveive,这样线程总数固定。好处是编程简单,计算任务可以直接在send/receive线程泓完成,
当某个计算任务过大时,不会把系统跑死。一般采用这个方案就行了
2.复杂方案:按照千兆比特每秒配置send/receive线程,即一个千兆以太网卡一个线程,计算任务放线程池。
3.更复杂方案:半同步半异步模式、领导追随者模式
十三、单线程,多线程,多进程的应用场景
一、单线程 缺点:不可伸缩,不能发挥多核优势。
1.两种场合必须单线程
1.调用fork,可用多线程来替换有fork的单线程程序,除了“看门狗进程”
2.需要限制cpu的占用率。比如辅助程序,如日志压缩等。如果使用了多线程,会导致非关键任务占用CPU资源。因为在多核cpu,
单线程程序最多也就耗尽一个核资源,不影响其他核的服务进程
二、多线程 
1.相对于多进程,多线程对程序性能并不能有多大提升:原因是在提供静态web或ftp等的服务中,cpu负载并不高,性能瓶颈在磁盘I/O和网络IO上,此时,一个
单线程的进程都有可能把磁盘IO沾满了,有再多的CPU资源也无法提高吞吐量。但多线程的一个好处是使io和计算重叠,降低了时延,
虽然没提升绝对性能,但提升了平均性能
2.多线程的线程分为3类
1.io线程,主循环是IO复用,阻塞在epoll_wait等,也处理定时器事件
2.计算线程,主循环是block_queue,阻塞在条件变量,一般位于线程池中,不涉及IO,避免阻塞
3.第三方库线程,如数据库连接,日志等
3.让IO和计算相互重叠,降低延迟。方法:
把IO操作(通常是写操作)放进block_queue,让线程池线程处理。比如写日志文件,由于文件操作慢,需要单独一个线程处理写日志,
可以将要写的消息放入block_queue.
4.线程创建和销毁
1.线程创建最好在初始化阶段完成
2.线程销毁
1.线程销毁方式:1.自然销毁2.自杀,自身调用pthread_exit()3.它杀,其他调用pthread_cancle 4.程序异常
1.禁止从外部强制终止线程,因为异常终止的线程可能来不及销毁占用的资源,如锁,只能让线程自然退出即从线程函数返回。
2.在初始化中将线程全部创建完成,然后伴随主进程一直运行,就可以避开线程安全退出可能面临的各种困难:线程对象的生命期管理,资源释放等。

三、线程池
1.大小,计算密集型采用阻抗匹配原则:有T个Cpu,计算任务大概占到总任务的百分比为x,则线程池大小为T/x.可上下浮动50%。
T也可以是分配给这项任务的CPU数目,不一定总数。如果x小于20%,则线程池大小需要取个固定值如5*T
四、多线程和多进程如何取舍:可根据工作集(服务器在完成一次请求中所要访问的内存大小),如果工作集很大就用多线程 
五、高性能日志线程
1.日志库分为前端和后端,前端会提供api给每个需要写日志的线程,后端负责写到文件,是公用的。难点在于如何从多个前端将数据高效的传到后端,
这是一个多生产者-单消费者模型,生产者要低延迟,低cpu,无阻塞,消费者要有大吞吐量,低资源占用。如何做到?
1.前端日志要分级别,支持在线调整级别
2.后端要支持日志根据大小(超过1g就换新文件)和时间(每天0点新建日志文件,不管旧日志有没有满)滚动
3.大吞吐量
1.前端写入队列中,而非文件
2.后端采用双缓冲队列,buffa和buffb,前端写入buffa,后端将buffb写入文件,如果buffa满了,就交换a和b,这样前端写日志时,不用等磁盘IO。
3.为了及时写入文件,日志每3秒写一次文件
2.多线程中,必须是非阻塞日志

六、多线程和io
1.多个线程读取同一个文件,或者用多个线程读取同一磁盘的多个文件,能提速吗?
不能,因为内核中会对每个磁盘维护一个队列。所有读写请求,会放到这个队列中。所以,应该将对此磁盘的io操作放到一个线程中。
2.一个文件句柄能否放到多个线程中同时处理?
不能,会增加同步工作了。应该一个句柄只出现在一个线程中。
3.当一个线程正在epoll_wait时,另一个线程向此epollfd添加新的监听fd,那么新fd的监听事件能在epoll_wait中返回吗?
这个不确定,稳妥期间还是按照2,将对同一个epollfd的操作,都放到一个线程中。
4.多线程下,如何处理请求?
通常是当收到一个请求,为了避免阻塞其他请求,会记住该请求的tcp连接,将该请求放到线程池中处理,处理完成后将响应发出。
4.多线程中应该如何管理文件描述符
1.如何防网络串包?
使用socket对象(RA11)管理描述符,每个socket对象描述符是唯一的,使用该对象对描述符进行操作。只要对象还活着,就没有其他对象使用和它一样的描述符
了。并且socket对象要持有该连接的connect对象,这样在处理该连接的时候,就能知道它是不是已经关闭了。
2.为什么服务器不能关闭stdin,out,err呢?
一些第三方库可能会使用这些,如果关了,一些网络IO会占用,那就会发错消息了
3.多线程如何管理连接对象,比如正在处理过程中,对端关闭了连接
这种情况下,不能立刻delete连接对象,因为其他地方可能会正在用它,要对此连接对象采用引用计数。为0才关闭。可采用shared_ptr管理连接对象
综上所述,要有一个socket对象,该对象要有该描述符的tcp连接对象,要有引用计数。

七.多线程的对象生命期管理,第一章
八、子进程不能继承父进程的锁和定时器资源
九、多线程中能否fork子进程
不能,因为fork只能克隆当前线程的空间,不克隆其他线程,fork之后,子进程中只有一个线程,其他线程都消失了。如果fork的时候,
有线程加了锁,fork之后,它就消失了,没有机会再解锁了。
十、多线程中对信号处理:尽量不用信号,因为信号中断函数会中断其他线程处理,并且中断函数中只能调用可重入函数和原子类型的全局变量,
特别是它如果修改了全局变量,不能调用pthread函数和条件变量来通知其他线程
sigpipe:对端关闭,本端仍去写,就会触发,通常要在程序中忽略它,不忽略的话,程序终止
sigchild:转换为文件描述符。
十二、进线程间通知机制:int eventfd(uint,int flag)创建了一个事件对象,保存一个8字节的整形数,flag可设置为EFD_NONBLOCK,返回一个fd,可放入epoll中检测
十三、多线程下,非阻塞IO问题
1.如果要主动关闭连接,如何保证对端能接收到全部数据?如果应用层有缓冲,如何保证先发送完缓冲中的数据,再断开连接?
2.如果主动发起连接,对方拒绝,如何定期重试?
3.非阻塞IO应该采用水平还是边缘触发?
4.如果是水平触发,什么时候关注epollout事件?会不会造成busy-loop?
5.如果是边缘触发,如何防止漏读?
6.epoll一定比poll快吗?
7.在非阻塞IO,为什么一定要使用应用层发送缓冲?为什么一定要使用应用层接收缓冲?
因为如果要发送的数据大于tcp发送缓冲,剩余的数据要等到可写的时候才能写,造成阻塞。
因为一次接受的数据可能不够一个包长,需要先缓存。
8.在非阻塞IO,如何设计缓冲区?大小,结构?
mudou采用readv,writev解决
9.如果使用应用层缓冲,对端接收很慢,数据会不会一直堆在发送缓冲,造成占用内存越来越大?
10.如何设计并使用定时器,使其与socketio共用一个线程,避免使用锁?
11.基于事件的非阻塞编程特点是:通过注册回调函数,等连接事件,或读事件到来时,自动调用回调函数。


原文:https://blog.csdn.net/zhbt1234/article/details/79080552 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值