进程通信

This commit is contained in:
yinkanglong_lab
2021-04-06 12:06:44 +08:00
parent a32610d503
commit 650ba6cec8
12 changed files with 644 additions and 111 deletions

View File

@@ -2,7 +2,7 @@
> 完成昨天的任务。
- [ ] 学习、复习图算法,动手实现所有的图算法。
- [x] 学习、复习图算法,动手实现所有的图算法。
- [ ] 看完数据结构与算法的三本书!!!对相关的原理进行复习和总结。
- [x] 学习机器学习的实现方案。毕设计划真正的开始执行。
- [x] 关于字符串分割。字符串格式化方法的总结。转换成流,作为流对象处理。转换为容器。作为容器对象处理,使用泛型算法。

View File

@@ -6,9 +6,9 @@
- [x] 基础知识与项目经历——操作系统整理完成part2
- [ ] 联邦学习与恶意软件——TensorFlow学习开始part1
- [ ] 操作系统关键知识点记忆。进程、线程、IO、中断、同步异步、阻塞非阻塞、分段、分页等。
- [ ] 整理进程、线程同步与通信的方式。并对二者进行区别。
- [ ] 整理同步异步、阻塞非阻塞的知识。并找到主要的通信实现方式。如何实现阻塞、非阻塞通信、同步异步通信。
- [ ] 整理操作系统中涉及到的算法(页面调度、页面置换等)
- [x] 操作系统关键知识点记忆。进程、线程、IO、中断、同步异步、阻塞非阻塞、分段、分页等。
- [x] 整理进程、线程同步与通信的方式。并对二者进行区别。
- [x] 整理同步异步、阻塞非阻塞的知识。并找到主要的通信实现方式。如何实现阻塞、非阻塞通信、同步异步通信。
- [x] 整理操作系统中涉及到的算法(页面调度、页面置换等)
## 收获

View File

@@ -1,7 +1,7 @@
## 安排
> 最后一天用来补之前的任务。绝对不可能再拖下去了。明天就要开始全新的计划。
- [ ] 五种IO模型和epoll机制
- [ ] 同步异步、阻塞非阻塞:定义及实现
- [x] 五种IO模型和epoll机制
- [x] 同步异步、阻塞非阻塞:定义及实现
## 收获

View File

