多进程并发服务器
服务端
#include <stdio.h>
#include <string.h>
#include <sys/errno.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <ctype.h>
#include <pthread.h>
#define PORT 8888
#define BUFFSIZE 1024
#define ERRORCODE -1
void Do_Work(int fd)
{
int afd = fd;
while (1)
{
int i;
char recv_buf[BUFFSIZE];
int n = read(afd, recv_buf, sizeof(recv_buf));
if (n == 0)
{
perror("connect interrupt");
break;
}
fputs(recv_buf, stdout);
for (i = 0; i < n; i++)
{
recv_buf[i] = toupper(recv_buf[i]);
}
write(afd, recv_buf, sizeof(recv_buf));
}
close(afd);
exit(0);
}
int main(int argc, char *argv[])
{
char buf[BUFSIZ], client_addr_INFO[BUFFSIZE];
int accept_fd, listen_fd;
socklen_t client_len;
struct sockaddr_in listen_addr, accept_addr;
char buffer[BUFFSIZE];
int buffer_len, i;
int on = 1;
pid_t pid1;
// 创建socket
listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1)
{
printf("创建socket error: %s \n", strerror(errno));
return ERRORCODE;
}
// bind
if (setsockopt(listen_fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)))
{
printf("setsockopt error: %s \n", strerror(errno));
return ERRORCODE;
}
listen_addr.sin_family = AF_INET;
listen_addr.sin_port = htons(PORT);
listen_addr.sin_addr.s_addr = htonl(INADDR_ANY);
if (bind(listen_fd, (struct sockaddr *)&listen_addr, sizeof(listen_addr)) < 0)
{
printf("bind error: %s \n", strerror(errno));
return ERRORCODE;
}
// listen
if (listen(listen_fd, 5) == -1)
{
printf("listen error: %s \n", strerror(errno));
return ERRORCODE;
}
printf("服务端创建成功,等待连接\n");
// accept
while (1)
{
client_len = sizeof(accept_addr);
/* code */
accept_fd = accept(listen_fd, (struct sockaddr *)&accept_addr, &client_len);
if (accept_fd == -1)
{
printf("accept error: %s \n", strerror(errno));
return ERRORCODE;
}
printf("accept ip : %s,port : %d\n",
inet_ntop(AF_INET, &accept_addr.sin_addr.s_addr, client_addr_INFO, sizeof(client_addr_INFO)),
ntohs(accept_addr.sin_port));
//printf("accept ip : %s,port : %d\n", inet_ntoa(accept_addr.sin_addr), ntohs(accept_addr.sin_port));
pid1 = fork();
if (pid1 == -1)
{
printf("fork erro : %s\n",strerror(errno));
close(accept_fd);
continue;
}else if (pid1 == 0)
/*子进程创建成功之后,子进程会拥有和parent process一样的资源,子进程会判断pid1为0,进行下面的处理
*/
{
close(listen_fd);
Do_Work(accept_fd);
exit(0);
//子进程人物完成,终止子进程
}else{
/*父进程创建成功之后,父进程会判断pid1不为0,进行下面的处理
*/
close(accept_fd);
//进程回收,但是会阻塞
int status;
//WHOHANG设置为非阻塞
waitpid(pid1,&status,WHOHANG);
}
}
close(listen_fd);
return 0;
}
代码运行
zombie process
僵尸进程是已经完成执行但尚未被父进程回收其终止状态的进程。随着时间的推移,如果服务器持续创建子进程而不回收它们,系统中可能会积累大量僵尸进程,这可能会成为问题。
我们可以通过使用waitpid()函数来等待子进程结束。这样,父进程可以回收子进程的资源,防止僵尸进程的产生。
waitpid()
函数原型
pid_t waitpid(pid_t pid, int *wstatus, int options);
函数参数
pid_t pid
如果 pid > 0,waitpid() 将等待 pid 指定的子进程。
如果 pid == 0,waitpid() 将等待任何子进程,但其进程组ID必须与调用进程的进程组ID相同。
如果 pid < 0,waitpid() 将等待任何子进程,其进程组ID必须与 pid 的绝对值相同。
如果 pid == -1,waitpid() 将等待任何子进程。
int *wstatus
这是一个指向整数的指针,用于存储子进程的退出状态。如果不需要这个信息,可以传递 NULL。
通过检查这个整数,可以确定子进程是如何终止的,例如是否是正常退出,或者是因为信号而终止。
options
这是一个选项标志,用于控制 waitpid() 的行为。可以设置以下选项:
WNOHANG:非阻塞等待。如果没有任何子进程状态改变,waitpid() 将立即返回 0,而不是阻塞。
WUNTRACED:如果设置了这个选项,waitpid() 也将返回停止(但未终止)的子进程的状态。
函数功能描述
All of these system calls are used to wait for state changes in a child
of the calling process, and obtain information about the child whose
state has changed. A state change is considered to be: the child ter‐
minated; the child was stopped by a signal; or the child was resumed by
a signal. In the case of a terminated child, performing a wait allows
the system to release the resources associated with the child; if a
wait is not performed, then the terminated child remains in a "zombie"
state
大致意思就是系统调用wait来监听子进程的状态变化,这个状态会被告知父进程,当子进程终止,父进程就会将其回收,系统会释放这个子进程的资源,从而避免僵尸。
函数返回值
waitpid(): on success, returns the process ID of the child whose state
has changed; if WNOHANG was specified and one or more child(ren) speci‐
fied by pid exist, but have not yet changed state, then 0 is returned.
On error, -1 is returned.
如果成功,waitpid() 返回被等待子进程的进程ID。
如果出错,返回 -1,并设置 errno 以指示错误原因。
线程创建pthread_create函数
创建时机
服务端在accept成功之后,创建一个线程,将接收到的accept_fd交给线程处理,线程通过这个accept_fd与客户端进行读写操作。
创建线程函数原型
pid_t fork(void);
函数功能描述
fork() creates a new process by duplicating the calling process. The
new process is referred to as the child process. The calling process
is referred to as the parent process.
这个函数总体来说是在一个正在调用的进程通过赋值原有进程的资源创建一个新的进程,而这个新的进程相对与原有的进程是一个子进程。
函数返回值
On success, the PID of the child process is returned in the parent, and
0 is returned in the child. On failure, -1 is returned in the parent,
no child process is created, and errno is set appropriately.
如果成功,这个child process的PID(进程标识符)会返回到parent process,同时会返回0到这个child process。
如果失败,会返回-1到parent process
如果没有创建成功,会有相应的errno被设置。
errno
errno 是一个 C/C++ 标准库提供的外部全局变量,它用于表示在发生错误时的错误码。它是一个整数,通常用来指示最近一次系统调用(如文件操作、网络通信、内存分配等)失败的原因。
以下是一些常见的错误码,它们定义在 <errno.h>(在 C 语言中)或 (在 C++ 中):
EINVAL:无效的参数或操作。
ENOMEM:内存不足。
EIO:输入/输出错误。
ENFILE:打开的文件数量达到系统限制。
EAGAIN:资源暂时不可用,例如在非阻塞操作中。
EACCES:权限不足。
ECONNREFUSED:连接被拒绝。