# I/O Continued, Sockets, Networking

## Communication Between Processes

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

![](https://1008303647-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LMjQD5UezC9P8miypMG%2F-L_GoGy2q1AGY68-R2Nx%2F-L_GmvyF6Mb2M8X8LKYr%2FScreen%20Shot%202019-03-06%20at%202.58.10%20PM.jpg?alt=media\&token=b931ca33-3324-41aa-91af-3f587e2f69b9)

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

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

![](https://1008303647-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LMjQD5UezC9P8miypMG%2F-L_GoGy2q1AGY68-R2Nx%2F-L_GneGKB3aHVL7n47PG%2FScreen%20Shot%202019-03-06%20at%203.01.23%20PM.jpg?alt=media\&token=5ce76db9-aa6e-4ed8-b694-5e2d0d980a9f)

这就是 Client/Server 模型：

![](https://1008303647-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LMjQD5UezC9P8miypMG%2F-L_GoGy2q1AGY68-R2Nx%2F-L_Go4z765G8MuP-Nvu9%2FScreen%20Shot%202019-03-06%20at%203.03.04%20PM.jpg?alt=media\&token=a7255030-cf84-4f61-8ee6-e437dd4c3414)

* 假设 Client, Server 之间存在两个文件，其中一个文件叫上行文件，Client 写，Server 读；另一个文件叫下行文件，Client 读，Server 写
* Client 将请求写入上行文件，然后进入等待状态
* Server 从上行文件中读取请求信息，处理请求后，将响应写入下行文件中
* 下行文件接受到数据后，Client 被唤醒，读取响应结果

### Sockets

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

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

{% code title="client.c" %}

```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);
}
```

{% endcode %}

{% code title="server.c" %}

```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 */
  }
}
```

{% endcode %}

#### 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，并发地处理连接请求，提高吞吐量，示意图如下：

![](https://1008303647-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LMjQD5UezC9P8miypMG%2F-L_GoGy2q1AGY68-R2Nx%2F-L_HD_qfdhB-q1kXk8pB%2FScreen%20Shot%202019-03-06%20at%204.59.03%20PM.jpg?alt=media\&token=9fba5d4e-829b-48eb-9896-6a0156325b79)

从图中可以看出，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 模型

![](https://1008303647-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LMjQD5UezC9P8miypMG%2F-L_GoGy2q1AGY68-R2Nx%2F-L_HHSXFnCxfuisaVxXn%2FScreen%20Shot%202019-03-06%20at%205.15.55%20PM.jpg?alt=media\&token=2985c876-9c35-448d-87f0-a66088c1a14a)

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

![](https://1008303647-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LMjQD5UezC9P8miypMG%2F-L_GoGy2q1AGY68-R2Nx%2F-L_HF5qbqOdcqrfvEEsm%2FScreen%20Shot%202019-03-06%20at%205.05.40%20PM.jpg?alt=media\&token=b85cf923-79f8-4b5e-999a-e7873b1f6904)

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

![](https://1008303647-files.gitbook.io/~/files/v0/b/gitbook-legacy-files/o/assets%2F-LMjQD5UezC9P8miypMG%2F-L_GoGy2q1AGY68-R2Nx%2F-L_HIWhdAfJ4Qi6nMO9W%2FScreen%20Shot%202019-03-06%20at%205.20.36%20PM.jpg?alt=media\&token=9f16b70a-be13-49fb-8c31-b50b5d7898e3)

典型的 Server 逻辑如下所示：

```c
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
