实现功能

  • 深入理解多进程的相关概念,深入理解父子进程之间的关系与差异,熟练掌握基于fork()的多进程编程模式
  • 充分理解僵尸进程的产生原理,能基于sigacation()或signa(),waitpid()或wait()等函数进行程序设计,规避僵尸进程的产生;
  • 理解Linux文件系统的组织方式,掌握文件描述符的基本概念,深入理解当父进程使用fork之后,子进程对于父进程fork前创建的文件描述符继承关系;
  • 进一步实现
    • Socket网络编程系列的核心系统调用
    • 服务器对于客户端正常结束的识别处理
    • 客户端基于命令行指令的退出实现方式
    • 服务器基于SIGINT信号的退出方式
    • SIGPIPE信号产生与识别处理方式
    • 多进程并发服务器与客户端的Socket编程核心系调用模式
    • 多进程应用程序如何有效规避僵尸进程的产生;
    • 简单的应用层PDU设计,构建,与解析
    • 文件的读写应用

多进程服务器端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <semaphore.h>
#include <sys/wait.h>
#include <sys/types.h>

#define MAX_MES 1024
#define MAX_CMD_STR 100
#define bprintf(fp, format, ...) \
if (fp == NULL) \
{ \
printf(format, ##__VA_ARGS__); \
} \
else \
{ \
printf(format, ##__VA_ARGS__); \
fprintf(fp, format, ##__VA_ARGS__); \
fflush(fp); \
}

#define COM_ADD struct sockaddr
int echo_rqt(int sockfd, FILE *fd,char * old_file_name);
void sig_pipe(int signo);
int sig_type = 0, sig_to_exit = 0;
int install_sig_handlers();
void sig_int(int signo);

int main(int argc, char const *argv[])
{

if(argc != 3)
{
printf("Usage:%s <IP> <PORT>\n", argv[0]);
return -1;
}

install_sig_handlers();


FILE *fd;
fd = fopen("stu_srv_res_p.txt", "wb"); //父进程日志文件。
fprintf(stdout, "[srv](%d) stu_srv_res_p.txt is opened!\n", getpid()); //向stdout输入信息。


struct sockaddr_in srv_addr_v4, cli_addr;
socklen_t cli_addr_len;
cli_addr_len = sizeof(cli_addr); //用信号量解决最大并发数问腿。最大并发数由argv[]指定,包含父进程。第二个参数大于0表示进程共享。等于0表示线程共享。
int port = atoi(argv[2]); //argv[]指定port
inet_pton(AF_INET, argv[1], &srv_addr_v4.sin_addr); //argv[]指定ip
srv_addr_v4.sin_family = AF_INET;
srv_addr_v4.sin_port = htons(port);
bprintf(fd,"[srv](%d) server[%s:%d] is initializing\n",getpid(),argv[1],port);


int listenfd, confd;
listenfd = socket(AF_INET, SOCK_STREAM, 0);

int res = bind(listenfd, (COM_ADD *)&srv_addr_v4, sizeof(srv_addr_v4));
printf("bind erro = %d\n%s\n",errno,strerror(errno));
printf("bind_res = %d\n",res);
if (res)
{
printf("[srv](%d)bind failed\n", getpid());
}



res = listen(listenfd, 5);
printf("listen_res = %d\n",res);
if (res)
{
printf("[srv](%d)listen failed\n", getpid());
}

while (sig_to_exit != -1)
{
char h_addr[16] = {0};
int h_port;

printf("before accept\n");
confd = accept(listenfd, (COM_ADD *)&cli_addr,&cli_addr_len);
printf("confd = %d,errono = %d \n",confd,errno);

if (confd == -1 && errno == EINTR)
{
printf("into sig int\n");
if (sig_type == SIGINT)
break;
}

inet_ntop(AF_INET, &cli_addr.sin_addr, h_addr, sizeof(h_addr));

h_port = ntohs(cli_addr.sin_port);
printf("port is = %d\n",h_port);
bprintf(fd, "[srv](%d) client[%s:%d] is accepted\n", getpid(), h_addr, h_port);

pid_t pd;
pd = fork();
if (pd < 0)
{
printf("fork error num:%d\n,error is :%s\n", errno, strerror(errno));
}

else if (pd == 0) //子进程中
{


char child_log_filename[20];
memset(child_log_filename, 0, sizeof(child_log_filename));

sprintf(child_log_filename, "stu_srv_res_%d.txt", getpid());
fd = fopen(child_log_filename, "wb"); //子进程日志文件。
if (!fd)
{
printf("[srv](%d) child exits, failed to open file \"stu_srv_res_%d.txt\"!\n",getpid(),getpid());
exit(-1);
}

fprintf(stdout, "[srv](%d) stu_srv_res_%d.txt is opened!\n",getpid(),getpid());
bprintf(fd, "[srv](%d) child process is created!\n",getpid()); //打印创建文件日志信息和子进程创建信息

close(listenfd);
bprintf(fd,"[srv](%d) listenfd is closed\n",getpid());

int pin;
pin = echo_rqt(confd, fd,child_log_filename);

if( pin == -1 )
{
close(confd);
bprintf(fd,"[srv](%d) connfd is closed\n",getpid());
bprintf(fd,"[srv](%d) child process is goning to exit!\n",getpid())
fclose(fd);
fprintf(stdout, "[srv](%d) stu_srv_res_%d is closed\n",getpid(),getpid());
bprintf(fd, "[srv](%d) child exits, client PIN returned by echo_rqt() error!\n", getpid());
exit(-1);
}
}
else
{
close(confd);
}
}

close(listenfd);
bprintf(fd, "[srv](%d) listenfd is closed!\n", getpid());
bprintf(fd, "[srv](%d) parent process is going to exit!\n",getpid());

fclose(fd);
fprintf(stdout, "[srv](%d) stu_srv_res_p is closed\n",getpid());
printf("....\n");
return 0;
}

int echo_rqt(int sock_confd, FILE *fd,char *old_file_name)
{
int n_pin;
int h_pin;
int n_len;
int h_len;


char send_data[MAX_CMD_STR + 1 + 8] = {0};
char rcv_data[MAX_CMD_STR + 1 + 8] = {0};


// sprintf(send_data + 8,"hello word\n");
// sprintf(send_data1 + 8,"hello word\n");

do
{
// 读取客户端PIN(Process Index, 0-最大并发数),并创建记录文件
do
{
int res = read(sock_confd,&n_pin,4);


printf("res_pin = %d\n",res);
printf("n_pin =%d\n",n_pin);
if (res < 0)
{
bprintf(fd, "[srv](%d) read pin_n return %d and errno is %d!\n",getpid(),res,errno);
if (errno == EINTR)
{
if (sig_type == SIGINT)
return -1;
continue;
}
return -1;
}
if (!res)
{
char new_child_log_filename[20];

memset(new_child_log_filename, 0, sizeof(new_child_log_filename));
sprintf(new_child_log_filename, "stu_srv_res_%d.txt", h_pin);

if (!rename(old_file_name, new_child_log_filename))
{
bprintf(fd, "[srv](%d) res file rename done!\n",getpid());
}

else
{
bprintf(fd, "[srv](%d) child exits, res file rename failed!\n",getpid());
}

return -1;
}

h_pin = ntohl(n_pin);
//TODO 将pin_n字节序转换后存放到pin_h中
printf("h_pin=%d\n",h_pin);
break;
} while (1);

// 读取客户端echo_rqt数据长度
do
{
//TODO 用read读取客户端echo_rqt数据长度(网络字节序)到len_n中:返回值赋给res
int res = read(sock_confd,&n_len,4);
printf("res_len = %d\n",res);
if (res < 0)
{
bprintf(fd, "[srv](%d) read len_n return %d and errno is %d\n",getpid(),res,errno);
if (errno == EINTR)
{
if (sig_type == SIGINT)
return -1;
continue;
}
return -1;
}
if (!res)
{
return -1;
}
//TODO 将len_n字节序转换后存放到len_h中
h_len = ntohl(n_len);
printf("h_len = %d\n",h_len);
break;
} while (1);

// 读取客户端echo_rqt数据
// buf = (char *)malloc(len_h * sizeof(char) + 8); // 预留PID与数据长度的存储空间,为后续回传做准备



//TODO 客户端数据可能一次read不完,要多次读取,因此要定义一个变量read_amnt来存放每次已累计读取的字节数,
// 以及另一个变量len_to_read来存放每次还需要读取多少数据(等于len_h减去read_amnt),read函数的参数2和参数3
// 的设定需要用到这两个变量

do
{
//TODO 使用read读取客户端数据,返回值赋给res。注意read第2、3个参数,即每次存放的缓冲区的首地址及所需读取的长度如何设定
memset(rcv_data,0,sizeof(rcv_data));
int res = read(sock_confd, rcv_data, h_len);
printf("res_str = %d\n",res);

printf("[srv](%d)str erro is %d:%s\n",getpid(),errno,strerror(errno));
while (res < h_len)
{
printf("res = %d\n",res);
res += read(sock_confd, rcv_data + res, h_len-res);
}

if (res < 0)
{
bprintf(fd, "[srv](%d) read data return %d and errno is %d,\n", getpid(), res, errno);
if (errno == EINTR)
{
if (sig_type == SIGINT)
{
return -1;
}
continue;
}
return -1;
}
if (!res)
{
return -1;
}

break;
//TODO 此处计算read_amnt及len_to_read的值,注意考虑已读完和未读完两种情况,以及其它情况(此时直接用上面的代码操作,free(buf),并 return pin_h)

} while (1);

//TODO 解析客户端echo_rqt数据并写入res文件;注意缓冲区起始位置哦
bprintf(fd,"[echo_rqt](%d) %s\n",getpid(),rcv_data);
// 将客户端PIN写入PDU缓存(网络字节序)


memset(send_data + 8,0,sizeof(send_data) - 8);
memcpy(send_data + 8,rcv_data,h_len);
memcpy(send_data, &n_pin,4);
// 将echo_rep数据长度写入PDU缓存(网络字节序)
memcpy(send_data + 4, &n_len,4);




int res = write(sock_confd,send_data,h_len + 8);
memset(rcv_data,0,sizeof(rcv_data));

printf("send data is = %s\n",send_data + 8);
printf("...\n");
//TODO 用write发送echo_rep数据并释放buf:
// break;
} while (1);
}

void sig_pipe(int signo)
{
// TODO 记录本次系统信号编号到sig_type中;通过getpid()获取进程ID,按照指导书上的要求打印相关信息,并设置sig_to_exit的值
sig_type = signo;
fprintf(stdout, "[srv](%d) SIGPIPE is coming!\n",getpid());
}

void sig_child(int signo)
{
sig_type = signo;
pid_t pid_child;
fprintf(stdout, "[srv](%d) SIGCHILD is coming!\n",getpid());
int stat;
while ((pid_child = waitpid(-1, &stat, WNOHANG)) > 0)
{
printf("[srv](%d) child terminated\n",pid_child);
};
}
void sig_int(int signo)
{
sig_type = signo;
sig_to_exit = -1;
fprintf(stdout, "[srv](%d) SIGINT is coming!\n",getpid());
}

int install_sig_handlers()
{
int res;
struct sigaction sigact_pipe, old_sigact;
sigact_pipe.sa_handler = sig_pipe; //sig_pipe(),信号处理函数
sigact_pipe.sa_flags = 0;
sigact_pipe.sa_flags |= SA_RESTART; //设置受影响的慢系统调用重启
sigemptyset(&sigact_pipe.sa_mask);
res = sigaction(SIGPIPE, &sigact_pipe, &old_sigact);
if (res)
{
return -1;
}
// TODO 安装SIGCHLD信号处理器,若失败返回-2.这里可直接将handler设为SIG_IGN,忽略SIGCHLD信号即可,
// 注意和上述SIGPIPE一样,也要设置受影响的慢系统调用重启。也可以按指导书说明用一个自定义的sig_chld
// 函数来处理SIGCHLD信号(复杂些)

struct sigaction sigact_child;
sigact_child.sa_handler = sig_child;
sigact_child.sa_flags = 0;
sigact_child.sa_flags |= SA_RESTART;
sigemptyset(&sigact_child.sa_mask);
res = sigaction(SIGCHLD, &sigact_child, &old_sigact);
if (res)
{
return -2;
}
// TODO 安装SIGINT信号处理器,若失败返回-3
struct sigaction sigact_int;
sigact_int.sa_flags = 0;
sigact_int.sa_handler = sig_int;
sigemptyset(&sigact_int.sa_mask);
res = sigaction(SIGINT, &sigact_int, &old_sigact);
if (res)
{
return -3;
}
return 0;
}

多进程客户端

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
#include <signal.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <semaphore.h>
#include<wait.h>
#define MAX_MES 1024
#define MAX_CMD_STR 100
#define bprintf(fp, format, ...) \
if (fp == NULL) \
{ \
printf(format, ##__VA_ARGS__); \
} \
else \
{ \
printf(format, ##__VA_ARGS__); \
fprintf(fp, format, ##__VA_ARGS__); \
fflush(fp); \
}

#define COM_ADD struct sockaddr
int echo_rqt(int sockfd, int pin, FILE *fd);
void sig_pipe(int signo);
int sig_type;

void sig_child(int signo);

int main(int argc, char const *argv[])
{

if(argc != 4){
printf("Usage:%s <IP> <PORT> <CONCURRENT AMOUNT>\n", argv[0]);
return 0;
}

struct sigaction sigact_pipe, old_sigact_pipe;
sigact_pipe.sa_handler = sig_pipe; //sig_pipe(),信号处理函数
sigemptyset(&sigact_pipe.sa_mask);
sigact_pipe.sa_flags = 0;
sigact_pipe.sa_flags |= SA_RESTART; //设置受影响的慢系统调用重启
sigaction(SIGPIPE, &sigact_pipe, &old_sigact_pipe);

struct sigaction sigact_child;
sigact_child.sa_handler = sig_child;
sigact_child.sa_flags = 0;
sigact_child.sa_flags |= SA_RESTART;
sigaction(SIGCHLD, &sigact_pipe, NULL);



struct sockaddr_in cli_addr_v4;
int con_count = atoi(argv[3]); //argv[]指定最连接数
int port = atoi(argv[2]); //argv[]指定port
inet_pton(AF_INET, argv[1], &cli_addr_v4.sin_addr); //argv[]指定ip
cli_addr_v4.sin_family = AF_INET;
cli_addr_v4.sin_port = htons(port);

FILE *fd;

fd = fopen("stu_cli_res_0.txt", "wb"); //父进程日志文件。
fprintf(stdout, "[cli](%d) stu_cli_res_0.txt is created!\n", getpid()); //向stdout输入信息。



for (int i = 1 ; i < con_count ; i++)
{ //argv提供
pid_t pd;
pd = fork();
if (pd < 0)
{
printf("fork error num:%d\n,error is :%s\n", errno, strerror(errno));
}

else if (pd == 0) //子进程中
{
int PIN;
PIN = i;
printf("current PIN= %d\n", PIN);

char child_log_filename[20];
memset(child_log_filename, 0, sizeof(child_log_filename));

sprintf(child_log_filename, "stu_cli_res_%d.txt", PIN);
fd = fopen(child_log_filename, "wb"); //子进程日志文件。
if (!fd)
{
printf("[cli](%d) child exits, failed to open file \"stu_cli_res_%d.txt\"!\n", getpid(), PIN);
exit(-1);
}

fprintf(stdout, "[cli](%d) stu_cli_res_%d.txt is created!\n", getpid(), PIN);
bprintf(fd, "[cli](%d) child process %d is created!\n", getpid(), PIN); //打印创建文件日志信息和子进程创建信息

int cli_sock_confd;
cli_sock_confd = socket(AF_INET, SOCK_STREAM, 0);

if (cli_sock_confd == -1)
{
printf("[cli](%d)(PIN:%d) fail to create socket!\n", getpid(), PIN);
}

for (;;)
{
printf("before con\n");
int stat = connect(cli_sock_confd, (COM_ADD *)&cli_addr_v4, sizeof(cli_addr_v4));
// printf("after con\n");
// printf("stat = %d\n",stat);
// printf("erro is = %d\n%s\n",errno,strerror(errno));

if (stat == -1)
{
printf("[cli](%d)(PIN:%d)wrong serve connection:%d\n%s\n", getpid(), PIN, errno, strerror(errno));
cli_sock_confd = socket(AF_INET, SOCK_STREAM, 0);
break;
}
else
{

bprintf(fd, "[cli](%d) server[%s:%d] is connected!\n", getpid(),argv[1],port); //argv[]指定
echo_rqt(cli_sock_confd, PIN, fd);
break;
}


}

close(cli_sock_confd);
bprintf(fd, "[cli](%d) connfd is closed!\n", getpid());
bprintf(fd, "[cli](%d) child process is going to exit!\n", getpid());
exit(0);

}
}

int clip_sock_confd;
clip_sock_confd = socket(AF_INET, SOCK_STREAM, 0);

if (clip_sock_confd == -1)
{
printf("[cli](%d)(PIN:%d) fail to create socket!\n", getpid(), 0);
}

// short port = 1234; //argv[]指定port
// struct sockaddr_in cli_addr_v4;
// inet_pton(AF_INET, "127.0.0.1", &cli_addr_v4.sin_addr); //argv[]指定ip
// cli_addr_v4.sin_family = AF_INET;
// cli_addr_v4.sin_port = htons(port);


printf("port is = %d\n",port);
printf("sin_port is = %d\n",cli_addr_v4.sin_port);

for (;;)
{
int stat = connect(clip_sock_confd, (COM_ADD *)&cli_addr_v4, sizeof(cli_addr_v4));
if (stat == -1)
{
printf("[cli](%d) wrong serve connection:%d\n%s", getpid(),errno, strerror(errno));
clip_sock_confd = socket(AF_INET, SOCK_STREAM, 0);
break;
}

else
{
bprintf(fd, "[cli](%d) server[%s:%d] is connected!\n", getpid(),argv[1],port); //argv[]指定
echo_rqt(clip_sock_confd, 0, fd);
break;
}

}
close(clip_sock_confd);
bprintf(fd, "[cli](%d) connfd is closed!\n", getpid());
bprintf(fd, "[cli](%d) parent process is going to exit!\n", getpid());
return 0;

}

int echo_rqt(int sock_confd, int pin, FILE *fd)
{
int n_pin;
int h_pin;
int n_len;
int h_len;

char data_file[10] = {0};
char data[MAX_CMD_STR] = {0};
FILE * read_fd;
sprintf(data_file,"td%d.txt",pin);
printf("data_file:%s\n",data_file);
read_fd = fopen(data_file,"r");

while(fgets(data,MAX_CMD_STR + 1,read_fd))
{

if(strncmp(data,"exit",4) == 0)
{
break;
}
printf("last one = %d\n",data[strlen(data)-1]);

h_len = strlen(data);
data[strlen(data) - 1] = '\0';

printf("last one = %d\n",data[strlen(data)]);

char send_data[MAX_CMD_STR + 1 + 8];
char rcv_data[MAX_CMD_STR + 1 + 8];
memset(rcv_data, 0, sizeof(rcv_data));
memset(send_data, 0, sizeof(send_data));



h_pin = pin;


printf("h_len = %d\n",h_len);

n_pin = htonl(h_pin);
n_len = htonl(h_len);

memcpy(send_data + 8,data,h_len);
memcpy(send_data,&n_pin,4);
memcpy(send_data + 4,&n_len,4);

printf("pdu is:%s\n", send_data+8);

int res = write(sock_confd, send_data, h_len + 8);
printf("write res = %d ,erro is = %s\n",res,strerror(errno));


res = read(sock_confd,&n_pin,4);
if(res < 0)
{
bprintf(fd, "[srv](%d) read len_n return %d and errno is %d\n",getpid(),res,errno);
}
if (!res)
{
return -1;
}

printf("res_pin = %d\n",res);
h_pin = ntohl(n_pin);
printf("[echo_rep](%d) h_pin=%d\n",getpid(),h_pin);


res = read(sock_confd,&n_len,4);
if(res < 0)
{
bprintf(fd, "[srv](%d) read len_n return %d and errno is %d\n",getpid(),res,errno);
}
if (!res)
{
return -1;
}

printf("res_len = %d\n",res);
h_len = ntohl(n_len);
printf("[echo_rep](%d) h_len=%d\n",getpid(),h_len);



int state = read(sock_confd, rcv_data, h_len);
printf("read res = %d ,erro is = %s\n",state,strerror(errno));
while (state < h_len )
{
state += read(sock_confd, rcv_data + state, h_len - state);
}

bprintf(fd, "[echo_rep](%d) %s\n", getpid(), rcv_data);
printf("...\n");
}
return 0;
}

void sig_pipe(int signo)
{
// TODO 记录本次系统信号编号到sig_type中;通过getpid()获取进程ID,按照指导书上的要求打印相关信息,并设置sig_to_exit的值
sig_type = signo;
fprintf(stdout, "[cli](%d) SIGPIPE is coming!\n", getpid());
}

void sig_child(int signo){
pid_t pid_child;
int stat;
while((pid_child = waitpid(-1,&stat,WNOHANG)) > 0){
printf("child :%d terminated\n",pid_child);
};
}c