由于,accept函数,read,write,recv,send等函数都是阻塞式的,在同一个进程中只要有任何一个函数没有执行完成,处于阻塞状态,之后的函数与功能就不能处理,很难实现点对点的server-client全双工通信。因为全双工通信是非阻塞的通信方式,及时对方没有回复任何消息,都可以随时发送。如果只是电报机是的半双工通信,之前已经实现。面对QQ点对点的聊天式全双工通信,又怎么实现?
对于当前所学只能想到使用fork函数创建一个子进程,其中父进程用来处理发(收),而子进程用来收(发)的过程,fork函数的一些基本使用:如下:
1、进程:正在运行的程序的实例,运行中的程序。他是一个实体,任何一个进程都有自己独立的内存空间:文本区,数据区,堆,栈,等。其次,进程是一个执行中的程序。程序是一个没有生命的实体,只有在它获得一系列系统资源而被系统执行时,它才能称为一个活动的实体,我们称其为进程。
进程 = 程序 + 数据 + 进程控制块。PCB是一种数据结构而且PCB是系统感知进程存在的唯一标志,常驻内存以提高速度。PCB的存在可以使多个程序可以并发执行。
2、(1)进程的三种/五种状态:新建,结束,就绪(除了CPU以外的资源都已经分配),运行(已经获取CPU资源正在执行),等待(阻塞cpu以及主要资源都缺少,需要I/O)
(2)状态转换步骤:
①、新建进程,分配资源除CPU之外的主要资源;
②、分配CPU资源;
③、时间片到;
④、需要I/O;
⑤、获取I/O结束;
⑥、进程执行结束,释放回收资源。
二、fork()函数
1、#include <unistd.h>
pid_t fork(void);
(1)作用:一个现有的进程调用fork()函数,创建一个新的进程。原有的进程称为新进程的父亲进程,父子进程共享正文段,但并不共享存储空间,每个进程都有自己独立的数据空间,堆和栈的副本。
(2)返回值:三种返回值。三个返回值,不同时返回,也并不是一个进程中返回。
成功:在父进程中返回PID,在子进程中返回0;
失败:在父进程中返回-1,子进程没有创建,并且设置errno。
注意:子进程只能访问父进程在创建子进程之后的正文段,(这就想儿子出生之后,不知道父亲以前干过什么,他会有继承父亲的一套DNA系统与血肉之躯)。子进程有父进程数据与内存空间的副本。但是父子进程并不共享数据与内存空间,各自修改数据值自然不会影响到对方数据。
(3)kill函数的用法:用于向任何进程组或是进程发送信号。
#include<sys/socket.h>
#include <singnal.h>
#include <sys/types.h>
int kill(pid_t pid,int sig);
参数:有以下四种:
(1)pid大于零,pid是信号想要送往的进程的标识。
(2)pid等于零,信号将送往所有与调用kill()的那个进程同属于一个使用组的进程。
(3)pid等于-1,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
(4)pid小于-1,信号将送往以-pid为组标识的进程。
sig:准备发送的信号代码,假如其值为零则没有任何信号发出,但是系统会执行错误检查,通常会利用sig为值为零来检验某个进程是否任然在运行。
返回值说明:成功执行时,返回0.失败返回-1,errno被设置为以下某个值,
EINVAL:制定的信号码无效(参数sig码不合法)
EPERM:权限不够无法传递信号给制定进程。
ESRCH:参数pid所指定的进程或者进程组不存在。
#include <sys/wait.h>
#include <sys/types.h>
#include <stdio.h>
#incldue <stdlib.h>
#include <signal.h>
int main(void)
{
pid_t childpid;
int status;
int retval;
childpid = fork();
if(-1== childpid){
perror("fork");
exit(EXIT_FAILURE);
}
else if(0==childpid){
cout<<"in child process";
sleep(100);//让子进程进入睡眠,看看父进程的行为
exit(EXIT_SUCCESS);
}
else{
if(0 == (waitpid(childpid,&status,WNOHANG))){
retval = kill(childpid,SIGKILL);
if(retval){
puts("kill failed.");
perror("kill");
waitpid(childpid,&status,0);
}
else{
cout<<childpid<<"killed"<<endl;
}
}
}
exit(EXIT_SUCCESS);
}
/*
g++ killer.cpp -o killer
./killer
in child process
4545 killed
在确信fork调用成功之后,子进程进入睡眠100s,然后退出。
同时,父进程在子进程上调用waitpid函数,但使用了WNOHANG选项(WNOHANG如果没有任何已经结束的子进程则马上返回,不予以等待),所以调用waitpid后立即返回。父进程接着杀死子进程,如果kill失败,返回-1,否则返回-1,否则返回0。如果kill执行失败,父进程第二次调用waitpid,保证他在子进程退出后在停止执行。否则父进程显示一条成功消息后退出。
*/
注意:
1、wait函数说明:当一个进程正常或是异常终止时,内核就向其父进程发送SIGCHLD信号。因为子进程终止是个异步事件,这种信号也是内核向父进程发的异步通知。父进程可以忽略该信号,或者提供一个该信号发生是即被调用执行的函数(信号处理函数)。
父进程同步等待子进程退出时则调用wait函数,此时父进程可能会有如下三种情形:
(1)阻塞:如果其所有子进程都还在运行。
(2)带回子进程的终止状态立即返回(如果已有一个子进程终止,正等待父进程取其种植状态)。
(3)出错立即返沪(如果她没有任何子进程)。
2、wait和waitpid函数原型:
wait(等待子进程的中断和结束)
所需头文件 |
#include <sys/types.h> #include <sys/wait.h> |
|
函数说明 |
wait()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用wait()时子进程已经结束,则wait()会立即返回子进程结束状态值。子进程的结束状态值会由参数status 返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则status可以设成NULL |
|
函数原型 |
pid_t wait (int *status) |
|
函数传入值 |
status |
这里的status 是一个整型指针,是该子进程退出时的状态: status 若为空,则代表不记录子进程结束状态 status 若不为空,则由status记录子进程的结束状态值 另外,子进程的结束状态可由 Linux中一些特定的宏来测定 |
函数返回值 |
成功 |
返回子进程识别码(PID) |
出错 |
-1,失败原因存于errno中 |
waitpid(等待子进程的中断和结束)
所需头文件 |
#include <sys/types.h> #include <sys/wait.h> |
|
函数说明 |
waitpid()会暂时停止目前进程的执行,直到有信号来到或子进程结束。如果在调用waitpid()子进程已经结束,则waitpid()会立即返回子进程结束状态值。子进程的结束状态值会由参数status返回,而子进程的进程识别码也会一起返回。如果不在意结束状态值,则参数status可以设成NULL。参数pid为欲等待的子进程识别码 |
|
函数原型 |
pid_t waitpid(pid_t pid,int * status,int options) |
|
函数传入值
|
pid |
<-1:等待进程组识别码为pid绝对值的任何子进程 |
-1: 等待任何子进程,相当于wait() |
||
0:等待进程组识别码与目前进程相同的任何子进程 |
||
>0:等待任何子进程识别码为pid的子进程 |
||
options |
参数options可以为0 或下面的OR 组合 |
|
WNOHANG:如果没有任何已经结束的子进程则马上返回,不予以等待。此时返回值为0 |
||
WUNTRACED:如果子进程进入暂停执行情况则马上返回,但结束状态不予以理会 |
||
函数传出值 |
status |
同wait函数 |
函数返回值 |
成功 |
返回子进程识别码(PID) |
使用选项WNOHANG且没有子进程退出返回0 |
||
出错 |
-1,失败原因存于errno中 |
对status状态判断的宏
说明 |
子进程的结束状态返回后存于status |
宏 |
宏意义说明 |
WIFEXITED(status) |
如果子进程正常结束返回的值。取exit或_exit的低8位 |
WEXITSTATUS(status) |
取得子进程exit()返回的结束代码,一般会先用WIFEXITED 来判断是否正常结束才能使用此宏 |
WIFSIGNALED(status) |
如果子进程是因为信号而结束则此宏值为真 |
WTERMSIG(status) |
取得子进程因信号而中止的信号代码,一般会先用WIFSIGNALED 来判断后才使用此宏 |
WIFSTOPPED(status) |
如果子进程处于暂停执行情况则此宏值为真。一般只有使用WUNTRACED 时才会有此情况 |
WSTOPSIG(status) |
取得引发子进程暂停的信号代码,一般会先用WIFSTOPPED来判断后才使用此宏 |
子进程的终止信息存放在一个int变量中,其中包含了多个字段位。用宏定义可以取出其中的每个字段位:如果子进程是正常终止的,WIFEXITED取出的字段值非零,WEXITSTATUS取出的字段值就是子进程的退出状态。如果子进程是收到信号而异常终止的,WIFSIGNALED取出的字段值非零,WTERMSIG取出的字段值就是信号的编号。
(3)wait和waitpid俩个函数的说明。
如果父进程的所有子进程都还在运行,调用wait将使得父进程阻塞,而调用waitpid时候,如果在options参数中制定WHOHANG可以使父进程不阻塞而立即返回0。
wait等待第一个终止的子进程,而waitpid可以通过pid参数制定等待哪一个子进程。
当pid = -1,options = 0 时,waitpid=wait。
可见调用wait和waitpid不仅可以获得子进程的终止信息,还可以使得父进程阻塞等待子进程终止,起到进程间同步的作用。如果参数status不是空指针,则自进程的终止信息通过这个参数传出,如果只是为了同步而不关心子进程的终止信息,可以将status参数指定为NULL。
waitpid()提供了wait()没有的三个功能:
(1)waitpid等待一个特定的进程,而wait则返回任一终止子进程的状态。
(2)waitpid提供了一个wait的非阻塞版本,有时希望取得一个子进程的状态,但不想进程阻塞。
(3)waitpid支持作业机制。
wait函数使用实例:
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; int main() { pid_t pid; int status,i; if(fork()==0){cout<<"this is the child process pid = "<<getpid()<<endl;exit(5);} else{sleep(1);cout<<"this the parent process,wait for child..."<<endl; pid = wait(&status); i = WEXITSTATUS(status); cout<<"child pid = "<<pid<<" exit status = " <<i<<endl; } return 0; }
waitpid函数使用实例:
#include <iostream> #include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <sys/types.h> #include <sys/wait.h> using namespace std; int main() { pid_t pid; pid = fork(); if(pid<0){cout<<"fork failed"<<endl;} if(pid == 0){int i ; for(i=3;i>0;i--){cout<<"this is the child"<<endl;sleep(1);} exit(3); }else{int stat_val; waitpid(pid,&stat_val,0);//zuse deng dai zijincheng if(WIFSIGNALED(stat_val)){cout<<"child terminated abnormally,signal"<<WTERMSIG(stat_val);} else if(WIFSIGNALED(stat_val)){cout<<"child terminated abnormally ,signal"<<WTERMSIG(stat_val);} } return 0; }
最后,添加上全双工的socket通信实例:
client.cpp
#include<stdio.h> #include<stdlib.h> #include<string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <signal.h> #include <iostream> using namespace std; void quit_tranmission(int sig){ cout<<"recv a quit signal = %d "<<sig<<endl; exit(EXIT_SUCCESS); } void error_print(char* ptr){ perror(ptr); exit(EXIT_FAILURE); } #define buflen 128 int main() { int sockfd = socket(AF_INET,SOCK_STREAM,0); //int sockfd = socket(AF_INET,SOCk_STREAM,0); if(sockfd<0){cout<<"socket failed"<<endl;} struct sockaddr_in server_address; server_address.sin_family = AF_INET; server_address.sin_port = 1234; server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); int conn = connect(sockfd,(struct sockaddr*)&server_address,sizeof(server_address)); if(conn<0){cout<<"connect failed"<<endl;} pid_t pid; pid = fork(); if(pid == -1){cout<<"fork failed"<<endl;} if(pid == 0){ char recv_buf[buflen]={0}; while(1){ bzero(recv_buf,sizeof(recv_buf)); //int ret = read(sockfd,recv_buf,sizeof(recv_buf)); int ret = recv(sockfd,recv_buf,buflen-1,0); if(ret == -1){cout<<"read failed"<<endl;} else if(ret == 0){cout<<"server is closed"<<endl;break;} cout<<"recv_buf="<<recv_buf<<endl; } close(sockfd); kill(getppid(),SIGUSR1); exit(EXIT_SUCCESS); } else{ signal(SIGUSR1,quit_tranmission); char send_buf[buflen]={0}; while(fgets(send_buf,sizeof(send_buf),stdin)){ //int set = write(sockfd,send_buf,strlen(send_buf)); int set = send(sockfd,send_buf,buflen-1,0); if(set <0){cout<<"write failed"<<endl;} bzero(send_buf,strlen(send_buf)); } close(sockfd); } return 0; }
sever.cpp
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/socket.h> #include <arpa/inet.h> #include <netinet/in.h> #include <signal.h> #include <iostream> #define buflen 128 using namespace std; void error_print(char* ptr){ perror(ptr); exit(EXIT_FAILURE); } void quit_tranmission(int sig){ cout<<"recv a quit signal"<<sig<<endl; exit(EXIT_SUCCESS); } int main(){ int sockfd = socket(AF_INET,SOCK_STREAM,0); if(sockfd<0){cout<<"sockfd failed"<<endl;} struct sockaddr_in server_address; bzero(&server_address,sizeof(server_address)); server_address.sin_port = 1234; server_address.sin_family = AF_INET; server_address.sin_addr.s_addr = inet_addr("127.0.0.1"); int ret = bind(sockfd,(struct sockaddr*)&server_address,sizeof(server_address)); if(ret <0){cout<<"bind failed"<<endl;} ret = listen(sockfd,10); if(ret< 0){cout<<"listen failed"<<endl;} struct sockaddr_in client; socklen_t client_addrlength = sizeof(client); int conn = accept(sockfd,(struct sockaddr*)&client,&client_addrlength); pid_t pid; pid = fork(); if(pid == -1){cout<<"fork failedp"<<endl;} if(0 == pid){ char send_buf[buflen] = {0}; while(fgets(send_buf,sizeof(send_buf),stdin)!=NULL){ write(conn,send_buf,strlen(send_buf)); bzero(send_buf,strlen(send_buf)); } //exit(EXIT_SUCCESS); } else{ char recv_buf[buflen] = {0}; while(1){ bzero(recv_buf,strlen(recv_buf)); int ret = read(conn,recv_buf,sizeof(recv_buf)); if(ret<0){cout<<"read failed"<<endl;} else if(ret == 0){cout<<"client is close"<<endl;break;} cout<<recv_buf<<endl; } //kill(pid,SIGUSR1); } close(conn); close(sockfd); return 0; }