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

专业建筑工程网站移动宽带

专业建筑工程网站,移动宽带,注册公司后不经营什么后果,wordpress 锚点定位书接上回#xff0c;我们之前学习的文件系统编程都是在内存空间中的文件流#xff08;用户态文件缓冲区#xff09;内进行操作的#xff0c;比如使用的fopen、fclose、fread和fwrite等等都是库函数#xff0c;并没有用到内核态的功能#xff08;实际上库函数中调用的是内…书接上回我们之前学习的文件系统编程都是在内存空间中的文件流用户态文件缓冲区内进行操作的比如使用的fopen、fclose、fread和fwrite等等都是库函数并没有用到内核态的功能实际上库函数中调用的是内核态的功能库函数是内核调用的封装而库函数间接调用内核功能的话就会造成性能的损失所以我们考虑直接在内核态调用内核功能即不带缓冲的文件IO操作。 不带缓冲的文件IO不带用户态缓冲区即文件流FILE 我们直接对在内核内存中的struct file文件对象进行操作因为它与硬件直接对应。 而在struct file文件对象中有一个缓冲区这个缓冲区中的内容真正的直接对接硬件这个缓冲区称为内核文件缓冲区。现在我们对文件进行操作就直接操作这块内核文件缓冲区逻辑上相当于直接和硬件进行通信。 但是struct file不好管理因为这是由内核控制的用户无法随意操作内核的地址空间这不安全。我们想要找到某个文件对象但是又不能知道其具体的地址所以肯定会有一个“中间人”的存在我们将要操作的请求交给该中间人由它来帮我们操作内核的地址空间。 可以考虑用一个指针数组而每一个数组元素就存储了每一个文件对象的首地址这个时候我们要找到某一个文件对象我们就只需要找到该文件对象在数组中的下标位置即可并不需要知道它真实的内存地址的值操作系统内核会处理下标位置到真实内存地址的转换。 即用户用的是一个个整数而操作系统用的是一个真实的地址。 明显这个整数不能为一个负数因为数组下标不可能为负数啊。所以这样的非负整数的一个数组下标的集合就是Linux系统中的文件描述符。 文件描述符 文件描述符形式上是一个非负整数作用是用来访问某个具体的文件对象。 但是这个整数呢有三个数是已经天生就被用掉了分别0、1、20表示标准输入1表示标准输出、2表示标准错误。 open系统调用 open 系统调用的简单测试 测试一下多属性open系统调用传入参数 open常用属性 read和write读写文件系统调用 read系统调用 read系统调用的一些细节不过这些在man手册中都是写的比较清楚的 count应当是申请内存的大小read之前注意要先清空buf。 write系统调用 简单的测试 ftruncate系统调用文件截断 简单测试 ftruncate系统调用的最大意义就是用来创建一个固定大小的文件。 用在什么地方呢内存映射mmap。 内存映射mmap mmap系统调用 简要说明 对应的测试代码 lseek系统调用 这个系统调用和之前学的一个fseek很像其实功能完全一样只不过操作的位置不一样 很明显lseek操作的是内核区的文件对象缓冲区而fseek操作的是目录流里的缓冲区。 dup系统调用文件描述符的复制 有名管道named pipe/FIFO文件 这是一种进程间通信机制在文件系统中的映射即操作管道和操作文件是一样的如用open 打开管道。 传输方式 1、单工方式A-B 2、半双工能从A-B 也能B-A但是不能同时双向通信 3、全双工能同时双向通信 而管道至少是半双工通信不同OS的设计不一样。 使用makefifo创建一个管道 可以看到创建的管道的大小为0这意味着管道只能用来通信并不能拿来存数据所以用任何命令去访问它都是错误的比如vim。 删除管道的话就跟普通的删除文件是一样的 我们现在往里面写一个hello数据 可以看见此时当前进程被阻塞因为写进去的数据还没有被读取。 此时我新开一个进程进行读取操作再返回回来就可以看见原进程就被释放了 这就是简单的进程间通信。 用系统调用操作管道 之前说过管道本质也就是文件所以我们可以通过文件的系统调用来操作管道写一个读程序和一个写程序 运行时依然可以得到我们之前使用管道命令的效果值得注意的是当一个进程打开(open)了管道一端的时候如果对端未被打开进程就会处于阻塞状态直到对端被另一个进程打开并不论读先还是写先。 那怎么实现全双工通信用两根管道即可实现注意可能产生死锁问题。 我们可以来试一下这个事情简单写一个两个进程聊天的程序。 实现简易进程聊天程序 首先建立两根管道1.pipe和2.pipe来实现全双工通信 然后写两个c程序采用系统调用的方式来实现聊天程序 chat1.c : 1 #include 43func.h2 3 int main(int argc,char* argc[]){4 5 //./chat1 1.pipe 2.pipe6 ARGS_CHECK(argc,3); //检查是否为三个命令行参数7 int fdr open(argv[1],O_RDONLY); //使用open系统调用以只读方式打开管道18 int fdw open(argv[2],O_WRONLY); //只写方式打开管道29 puts(pipe open); //标识管道打开10 char buf[4096] {0}; //存储数据的缓冲数组 11 while(1){ //永真循环一直维持对话的进行12 memset(buf,0,sizeof(buf));//每次对话结束都刷新一下数组信息13 read(fdr,buf,sizeof(buf));//采用read系统调用读取管道1内的信息14 puts(buf); //打印管道1中来自进程2的消息15 memset(buf,0,sizeof(buf)); //刷新数组信息好接下来使用其来进行写数据16 read(STDIN_FILENO,buf,sizeof(buf)); //使用read系统调用从标准输入读取控制台上的内容存储进buf数组中17 write(fdw,buf,strlen(buf)); //使用write系统调用将buf数组中存在的数据写进管道2 18 } 19 } chat2.c 1 #include 43func.h2 3 int main(int argc,char* argv[]){4 5 //./chat2 1.pipe 2.pipe6 ARGS_CHECK(argc,3); //检查是否为三个命令行参数7 int fdw open(argv[1],O_WRONLY); //使用open系统调用以只写方式打开管道18 int fdr open(argv[2],O_RDONLY); //只读方式打开管道29 puts(pipe open); //标识管道打开10 char buf[4096] {0}; //存储数据的缓冲数组 11 while(1){ //永真循环一直维持对话的进行12 memset(buf,0,sizeof(buf));//每次对话结束都刷新一下数组信息13 read(STDIN_FILENO,buf,sizeof(buf)); //使用read系统调用从标准输入读取控制台上的内容存储进buf数组中14 write(fdw,buf,strlen(buf)); //使用write系统调用将buf数组中存在的数据写进管道1 15 memset(buf,0,sizeof(buf)); //刷新数组信息好接下来使用其来进行写数据16 read(fdr,buf,sizeof(buf));//采用read系统调用读取管道2内的信息17 puts(buf); //打印管道1中来自进程2的消息 18 }19 } 现在我们开启两个程序 我们让chat2发送nihao 此时可以看见chat1接收到了消息我们再让chat2发送chilem 可以发现我们的chat1并没有收到其消息当我们让chat1回复niyehao后才能收到chilem的消息 上面情况的发生也就意味着我们的这个聊天是串行执行的是一问一答的形式这是为什么呢 这其实和我们的代码逻辑有关系我们代码中的循环执行两个事情读管道和读标准输入读标准输入也就是为了写管道是有先后顺序的要么先读管道再读输入要么先读输入再读管道所以一问一答串行执行时并没有问题。但当假设管道一端为程序A连续朝一个管道里面发送多条数据读输入操作而另一端B程序没来得及读取该管道消息时就会导致A端的读管道操作被阻塞因为B端即然没读管道数据自然也就不会执行读输入也就是写管道的操作那A端的读管道也会因为管道没有数据而被停滞。 怎么解决就是让串行变成并行即可而这个方法就叫IO多路复用这是现代服务器的基础 IO多路复用超级重点 IO多路复用的思路 我们将上面的串行执行具象话用投简历的过程来模拟一下 串行执行我一开始给百度投简历然后给阿里投简历然后给腾讯投简历然后我必须得等百度的意向它录取我了或者没录取我拿到结果之后我才能去看阿里对我的结果这就是串行执行但很明显这很蠢因为如果阿里或者腾讯先给我offer了我是不是就可以先跳过百度拿到后两家的offer呢当然应该是可以的所以这是串行执行的弊端。 并行执行此时我们会采用一个中间人或者说用一个小助手由它来管理我的所有意向消息即这三家的意向都会发给该小助手我只要去找小助手就能知道这几家有哪一家给我发意向了没这样就能变串行被并行也就是IO多路复用的思路。 IO多路复用的实现select系统调用 记得使用的时候要加头文件嗷。 我们前面所提到的中间人或者说小助手就是指的select系统调用中的 fd_set 监听集合。 对于监听集合要使用的话首先要创建 fd_set也就是给其分配内存。 第二步是设置合适的监听也就是要监听的都有哪些公司相关的函数有上面的fd_zero用来清空监听集合fd_set用来将某一个会产生阻塞行为的文件描述符加入监听。 第三步调用select函数让进程阻塞当监听的文件描述符中有任何一个满足条件的时候select才会就绪。 最后使用FD_ISSET函数轮流询问所有监听的文件描述符是否就绪如果有就绪的话就可以读取该文件描述符这样读取就不会引发阻塞了。 我们使用上述IO多路复用的知识来改造我们的简易聊天程序 chat1.c 1 #include 43func.h2 3 int main(int argc,char* argv[]){4 5 //./chat1 1.pipe 2.pipe6 ARGS_CHECK(argc,3); //检查是否为三个命令行参数7 int fdr open(argv[1],O_RDONLY); //使用open系统调用以只读方式打开管道18 int fdw open(argv[2],O_WRONLY); //只写方式打开管道29 puts(pipe open); //标识管道打开10 char buf[4096] {0}; //存储数据的缓冲数组 11 fd_set rdset; //设置监听集合,这里是从栈上开辟的内存 12 while(1){ //永真循环一直维持对话的进行13 FD_ZERO(rdset);//清空监听集合/*为什么需要清空监听集合重新再监听这是由于select函数的设计导致的在select函数的参数列表中明显其参数并没有用const修饰起来这意味着我们传入的参数和最后函数执行完所返回的参数结果有可能并不一致所以我们才需要重新设置监听集合*/14 FD_SET(STDIN_FILENO,rdset); //将标准输入加入监听15 FD_SET(fdr,rdset); //将管道读操作加入监听16 //第一个参数是固定用法集合中哪个文件描述符的值大就填其1的值进去这表示将所有描述符监听起来17 //第二个参数是填入读监听的监听集合地址18 //第三个参数是填入写监听的监听集合地址没有就填NULL19 //第四个参数是填入异常监听的监听集合地址没有就填NULL20 //第五个参数是填超时时间填NULL表示永久等待永不超时21 select(fdr1,rdset,NULL,NULL,NULL);22 //开始轮询看被监听集合中是否有文件描述符所描述的文件来数据了23 if(FD_ISSET(fdr,rdset)){//如果管道来数据了 24 puts(msg from pipe);25 memset(buf,0,sizeof(buf));//每次对话结束都刷新一下数组信息26 read(fdr,buf,sizeof(buf));//采用read系统调用读取管道1内的信息27 puts(buf); //打印管道1中来自进程2的消息28 }29 if(FD_ISSET(STDIN_FILENO,rdset)){30 puts(msg from stdin);31 memset(buf,0,sizeof(buf)); //刷新数组信息好接下来使用其来进行写数据32 read(STDIN_FILENO,buf,sizeof(buf)); //使用read系统调用从标准输入读取控制台上的内容存储进buf数组中33 write(fdw,buf,strlen(buf)); //使用write系统调用将buf数组中存在的数据写进管道2 34 }35 }36 } chat2.c 1 #include 43func.h2 3 int main(int argc,char* argv[]){4 5 //./chat2 1.pipe 2.pipe6 ARGS_CHECK(argc,3); //检查是否为三个命令行参数7 int fdw open(argv[1],O_WRONLY); //使用open系统调用以只写方式打开管道18 int fdr open(argv[2],O_RDONLY); //只读方式打开管道29 puts(pipe open); //标识管道打开10 char buf[4096] {0}; //存储数据的缓冲数组 11 fd_set rdset; 12 while(1){ //永真循环一直维持对话的进行13 FD_ZERO(rdset);//清空监听集合14 FD_SET(STDIN_FILENO,rdset); //将标准输入加入监听15 FD_SET(fdr,rdset); //将管道读操作加入监听16 //第一个参数是固定用法集合中哪个文件描述符的值大就填其1的值进去这表示将所有描述符监听起来17 //第二个参数是填入读监听的监听集合地址18 //第三个参数是填入写监听的监听集合地址没有就填NULL19 //第四个参数是填入异常监听的监听集合地址没有就填NULL20 //第五个参数是填超时时间填NULL表示永久等待永不超时21 select(fdr1,rdset,NULL,NULL,NULL);22 //开始轮询看被监听集合中是否有文件描述符所描述的文件来数据了23 if(FD_ISSET(fdr,rdset)){//如果管道来数据了 24 puts(msg from pipe);25 memset(buf,0,sizeof(buf));//每次对话结束都刷新一下数组信息26 read(fdr,buf,sizeof(buf));//采用read系统调用读取管道1内的信息27 puts(buf); //打印管道1中来自进程2的消息28 }29 if(FD_ISSET(STDIN_FILENO,rdset)){30 puts(msg from stdin);31 memset(buf,0,sizeof(buf)); //刷新数组信息好接下来使用其来进行写数据32 read(STDIN_FILENO,buf,sizeof(buf)); //使用read系统调用从标准输入读取控制台上的内容存储进buf数组中33 write(fdw,buf,strlen(buf)); //使用write系统调用将buf数组中存在的数据写进管道2 34 }35 }36 } 测试 现在我们让chat2随意的向chat1发送下消息 可以看见不会再被阻塞了让chat1向chat2发消息也一样 完美解决。 简易进程聊天程序的关闭 关闭程序我们都知道直接ctrlc即可但是其实这是有问题的因为ctrlc是直接杀死进程此时一个进程结束而另一个进程还没有那么就会产生一些离谱的情况这里我们杀死chat1 因为图片只能显示静态的东西实际上下面的进程是一直在重复打印msg from pipe 的。 这意味着一方进程被杀死另一方进程就陷入了死循环。 另外从图中结合代码也可以看到是因为管道一直就绪导致的死循环我们来分析一下可能出现死循环的原因 1、写端先关闭那么读端read会读到EOF文件终止符文件终止符有一样算要读的数据嗷这其实意味着读端是就绪的那么select也就可以就绪且管道又没有关掉那么就陷入死循环喽。 解决办法是读到文件终止符时我们就终止读取就好了。 另外使用ctrlc终止进程的方式太过粗暴我们可以使用ctrld该操作会向标准输入输入一个文件终止符EOF更推荐这种方式所以改造后的代码如下 1 #include 43func.h2 3 int main(int argc,char* argv[]){4 5 //./chat1 1.pipe 2.pipe6 ARGS_CHECK(argc,3); //检查是否为三个命令行参数7 int fdr open(argv[1],O_RDONLY); //使用open系统调用以只读方式打开管道18 int fdw open(argv[2],O_WRONLY); //只写方式打开管道29 puts(pipe open); //标识管道打开10 char buf[4096] {0}; //存储数据的缓冲数组 11 fd_set rdset; //设置监听集合,这里是从栈上开辟的内存 12 while(1){ //永真循环一直维持对话的进行13 FD_ZERO(rdset);//清空监听集合/*为什么需要清空监听集合重新再监听这是由于select函数的设计导致的在select函数的参数列表中明显其参数并没有用const修饰起来这意味着我们传入的参数和最后函数执行完所返回的参数结果有可能并不一致所以我们才需要重新设置监听集合*/14 FD_SET(STDIN_FILENO,rdset); //将标准输入加入监听15 FD_SET(fdr,rdset); //将管道读操作加入监听16 //第一个参数是固定用法集合中哪个文件描述符的值大就填其1的值进去这表示将所有描述符监听起来17 //第二个参数是填入读监听的监听集合地址18 //第三个参数是填入写监听的监听集合地址没有就填NULL19 //第四个参数是填入异常监听的监听集合地址没有就填NULL20 //第五个参数是填超时时间填NULL表示永久等待永不超时21 select(fdr1,rdset,NULL,NULL,NULL);22 //开始轮询看被监听集合中是否有文件描述符所描述的文件来数据了23 if(FD_ISSET(fdr,rdset)){//如果管道来数据了 24 puts(msg from pipe);25 memset(buf,0,sizeof(buf));//每次对话结束都刷新一下数组信息26 int ret read(fdr,buf,sizeof(buf));//采用read系统调用读取管道1内的信息27 if(ret 0){ //如果读到文件终止符我们就终止进程读取28 printf(end!\n); 29 break;30 }27 puts(buf); //打印管道1中来自进程2的消息28 }29 if(FD_ISSET(STDIN_FILENO,rdset)){30 puts(msg from stdin);31 memset(buf,0,sizeof(buf)); //刷新数组信息好接下来使用其来进行写数据32 int ret read(STDIN_FILENO,buf,sizeof(buf)); //使用read系统调用从标准输入读取控制台上的内容存储进buf数组中33 if(ret 0){ //表示标准输入输入了一个文件终止符write(fdw,nishigehaoren,13);break;}write(fdw,buf,strlen(buf)); //使用write系统调用将buf数组中存在的数据写进管道2 34 }35 }36 } chat2.c也要改 1 #include 43func.h2 3 int main(int argc,char* argv[]){4 5 //./chat2 1.pipe 2.pipe6 ARGS_CHECK(argc,3); //检查是否为三个命令行参数7 int fdw open(argv[1],O_WRONLY); //使用open系统调用以只写方式打开管道18 int fdr open(argv[2],O_RDONLY); //只读方式打开管道29 puts(pipe open); //标识管道打开10 char buf[4096] {0}; //存储数据的缓冲数组 11 fd_set rdset; 12 while(1){ //永真循环一直维持对话的进行13 FD_ZERO(rdset);//清空监听集合14 FD_SET(STDIN_FILENO,rdset); //将标准输入加入监听15 FD_SET(fdr,rdset); //将管道读操作加入监听16 //第一个参数是固定用法集合中哪个文件描述符的值大就填其1的值进去这表示将所有描述符监听起来17 //第二个参数是填入读监听的监听集合地址18 //第三个参数是填入写监听的监听集合地址没有就填NULL19 //第四个参数是填入异常监听的监听集合地址没有就填NULL20 //第五个参数是填超时时间填NULL表示永久等待永不超时21 select(fdr1,rdset,NULL,NULL,NULL);22 //开始轮询看被监听集合中是否有文件描述符所描述的文件来数据了23 if(FD_ISSET(fdr,rdset)){//如果管道来数据了 24 puts(msg from pipe);25 memset(buf,0,sizeof(buf));//每次对话结束都刷新一下数组信息26 int ret read(fdr,buf,sizeof(buf));//采用read系统调用读取管道1内的信息27 if(ret 0){printf(end!\n);break;}puts(buf); //打印管道1中来自进程2的消息28 }29 if(FD_ISSET(STDIN_FILENO,rdset)){30 puts(msg from stdin);31 memset(buf,0,sizeof(buf)); //刷新数组信息好接下来使用其来进行写数据32 int ret read(STDIN_FILENO,buf,sizeof(buf)); //使用read系统调用从标准输入读取控制台上的内容存储进buf数组中33 if(ret 0){write(fdw,nishigehaoren,13);break;}write(fdw,buf,strlen(buf)); //使用write系统调用将buf数组中存在的数据写进管道2 34 }35 }36 } 再来测试 可以看见此时杀掉其中一个进程另一个就直接结束了上面是用ctrlc结束的程序也可以用ctrld因为我们改写过代码的。 可能出现的异常的情况除了上面说的第一种还有一种 2、读端先关闭写端继续write此时进程会收到一个SIGPIPE信号然后直接崩溃。 不过这里暂时没什么影响后面学到网络时会有影响那个时候再重点说这个崩溃原理。 接下来我们解决会话超时的问题因为我们有需要让select 不永久的一直轮询等待的情况这就需要解决其超时所带来的一系列问题。 select的超时 之前提过在select函数的最后一个参数其实就是用来设置超时用的其结构体结构如下 time_t是秒suseconds_t是微秒。 我们可以使用select的返回值来区分超时导致的就绪上图是timeout的简单使用。 关于管道 非阻塞方式操控管道文件 学习过之前的知识我们知道一个管道一定会关联两个文件一端读一端写当我们在一端write的时候这部分数据会被送到另一端的读缓冲区中等待被读取但是如果不读取的话那么read缓冲区的数据就会堆积起来一直堆积到写缓冲区知道写缓冲区被填满再也写不进数据为止 之前我们都是以阻塞的方式来实现的管道通信现在我们可以使用非阻塞的方式来尝试通信的操作用非阻塞方式其实只要指定open 的打开方式为O_RDWR即可现在我们open不会阻塞所以可以先open一个再open一个所以现在也就不强调哪一段是读端哪一端是写端了一般由用户自己去决定哪一端是读哪一端是写这样我们就可以在一个进程中把管道的两端给打开出来相当于左手一个对讲机右手一个对讲机自己和自己说话。 1 #include 43func.h2 3 int main(int argc,char* argv[]){4 ARGS_CHECK(argc,2);5 int fdr open(argv[1],O_RDWR);//非阻塞方式打开人为设定的读端6 int fdw open(argv[1],O_RDWR);//非阻塞方式打开人为设定的写端7 puts(pipe open);8 char buf[4096];9 int cnt 0;10 while(1){ 11 printf(cnt %d\n,cnt);12 write(fdw,buf,sizeof(buf));13 }14 } 运行结果 可以看见缓冲区中是可以写入一些数据的但是如果读端一直不读取数据的话则会阻塞起来造成写阻塞也就是我们刚刚说的情况。 同理当缓冲区为空时进行数据的读取就会陷进读阻塞的情况中。 除了上面两种基本的情况还有几种情况比如先读管道再写管道如果管道为空则先读的时候就被阻塞那么写管道自然也被阻塞就成了永久阻塞交换顺序先写管道再读管道如果读的速度比写的速度要快那么数据先被读完了的话读一样会阻塞那写管道也就进行不了一样会阻塞如何解决这些种种问题我们依然可以用IO多路复用的思路来解决。 使用select同时监听读和写 测试代码如下 1 #include 43func.h2 3 int main(int argc,char* argv[]){4 ARGS_CHECK(argc,2);5 int fdr open(argv[1],O_RDWR);//非阻塞方式打开人为设定的读端6 int fdw open(argv[1],O_RDWR);//非阻塞方式打开人为设定的写端7 puts(pipe open); 8 char buf[4096];9 fd_set rdset;10 fd_set wrset;11 int cnt 0;12 while(1){13 FD_ZERO(rdset);14 FD_SET(fdr,rdset);15 FD_ZERO(wrset);16 FD_SET(fdw,wrset);17 select(fdw1,rdset,wrset,NULL,NULL);18 if(FD_ISSET(fdr,rdset)){19 printf(read cnt %d\n,cnt);20 read(fdr,buf,2048);21 }22 if(FD_ISSET(fdw,wrset)){23 printf(write cnt %d\n,cnt);24 write(fdw,buf,4096);25 }26 sleep(1); 27 }28 } ~ 运行结果 可以看见一开始管道读写缓冲区内没有数据所以读管道肯定会阻塞那么写管道肯定先执行即上面先执行write cnt 0 然后有数据了之后读管道就不会被阻塞了开始读数据然后二者交替进行。 当进行到一定时间因为每回写入4096字节却只读取2048个字节时间长了写空间肯定是会满的此时写进程就被堵塞当读取一定空间之后才会继续读具体表现为一开始是二者交替进行后面就变成了读的频次要高于写的频次但确实是可以永久执行下去的。 但如果我们稍微修改一下代码就会出问题我们将上面的代码的写入管道的数据大小改成4097理论上不应该会有影响但实际上会时该程序永久阻塞。 为什么 首先我们可以使用ulimit -a查看unix系统的所有限定大小的设定其中就包含pipe管道文件的大小 从上图的pipe size可以知道管道的读写缓冲区大小为512字节*8等于4096个字节大小因为select认为写就绪的条件是写缓冲区为空多空为空上图已经说过是4096个字节也就是说当写缓冲区有4096个空字节时说明该缓冲区为空这个时候才能往里面写入数据。 当我们改成4097时我们想象如下场景现在的管道和暂存区全部都填满了还剩下一个写缓冲区是空的这个时候select认为写管道应该是就绪的因为正好有4096个字节但此时我们往里面写的时候却是写入4097个空间但是此时多了一个字节写不进去写不进去就永远阻塞在这里了。 也就是实际上的写入行为和select认为的就绪行为之间有差异造成的问题所以我们写管道的时候填的数据大小不能超过4096小于4096是可以的但是大于4096的话就可能会造成永久阻塞。 select实现原理 我也听的不是很懂先记录一下老师的笔记等后面学会了再来详细解释一下。 调用select 等后面深入理解了再来填坑感觉云里雾里的这节。
http://www.tj-hxxt.cn/news/132500.html

