select 有3中应用场景
第一种是封装超时, connect accept read write
第二种优化客户端 stdio connfd
第三种优化服务器, 单进程去支持多个客户端
select管理了多个IO,用单进程轮训的方式去检索N个IO是否发生了变化。
Socket的性能
每个进程的最大连接树是1024 不过可以修改这个设置
select 管理的文件描述符的集合不能超过FD_SIZEMAX 也就是1024.
基于此 出现了poll epoll模型。
IO驱动模型 poll 和 select 都是拉模式,上层应用主动的从内核空间将数据拷贝到用户空间。
epoll是推模式,当数据到来的时候linux内核主动的将数据拷贝到你指定的内核空间中,这样大量避免了从用户空间到内核空间的转换 用的机制就是共享内存。这样就不用用户从内核空间中拷贝数据到用户空间了。
进程间通讯主要是用来做同步和互斥操作。
UNIX进程间通信(IPC)方式包括管道、FIFO以及信号。
System V进程间通信(IPC)包括System V消息队列、System V信号量以及System V共享内存区。
Posix 进程间通信(IPC)包括Posix消息队列、Posix信号量以及Posix共享内存区。
进程间通信分类
文件
文件锁
管道(pipe)和有名管理(FIFO)
信号(signal)
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
套接字(socket)
System V IPC & POSIX IPC
System V IPC
System V 消息队列
System V 共享内存
System V 信号量
POSIX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁
进程间共享信息的3中方式
管道 的本质 就是通过linux内核来发送信息,内部维护了一个buf
FIFO,信号,消息队列,都是通过linux内核来传递信息的。
上层应用调用系统调用陷入内核,内核进行操作,在回到上层应用。
共享内存 linux内核分配以内存空间,然后进程进行映射,映射以后这个区域就相当于是放在了上层应用这个区域了,相当于进程在调用的时候不在需要陷入内核了。
IPC对象的持续性由linux内核进行管理
随进程持续:一直存在直到打开的最后一个进程结束。(如pipe和FIFO)
随内核持续:一直存在直到内核自举或显式删除(如System V消息队列、共享内存、信号量)
随文件系统持续:一直存在直到显式删除,即使内核自举还存在。(POSIX消息队列、共享内存、信号量如果是使用映射文件来实现)
消息队列
消息队列提供了一个从一个进程向另外一个进程发送一块数据的方法
每个数据块都被认为是有一个类型,接收者进程接收的数据块可以有不同的类型值( linux对消息分了类型,通过类型来进行管理)
消息队列也有管道一样的不足,就是每个消息的最大长度是有上限的(MSGMAX),每个消息队列的总的字节数是有上限的(MSGMNB),系统上消息队列的总数也有一个上限(MSGMNI)
相当于是在内核中开辟了一噶缓冲区,然后A给B发送消息,B可以接受这个消息,只能限制于本机通讯。
对比:
管道:流管道像TCP一样是一个流 消息:有边界
先进先出 可以后进入、先出来(接受的时候可以接受指定类型的消息)
消息大小三大限制
cat /proc/sys/kernel/msgmax 最大消息长度 限制 最大是8K
cat /proc/sys/kernel/msgmnb 消息队列总的字节数 16K
cat /proc/sys/kernel/msgmni 消息条目数 32000 多余条目数以后会阻塞上层应用程序
IPC对象数据结构
内核为每个IPC对象维护一个数据结构
struct ipc_perm {
key_t __key; /* Key supplied to xxxget(2) */
uid_t uid; /* Effective UID of owner */
gid_t gid; /* Effective GID of owner */
uid_t cuid; /* Effective UID of creator */
gid_t cgid; /* Effective GID of creator */
unsigned short mode; /* Permissions */
unsigned short __seq; /* Sequence number */
};
struct msqid_ds {
struct ipc_perm msg_perm; /* Ownership and permissions */ //就是上面的结构体信息
time_t msg_stime; /* Time of last msgsnd(2) */
time_t msg_rtime; /* Time of last msgrcv(2) */
time_t msg_ctime; /* Time of last change */
unsigned long __msg_cbytes; /* Current number of bytes in
queue (nonstandard) */
msgqnum_t msg_qnum; /* Current number of messages
in queue */
msglen_t msg_qbytes; /* Maximum number of bytes
allowed in queue */
pid_t msg_lspid; /* PID of last msgsnd(2) */
pid_t msg_lrpid; /* PID of last msgrcv(2) */
};
消息队列在内核中的表示,键值就相当于是一个指针,指向一个具体的内存地址
int msgget(key_t key, int msgflg);
获取消息队列还是获取共享内存,就像打开文件一样,这个文件可能存在也可能不存在。
获取一个消息队列之后会返回一个消息队列的句柄
操作消息队列
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
消息队列的发送
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
获取消息队列
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgget函数
功能:用来创建和访问一个消息队列,如果消息队列已经存在了,就直接访问消息队列了。
int msgget(key_t key, int msgflg);
key: 某个消息队列的名字
msgflg:由九个权限标志构成,它们的用法和创建文件时使用的mode模式标志是一样的
返回值:成功返回一个非负整数,即该消息队列的标识码;失败返回-1
下面是函数的具体详细用法
msgid = msgget(0x1234,0666); 打开消息队列,如果消息队列不存在,则errno为ENOENT(Erroe No Entry)就像操作文件是一样的。
msgid = msgget(0x1234,0666 | IPC_CREAT); 这个表示如果有就直接使用,如果没有就创建消息队列,下面是函数返回结果
msgid = msgget(0x1234,0666 | IPC_CREAT |IPC_EXCL); 当IPC_EXCL和IPC_CREAT在一起的时候,意思就是如果没有就创建,如果有则提示已经存在错误,这样做的好处就是,如果已经存在了,防止旧的消息队列进行覆盖,一般都是先判断是否存在,如果已经存在了,就直接使用个,不存在则进行创建工作。这是一种比较稳妥的方式。单独使用IPC_EXCL是没有意义的。
msgid = msgget(IPC_PRIVATE,0666);
创建私有的消息队列,也就是说这个消息队列只能被自己或者是自己的家族使用,不能被其他的应用程序使用。不在没有亲缘关系的进程间使用。每次创建,得到的msgid的值都是不一样的,这样也就没有办法给外部使用了,因为同一个key,对应了多个id值。 即使把这个id给了别人,因为每次都不一样,因此也没有办法使用。言外之意是说,magid即使传送给其他进程,其他进程也不能使用,血缘关系fork是可以使用的。
IP_PRIVATE 是一个宏,值为0;
关于访问权限的问题
msgid = msgget(0x1234,0444 | IPC_CREAT);
msgid = msgget(0x1234,0666);
用低权限位创建,用高权限位获取,结果如下
错误码为EACCES。
整体的流程图是这样的。
msgctl函数
消息队列的控制函数
int msgctl(int msqid, int cmd, struct msqid_ds *buf);
msqid: 由msgget函数返回的消息队列标识码
cmd:是将要采取的动作,(有三个可取值)
返回值:成功返回0,失败返回-1。
如果不删除消息队列,则IPC对象则永久的在 内核中。
设置的思想是先获取在进行设置,而不是直接设置。
可以通过编程的方式来删除消息队列。 就是使用IPC_RMID
msgsnd函数
把一条消息添加到消息队列中
int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备发送的消息,
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgflg:控制着当前消息队列满或到达系统上限时将要发生的事情
返回值:成功返回0;失败返回-1
msgflg=IPC_NOWAIT表示队列满不等待,返回EAGAIN错误。
消息结构在两方面受到制约。首先,它必须小于系统规定的上限值;其次,它必须以一个long int长整数开始,接收者函数将利用这个长整数确定消息的类型
消息结构参考形式如下:
struct msgbuf {
long mtype; //类型
char mtext[100];//占位符 和外面的size_t msgsz的大小。
}
msgrcv函数
功能:是从一个消息队列接收消息
ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp, int msgflg);
msgid: 由msgget函数返回的消息队列标识码
msgp:是一个指针,指针指向准备接收的消息,
msgsz:是msgp指向的消息长度,这个长度不含保存消息类型的那个long int长整型
msgtype:它可以实现接收优先级的简单形式
msgtype=0返回队列第一条信息
msgtype>0返回队列第一条类型等于msgtype的消息
msgtype<0返回队列第一条类型小于等于msgtype绝对值的消息,并且是满足条件的消息类型最小的消息
msgflg:控制着队列中没有相应类型的消息可供接收时将要发生的事
msgflg=IPC_NOWAIT,队列没有可读消息不等待,返回ENOMSG错误。
msgflg=MSG_NOERROR,消息大小超过msgsz时被截断 消息的内容大于结构体的内容截断
msgtype>0且msgflg=MSG_EXCEPT,接收类型不等于msgtype的第一条消息。
返回值:成功返回实际放到接收缓冲区里去的字符个数,失败返回-1