@@ -11,44 +11,379 @@
* 效率低,生产者每次只能向缓冲池投放一个产品(消息),消费者每次只能从缓冲区中取得一个消息;
* 通信对用户不透明。
## 1 进程通信的类型
### 进程通信的原理划分
* 低级进程通信机制:由于进程的互斥和同步,需要在进程间交换一定的信息,故不少学者将它们也归为进程通信。只能传递状态和整数值(控制信息)。特点:传送信息量小,效率低,每次通信传递的信息量固定,若传递较多信息则需要进行多次通信。编程复杂:用户直接实现通信的细节,容易出错。
* 高级进程通信机制。提高信号通信的效率,传递大量数据,减轻程序编制的复杂度。
* 共享内存通信
* 消息传递通信
* 共享文件通信
### 进程通信的实现划分
* 基础进程通信机制
* 基础进程通信机制。只能传递状态和整数值(控制信息)。特点:传送信息量小,效率低,每次通信传递的信息量固定,若传递较多信息则需要进行多次通信。编程复杂:用户直接实现通信的细节,容易出错。
* 条件变量、信号量、管程
* 信号signal wait notify
* 共享内存通信机制
* 通过信号量控制共享内存。
* 消息传递通信机制
* IPC消息队列
* 共享内存。(可以通过信号量控制
* 管道文件通信机制(文件进程通信机制)
* PIPE管道
* PIPE匿名管道
* FIFO命名管道
* 网络进程通信机制
* 高级管道通信
* 消息传递通信机制
* 消息队列
* 网络进程通信机制。主要是通过socket实现的tcp或者是udp通信。
* socket
![](image/2021-03-30-15-24-40.png)
## 2 共享内存通信
### 进程通信的分类重述
> [参考文献](http://www.xitongzhijia.net/xtjc/20171024/109869.html)
>
> IPC inter process communication是一组编程接口让程序员能够协调不同的进程使之能在一个操作系统里同时运行并相互传递、交换信息。
1. **信号量通信**。信号量( semophore 信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
2. **信号**。信号 sinal 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。
3. **共享内存通信**。共享内存( shared memory :共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号两,配合使用,来实现进程间的同步和通信。
4. **无名管道通信**。无名管道( pipe ):管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
5. **高级管道通**信。高级管道popen将另一个程序当做一个新的进程在当前程序进程中启动则它算是当前程序的子进程这种方式我们成为高级管道方式。
6. **有名管道通信**。有名管道 named pipe 有名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信。
7. 消息队列通信。消息队列( message queue 消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
8. **套接字通信**。套接字( socket 套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器间的进程通信。
## 1 信号量通信
> 参考文献
> * [https://blog.csdn.net/csdn_kou/article/details/81240666](https://blog.csdn.net/csdn_kou/article/details/81240666)
### 函数接口
* 进程中的信号量函数
```
```
* 线程中的信号量函数。
```
pthread_mutex_t
pthread_mutex_init
pthread_mutex_lock
pthread_mutex_unlock
pthread_mutex_destroy
```
### 编程实现
```C
#include <time.h>
#include <pthread.h>
#include "tlpi_hdr.h"
static pthread_mutex_t mtx = PTHREAD_MUTEX_INITIALIZER;
static pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
static int avail = 0;
static void * threadFunc(void *arg)
{
int cnt = atoi((char *) arg);
int s, j;
for (j = 0; j < cnt; j++) {
sleep(1);
/* Code to produce a unit omitted */
s = pthread_mutex_lock(&mtx);
if (s != 0)
errExitEN(s, "pthread_mutex_lock");
avail++; /* Let consumer know another unit is available */
s = pthread_mutex_unlock(&mtx);
if (s != 0)
errExitEN(s, "pthread_mutex_unlock");
s = pthread_cond_signal(&cond); /* Wake sleeping consumer */
if (s != 0)
errExitEN(s, "pthread_cond_signal");
}
return NULL;
}
int main(int argc, char *argv[])
{
pthread_t tid;
int s, j;
int totRequired; /* Total number of units that all threads
will produce */
int numConsumed; /* Total units so far consumed */
Boolean done;
time_t t;
t = time(NULL);
/* Create all threads */
totRequired = 0;
for (j = 1; j < argc; j++) {
totRequired += atoi(argv[j]);
s = pthread_create(&tid, NULL, threadFunc, argv[j]);
if (s != 0)
errExitEN(s, "pthread_create");
}
/* Loop to consume available units */
numConsumed = 0;
done = FALSE;
for (;;) {
s = pthread_mutex_lock(&mtx);
if (s != 0)
errExitEN(s, "pthread_mutex_lock");
while (avail == 0) { /* Wait for something to consume */
s = pthread_cond_wait(&cond, &mtx);
if (s != 0)
errExitEN(s, "pthread_cond_wait");
}
/* At this point, 'mtx' is locked... */
while (avail > 0) { /* Consume all available units */
/* Do something with produced unit */
numConsumed ++;
avail--;
printf("T=%ld: numConsumed=%d\n", (long) (time(NULL) - t),
numConsumed);
done = numConsumed >= totRequired;
}
s = pthread_mutex_unlock(&mtx);
if (s != 0)
errExitEN(s, "pthread_mutex_unlock");
if (done)
break;
/* Perhaps do other work here that does not require mutex lock */
}
exit(EXIT_SUCCESS);
}
```
## 2 信号通信
### 编程实现
```C
```
## 3 共享内存通信
> 参考文献
> * [https://blog.csdn.net/csdn_kou/article/details/82908922](https://blog.csdn.net/csdn_kou/article/details/82908922)
### 类型
* **共享存储器系统(Shared-Memory System)** 相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信。据此,又可把它们分成以下两种类型:
* **共享存** 相互通信的进程共享某些数据结构或共享存储区,进程之间能够通过这些空间进行通信。据此,又可把它们分成以下两种类型:
* **基于共享数据结构的通信方式**。在这种通信方式中,要求诸进程公用某些数据结构。借以实现诸进程间的信息交换。如在生产者—消费者问题中,就是用有界缓冲区这种数据结构来实现通信的。
* **基于共享存储区的通信方式**。为了传输大量数据,在存储器中划出了一块共享存储区,诸进程可通过对共享存储区中数据的读或写来实现通信。
### 函数接口
```
int shmget(key_t key, size_t size, int shmflg);
```
* 用来创建共享内存
* 参数
* key: 这个共享内存段名字
* size: 共享内存大小
* shmflg: 由九个权限标志构成它们的用法和创建文件时使用的mode模式标志是一样的
* 返回值:
* 成功返回⼀一个⾮非负整数,即该共享内存段的标识码;失败返回-1
## 3 消息传递通信
```
void *shmat(int shmid, const void *shmaddr, int shmflg);
```
* 功能: 将共享内存段连接到进程地址空间
* 参数
* shmid: 共享内存标识
* shmaddr: 指定连接的地址
* shmflg: 它的两个可能取值是SHM_RND和SHM_RDONLY
* 返回值:
* 成功返回⼀一个指针,指向共享内存第⼀一个节;失败返回-1
```
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
```
* 功能: ⽤用于控制共享内存
* 参数
* shmid: 由shmget返回的共享内存标识码
* cmd: 将要采取的动作(有三个可取值)
* buf: 指向⼀一个保存着共享内存的模式状态和访问权限的数据结构
* 返回值:
* 成功返回0失败返回-1
```
int shmdt(const void *shmaddr);
```
* 功能: 将共享内存段与当前进程脱离
* 参数
* shmaddr: 由shmat所返回的指针
* 返回值:
* 成功返回0失败返回-1
* 注意: 将共享内存段与当前进程脱离不等于删除共享内存段
### 编程实现
```C
// 1.cpp
#include<stdio.h>
#include<stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
#include<errno.h>
typedef struct _Teacher
{
char name[64];
int age;
}Teacher;
int main(int argc, char *argv[])
{
int ret = 0;
int shmid;
//创建共享内存 ,相当于打开文件,文件不存在则创建
shmid = shmget(0x2234, sizeof(Teacher), IPC_CREAT | 0666);
if (shmid == -1)
{
perror("shmget err");
return errno;
}
printf("shmid:%d \n", shmid);
Teacher *p = NULL;
//将共享内存段连接到进程地址空间
p = shmat(shmid, NULL, 0);//第二个参数shmaddr为NULL核心自动选择一个地址
if (p == (void *)-1 )
{
perror("shmget err");
return errno;
}
strcpy(p->name, "aaaa");
p->age = 33;
//将共享内存段与当前进程脱离
shmdt(p);
printf("键入1 删除共享内存,其他不删除\n");
int num;
scanf("%d", &num);
if (num == 1)
{
//用于控制共享内存
ret = shmctl(shmid, IPC_RMID, NULL);//IPC_RMID为删除内存段
if (ret < 0)
{
perror("rmerrr\n");
}
}
return 0;
}
// 2.cpp
#include<stdio.h>
#include<stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include<string.h>
#include<errno.h>
typedef struct _Teacher
{
char name[64];
int age;
}Teacher;
int main(int argc, char *argv[])
{
int ret = 0;
int shmid;
//shmid = shmget(0x2234, sizeof(Teacher), IPC_CREAT |IPC_EXCL | 0666);
//打开获取共享内存
shmid = shmget(0x2234, 0, 0);
if (shmid == -1)
{
perror("shmget err");
return errno;
}
printf("shmid:%d \n", shmid);
Teacher *p = NULL;
//将共享内存段连接到进程地址空间
p = shmat(shmid, NULL, 0);
if (p == (void *)-1 )
{
perror("shmget err");
return errno;
}
printf("name:%s\n", p->name);
printf("age:%d \n", p->age);
//将共享内存段与当前进程脱离
shmdt(p);
printf("键入1 程序暂停,其他退出\n");
int num;
scanf("%d", &num);
if (num == 1)
{
pause();
}
return 0;
}
```
## 4 管道通信机制的实例——PIPE匿名管道通信
### 管道定义
* **“管道”** 是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名 pipe 文件,基于文件的通信机制。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道;而接受管道输出的接收进程(即读进程),则从管道中接收(读)数据。由于发送进程和接收进程是利用管道进行通信的,故又称为管道通信。
* 有部分博客说。管道通信也属于消息通信中的直接通信的一种。
### 管道实现
* 管道是通过调用 pipe 函数创建的fd[0] 用于读fd[1] 用于写。
```c
#include <unistd.h>
int pipe(int fd[2]);
```
* 它具有以下限制:
- 只支持半双工通信(单向交替传输);
- 只能在父子进程或者兄弟进程中使用。
![](image/2021-03-30-08-58-55.png)
### 编程实现
```C
```
## 5 管道通信机制的实例——FIFO命名管道通信
* 也称为命名管道,去除了管道只能在父子进程中使用的限制。
```c
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
```
* FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。
![](image/2021-03-30-08-59-57.png)
### 编程实现
```C
```
## 6 高级管道通信
### 编程实现
```C
```
## 7 消息队列通信
### 定义
@@ -86,21 +421,11 @@ Receive(Sendermessage) 接收 Sender 发来的消息;
### 消息传递系统中实现的若干问题。
1. 通信链路。为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路(communication link)。单向通信链路、双向通信链路。点对点通信链路、多点通信链路。
1. 通信链路。为使在发送进程和接收进程之间能进行通信,必须在两者之间建立一条通信链路(communication link)。单向通信链路、双向通信链路(半双工、全双工)。点对点通信链路、多点通信链路。
2. 消息的格式.在消息传递系统中所传递的消息,必须具有一定的消息格式。
3. 进程同步方式。在进程之间进行通信时,同样需要有进程同步机制,以使诸进程间能协调通信。不论是发送进程,还是接收进程,在完成消息的发送或接收后,都存在两种可能性,继续或阻塞。
1. 发送进程阻塞,接收进程阻塞。这种情况主要用于进程之间紧密同步(tightsynchronization),发送进程和接收进程之间无缓冲时。这两个进程平时都处于阻塞状态,直到有消息传递时。这种同步方式称为汇合(rendezrous)。
2. 发送进程不阻塞,接收进程阻塞。这是一种应用最广的进程同步方式。平时,发送进程不阻塞,因而它可以尽快地把一个或多个消息发送给多个目标; 而接收进程平时则处于阻塞状态,直到发送进程发来消息时才被唤醒。在服务器上通常都设置了多个服务进程,它们分别用于提供不同的服务,处于阻塞收状态。
3. 发送进程和接收进程均不阻塞。这也是一种较常见的进程同步形式。平时,发送进程和接收进程都在忙于自己的事情,仅当发生某事件使它无法继续运行时,才把自己阻塞起来等待。在发送进程和接收进程之间联系着一个消息队列时,该消息队列最多能接纳 n 个消息,这样,发送进程便可以连续地向消息队列中发送消息而不必等待;接收进程也可以连续地从消息队列中取得消息,也不必等待。
## 4 消息传递通信机制的实例——IPC消息队列通信
### 实现方式
* 属于消息通信的直接通信方式。
![](image/2021-03-30-14-44-16.png)
* 消息缓冲队列通信机制中的数据结构消息缓冲区在消息缓冲队列通信方式中主要利用的数据结构是消息缓冲区PCB 中有关通信的数据项。
@@ -113,45 +438,194 @@ Receive(Sendermessage) 接收 Sender 发来的消息;
- 避免了 FIFO 的同步阻塞问题,不需要进程自己提供同步方法;
- 读进程可以根据消息类型有选择地接收消息,而不像 FIFO 那样只能默认地接收。
### 编程实现
```C
```
## 8 网络通信——Socket套接字
## 5 管道通信机制的实例——PIPE管道通信
### 管道定义
> 参考文献
> * [https://blog.csdn.net/u014465934/article/details/81350883](https://blog.csdn.net/u014465934/article/details/81350883)
> * [https://www.cnblogs.com/zzyoucan/p/3801295.html](https://www.cnblogs.com/zzyoucan/p/3801295.html)
### socket概念
* 用于不同机器间的进程通信。
* Socket是应用层与TCP/IP协议族通信的中间软件抽象层它是一组接口。在设计模式中Socket其实就是一个门面模式它把复杂的TCP/IP协议族隐藏在Socket接口后面对用户来说一组简单的接口就是全部让Socket去组织数据以符合指定的协议。
* Socket主要有流式套接字、数据报套接字和原始套接字三种其中流式套接字表示传输层使用TCP协议提供面向连接的可靠的传输服务。数据报套接字表示传输层使用UDP协议提供面向连接的可靠的传输服务。流式和数据报就是指数据在传输时的形式以字节流形式还是以消息结构体将消息打包封装为一种结构
* **“管道”** 是指用于连接一个读进程和一个写进程以实现它们之间通信的一个共享文件,又名 pipe 文件,基于文件的通信机制。向管道(共享文件)提供输入的发送进程(即写进程),以字符流形式将大量的数据送入管道;而接受管道输出的接收进程(即读进程),则从管道中接收(读)数据。由于发送进程和接收进程是利用管道进行通信的,故又称为管道通信。
* 有部分博客说。管道通信也属于消息通信中的直接通信的一种。
### 管道实现
* 管道是通过调用 pipe 函数创建的fd[0] 用于读fd[1] 用于写。
### socket原理
```c
#include <unistd.h>
int pipe(int fd[2]);
Socket通信过程先从服务器端说起。服务器端先初始化Socket然后与端口绑定(bind),对端口进行监听(listen)调用accept阻塞等待客户端连接。在这时如果有个客户端初始化一个Socket然后连接服务器(connect),如果连接成功,这时客户端与服务器端的连接就建立了。客户端发送数据请求,服务器端接收请求并处理请求,然后把回应数据发送给客户端,客户端读取数据,最后关闭连接,一次交互结束。
![](image/2021-04-06-10-44-31.png)
### 实现方式
```
int socket(int domain, int type, int protocol);
```
* socket函数对应于普通文件的打开操作。普通文件的打开操作返回一个文件描述字而socket()用于创建一个socket描述符socket descriptor它唯一标识一个socket。这个socket描述字跟文件描述字一样后续的操作都有用到它把它作为参数通过它来进行一些读写操作。
* 参数描述:
* domain即协议域/族协议族决定了socket的地址类型在通信中必须采用对应的地址。
* type指定socket类型。
* protocol指定协议。
```
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
```
* bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。
* 参数描述:
* sockfd即socket描述字它是通过socket()函数创建了唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
* addr一个const struct sockaddr *指针指向要绑定给sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同。
* addrlen对应的是地址的长度。
```
int listen(int sockfd, int backlog);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
```
* 如果作为一个服务器在调用socket()、bind()之后就会调用listen()来监听这个socket如果客户端这时调用connect()发出连接请求,服务器端就会接收到这个请求。
* listen函数的第一个参数即为要监听的socket描述字第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的listen函数将socket变为被动类型的等待客户的连接请求。
* connect函数的第一个参数即为客户端的socket描述字第二参数为服务器的socket地址第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
```
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
```
* TCP服务器端依次调用socket()、bind()、listen()之后就会监听指定的socket地址了。TCP客户端依次调用socket()、connect()之后就想TCP服务器发送了一个连接请求。TCP服务器监听到这个请求之后就会调用accept()函数取接收请求这样连接就建立好了。之后就可以开始网络I/O操作了即类同于普通文件的读写I/O操作。
* accept函数的第一个参数为服务器的socket描述字第二个参数为指向struct sockaddr *的指针用于返回客户端的协议地址第三个参数为协议地址的长度。如果accpet成功那么其返回值是由内核自动生成的一个全新的描述字代表与返回客户的TCP连接。
* 注意accept的第一个参数为服务器的socket描述字是服务器开始调用socket()函数生成的称为监听socket描述字而accept函数返回的是已连接的socket描述字。一个服务器通常通常仅仅只创建一个监听socket描述字它在该服务器的生命周期内一直存在。内核为每个由服务器进程接受的客户连接创建了一个已连接socket描述字当服务器完成了对某个客户的服务相应的已连接socket描述字就被关闭。
```
read()/write()
recv()/send()
readv()/writev()
recvmsg()/sendmsg()
recvfrom()/sendto()
```
* 我推荐使用recvmsg()/sendmsg()函数这两个函数是最通用的I/O函数实际上可以把上面的其它函数都替换成这两个函数。
* read函数是负责从fd中读取内容.当读成功时read返回实际所读的字节数如果返回的值是0表示已经读到文件的结束了小于0表示出现了错误。如果错误为EINTR说明读是由中断引起的如果是ECONNREST表示网络连接出了问题。
* write函数将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节 数。失败时返回-1并设置errno变量。在网络程序中当我们向套接字文件描述符写时有俩种可能。1)write的返回值大于0表示写了部分或者是 全部的数据。2)返回的值小于0此时出现了错误。我们要根据错误类型来处理。如果错误为EINTR表示在写的时候出现了中断错误。如果为EPIPE表示 网络连接出现了问题(对方已经关闭了连接)。
```
int close(int fd);
```
* 在服务器与客户端建立连接之后会进行一些读写操作完成了读写操作就要关闭相应的socket描述字好比操作完打开的文件要调用fclose关闭打开的文件。
### 编程实现
```C
// server
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int listenfd, connfd;
struct sockaddr_in servaddr;
char buff[4096];
int n;
if( (listenfd = socket(AF_INET, SOCK_STREAM, 0)) == -1 ){
printf("create socket error: %s(errno: %d)/n",strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
if( bind(listenfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) == -1){
printf("bind socket error: %s(errno: %d)/n",strerror(errno),errno);
exit(0);
}
if( listen(listenfd, 10) == -1){
printf("listen socket error: %s(errno: %d)/n",strerror(errno),errno);
exit(0);
}
printf("======waiting for client's request======/n");
while(1){
if( (connfd = accept(listenfd, (struct sockaddr*)NULL, NULL)) == -1){
printf("accept socket error: %s(errno: %d)",strerror(errno),errno);
continue;
}
n = recv(connfd, buff, MAXLINE, 0);
buff[n] = '/0';
printf("recv msg from client: %s/n", buff);
close(connfd);
}
close(listenfd);
}
// client
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#define MAXLINE 4096
int main(int argc, char** argv)
{
int sockfd, n;
char recvline[4096], sendline[4096];
struct sockaddr_in servaddr;
if( argc != 2){
printf("usage: ./client <ipaddress>/n");
exit(0);
}
if( (sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0){
printf("create socket error: %s(errno: %d)/n", strerror(errno),errno);
exit(0);
}
memset(&servaddr, 0, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(6666);
if( inet_pton(AF_INET, argv[1], &servaddr.sin_addr) <= 0){
printf("inet_pton error for %s/n",argv[1]);
exit(0);
}
if( connect(sockfd, (struct sockaddr*)&servaddr, sizeof(servaddr)) < 0){
printf("connect error: %s(errno: %d)/n",strerror(errno),errno);
exit(0);
}
printf("send msg to server: /n");
fgets(sendline, 4096, stdin);
if( send(sockfd, sendline, strlen(sendline), 0) < 0)
{
printf("send msg error: %s(errno: %d)/n", strerror(errno), errno);
exit(0);
}
close(sockfd);
exit(0);
}
```
* 它具有以下限制:
## 9 进程通信的同步方式
- 只支持半双工通信(单向交替传输);
- 只能在父子进程或者兄弟进程中使用。
### 同步异步、阻塞非阻塞
![](image/2021-03-30-08-58-55.png)
进程同步方式。在进程之间进行通信时,同样需要有进程同步机制,以使诸进程间能协调通信。不论是发送进程,还是接收进程,在完成消息的发送或接收后,都存在两种可能性,继续或阻塞。
1. 发送进程阻塞,接收进程阻塞。这种情况主要用于进程之间紧密同步(tightsynchronization),发送进程和接收进程之间无缓冲时。这两个进程平时都处于阻塞状态,直到有消息传递时。这种同步方式称为汇合(rendezrous)。
2. 发送进程不阻塞,接收进程阻塞。这是一种应用最广的进程同步方式。平时,发送进程不阻塞,因而它可以尽快地把一个或多个消息发送给多个目标; 而接收进程平时则处于阻塞状态,直到发送进程发来消息时才被唤醒。在服务器上通常都设置了多个服务进程,它们分别用于提供不同的服务,处于阻塞收状态。
3. 发送进程和接收进程均不阻塞。这也是一种较常见的进程同步形式。平时,发送进程和接收进程都在忙于自己的事情,仅当发生某事件使它无法继续运行时,才把自己阻塞起来等待。在发送进程和接收进程之间联系着一个消息队列时,该消息队列最多能接纳 n 个消息,这样,发送进程便可以连续地向消息队列中发送消息而不必等待;接收进程也可以连续地从消息队列中取得消息,也不必等待。
### 本地进程的实现方式
## 6 管道通信机制的实例——FIFO命名管道通信
* 也称为命名管道,去除了管道只能在父子进程中使用的限制。
```c
#include <sys/stat.h>
int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
```
* FIFO 常用于客户-服务器应用程序中FIFO 用作汇聚点,在客户进程和服务器进程之间传递数据。
![](image/2021-03-30-08-59-57.png)
## 7 网络通信机制——Socket套接字
* 与其它通信机制不同的是,它可用于不同机器间的进程通信。
### 网络进程的实现方式

Binary file not shown.

After

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 64 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 108 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 91 KiB

View File

@@ -29,6 +29,10 @@
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
```
![](image/2021-03-30-22-02-32.png)
![](image/2021-04-06-11-43-08.png)
### 非阻塞式 I/O
* 应用进程执行系统调用之后,内核返回一个错误码。应用进程可以继续执行,但是需要不断的执行系统调用来获知 I/O 是否完成这种方式称为轮询polling
@@ -37,11 +41,11 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
![](image/2021-03-30-22-02-42.png)
### I/O 复用
### I/O 复用事件驱动IO
* 使用 select 或者 poll 等待数据,并且可以等待多个套接字中的任何一个变为可读。这一过程会被阻塞,当某一个套接字可读时返回,之后再使用 recvfrom 把数据从内核复制到进程中。
* 它可以让单个进程具有处理多个 I/O 事件的能力。又被称为 Event Driven I/O即事件驱动 I/O。
* 主要是select、poll、epoll对一个IO端口两次调用两次返回比阻塞IO并没有什么优越性关键是能实现同时对**多个IO端口进行监听**
* I/O复用模型会用到select、poll、epoll函数这几个函数也会使进程阻塞但是和阻塞I/O所不同的的这两个函数可以同时阻塞多个I/O操作。而且可以同时对多个读操作多个写操作的I/O函数进行检测直到有数据可读或可写时才真正调用I/O操作函数。当某一个套接字可读时返回之后再使用 recvfrom 把数据从内核复制到进程中。
* 它可以让**单个进程具有处理多个 I/O 事件的能力**。又被称为 Event Driven I/O**事件驱动 I/O**
* 如果一个 Web 服务器没有 I/O 复用,那么每一个 Socket 连接都需要创建一个线程去处理。如果同时有几万个连接那么就需要创建相同数量的线程。相比于多进程和多线程技术I/O 复用不需要进程线程创建和切换的开销,系统开销更小。
@@ -55,6 +59,8 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
![](image/2021-03-30-22-03-00.png)
![](image/2021-04-06-11-44-30.png)
### 异步 I/O
* 应用进程执行 aio_read 系统调用会立即返回,应用进程可以继续执行,不会被阻塞,内核会在所有操作完成之后向应用进程发送信号。
@@ -63,16 +69,14 @@ ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *
![](image/2021-03-30-22-03-09.png)
### 五大 I/O 模型比较
![](image/2021-04-06-11-58-05.png)
### 五大 IO 模型比较
- 同步 I/O将数据从内核缓冲区复制到应用进程缓冲区的阶段第二阶段应用进程会阻塞。
- 异步 I/O第二阶段应用进程不会阻塞。
- 同步IO将数据从内核缓冲区复制到应用进程缓冲区的阶段第二阶段应用进程会阻塞。包括阻塞式 IO、非阻塞式 IO、IO 复用和信号驱动 IO 。
- 异步IO第一阶段和第二阶段应用进程不会阻塞。
* 同步 I/O 包括阻塞式 I/O、非阻塞式 I/O、I/O 复用和信号驱动 I/O ,它们的主要区别在第一阶段。
* 非阻塞式 I/O 、信号驱动 I/O 和异步 I/O 在第一阶段不会阻塞。
* 同步IO的主要区别在第一个阶段。阻塞式IO、IO复用第一阶段会阻塞。非阻塞式IO、信号驱动IO在第一阶段不会阻塞
![](image/2021-03-30-22-04-01.png)
## 2 I/O 复用
* select/poll/epoll 都是 I/O 多路复用的具体实现select 出现的最早,之后是 poll再是 epoll。

View File

@@ -0,0 +1,53 @@
# 同步异步、阻塞非阻塞、网络编程与并行编程
> 参考文献
> * [https://www.zhihu.com/question/19732473/answer/14413599](https://www.zhihu.com/question/19732473/answer/14413599)
> * [https://www.cnblogs.com/shiysin/articles/10689761.html](https://www.cnblogs.com/shiysin/articles/10689761.html)
> * [https://blog.csdn.net/jolin678/article/details/49611587](https://blog.csdn.net/jolin678/article/details/49611587)
## 1 同步异步、阻塞非阻塞
### nodejs中相关定义
* **同步式I/OSynchronous I/O或阻塞式I /O Blocking I/O**。线程/进程在执行中如果遇到磁盘读写或网络通信统称为I/O 操作),通常要耗费较长的时间,这时 操作系统会剥夺这个线程/进程的CPU 控制权,使其暂停执行,同时将资源让给其他的工作线程,这种线程调度方式称为阻塞 。当I/O 操作完毕时,操作系统将 这个线程的阻塞状态解除恢复其对CPU的控制权令其继续执行。
* **异步式 I/O Asynchronous I/O或非阻塞式I/O Non-blocking I/O**。则针对所有I/O 操作不采用阻塞的策略。当线程 遇到I/O 操作时不会以阻塞的方式等待I/O 操作的完成或数据的返回而只是将I/O 请求发送给操作系统继续执行下一条语句。当操作系统完成I /O 操作时以事件的形式通知执行I/O 操作的线程线程会在特定时候处理这个事件。为了处理异步I/O线程必须有事件循环不断地检查有没有未处 理的事件,依次予以处理。
* **并发方式**。阻塞模式下,一个线程只能处理一项任务,要想提高吞吐量必须通过**多线程**。而非阻塞模式下一个线程永远在执行计算操作这个线程所使用的CPU 核心利用率永远是100%**I/O 以事件的方式通知**。在阻塞模式下多线程往往能提高系统吞吐量因为一个线程阻塞时还有其他线程在工作多线程可以让CPU 资源不被阻塞中的线程浪费。而在非阻塞模式下线程不会被I/O 阻塞永远在利用CPU。多线程带来的好处仅仅是在多核CPU 的情况下利用更多的核而Node.js的单线程也能带来同样的好处。这就是为什么Node.js 使用了单线程、非阻塞的**事件编程模式**。
### 同步异步
* 同步与异步关注的是消息通信机制。是**两个进程之间的关系**。
* 同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
* 异步,当一个异步过程调用发出后,调用就直接返回,调用者不会立刻得到结果。被调用者通过状态、通知和回调函数来通知调用者。
### 阻塞非阻塞
* 阻塞和非阻塞关注的是程序在等待调用结果时的状态。是**一个进程的本身的状态**。
* 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
* 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
## 3 实现方式
### 同步阻塞通信
* java socket网络编程都是阻塞通信。接收端在接受到数据之前一直处于阻塞状态。通过多线程实现并行编程。
* 在linux下select/poll/epoll也是同步阻塞IO也被称为事件IO同时堵塞多个IO。不需要多线程当某个IO可用时发送IO事件使用事件驱动的IO实现并行编程。
* 普通B/S模式同步提交请求->等待服务器处理->处理完毕返回 这个期间客户端浏览器不能干任何事。也是通过开启多个浏览器线程实现并行编程。
### 异步非阻塞通信
* node.js是单线程的异步非阻塞(事件循环检测通知的机制)
* windows完全端口是机器上有几个cpu创建几个线程的异步非阻塞机制多线程池 + 事件触发机制)
* linux epoll也是创建合理的线程池的异步非阻塞机制线程池+记录活跃socket + 边缘事件触发机制 + mmap文件映射内存减少复制开销)。
* ajax请求异步: 请求通过事件触发->服务器处理(这是浏览器仍然可以作其他事情)->处理完毕
### 同步非阻塞
* poll机制。轮训IO信号虽然没有阻塞但是在循环中一致确认IO是否完成。效率很低。
### 实现并行编程的几种方法
1. java 多线程
2. nodejs 异步回调
3. linux IO复用。epoll事件回调

View File

@@ -1,27 +0,0 @@
# 同步异步、阻塞和非阻塞
> 参考文献
> * [https://www.zhihu.com/question/19732473/answer/14413599](https://www.zhihu.com/question/19732473/answer/14413599)
> * [https://www.cnblogs.com/shiysin/articles/10689761.html](https://www.cnblogs.com/shiysin/articles/10689761.html)
## 1 同步异步
* 同步与异步关注的是消息通信机制。是**两个进程之间的关系**。
* 同步,就是在发出一个调用时,在没有得到结果之前,该调用就不返回。但是一旦调用返回,就得到返回值了。
* 异步则是相反,调用在发出之后,这个调用就直接返回了,所以没有返回结果。换句话说,当一个异步过程调用发出后,调用者不会立刻得到结果。而是在调用发出后,被调用者通过状态、通知来通知调用者,或通过回调函数处理这个调用。
## 2 阻塞与非阻塞
* 阻塞和非阻塞关注的是程序在等待调用结果时的状态。是**一个进程的本身的状态**。
* 阻塞调用是指调用结果返回之前,当前线程会被挂起。调用线程只有在得到结果之后才会返回。
* 非阻塞调用指在不能立刻得到结果之前,该调用不会阻塞当前线程。
## 3 通信方式及实现
### 同步阻塞通信
### 异步阻塞通信
### 异步非阻塞通信

View File

@@ -0,0 +1,29 @@
## 1 web开发
### web框架
用来进行web开发的前端后端脚本的框架包括大量集成的方法通过框架接口进行调用。包括spring spring boot 等框架。
### web服务器web容器
web开发的网络通信模块在Java中以web容器Tomcatjetty等。**使用网络编程封装了http网络通信模块**解析http的请求并发送http的请求。Web服务器的种类有1、Apache 2、IIS 3、Nginx 4、Tomcat 5、Lighttpd 6、Zeus等。
### web开发
主要是指利用web框架在web容器的基础上快速搭建web应用。
## 2 网络编程
利用操作系统提供的网络通信模块实现通信。包括socket通信tcp/ip udp通信等。Java socket 模块和netty框架。
## 3 C++
### C++网络编程
对于C++ 来说主流的网络编程框架是linux/unix和Windows提供的网络通信接口。跨平台的是boost提供的asio网络编程框架。用来实现各种形式的网络通信。
### C++web开发
对于C++来说很少实现web服务器web容器。常见的有moogse,sougou两个web服务器。
对于C++ 来说web开发框架没有好吧。基本用C++只有前后端完全分离的web开发过程。前端mfc,qt等或者html css js 等。后端使用C++实现高性能的并行服务。