fork 函数
fork函数用于创建一个子线程,函数原型如下:
#include <unistd.h>
pit_t fork(void);
fork函数调用一次,返回2次,在父进程中返回一次,在子进程中也返回一次。在子进程中返回0,在父进程中返回子进程的pid,因为父进程无法通过额外的手段获取子进程的id,所以必须记录fork之后的子进程id,而子进程在任何时候都可以通过getppid
来获取其父进程的id。
僵尸进程
僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。
SIGCHLD 信号
当任何一个进程终止时都会向其父进程发送SIGCHLD信号,告知父进程回收自己,但该信号的默认处理动作为忽略,忽略该信号可能会导致出现僵尸进程。
signal 函数
signal函数用来设置一个信号处理函数来处理信号,函数原型如下:
#include <signal.h>
void (*signal(int sig, void (*func)(int)))(int);
中间部分:signal函数有2个参数,第一个是int,第二个是无返回值,带一个int参数的函数指针。
外围:signal函数返回的是一个函数指针,无返回值,有一个int参数。
通过typedef的方式进行简化,简化后如下:
typedef void Sigfunc(int)
// Sigfunc就是一个返回值是一个无返回值,有一个int参数的函数
Sigfunc *signal(int, Sigfunc*);
waitpid 函数
waitpid函数用来清理已经停止的子进程,可以防止出现僵尸进程,函数原型如下:
#include <sys/wait.h>
/*
* pid: 子进程的ID,-1表示等待第一个终止的子进程
* statloc: 返回子进程的终止状态
* options: 附加选项,常用的为WNOHANG,意为告知内核在没有已终止子进程时候不要阻塞
*/
pid_t waitpid(pid_t pid, int* statloc, int options);
并发服务端程序
父进程来监听所有的客户端连接,当连接上时候,fork产生一个子进程来处理信息交互。同时父进程监听SIGCHLD信号,通过函数sig_func来处理该信号,在sig_func函数中通过waitpid来防止产生僵尸进程。
服务端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <arpa/inet.h>
static void do_msg_process(int accsocfd) {
printf("do_msg_process enter!\n");
int size = 0;
char read_buffer[512] = {0};
char write_buffer[1024] = {0};
while (true) {
memset(read_buffer, 0x00, sizeof(read_buffer));
memset(write_buffer, 0x00, sizeof(write_buffer));
size = read(accsocfd, read_buffer, 1024);
if (size > 0) {
printf("pid: %d, client say: %s\n", getpid(), read_buffer);
snprintf(write_buffer, 1024, "%s%s", "[SERVER RECV]: ", read_buffer);
size = write(accsocfd, write_buffer, strlen(write_buffer));
} else {
break;
}
}
printf("do_msg_process exit!\n");
}
static void sig_func(int arg) {
pid_t pid;
int stat;
while ((pid = waitpid(-1, &stat, WNOHANG)) > 0) {
printf("child %d terminated!\n", pid);
}
}
int main(int argc, char const *argv[]) {
// 调用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
int socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketfd < 0) {
perror("socket create failed!\n");
return 0;
}
// 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,IP地址使用INADDR_ANY表示任意IP
struct sockaddr_in s_addr;
memset(&s_addr, 0x00, sizeof(struct sockaddr_in));
s_addr.sin_family = AF_INET;
s_addr.sin_port = htons(6666);
s_addr.sin_addr.s_addr = htonl(INADDR_ANY);
// 调用bind函数绑定所创建的套接字
bind(socketfd, (struct sockaddr*)&s_addr, sizeof(struct sockaddr));
// 调用listen将套接字转换为监听套接字,这样外来连接可以通过该套接字被内核接受
listen(socketfd, 10);
// 监听SIGCHLD信号,通过函数sig_func来处理该信号
signal(SIGCHLD, sig_func);
while (true) {
// 调用accept,服务器进入睡眠状态,等待客户的连接,完成3次握手,然后返回一个握手成功的描述符,新描述符用于与客户端通讯
int accsocfd = accept(socketfd, NULL, NULL);
pid_t forkpid = fork();
if (forkpid == 0) {
// 子进程不需要监听
close(socketfd);
// 处理消息
do_msg_process(accsocfd);
// 子进程退出
exit(0);
}
// 调由子进程处理了,父进程用close来关闭已连接的套接字
close(accsocfd);
}
close(socketfd);
return 0;
}
客户端程序
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <stdbool.h>
#include <unistd.h>
#include <netinet/in.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
static void do_msg_process(int socketfd) {
printf("do_msg_process enter!\n");
int size = 0;
char read_buffer[512] = {0};
char write_buffer[1024] = {0};
while(true) {
memset(read_buffer, 0x00, sizeof(read_buffer));
memset(write_buffer, 0x00, sizeof(write_buffer));
printf("input:> ");
fgets(write_buffer, 1024, stdin);
size = write(socketfd, write_buffer, strlen(write_buffer));
size = read(socketfd, read_buffer, 1024);
printf("server say: %s\n", read_buffer);
}
printf("do_msg_process exit!\n");
}
int main(int argc, char const *argv[]) {
// 利用socket函数来创建一个IPV4(AF_INET)字节流(SOCK_STREAM)套接字,返回标识符
int socketfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (socketfd < 0) {
perror("socket create failed!\n");
return 0;
}
// 将服务器的IP地址和端口号填入sockaddr_t结构的变量中,地址族设定为AF_INET,IP地址和端口必须要是特地格式,使用htons转换端口,使用inet_pton转换IP地址
struct sockaddr_in c_addr;
memset(&c_addr, 0x00, sizeof(struct sockaddr_in));
c_addr.sin_family = AF_INET;
c_addr.sin_port = htons(6666);
inet_pton(AF_INET, "127.0.0.1", &c_addr.sin_addr);
// connet将和指定的服务器建立一个TCP连接
int result = connect(socketfd, (struct sockaddr*)&c_addr, sizeof(struct sockaddr_in));
if (result < 0) {
perror("connect failed!\n");
return 0;
}
// 处理消息
do_msg_process(socketfd);
close(socketfd);
return 0;
}
运行结果
两个客户端分别是由2个子进程处理,pid = 54212和pid = 54214,客户端退出后,相应的处理消息的子进程也退出,父进程捕获到了信号利用waitpid函数进行了资源回收。