信息

多进程初试

Demo

Linux 系统提供了一系列内核函数供我们使用: fork()exec()wait() 等。

有这样一个小 Demo 可以让我们一览 Linux 下多进程程序的运行场景。

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>

int glob = 6;
int main() {
  pid_t pid;
  int x = 1;

  pid = fork();
  if (pid < 0) {
    perror("Error: ");
  } else if (pid == 0) {
    sleep(1);
		glob = glob + 2;
    x = x + 2;
    printf("child : glob(%p)=%d, x(%p)=%d\\n", &glob, glob, &x, x);
  } else {
    sleep(2);
		glob = glob + 1;
    x = x + 1;
    printf("parent: glob(%p)=%d, x(%p)=%d\\n", &glob, glob, &x, x);
  }
}

可以看到,在调用 fork() 之后,程序瞬间分裂成为两个分支:主进程和子进程,判断主进程还是子进程的方法是查看 fork() 函数的返回值。

调整 Sleep(x) 的参数可以改变主进程和子进程的运行先后顺序。如果子进程先运行,输出是这样:

child : glob(0x555555558010)=8, x(0x7fffffffd580)=3
parent: glob(0x555555558010)=7, x(0x7fffffffd580)=2

如果主进程先运行,输出:

parent: glob(0x555555558010)=7, x(0x7fffffffd580)=2
child : glob(0x555555558010)=8, x(0x7fffffffd580)=3

可以看到,输出的这两行虽然行的顺序不同,但是行的内容是一样的,甚至变量的地址都是一样——但是变量在两个进程之间的值却不同。

“全局”变量竟然不再是“全局”。

fork()

fork() 函数是一个特殊的函数,它能返回两次。对于主进程,返回值为子进程的进程 ID(不是PID);对于子进程,它的返回值是 0

在这样一个“分裂”过程中,程序的堆栈被整体复制,所以子程序和主程序拥有相同的变量和函数;由于虚拟地址,所以变量的地址也是不变的,但是变量已经不是在同一个物理地址了。

在简单 HTTP Server 上使用多进程处理

这是一个基于 Socket 编程的简单 HTTP Server。

#include <arpa/inet.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/un.h>
#include <unistd.h>

#define PORT 8000  // 服务器监听端口

void send_handle(int client_socket) {
  char status[] = "HTTP/1.0 200 OK\\r\\n";
  char header[] =
      "Server: DWBServer\\r\\nContent-Type: text/html;charset=utf-8\\r\\n\\r\\n";
  char body[] =
      "<html><head><title>C语言构建小型Web服务器</title></head><body><h2>欢迎</"
      "h2><p>Hello,World</p></body></html>";

  printf("enter sleeping...\\n");
  sleep(10);  //让进程进入睡眠状态,单位是秒。
  printf("finish sleeping...\\n");

  write(client_socket, status, sizeof(status));
  write(client_socket, header, sizeof(header));
  write(client_socket, body, sizeof(body));

  close(client_socket);
}

int main() {
  int server_socket = socket(AF_INET, SOCK_STREAM, 0);  //初始化套接字
  struct sockaddr_in server_addr;

  memset(&server_addr, 0, sizeof(server_addr));
  server_addr.sin_family = AF_INET;  // 服务地址和端口配置
  server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
  server_addr.sin_port = htons(PORT);

  bind(server_socket, (struct sockaddr *)&server_addr,
       sizeof(server_addr));  //将本地地址绑定到所创建的套接字上

  listen(server_socket, 5);  //开始监听是否有客户端连接,第二个参数是最大监听数

  char buf[1024];
  int client_socket;

  while (1) {
    printf("======waiting for client's request=====\\n");
    if ((client_socket = accept(server_socket, (struct sockaddr *)NULL,
                                NULL)) == -1) {  //等待客户端(浏览器)连接
      printf("accept socket error :%s(errno:%d)\\n", strerror(errno), errno);
      continue;
    }
    printf("======waiting for read request data=====\\n");
    read(client_socket, buf, 1024);  //读取客户端内容,这里是HTTP的请求数据
    // printf("%s",buf);     // 打印读取的内容

    send_handle(client_socket);
  }
  close(server_socket);

  return 0;
}