当前位置: 首页 > news >正文

劳务公司网站建设做食品网站有哪些内容

劳务公司网站建设,做食品网站有哪些内容,php网站开发实例源码,上海小程序开发哪家好进程间通信 一、初识进程间通信1. 进程间通信概念2. 进程间通信分类 二、管道1. 管道概念2. 管道原理3. 匿名管道4. 匿名管道系统接口5. 管道的特性和情况6. 匿名管道的应用#xff08;1#xff09;命令行#xff08;2#xff09;进程池 7. 命名管道#xff08;1#xff… 进程间通信 一、初识进程间通信1. 进程间通信概念2. 进程间通信分类 二、管道1. 管道概念2. 管道原理3. 匿名管道4. 匿名管道系统接口5. 管道的特性和情况6. 匿名管道的应用1命令行2进程池 7. 命名管道1命名管道的系统接口2理解命名管道3使用命名管道 三、初识日志1. 理解日志2. 获取时间3. 可变参数的使用 一、初识进程间通信 1. 进程间通信概念 进程间通信是两个或者多个进程实现数据层面的交换。但是由于进程间存在独立性所以导致进程间通信的成本比较高。 那么为什么要有进程间通信呢其中有以下几种目的 数据传输一个进程需要将它的数据发送给另一个进程资源共享多个进程之间共享同样的资源通知事件一个进程需要向另一个或一组进程发送消息通知它它们发生了某种事件如进程终止时要通知父进程进程控制有些进程希望完全控制另一个进程的执行如Debug进程此时控制进程希望能够拦截另一个进程的所有陷入和异常并能够及时知道它的状态改变。 那么怎么做到进程间通信呢由于进程之间具有独立性所以在不打破它们的独立性的前提下使它们看到同一份“资源”这就是进程间通信的本质。那么这个“资源”是什么呢是谁提供的呢其实就是特定形式的空间一般由操作系统提供。必定是不能由一个进程提供假设是由一个进程提供这个资源就是属于这个进程独有了破坏了进程的独立性 所以我们进程访问这个空间进行通信本质就是访问操作系统进程代表就是用户所以这个“资源”从创建、使用、释放都是通过系统调用接口从底层设计从接口设计都要由操作系统独立设计一般操作系统会有一个独立的通信模块它隶属于文件系统叫做 IPC通信模块。 2. 进程间通信分类 基于文件级别的通信方式 - - - 管道 匿名管道 pipe命名管道 System V IPC System V 消息队列System V 共享内存System V 信号量 POSIX IPC 消息队列共享内存信号量互斥量条件变量读写锁 二、管道 1. 管道概念 管道是Unix中最古老的进程间通信的形式我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”。 2. 管道原理 首先我们知道每一个进程都有自己的文件描述符表文件描述符表中 0、1、2 默认已经被打开分别指向键盘、显示器、显示器。如今我们新建一个文件我们是否能做到该文件不在磁盘中被打开呢也就是说我们有该文件的 fd、inode、file_operators、缓冲区但是该文件就不在磁盘中也就是实现真正的内存级文件。答案是可以做到的而且在操作系统内核中会存在非常多的内存级文件而这些文件不在磁盘中真正存在只要它们能在内存里被我们用起来即可。也就是下图的结构 当我们以只读方式打开一个文件时同时创建一个子进程操作系统会帮我们将父进程的 task_struct 拷贝给子进程也就是文件描述符表也拷贝给子进程了那么文件描述符表中的内容也和父进程一样我们知道文件描述符表其实是数组指针那么子进程中的文件描述表中的指针也指向了父进程的表中的指向也就是说父进程和子进程都能看见一样的文件资源如下图 那么如果父进程想向缓冲区里面写入子进程是不是就可以从缓冲区中读取呢是的这样就能实现进程间通信了所以管道的本质也叫做文件因为管道就是文件只是不是我们理解的磁盘文件只是内存级文件。 但是我们上面打开文件的时候是以只读方式打开的创建子进程时子进程也是只有只读的权限那么它们之间怎么通信呢没有办法一个读一个写。所以父进程在打开文件的时候就不能这么随意地打开啦接下来我们继续理解管道的原理。 所以在系统当中父进程在打开一个文件的时候并不是单方面的以读、写方式或者读写方式打开的。它在创建管道时把同一个文件既以读方式打开又以写方式打开 如下图 接下来父进程在 fork 创建子进程子进程它会拷贝父进程的文件描述符表所以它们都会有对应的读写端指向管道如下 紧接着需要结合具体场景我们是想让父进程读子进程写还是子进程读、父进程写。此时我们就要求父进程或子进程它们各自要关闭对应的读写端来形成一个单向通信的管道如下图 那么有了上面的初步理解后我们接下来站在内核的角度再次理解管道的本质。我们继续画图理解如下首先我们把同一个文件在同一个进程中再打开一次在操作系统层面上还是要给它创建一个 struct_file因为这两个文件的读写方式不一样其中我们知道每一个文件里都有自己当前的读写位置比如我们读写到哪个偏移量如果我们读写混合用的话会很容易出问题的。但是这两个文件是指向同一个 inode、方法集以及缓冲区 接下来父进程创建子进程子进程中的文件描述表也就指向了对应的 struct_file所以这时候就要实现父子进程的单向通信了所以此时就需要我们用户决定到底是父进程写还是子进程写那么我们在这就让子进程写入父进程读取。所以父进程就要根据文件描述符关闭对应的 struct_file 了子进程也如此如下图 至此这就是管道通信的原理正是因为它只能进行单向通信所以给它命名为管道。 那么我们上面讲解的原理都是通过父进程创建子进程实现的如果没有任何关系可以用上面的原理进行通信吗不可以必须是父子关系、兄弟关系、爷孙关系…所以管道通信必须具有血缘关系才可以常用于父子关系。 3. 匿名管道 我们上面讲的原理中我们打开的文件有名字吗有 inode 吗有路径吗都没有因为这个文件不需要有名字更不需要怎么去标定它所以我们把这种管道叫做匿名管道我们把红色框中的整体成为匿名管道。 至此我们还没有进行通信我们一直都在建立通信信道我们这么费劲建立就是因为进程具有独立性通信是有成本的 4. 匿名管道系统接口 接下来我们认识一下管道的系统接口pipe 那么 pipe 中的参数是什么呢为什么是一个只有两个变量的数组呢其实它就是输出型参数它会将文件的文件描述符数字带出来让用户使用 那么规定pipefd[0] 是读端pipefd[1] 是写端。 我们可以验证一下该结论如下代码 #include iostream#include unistd.husing namespace std;int pipefd[2] {0};int main(){int n pipe(pipefd);cout pipefd[0]: pipefd[0] endl pipefd[1]: pipefd[1] endl;return 0;}结果如下被打开的文件果然是 3 号和 4 号而3号fd就是读端4号fd就是写端 其中返回值如果成功返回0否则返回-1错误码被设置 接下来我们按照上面的原理建立一个管道。首先我们把整体架构搭建出来代码如下 #include iostream#include string#include unistd.h#include sys/types.h#include sys/wait.h#include cstring#include cstdlib#include cstdio#define NUM 1024using namespace std;int pipefd[2] {0};int main(){int n pipe(pipefd);if(n 0) return -1;// 创建子进程pid_t id fork();if(id 0) return -1;// 子进程写入if(id 0){close(pipefd[0]);Write(pipefd[1]);close(pipefd[1]);exit(0);} // 父进程读取close(pipefd[1]);Read(pipefd[0]);pid_t wid waitpid(id, nullptr, 0);if(wid 0) return -1;close(pipefd[0]);return 0;}接下来子进程进行写入其中 snprintf 是以字符串格式向 buffer 写入我们先打印出来观察结果 // 子进程写入void Write(int wfd){string str hello, world;pid_t myid getpid();int number 0;char buffer[NUM];while(true){buffer[0] 0;snprintf(buffer, sizeof(buffer), %s - %d - %d, str.c_str(), myid, number);write(wfd, buffer, strlen(buffer)); // strlen(buffer) 只需要写入长度个字节如果用sizeof(buffer)就相当于是buffer指针的大小了cout buffer endl;sleep(1);}}接下来父进程向管道中读取数据我们也先打印出来观察结果我们关闭子进程的打印结果观察父进程的打印结果 // 父进程读取void Read(int rfd){char buffer[NUM];while(true){buffer[0] 0;ssize_t n read(rfd, buffer, sizeof(buffer));if(n 0){buffer[n] 0;cout father get message[ getpid() ]# buffer endl;}else if(n 0){cout read done! endl;break;}else break;}}如上图父进程确实向管道中读取到了子进程写入的结果。接下来我们验证一些问题。 假设我们让子进程在写入前休眠一段时间而现在的管道是空的我们观察父进程会如何 // 子进程写入void Write(int wfd){string str hello, world;pid_t myid getpid();int number 0;char buffer[NUM];sleep(10);while(true){buffer[0] 0;snprintf(buffer, sizeof(buffer), %s - %d - %d, str.c_str(), myid, number);write(wfd, buffer, strlen(buffer)); // strlen(buffer) 只需要写入长度个字节如果用sizeof(buffer)就相当于是buffer指针的大小了}}我们观察到父进程阻塞了所以我们得出一个结论读写端正常管道如果为空读端就要阻塞。 所以我们也就知道父子进程是会进程协同的同步和互斥的这是为了保护管道文件的数据安全这个我们后面再讨论。 下面我们验证如果管道被写满会发生什么情况 所以我们在父进程中先让父进程休眠一段时间并在子进程中打印 number观察写端写满管道后会发生什么情况 // 父进程读取void Read(int rfd){char buffer[NUM];while(true){sleep(5);buffer[0] 0;ssize_t n read(rfd, buffer, sizeof(buffer));if(n 0){buffer[n] 0;cout father get message[ getpid() ]# buffer endl;}else if(n 0){cout read done! endl;break;}else break;}}如上我们发现写端也阻塞了但是五秒后父进程休眠完毕就开始读取数据了 所以我们得出结论读写端正常管道如果被写满写端就会阻塞 但是根据以上现象我们延申出另外一个问题对于父进程来说子进程写了多少次根本不重要只要管道里有数据有多少就会读多少前提条件是我们缓冲区足够大。也就是说当子进程向管道写满了当父进程在读的时候就会把多次写的信息一次读了出来在父进程看来它读到的就是一个一个的字符对于我们用户用什么存取如何区分这是我们用户的事。所以我们得出一个管道的特点管道是面向字节流的 当两个进程退出时文件会被操作系统自动退出所以管道资源会被自动释放就像我们的0、1、2号fd文件我们也从来没有打开和关闭过这就是操作系统帮我们做的。所以我们又得出管道的一个特点管道是基于文件的而文件的生命周期是随进程的 我们上面也看到管道会被写满那么管道的大小是多少呢我们可以在系统中查看一下指令为 ulimit -aulimit 是一条命令用来查看操作系统对于很多重要资源的限制如下 我们可以看到8指的是单个进程可以打开文件的个数大小是512字节所以管道的大小也就是4KB我们可以验证一下。我们现在只需要子进程写入父进程休眠即可我们让子进程一次写入一个字节也就是一个字符写入一次number一次所以代码如下 void Write(int wfd){string str hello, world;pid_t myid getpid();int number 0;char buffer[NUM];while(true){char c c;write(wfd, c, 1); number;cout number endl;}}结果如下 我们可以看到管道写入了 65536 个字节也就是 64KB也就是说在我们的机器下管道的大小是 64KB. 那么我们要说一下了在不同的内核里管道的大小是有差异的。我们也可以读一下管道的手册 如上也就是说从 Linux 2.6.11 内核之后管道的大小就变成了 64KB我们接着看 我们可以看到有一个 PIPE_BUF 的东西其实它就是单次向管道中写入的大小我们可以看到它的大小是 4KB上面的手册中提到了原子性的问题例如当子进程往管道中写数据时父进程读数据当子进程只写了一部分数据还没有写完就被父进程读走了这就导致读取到的数据不完整。那么所以我们要保证父进程在读的时候要么不读要么就把完整的数据读取这就叫做读取的原子性问题。所以管道在保证读取的原子性它规定 PIPE_BUF 的大小只要是父进程或子进程读写的单位是小于 PIPE_BUF 的它们读写的过程就是原子的也就是说当子进程写入的数据小于 PIPE_BUF父进程也不会来读取的这就是 PIPE_BUF 的本质所以我们在 ulimit 中查到的管道大小我们可以理解成 PIPE_BUF 的大小。 读端正常读写端关闭 接下来我们验证另一个问题当读端正常写端关闭会出现什么情况。接下来我们让子进程在写的时候写入10个字节就退出如下代码 // 子进程写入void Write(int wfd){string str hello, world;pid_t myid getpid();int number 0;char buffer[NUM];while(true){ sleep(1);char c c;write(wfd, c, 1); // strlen(buffer) 1???number;cout number endl;if(number 10) break;}}结果如下 我们观察到当写端还在写入的时候读端在正常读而写端退出后呢如下 而 read 的返回值返回的是读到的数据大小以字节为单位当返回值为0时父进程也就退出了循环所以我们得出结论读端正常读写端关闭读端就会读到0表明读到了文件(pipe)结尾不会被阻塞 写端正常读端关闭 首先我们要知道操作系统是不会做低效、浪费资源和时间等类似的工作的如果做了操作系统就是bug所以我们想写端正常读端关闭后还有实际意义吗没有了因为写满了又怎样呢又没有进程去读所以当写端正常读端关闭了操作系统就要 kill 掉正在写入的进程。如何 kill 呢通过信号其实操作系统会使用13号信号 SIGPIPE kill 掉正在写入的进程 5. 管道的特性和情况 所以对上面的现象总结我们可以分别得到管道的四种情况和物种特性。 管道情况 读写端正常管道如果为空读端就要阻塞读写端正常管道如果被写满写端就会阻塞读端正常读写端关闭读端就会读到0表明读到了文件(pipe)结尾不会被阻塞写端正常写入读端关闭操作系统会 kill 掉正在写入的进程。 管道特性 具有血缘关系的进程才能进行进程间通信管道只能单向通信父子进程是会进程协同的同步和互斥的这是为了保护管道文件的数据安全管道是面向字节流的管道是基于文件的而文件的生命周期是随进程的。 6. 匿名管道的应用 1命令行 那么我们上面学的管道和我们以前学过的哪些有关系呢首先我们以前接触过 | 这个符号其实这个就是管道例如我们在多条指令中使用 | cat test.c | head -10 | tail -5那么它和我们上面学的管道又有什么关系呢我们知道每一条指令在命令行运行时都是会创建进程去执行的。下面我们观察现象得出结论 如上图我们使用管道运行 sleep 指令而在右端终端我们看到它们最终都会变成进程它们三个的 pid 是不一样的而 ppid 是同一个我们查看一下该相同的 ppid 究竟是什么 如图它就是 bash所以它们的父进程都是 bash都是一样的所以它们是具有血缘关系的进程。所以在我们使用 | 的时候在上面的语句中操作系统为我们创建两个管道因为有两个 |然后再连续创建三个进程然后每个进程程序替换执行不同的命令。所以我们使用 | 的原理就是我们上面所说的 pipe 2进程池 通过上面的学习我们可以使用管道实现一个简易的进程池。什么是进程池呢例如内存池内存池是提前向操作系统一次性申请一大片内存供我们用户使用这就可以有效减少我们调用系统接口的次数因为系统调用是有成本的因为涉及到操作系统帮我们申请空间、做空间内部的调整等等。 那么进程的本质其实就是帮助我们执行代码让操作系统去调度的如果我们执行任务的时候总是来一个任务才创建进程然后去执行这样是可以的但是这样会非常慢。那么我们就可以提前将一些进程创建好当有任务到来时我们只派给其中一个进程。其中一次性把一批进程创建好这个工作就叫做进程池的储备提前储备好当需要的时候再派任务给它们。 那么如下图当父进程接收任务之前它先一次性同时创建出若干个子进程。然后为了更好地控制这些子进程父进程和每一个子进程都建立一条管道的信道然后让每一个子进程只从管道中进行读取而父进程每次想往哪个管道里写内容就往哪个管道写内容。当父进程没有向管道里写内容时对应的子进程就会阻塞等待父进程派任务一旦父进程向管道中写了子进程会读取对应的数据然后继续向后执行结合读取的数据就可以执行对应的任务了。接下来我们规定父进程向子进程管道里写的都叫做任务码也就是我们规定好父子通信时父进程每次写入时只能写入4字节子进程在读取时也只能读取4字节。所以我们让父进程向管道里写入4字节 数据数据不同值代表不同任务我们就可以想让哪个子进程执行什么任务就让哪个子进程执行什么任务。 所以当父进程想布置任务的时候无非就是做两件事一就是选择任务二是选择进程。接下来我们就可以实现这样的代码参考代码链接为进程池. 7. 命名管道 1命名管道的系统接口 上面我们学到的匿名管道是没有名字的因为打开那个文件的时候并没有告诉我们文件名也就是管道并没有命名。我们直接以读方式写方式打开父子进程各自拿一个读写端就可以通行。正是因为它没有名字那么所以它必须得让我们对应的父子进程看到通信资源它采用的是让父子继承的方案看到的。 那么有没有一种其他的方案呢因为我们发现匿名管道只能用来进行具有血缘。如果毫不相关的进程进行进程间通信呢。所以我们需要有下一个方案叫做命名管道。接下来我们先使用一下命名管道先看现象再解释。其中建立命名管道的接口为 mkfifo. 我们先看手册的介绍我们可以在当前的工作目录下建立命名管道 例如我们在当前目录下创建一个命名管道名字为 pipefifo 如上我们就创建了以 p 属性开头的管道文件该管道就是命名管道。该管道看起来是在磁盘中存在但是它实际数据并不会刷新到磁盘上。 那么如何让两个进程进行通信呢我们创建两个终端两个终端都在当前目录下一个写另一个读。观察现象 如上图当写端进行写入的时候命令行会变成一个进程向管道里写入此时读端没有读取所以写端正在阻塞。当读端进行读取后 此时左侧的字符串会到了右侧。如果我们一直往管道里写该管道的大小也不会有变化这就是命名管道 2理解命名管道 那么关于命名管道的理解首先如果两个不同的进程打开同一个文件的时候在内核中操作系统会打开几个文件呢因为我们要打开的时候是以不同的读写方式打开那么对应的这个被打开的文件它的内核里的属性和它所谓的那些操作方法还有缓冲区了这些东西其实操作系统只会给我们维持一份。为什么呢因为对于操作系统而言没必要给你把属性相同的类似的属性写两份写两份还不方便进行维护那方法也只有一套就行了属性也只有一套。更重要的是缓冲区我要给你留一个就行了那为什么留一个呢它不怕我们文件读写的时候出现错乱吗我们都用两个进程打开同一个文件了它在读写时不加保护的情况下它在读写是注定会错乱的你都不怕我怕什么所以对我们来讲你会发现我们如果两个不同的进程打开同一个文件时实际上在内核里它还是这张图 当两个进程打开同一个文件时在操作系统层面上还是这种结构。所以可能有不同的读取文件对象但是文件还是同一个。所以我们就理解我们把它叫做命名管道的原因了。 因为它也是基于文件因为我们正常进程通信我们只想用它的内存及缓冲区不想让我们对应的这个数据再进行刷盘。我们是想让一个进程将数据交给另一个进程它只需要放到缓冲区里然后不需要进行刷盘另一个进程读取就可以了。如果打开普通文件它就必须得刷盘了。所以我们就有了一个文件类型叫做管道文件属性以 p 开头的。所以当看到这个管道文件时原来系统里单独创建一个叫做管道文件那么这个文件当我们的进程在不同地方在打开的时候就知道了这个文件不需要刷盘也就是说它只是一个内存及文件 那么问题又来了两个不同的进程打开同一个文件时它们是怎么知道打开的是同一个文件比如说我们上面讲的匿名管道父子进程怎么知道打开的是同一个管道文件因为可以通过继承的方式来进行。能按通过继承的方式让父子看到不同对应的文件。可是命名管道不一样怎么知道我们两个进程打开的是同一个文件的呢 很简单两个进程只要看到同一个文件名那么此时这两个进程就可以打开同一个文件了。可是其实可不仅仅只有文件名还有一个前提条件叫做 pwd 因为我们在上面使用的命名管道都是在同一路径下的文件名所以怎么知道两个进程打开的是同一个文件呢就是用路径文件名确定的而路径文件名具有唯一性而且该文件是 p是管道文件所以就进行内存级通信就可以了这就是命名管道。 3使用命名管道 接下来我们使用两个毫不相干的进程进行建立命名管道并且进行命名管道间的通信形成两个可执行程序分别是 server 和 client. 其中 server 是管理管道文件的也就是说创建、删除等工作。 其中我们使用到的系统接口是 mkfifo参考手册 其中参数 pathname 是哪一个路径下的文件名也就是保证唯一性的第二个 mode因为管道也是文件所以这个文件的权限也要有。 其中返回值成功返回0失败返回-1errno 被设置。 参考代码链接为命名管道使用. 三、初识日志 1. 理解日志 关于日志实际上我们程序在运行期间需要不断向显示器或者文件进行信息输出的我们在运行代码时想产生各种各样的日志数据这些日志数据方便我们记录程序运行的痕迹方便后期进行排查。 而日志的格式并没有严格的要求我们可以按照自己的要求定制但是一般都会有日志时间日志等级日志内容等可能还会有文件的名称和行号。 那么什么是日志等级呢对于我们写的软件还是服务器在运行过程中避免不了各种各样的问题所以根据严重程度的不同我们的处理方法是不一样的。常见的日志等级有 Info常规消息Warning警告信息Error比较严重可能需要立即处理Fatal致命问题Debug调试 那么我们上面实现的使用命名管道中我们看见有许多的 perror 的信息还可以加上一些常规消息比如创建文件成功后打印一些数据而这些信息我们都可以用一个日志函数处理我们可以让这些信息直接打印在显示屏上也可以让这些信息写到文件中这个看我们的需求。所以我们下面实现一个简单的日志函数引入到命名管道的代码中。 参考代码链接为日志. 其中补充知识如下。 2. 获取时间 C语言 当中获取时间的方式非常多接下来介绍一种localtime 那么它的参数是 time_t 类型那么就是 time() 接口的返回值如下 那么 localtime 就是我们传入一个 time_t 类型它会帮我们转化为 struct tm 这样的结构我们可以看一下这个结构 所以这个 localtime 就可以让我们控制自己打印的时间。 注意tm_year 是从 1900 年开始算的tm_mon 是从 0 开始的。 3. 可变参数的使用 我们都见过可可变参数但是还没有使用过接下来介绍一下如何使用可变参数。假设我们有一个 sum 函数是求任意个数个元素的求和如下 int sum(int n, ...);使用可变参数之前必须要有一个 va_list 结构其实它就是一个 char* 类型的结构。 那么我们在调函数的时候函数创建栈帧结构无论是可变参数还是确定参数在使用的时候参数是要压栈的在形成栈帧结构之后要将传入的参数从右向左依次入栈。 那么上面说 va_list 其实就是一个指针所以我们创建一个 va_list 的对象就是一个 char* 指针它可以根据 va_list 帮我们提取可变参数一个一个的参数。我们初始化一个 va_list 的对象如下 int sum(int n, ...){va_list s;va_start(s, n);// ...}因为参数在压栈的时候是从右往左压的所以参数 n 是最后一个入栈的所以 va_start 就是使用 s 指向 n也就是说我们想让 s 指向可变部分的开头其实只要让 s 指针指向 n 的地址然后加上 n 的大小个字节就可以让 s 指向可变部分的开头如下图 也就是说 va_start 我们可以把它看作是 s n 1其中 1 就是让指针向后移动 n 自身大小个字节。 当 s 指向了可变部分的开头处我们只需要知道开头处的类型就可以把这个元素访问出来然后再让 s 加上类型大小就可以继续指向下一个可变参数依次解析。 其实 va_start 就是一个宏还有其他的 va_list 、va_end() 等都是宏它在实现的时候会自己取地址的。 所以在可变参数中必须要有至少一个具体的参数因为它要找可变部分的起始地址那么如果我们想用上面的 sum 函数就可以像如下代码使用 int sum(int n, ...){va_list s;va_start(s, n);int _sum 0;while(n){va_arg(s, int);n--;}va_end(s); // s NULL;return _sum;}那么 va_arg() 就是用 s 根据类型来提取参数第二个参数就是根据的类型如今我们的代码是写固定了 int 类型实际上需要像 printf 函数那样进行格式控制进行字符串分析。
http://www.hkea.cn/news/14526803/

