I/O Continued, Sockets, Networking

进程间通信

Communication Between Processes

UNIX 的一大 mental model 就是将进程间的通信看作是文件读写的过程,如下图所示:

文件内容的生产者、消费者可以是不同时间运行的不同的进程;有时候我们只希望生产、消费一次,仍然可以使用文件读写作为 mental model,但在这种情况下,queue 的 enqueue、dequeue 更适合这种情形。

如果是通过网络连接的不同进程呢?在上图中间加一朵云即可:

这就是 Client/Server 模型:

  • 假设 Client, Server 之间存在两个文件,其中一个文件叫上行文件,Client 写,Server 读;另一个文件叫下行文件,Client 读,Server 写

  • Client 将请求写入上行文件,然后进入等待状态

  • Server 从上行文件中读取请求信息,处理请求后,将响应写入下行文件中

  • 下行文件接受到数据后,Client 被唤醒,读取响应结果

Sockets

像上述这种两个进程间可双向交流,每条信息都只生产一次、被消费一次的情形,就可以被抽象成 connection,而 connection 的两端就是 socket。sockets 之间的连接不一定通过网络,也可以是本地机器上的两个进程,前者成为 network socket,后者成为 UNIX socket。在 sockets 之间,数据读写的接口如文件读写一般,而它们之间的介质则是透明的。

利用 sockets 开发的 Client/Server 示例如下:

client.c
void client(int sockfd) {
  int n;
  char sndbuf[MAXIN]; 
  char rcvbuf[MAXOUT];
  getreq(sndbuf, MAXIN);
  while (strlen(sndbuf) > 0) {
    write(sockfd, sndbuf, strlen(sndbuf)); /* send */
    memset(rcvbuf, 0, MAXOUT);             /* clear */
    n = read(sockfd, rcvbuf, MAXOUT-1);    /* receive */
    write(STDOUT_FILENO, rcvbuf, n);       /* echo */
    getreq(sndbuf, MAXIN);
  }
}

char *getreq(char *inbuf, int len) {
  /* Get request char stream */
  printf("REQ: ");
  memset(inbuf, 0, len);
  return fgets(inbuf, len, stdin);
}
server.c
void server(int consockfd) {
  char reqbuf[MAXREQ];
  int n;
  while (1) {
    memset(reqbuf, 0, MAXREQ);
    n = read(consockfd, reqbuf, MAXREQ-1); /* Recv */
    if (n <= 0) return;
    n = write(STDOUT_FILENO, reqbuf, strlen(reqbuf));
    n = write(consockfd, reqbuf, strlen(reqbuf)); /* echo */
  }
}

Socket creation and connection

创建 socket 以及和其它 socket 的 connection 要比创建文件及读写文件更复杂一些。文件系统提供了:

  • structured namespace:便于组织文件

  • open, read, write, close 等基本接口

  • 文件本身与进程之间相互独立

那么对于 network socket 来说,它的 structured namespace 是:

  • Hostname

  • IP address

  • Port number

Socket setup over TCP/IP

本节开头处提出的 Client/Server 模型中,Server 一次只能处理一个连接请求,等待连接断开后才可以处理下一个连接,我们可以让 server 每接收到新的连接请求就 fork 新的 process,创建新的 socket,并发地处理连接请求,提高吞吐量,示意图如下:

从图中可以看出,1 个 connection 至少由 2 个 sockets 组成,事实上,connection 由一个 5 元组定义:

[ Client Addr, Client Port, Server Addr, Server Port, Protocol ]

通常 Client Port 被随机赋值,而 Server Port 通常在固定的范围中(0-1023),如:

  • 80: web

  • 443: secure web

  • 25: sendmail

而 Protocol 为 tcp, udp 等传输层协议。

我们先回顾一下基本版的 C/S 模型

但在该版本中,连接处理以及请求处理逻辑与 server 的建立连接、关闭连接等逻辑处在同一个空间中,没有隔离保护,因此更好的方法是在建立新连接时 fork child process,由 child process 负责处理相关逻辑,parent process 等待 child process 搞定后再处理新的连接请求,如下图所示:

但这样并没有利用到多核处理器并行带来的好处,我们可以将上图中的 "Wait for child“ 去除,就得到了并行版本的 Client/Server 模型:

典型的 Server 逻辑如下所示:

while (1) {
  listen(lstnsockfd, MAXQUEUE)
  consockfd = accept(lstnsockfd, (struct sockaddr *) &cli_addr, &client);
  
  cpid = fork();                    /* new process for connection */
  if (cpid > 0) {                   /* parent process */
    close(consockfd); 
  } else if (cpid == 0) {           /* child process */
    close(lstnsockfd);              /* let go of listen socket */
    server(consockfd);
    close(consockfd);
    exit(EXIT_SUCCESS);             /* exit child normally */
  }
}
close(lstnsockfd);

OS Basics 小结

至此,我们介绍了 OS 的一些基本概念:

  • Processes

  • Address Space

  • Protection

  • Dual Mode

  • Interrupt handlers (including syscall and trap)

  • File System

  • Key Layers

    • OS Lib

    • Syscall

    • Subsystem

    • Driver

  • Process control

    • fork

    • wait

    • signal

    • exec

  • Communication through sockets

  • Client-Server Protocol

Last updated