热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

<Linux进程通信之管道>——《Linux》

目录一、进程通信1.进程间通信介绍2.进程间通信目的3.进程间通信发展4.进程间通信分类二、管道1.什么是管道2.匿名管道3.用fork来共享管道原理4.站在文

目录

一、进程通信

1.进程间通信介绍

2.进程间通信目的

3.进程间通信发展

4.进程间通信分类

二、管道

1.什么是管道

2.匿名管道

3.用fork来共享管道原理

4.站在文件描述符角度-深度理解管道​编辑

5.编程模拟实现父子进程在管道读写通信​编辑

6.进程控制:

6.1 父进程控制单个子进程

6.2父进程控制批量子进程

 6.3初识负载均衡:​

7.站在内核角度-管道本质​

 7.1例:在minishell中添加管道的实现:

8.管道读写规则

9.管道特点

10.管道的特征总结:

11.命名管道

11.1 创建一个命名管道

11.2 匿名管道与命名管道的区别

11.3 命名管道的打开规则

11.4 例:用命名管道实现文件拷贝

11.5 例:用命名管道实现server&client通信(C语言实现)

12. 用命名管道实现server&client通信(C++实现)

13.模拟实现两个互不相干的进程进行通信:

后记:●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!

                                                                           ——By 作者:新晓·故知




一、进程通信


1.进程间通信介绍


通信之前,需要让不同的进程(进程具有独立性)看到同一份资源(文件、内存块等)

我们要学的进程间通信,不是告诉我们如何通信,而是两个进程如何先看到同一份资源!

资源的不同,决定了不同种类的通信方式!



2.进程间通信目的


数据传输:一个进程需要将它的数据发送给另一个进程
资源共享:多个进程之间共享同样的资源。
通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。
进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变。


3.进程间通信发展


管道
System V进程间通信
POSIX进程间通信


4.进程间通信分类


管道
匿名管道pipe
命名管道
System V IPC
System V 消息队列
System V 共享内存
System V 信号量
POSIX IPC
消息队列
共享内存
信号量
互斥量
条件变量
读写锁


二、管道


1.什么是管道


管道是Unix中最古老的进程间通信的形式。
我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”
管道:提供共享资源的一种手段!

2.匿名管道

#include
功能创建一无名管道
原型: int pipe(int fd[2]);
参数: fd:文件描述符数组,其中fd[0]表示读端, fd[1]表示写端
返回值:成功返回0,失败返回错误代码

实例代码

例子:从键盘读取数据,写入管道,读取管道,写到屏幕
#include
#include
#include
#include
int main( void )
{int fds[2];char buf[100];int len;if ( pipe(fds) == -1 )perror("make pipe"),exit(1);// read from stdinwhile ( fgets(buf, 100, stdin) ) {len = strlen(buf);// write into pipeif ( write(fds[1], buf, len) != len ) {perror("write to pipe");break;}memset(buf, 0x00, sizeof(buf));// read from pipeif ( (len=read(fds[0], buf, 100)) == -1 ) {perror("read from pipe");break;}// write to stdoutif ( write(1, buf, len) != len ) {perror("write to stdout");break;}}
}

3.用fork来共享管道原理

4.站在文件描述符角度-深度理解管道

 5.编程模拟实现父子进程在管道读写通信

mypipe:pipe.ccg++ -o $@ $^ -std=c++11
.PHONY:clean
clean:rm -f mypipe

 