相关文章:

  • 活泼风格的网站html网站源码
  • 人工智能网站应怎么做网站建设的公司怎么做
  • 网站最新点击量排名网站制作的常见布局
  • 建站服务器北京专业响应式网站建设
  • 营销型网站哪家做的好wordpress直播平台
  • 云服务器和网站备案吗手机直播软件
  • 山东经济建设网站济南seo的排名优化
  • 各网站封面尺寸h5网站怎么做的
  • 专门做橱柜衣柜效果图的网站免费网站后台模板下载
  • 两学一做考试网站装修做劳务去哪个网站找工地
  • 刘强东自己做网站如何进行网络推广和宣传
  • 咸宁市做网站WordPress修改模板相对路径
  • 不花钱网站怎么做asp.net mvc5网站开发之美
  • 如何用c语言做钓鱼网站网站qq弹窗代码
  • 印刷报价网站源码珠海网站建设 amp 超凡科技
  • 外贸网站装修书城网站开发
  • 怎么做网站访问截取ip教做软件的网站
  • pc网站建设意见火车头采集器wordpress下载
  • 广州企业网站设计上海有哪几家做新房的网站
  • 南山网站建设深圳信科优化网站平台
  • 网站模板html5营销培训计划
  • 修改散文网站wordpress新建404页面
  • 网站首页设计过程查找网站注册时间
  • 苏州网站建设新手网站是自己做还是让别人仿
  • 个人网站有商业内容备案长沙高端网站建设品牌
  • wordpress中的角色优化网站公司外包
  • 体检中心 网站建设方案贵阳建设厅网站
  • 网站建设设计报价网站管理登录
  • 自定义网站建站公司南京公司网站建设费用
  • 桂林市网站设计wordpress宗旨是什么