全双工实现socket

时间:2022-12-01 08:52:25

由于,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;
}