不外乎: 1. 信号; 2. 管道; (System V IPC->)3. 消息队列; 4. 信号量; 5. 共享内存;
网络程序设计中通常包括两个或更多的需要互相对话的进程,因此进程通信的方法在网络程序设计中
是极为重要的.
网络程序设计的一个重要目标是保证进程间不互相干涉,否则系统可能被挂起或自锁,因此,进程间
必须使用简洁有效地方法进行通信. 管道, 队列.
1。 进程阻塞, 共享资源, 锁定.
2。 信号: 用它来通知一个或多个进程异步事件的发生,比如键盘上某个键按下. 处理某种严重的错误.
2.1 可用 kill 把SIGTERM 信号发送给这个进程.
2.2 信号可以中断一个进程.
2.3 bits/signum.h 对某些特定的信号名作了定义.
2.4 P57 "SIGUP" "SIGINT" "SIGQUIT" "SIGILL" "SIGTRAP" "SIGFPE" "SIGTERM"-终止进程
"SIGCHILD"--实现exit和wait.
2.5 void abort(void); -- stdlib.h
3. 信号的处理 [unix系统调用signal()用于接收一个指定类型的信号,并可以指定相应的方法.]
int signal(int sig, __sighandler_t handler);
1. func 2. SIG_IGN SIG_DFL
4. 在进程间发送信号
int kill(pid_t pid, int sig); --一般用于父子进程之间
if(pid==0), 则信号被发送到当前进程所在的进程组的所有进程;
-1, 则信号进程标识符从高到低的顺序发送给全部的进程(搜,当前进程本身权限的限制).
<-1, 则信号被发送给标识符为pid绝对值的进程组里的所有进程.
kill 默认信号为: SIGTERM.
kill -s SIGINT 1234
5. 系统调用 alarm() 和 pause()
5.1 unsigned int alarm(unsigned int secondes)-- unistd.h
alarm(60); -- 60s后发送SIGALRM信号
alarm(0);--使报警时钟失效.
当需要对某些工作设置时间限制时
5.2 pause()能使调用进程暂停执行,直至接收到某种信号为止.
int pause(void);-- unistd.h
6. 系统调用setjmp() 和 longjmp()
有时候,当接收到一个信号时,希望能跳回程序中以前的一个位置执行.
setjmp(), 保存程序中的当前位置(是通过保存堆栈环境实现的).
longjmp(), 能把控制转会被保存的位置.
int setjmp(jmp_buf env); --<setjmp.h
void longjmp(jmp_buf env, int val);
3。 管道, 就是将一个程序的输出和另外一个程序的输入连接起来的"单向通道".--shell[P71]
#ls -l|more ----> 流向
在Linux系统内核里, 每个管道都是用一个inode节点来表示的.(当然,你是不会看到这个节点的,它
只存在于系统的内核中.) 管道的I/O处理. 父子两个进程同时拥有对同一个管道的读写句柄.因为管道
必须是单向的(因为它没有提供锁定的保护机制),所以我们必须决定数据的流动方向(从父到资,还是从
子到父?), 然后在每个进程中关闭不需要的句柄.
pipe(), dup(), dup2(), popen()/pclose()
popen("ls ~roy", "r");
popen("sort > /tmp/zixia","w");
1. pipe()的调用必须在fork()之前;
2. 及时关闭不需要的管道句柄;
3. 使用dup()之前确定定向的目标是最小的文件句柄;
4. 管道只能实现父子进程间的通信,如果两个进程之间没有fork()关系,就必须考虑其他的进程
通信方法.
3. 有名管道
--用来解决管道不能提供非父/子关系进程间通信的缺陷,在管道的基础上发展了有名管道(FIFOs).
--尽管Linux系统内部是以文件节点(inode)的形式存在的,但是由于其对外的不可见性("无名行"),
我们无法创建新的句柄对其进行访问.而有名管道在Linux系统中以一种特殊的设备文件的形式
存在于文件系统中.这样, 它不仅具有管道的通信功能,也具有了普通文件的优点.
(可以同时被多个进程共享, 可以长期存在等等),有效地解决了管道通信的缺点.
--因为有名管道存在于"文件系统中的文件节点(inode)",可用文件节点的方式来建立有名管道.
#mknod sampleFIFO p 或 --使用chmod改变有名管道的存取权限
#mkfifo -m 0666 sampleFIFO --有存取权限
在当前的文件系统中建立一个名字为sampleFIFO的有名管道.
通过文件列表信息中的p指示符我们可以迅速的辨认出有名管道. #ls -l
prw-r--r-- l root root 0 May 14 16:25 sampleFIFO
-- mknod() ; mknod("/tmp/sampleFIFO",s_IFIFO|0666,0);
建立了名为"/tmp/sampleFIFO"的有名管道,其读写权限是0666(当然,最终的权限还和你的umask
值有关).mknod的第三个参数在创建有名管道时被胡烈,一般填0.
3.1 有名管道的I/o使用
有名管道和管道的操作是相同的,只要注意,在引用已经存在的有名管道时,首先要用系统中的文件
函数来打开它,才能接下来进行其他的操作.
4. 文件和记录锁定
"共享资源的保护问题"是多进程操作系统中一个非常重要的问题.
文件和记录锁定可分为"咨询式锁定"和"强制锁定"两种.
System V 和BSD提供的咨询式锁定方式, Linux都支持.
int lockf(int fd, int function, long size); --#include <unistd.h>
//记录锁定, 可以指定锁定的范围. 每个进程独有,用于父子进程间的共享锁定
BSD -- 对指定文件的咨询式锁定和解锁.
int flock(int fd, int operation); --#include <sys/file.h>
//文件多顶. 可以继承,父子进程间使用的是同一锁定,不能用于父子进程间的.
5. Linux的其它上锁技术
创建和使用一个辅助文件, 为了实现文件的锁操作, 采用的第一个技巧用到这样一个事实:
如果文件的"新链接名"已近存在,系统调用link()便会出错,在全程变量ermo中返回EEXIST.
int link(char* existingpath, char* newpath) --#include<unistd.h>
采取的技巧是: 创建一个独立独特临时文件的另一个链接. 如果链接成功, 进程便把文件锁定
了.这时有两个路径指向锁定文件(基于进程号的临时文件和锁文件).然后我们用unlink()系统调用
,把临时文件删除,只剩下一个指向该文件的链接. 当需要解除锁定时,我们就用unlink()删除解除
对该文件的链接.
6. System V IPC [P91]
几种新的进程通讯方式 : "消息队列" "信号量" "共享内存"
显著特点: 是它的具体实例在内核中是以对象的形式出现的, 称为"IPC"对象,并有一个唯一的标识符.
标识符的唯一性只在每一类的IPC对象内成立.
--标识符, 只在内核中使用, IPC对象在程序中式通过关键字(key)来访问的.
--1. 建立IPC的首要问题: 如何构造新的关键字使之不和已有的关键字冲突.
key_t mykey;
mykey = ftok("/tmp/myapp", 'a');
key_t mykey;
mykey = ftok(".", 'a');
只要我们保证server和client从同一个目录运行,我们就可以保证它们使用上面的代码产生的
关键字是相同的.
--2. ipcs命令
ipcs -q 只显示消息队列
ipcs -m 只显示共享内存
ipcs -s 只显示信号量
ipcrm <msg | sem | shm) <IPC_ID> --强制系统删除已存在的IPC对象
--一. ////////////////////******消息队列******////////////////////
. "消息队列" (Message Queue)
3.1 ipc_perm : 保存每个IPC对象权限信息. linux/ipc.h
3.2 msgbuf : 我们可以自己定义传递给队列的消息的数据类型. linux/msg.h
4B + 4056B
3.3 msg : 消息队列在系统内核中式以消息链表的形式出现的.完成消息链表每个节点结构定义的msg就够.
3.4 msgqid_ds : 被系统内核用来保存消息队列对象有关数据. linux/msg.h
有关函数
3.a [P95]
int msgget(key_t key, int msgflg); --创建新的消息队列或获取已有的消息队列.
3.b
用来向消息队列发送消息的. linux/msg.h
int msgsnd(int msqid, struct msgbuf* msgp, int msgsz, int msgflg);
// 消息发送函数
int open_queue(key_t keyval)
{
int qid;
if( (qid=msgget(keyval, IPC_CREAT|0660)) == -1){
return -1;
}
return qid;
}
int send_message(int qid, struct mymsgbuf *qbuf)
{
int result, length;
//The length is essentially the size of the structure minus sizeof(mytype)
length = sizeof(struct mymsgbuf) - sizeof(long);
if((result=msgsnd(qid, qbuf, length, 0)) == -1){
return -1;
}
return result;
}
int main()
{
int qid;
key_t msgkey;
struct mymsgbuf{
long mtype; //Message Type
int request; //Work request number
double salary; //Employee's salary
}msg;
//Generate our IPC key value
msgkey = ftok(".", "m");
//Open/create the queue
if( (qid=open_queue(msgkey)) == -1){
perror("open_queue");
exit(1);
}
//Load up the message with arbitrary test data
msg.mtype = 1; //>1
msg.request = 1; //data element #1
msg.salary = 1000.0; //data element #2
//Bombs awary!
if( (send_message(qid, &msg)) == -1){
perror("send_message");
exit(1);
}
return 0;
}
3.c
和msgsnd()函数对应, msgrcv()函数用来从消息队列中取出消息. linux/msg.h
int msgrcv(int msqid, struct msgbuf* msgp, int msgsz, long mtype, int msgflg);
int read_message(int qid, long type, struct mymsgbuf* qbuf)
{
int result, lenght;
length = sizeof(struct mymsgbuf) - sizeof(long);
if((result=msgrcv(qid, qbuf, length, type, 0)) == -1){
return -1;
}
return result;
}
//利用上面提到的msgrcv()对消息长度的处理, 我们可以使用下面的方法来检查队列内是否存在符合条件的消息:
int peek_message(int qid, long type)
{
int result, length;
//将msgp和msgsz分别设为NULL和零, 然后检查函数的返回值,如果是E2BIG则说明存在符合指定类型的消息.
if((result=msgrcv(qid, NULL, 0, type, IPC_NOWAIT))==-1){
if(ermo == E2BIG)
return TRUE;
}
return FALSE;
}
3.d
控制消息队列的行为 linux/msg.h
int msgctl(int msgqid, int cmd, struct msqid_ds *buf);
//////////////////////////////////////////////////////////////////
/*******************消息队列实例--msgtool(一个交互式的消息队列使用工具)[P100]*******************/
1. 背景:
直到目前, 我们所接触的有关消息队列的实例只有几个简单的封装函数. 虽然它们也很有用, 但是还不够深入.
因此,我们下面将提供一个将消息队列应用于实际的例子--命令行程序msgtool.使用它我们可以在命令行上提供
消息队列的功能.
--提供的这个版本只接收数组类型的数据, 接收其他类型数据的功能请读者自行完成.
4. ////////////////////******信号量******////////////////////
信号量, 简单说就是用来控制多个进程对共享资源使用的计数器.经常被用作一种锁定保护机制, 当某个进程在对资源
进行操作时防止其他进程对该资源的额访问.
System V中的信号量实际上是信号量的集合(Set), 它可以包含多个信号量,控制多个共享资源.
//数据结构
4.1 sem : 信号量对象实际是多个信号量的集合.数组的每个成员都是一个单独的信号量. 以sem结构的形式存储的. linux/sem.h
4.2 semun : 联合在senctl()使用, 提供senctl()操作所需要的信息. linux/sem.h
4.3 sembuf : 被semop()函数用来定义信号量对象的基本操作. linux/sem.h
struct sembuf{
unsigned short sem_num;
//决定在信号量上的操作,正,负,零
//sem_op是0, 那么调用semop()函数的进程就会被阻塞直到对应的信号量值为零.
//这种操作的实质就是等待信号量所监控的资源被全部使用.
short sem_op;
short sem_flg;
}
4.4 semid_qs: 和msgqid_ds类似, semid_qs结构被系统用来储存每个信号量对象的有关信息.
//有关的函数
4.a semget(); 建立新的信号量对象或者获取已有对象的标识符. linux/sem.h
int semget(key_t key, int nsems, int semflg);
4.b semop(); 用来改变信号量对象中各个信号了的状态.
int semop(int semid, struct sembuf* sops, unsigned nsops);
//操作序列
struct sembuf sem_get = {0, -1, IPC_NOWAIT};
if((semop(sid, &sem_get, 1)==-1)
perror("semop");
struct sembuf sem_release = {0, 1, IPC_NOWAIT};
semop(sid, &sem_release,1);
4.c semctl() 直接对信号量对象进行控制.
int semctl(int semid, int semnum, int cmd, union semun arg);
///////////////////信号量的实例--semtool,交互式信号量的使用/[P109]////////////
//////////////////********共享内存*********//////////////////////////
简单说: 就是被多个进程共享的内存. 它在各种进程通信方法中式最快的. 因为它是将信息直接映射到内存中,
省去了其他IPC方法的中间步骤.
//数据结构
1. shmid_ds
//有关函数
1. int shmget(key_t key, int size, int shmflg); --linux/shm.h
2. int shmat(int shmid, char* shmaddr, int shmflg);--将共享内存映射到进程自己的内存空间内.
3. int shmctl(int shmqid, int cmd, struct shmid_ds* buf);
4. int shmdt(char* shmaddr); //当一个进程不再需要某个共享内存的映射时,就应该使用shmdt()函数断开映射.
///////////共享内存应用举例--shmtool