#include
#include
#include
using namespace std;int main()
{int pipefd[2] = {0};if (pipe(pipefd) != 0){cerr <<"pipe" <

 模拟父子进程在管道读写通信:

#include
#include
#include
#include
#include
#include
using namespace std;int main()
{//1.创建管道int pipefd[2] = {0};if (pipe(pipefd) != 0){cerr <<"pipe" <0){//读取成功buffer[s] = &#39;\0&#39;;cout<<"子进程收到消息,内容是:"<嘴巴 ——>读//1——>笔 ——>写pid_t res = waitpid(id,nullptr,0);if(res > 0){cout<<"等待子进程成功!"<}

(1)当父进程没有写入数据的时候,子进程在等!所以,父进程写入之后,子进程才能read(会返回) 到数据,子进程打印读取数据要以父进程的节奏为主!

(2)父进程和子进程读写的时候,是有一定的顺序性的!

在管道这里,管道是可以被写满的。查看管道信息指令:ulimit -a

管道内部,如果没有数据,reader就必须阻塞等待(read),等管道有数据。

管道内部,如果数据被写满,writer就必须阻塞等待(write),等待管道中有空间。

以上,具有顺序性是因为pipe内部自带访问控制机制!(同步和互斥机制)

(3)我们之前所学的父、子进程各自printf(向显示器写入,显示器也是文件)的时候,会有顺序吗?

没有。这个没有访问控制机制!

(4)阻塞等待的本质:

将当前进程的task_struct放入等待队列中!

6.进程控制:

控制进程根据需求做出响应。

6.1 父进程控制单个子进程

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include using namespace std;typedef void (*functor)(); // 函数指针
vector functors; // 方法集合
// for debug
unordered_map info;void f1()
{cout <<"这是一个处理日志的任务,执行的进程 ID [" <}void f2()
{cout <<"这是一个备份任务,执行的进程 ID [" <}
void f3()
{cout <<"这是一个处理网络连接的任务,执行的进程 ID [" <}void loadFunctor()
{info.insert({functors.size(), "处理日志的任务"});functors.push_back(f1);info.insert({functors.size(), "备份数据任务"});functors.push_back(f2);info.insert({functors.size(), "处理网络连接的任务"});functors.push_back(f3);
}int main()
{// 0.加载任务列表loadFunctor();// 1.创建管道int pipefd[2] = {0};if (pipe(pipefd) != 0){cerr <<"pipe error" <}

6.2父进程控制批量子进程

进程如何控制批量进程呢?

这里进程池机制。那么将给哪一个进程指派?给它指派什么任务呢?通过什么指派呢?(通过管道指派)

shell脚本指令:

while :; do ps axj | head -1; ps ajx |grep mypipe | grep -v grep; sleep 1; done

 6.3初识负载均衡:

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include using namespace std;typedef void (*functor)(); // 函数指针
vector functors; // 方法集合
// for debug
unordered_map info;void f1()
{cout <<"这是一个处理日志的任务,执行的进程 ID [" <}void f2()
{cout <<"这是一个备份任务,执行的进程 ID [" <}
void f3()
{cout <<"这是一个处理网络连接的任务,执行的进程 ID [" <}void loadFunctor()
{info.insert({functors.size(), "处理日志的任务"});functors.push_back(f1);info.insert({functors.size(), "备份数据任务"});functors.push_back(f2);info.insert({functors.size(), "处理网络连接的任务"});functors.push_back(f3);
}typedef pair elem;
//第一个int32_t:进程pid,第二个int32_t:该进程对应的管道写端fd
vector assignMap;
int processNum = 5;void work(int blockFd)
{cout <<"进程[" <}
//[子进程的pid,子进程的管道fd]
void balancesendTask(const vector &processFds)
{srand((long long)time(nullptr));while(true){sleep(1);//选择一个进程,选择进程是随机的,没有压着一个进程给任务//较为均匀的将任务给所有的子进程——负载均衡uint32_t pick = rand()% processFds.size();//选择一个任务uint32_t task = rand()%functors.size();//把任务给一个指定的进程write(processFds[pick].second,&task,sizeof(task));//打印对应的提示信息cout <<"父进程指派任务——>" <}
int main()
{loadFunctor();vector assignMap;// 创建processNum个进程for(int i =0; i 0){cout <<"Wait for: pid=" <}

7.站在内核角度-管道本质

所以,看待管道,就如同看待文件一样!管道的使用和文件一致,迎合了“Linux一切皆文件思想

#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \perror(m); \exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{int pipefd[2];if (pipe(pipefd) == -1)ERR_EXIT("pipe error");pid_t pid;pid = fork();if (pid == -1)ERR_EXIT("fork error");if (pid == 0) {close(pipefd[0]);write(pipefd[1], "hello", 5);close(pipefd[1]);exit(EXIT_SUCCESS);}close(pipefd[1]);char buf[10] = {0};read(pipefd[0], buf, 10);printf("buf=%s\n", buf);return 0;
}

 7.1例:在minishell中添加管道的实现:

# include
# include
# include
# include
# include
# define MAX_CMD 1024
char command[MAX_CMD];
int do_face()
{memset(command, 0x00, MAX_CMD);printf("minishell$ ");fflush(stdout);if (scanf("%[^\n]%*c", command) == 0) {getchar();return -1; }   return 0;
}
char **do_parse(char *buff)
{int argc = 0;static char *argv[32];char *ptr = buff;while(*ptr != &#39;\0&#39;) {if (!isspace(*ptr)) {argv[argc++] = ptr;while((!isspace(*ptr)) && (*ptr) != &#39;\0&#39;) {ptr++;}continue;}*ptr = &#39;\0&#39;;ptr++;}argv[argc] = NULL;return argv;
}
int do_redirect(char *buff)
{char *ptr = buff, *file = NULL;int type = 0, fd, redirect_type = -1;while(*ptr != &#39;\0&#39;) {if (*ptr == &#39;>&#39;) {*ptr++ = &#39;\0&#39;;redirect_type++;if (*ptr == &#39;>&#39;) {*ptr++ = &#39;\0&#39;;redirect_type++;}while(isspace(*ptr)) {ptr++;}file = ptr;while((!isspace(*ptr)) && *ptr != &#39;\0&#39;) {ptr++;}*ptr = &#39;\0&#39;;if (redirect_type == 0) {fd = open(file, O_CREAT|O_TRUNC|O_WRONLY, 0664);}else {fd = open(file, O_CREAT|O_APPEND|O_WRONLY, 0664);}dup2(fd, 1);}ptr++;}return 0;
}
int do_command(char *buff)
{int pipe_num = 0, i;char *ptr = buff;int pipefd[32][2] = {{-1}};int pid = -1;pipe_command[pipe_num] = ptr;while(*ptr != &#39;\0&#39;) {if (*ptr == &#39;|&#39;) {pipe_num++;*ptr++ = &#39;\0&#39;;pipe_command[pipe_num] = ptr;continue;}ptr++;}pipe_command[pipe_num + 1] = NULL;return pipe_num;
}
int do_pipe(int pipe_num)
{int pid = 0, i;int pipefd[10][2] = {{0}};char **argv = {NULL};for (i = 0; i <= pipe_num; i++) {pipe(pipefd[i]);}for (i = 0; i <= pipe_num; i++) {pid = fork();if (pid == 0) {do_redirect(pipe_command[i]);argv = do_parse(pipe_command[i]);if (i != 0) {close(pipefd[i][1]);dup2(pipefd[i][0], 0);}if (i != pipe_num) {close(pipefd[i + 1][0]);dup2(pipefd[i + 1][1], 1);}execvp(argv[0], argv);}else {close(pipefd[i][0]);close(pipefd[i][1]);waitpid(pid, NULL, 0);}}return 0;
}
int main(int argc, char *argv[])
{           int num = 0; while(1) {  if (do_face() <0)continue;num = do_command(command);do_pipe(num);}return 0;
}

shell脚本:

ps ajx | head -1 && ps ajx | grep sleep

总结:

所谓的命令行中的“|”就是匿名管道!

8.管道读写规则

当没有数据可读时
  • O_NONBLOCK disableread调用阻塞,即进程暂停执行,一直等到有数据来到为止。
  • O_NONBLOCK enableread调用返回-1errno值为EAGAIN
当管道满的时候
  • O_NONBLOCK disable write调用阻塞,直到有进程读走数据
  • O_NONBLOCK enable:调用返回-1errno值为EAGAIN
  • 如果所有管道写端对应的文件描述符被关闭,则read返回0
  • 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
  • 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
  • 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。

9.管道特点

只能用于具有共同祖先的进程(具有亲缘关系的进程)之间进行通信;通常,一个管道由一个进程创建,然后该进程调用fork,此后父、子进程之间就可应用该管道。
管道提供流式服务
  • 一般而言,进程退出,管道释放,所以管道的生命周期随进程
  • 一般而言,内核会对管道操作进行同步与互斥
管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道

10.管道的特征总结:

1.管道只能用来进行具有血缘关系的进程之间,进行进程间通信,常用于父子进程通信。

2.管道只能单向通信(内核实现决定的)(是半双工的一种特殊情况)。

3.管道自带同步机制(pipe满,writer等待。pipe空,reader等待)  ——自带访问控制。

4.管道是面向字节流的。先写的字符,一定是先被读取,没有格式边界,需要用户自己来定义区分内容的边界,例如:[sizeof(uint32_t)]等。这里牵涉到网络TCP协议等知识。

5.管道的生命周期跟随进程:管道是文件,进程退出后,曾经打开的文件也会退出。

那么只能父子进程(或血缘)进程之间通信吗?毫不相干的进程进行通信可以吗?

答:可以。使用命名管道。

11.命名管道

管道应用的一个限制就是只能在具有共同祖先(具有亲缘关系)的进程间通信。
如果我们想在不相关的进程之间交换数据,可以使用FIFO文件来做这项工作,它经常被称为命名管道。
命名管道是一种特殊类型的文件

11.1 创建一个命名管道

命名管道可以从命令行上创建,命令行方法是使用下面这个命令:
命名管道也可以从程序里创建,相关函数有:
创建命名管道:
$ mkfifo filename
int mkfifo(const char *filename,mode_t mode);

int main(int argc, char *argv[])
{mkfifo("p2", 0644);return 0;
}

11.2 匿名管道与命名管道的区别

匿名管道由pipe函数创建并打开。
命名管道由mkfififo函数创建,打开用open
FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在它们创建与打开的方式不同,一但这些工作完成之后,它们具有相同的语义。

11.3 命名管道的打开规则

如果当前打开操作是为读而打开FIFO
  • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
  • O_NONBLOCK enable:立刻返回成功
如果当前打开操作是为写而打开FIFO
  • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
  • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO

11.4 例:用命名管道实现文件拷贝

读取文件,写入命名管道:

#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do \
{ \perror(m); \exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{mkfifo("tp", 0644);int infd;infd = open("abc", O_RDONLY);if (infd == -1) ERR_EXIT("open");int outfd;outfd = open("tp", O_WRONLY);if (outfd == -1) ERR_EXIT("open");char buf[1024];int n;while ((n=read(infd, buf, 1024))>0){write(outfd, buf, n);}close(infd);close(outfd);return 0;
}

读取管道,写入目标文件:

#include
#include
#include
#include
#include
#define ERR_EXIT(m) \do \{ \perror(m); \exit(EXIT_FAILURE); \} while(0)int main(int argc, char *argv[])
{int outfd;outfd = open("abc.bak", O_WRONLY | O_CREAT | O_TRUNC, 0644);if (outfd == -1) ERR_EXIT("open");int infd;infd = open("tp", O_RDONLY);if (outfd == -1)ERR_EXIT("open");char buf[1024];int n;while ((n=read(infd, buf, 1024))>0){write(outfd, buf, n);}close(infd);close(outfd);unlink("tp");return 0;
}

11.5 例:用命名管道实现server&client通信(C语言实现)

# ll
total 12
-rw-r--r--. 1 root root 46 Sep 18 22:37 clientPipe.c
-rw-r--r--. 1 root root 164 Sep 18 22:37 Makefile
-rw-r--r--. 1 root root 46 Sep 18 22:38 serverPipe.c
# cat Makefile
.PHONY:all
all:clientPipe serverPipe
clientPipe:clientPipe.cgcc -o $@ $^
serverPipe:serverPipe.cgcc -o $@ $^
.PHONY:clean
clean:rm -f clientPipe serverPipe

serverPipe.c

#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do{\perror(m);\exit(EXIT_FAILURE);\
}while(0)
int main()
{umask(0);if(mkfifo("mypipe", 0644) <0){ERR_EXIT("mkfifo");}int rfd = open("mypipe", O_RDONLY);if(rfd <0){ERR_EXIT("open");}char buf[1024];while(1){buf[0] = 0;printf("Please wait...\n");ssize_t s = read(rfd, buf, sizeof(buf)-1);if(s > 0 ){buf[s-1] = 0;printf("client say# %s\n", buf);}else if(s == 0){printf("client quit, exit now!\n");
exit(EXIT_SUCCESS);}else{ERR_EXIT("read");}}close(rfd);return 0;
}

clientPipe.c

#include
#include
#include
#include
#include
#include
#include
#define ERR_EXIT(m) \
do{\perror(m);\exit(EXIT_FAILURE);\
}while(0)
int main()
{int wfd = open("mypipe", O_WRONLY);if(wfd <0){ERR_EXIT("open");}char buf[1024];while(1){buf[0] = 0;printf("Please Enter# ");fflush(stdout);ssize_t s = read(0, buf, sizeof(buf)-1);if(s > 0 ){buf[s] = 0;write(wfd, buf, strlen(buf));}else if(s <= 0){ERR_EXIT("read");}}close(wfd);return 0;
}

结果:

12. 用命名管道实现server&client通信(C++实现)

 

 

在之前我们学习过,两个在内存中的进程在磁盘打开同一文件的情况。这其实就是命名管道的运用。

我们知道,进程间通信的本质是:不同的进程要看到同一份资源。

匿名管道:子进程继承父进程,找到同一个资源!

命名管道:通过一个fifo 文件—>有路径—>具有唯一性—>通过路径找到同一个资源! 

13.模拟实现两个互不相干的进程进行通信:

Makefile:

.PHONY:all
all:clientFifo serverFifoclientFifo:clientFifo.cppg++ -o $@ $^ -std=c++11
serverFifo:serverFifo.cppg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -rf clientFifo serverFifo

serverFifo:接收端行间距有空格是因为entel键。 

comm.h:

 

#pragma once#include
#include
#include
#include
#include
#include
#include
#include
#include #define IPC_PATH "./.fifo"

 makefile:

 

.PHONY:all
all:clientFifo serverFifoclientFifo:clientFifo.cppg++ -Wall -o $@ $^ -std=c++11
serverFifo:serverFifo.cppg++ -Wall -o $@ $^ -std=c++11.PHONY:clean
clean:rm -rf clientFifo serverFifo .fifo

serverFifo.cpp:

 

#include "comm.h"
using namespace std;// 读取
int main()
{// extern int errno;umask(0);if (mkfifo(IPC_PATH, 0600) != 0){cerr <<"mkfifo error" <#define NUM 1024char buffer[NUM];while (true){ssize_t s = read(pipeFd, buffer, sizeof(buffer) - 1);if (s > 0){buffer[s] = &#39;\0&#39;;cout <<"客户端——>服务器#" <}

clientFifo.cpp:

 

#include "comm.h"
using namespace std;// 写入
int main()
{int pipeFd = open(IPC_PATH, O_WRONLY);if (pipeFd <0){cerr <<"open:" <}



后记:
●由于作者水平有限,文章难免存在谬误之处,敬请读者斧正,俚语成篇,恳望指教!


                                                                           ——By 作者:新晓·故知

 


推荐阅读
  • 2012年9月12日优酷土豆校园招聘笔试题目解析与备考指南
    2012年9月12日,优酷土豆校园招聘笔试题目解析与备考指南。在选择题部分,有一道题目涉及中国人的血型分布情况,具体为A型30%、B型20%、O型40%、AB型10%。若需确保在随机选取的样本中,至少有一人为B型血的概率不低于90%,则需要选取的最少人数是多少?该问题不仅考察了概率统计的基本知识,还要求考生具备一定的逻辑推理能力。 ... [详细]
  • FastDFS Nginx 扩展模块的源代码解析与技术剖析
    FastDFS Nginx 扩展模块的源代码解析与技术剖析 ... [详细]
  • 本文详细探讨了Zebra路由软件中的线程机制及其实际应用。通过对Zebra线程模型的深入分析,揭示了其在高效处理网络路由任务中的关键作用。文章还介绍了线程同步与通信机制,以及如何通过优化线程管理提升系统性能。此外,结合具体应用场景,展示了Zebra线程机制在复杂网络环境下的优势和灵活性。 ... [详细]
  • 经过两天的努力,终于成功解决了半平面交模板题POJ3335的问题。原来是在`OnLeft`函数中漏掉了关键的等于号。通过这次训练,不仅加深了对半平面交算法的理解,还提升了调试和代码实现的能力。未来将继续深入研究计算几何的其他核心问题,进一步巩固和拓展相关知识。 ... [详细]
  • 具备括号和分数功能的高级四则运算计算器
    本研究基于C语言开发了一款支持括号和分数运算的高级四则运算计算器。该计算器通过模拟手算过程,对每个运算符进行优先级标记,并按优先级从高到低依次执行计算。其中,加减运算的优先级最低,为0。此外,该计算器还支持复杂的分数运算,能够处理包含括号的表达式,提高了计算的准确性和灵活性。 ... [详细]
  • 在C语言中,指针的高级应用及其实例分析具有重要意义。通过使用 `&` 符号可以获取变量的内存地址,而 `*` 符号则用于定义指针变量。例如,`int *p;` 定义了一个指向整型的指针变量 `p`。其中,`p` 代表指针变量本身,而 `*p` 则表示指针所指向的内存地址中的内容。此外,指针在不同函数中可以具有相同的变量名,但其作用域和生命周期会有所不同。指针的灵活运用能够有效提升程序的效率和可维护性。 ... [详细]
  • 在C语言程序开发中,调试和错误分析是确保代码正确性和效率的关键步骤。本文通过一个简单的递归函数示例,详细介绍了如何编写和调试C语言程序。具体而言,我们将创建一个名为 `factorial.c` 的文件,实现计算阶乘的功能,并通过逐步调试来分析和解决可能出现的错误。此外,文章还探讨了常见的调试工具和技术,如GDB和断点设置,以帮助开发者高效地定位和修复问题。 ... [详细]
  • 题目要求维护一个数列,并支持两种操作:一是查询操作,语法为QL,用于查询数列末尾L个数中的最大值;二是更新操作,用于修改数列中的某个元素。本文通过ST表(Sparse Table)优化查询效率,确保在O(1)时间内完成查询,同时保持较低的预处理时间复杂度。 ... [详细]
  • 无论是计算机专业学生还是非计算机专业的学习者,在掌握C语言的过程中可能会遇到诸多挑战,不清楚从何入手。为此,本文系统地梳理了2019年福建省C语言的核心知识点,并结合最新的技术进展进行了详细总结,旨在为初学者提供全面的学习指导。文章不仅涵盖了基础语法和数据结构,还深入探讨了指针、内存管理和算法优化等高级主题,帮助读者快速提升编程能力。 ... [详细]
  • 深入解析C语言中的动态规划算法:以背包问题为例
    本文深入探讨了C语言中动态规划算法的应用,以经典的背包问题为例进行详细解析。通过实例分析,展示了如何利用动态规划解决复杂优化问题,并提供了高效的代码实现方法。文章不仅涵盖了算法的基本原理,还讨论了其在实际编程中的应用技巧和优化策略,为读者提供了全面的理解和实践指导。 ... [详细]
  • 本文深入解析了Spring Cloud路由网关Zuul的核心功能及其典型应用场景。通过对方志朋老师教材的学习和实践,详细探讨了Zuul在微服务架构中的重要作用,包括请求路由、过滤器链管理以及服务动态扩展等关键特性。同时,结合实际案例,展示了Zuul在高并发和复杂业务场景下的应用优势,为读者提供了全面的技术参考。 ... [详细]
  • 深入解析 Java UTC 时间处理技术与应用 ... [详细]
  • 本文详细解析了高性能通信库 NanoMsg 的框架及其应用场景。其中,BUS模式支持多对多的简单通信方式,消息会传递给所有直接连接的节点。REQREP模式则适用于构建无状态的服务集群,用于处理用户的请求,每个请求都需要一个相应的响应。 ... [详细]
  • 在C语言中,定义一个包含学号、姓名和年龄的学生信息结构体,并遵循严格的命名规范。首先,初始化结构体变量的所有成员为默认值,然后将其学号设为88,姓名设为“liming”,年龄设为25。最后,在控制台上输出该结构体变量的详细信息,以验证数据的正确性。例如,使用 `typedef struct Student` 定义结构体类型。 ... [详细]
  • 本文详细介绍了使用C语言和C++实现的动态规划算法来解决数塔问题。通过具体的代码示例和算法解析,展示了如何高效地计算数塔的最大路径和。该方法不仅适用于数塔问题,还可应用于其他类似的组合优化问题。 ... [详细]
author-avatar
梧桐树信息科技
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有