相关文章:

  • 做设计做网站信息技术制作网站首页
  • 网站的空间和域名是啥湖北联诺建设网站
  • 做网站的目的与意义做做网页
  • 友情链接有哪些展现形式株洲seo优化官网
  • 邯郸市教育考试院网站网站首页适配规则
  • 国家骨干高职院校建设网站网站建设数据库软件英文
  • html网站登陆注册怎么做海南代理注册公司
  • 查询网站空间的服务商今天泰安刚刚发生的新闻
  • 成都网站建设 Vr功能 卓 公司php做网站技术
  • 新网站如何做搜索引擎收录上海哪家做网站关键词排名
  • 宝塔wordpress ip访问全网最低价seo
  • 海口免费做网站信息服务平台怎么赚钱
  • 漂亮的网站改版中 html代码网站建设和前端开发的区别
  • 网站建设与域名建设微信代运营
  • 企业网站名称怎么写企业邮箱域名解析
  • 重庆做木门网站公司简介本地营销型网站建设
  • 桂林的网站建设公司cms开发教程
  • 房地产网站的设计要求接兼职建设网站
  • 网站被加黑链免费推广网站排名
  • 网站的设计理念专业网站制作需要多少钱
  • 招聘网站开发需要多长时间什么响应式网站
  • 网站建设视频教程最新外贸网站高端定做
  • 黄岛网站建设个人注册公司代理
  • 馀姚网站建设wordpress怎么填写
  • 郑州 做网站宝和网站建设
  • 哪里有做网站系统的app平台搭建
  • 做网站接电话一般要会什么问题济南英文网站建设
  • 新网站如何做搜索引擎收录校园网站建设重要性
  • 额敏网站建设汕头seo建站
  • 二手车网站开发没有基础学做网站