C++ web server服务器 开发

article/2025/10/22 10:26:11

本文是牛客网Linux 高并发服务器开发视频教程的笔记


1、预备知识

1.1 Linux与远程

使用ssh在widows中控制Linux系统,使用vscode控制代码
使用g++编译

1.1 静态库与动态库

静态库与动态库的制作、区别

1.2 makefile

makefile文件操作就是指定所有源文件的编译顺序,因为一个正式的项目会有很多很多源文件,不可能一个个的手动g++,而且各种文件的编译可能还有先后关系,所有要有一个专门的东西来确定编译顺序和编译关系,这个命令就是make。makefile文件

1.3 GDB调试工具

编译时 -g

1.4 文件的io

标准c库io的实现是调用Linux本身的文件操作方式,这种涉及到内存与硬盘的交互的,在不同的平台都可以使用标准c库实现,但是c库回去调用不同平台对应的方法。c库对于文件操作是有缓冲区的存在的,对文件的操作会先对缓冲区操作,最后一下写进磁盘,因此一定要特别注意flush命令的使用

1.5 文件的创建与打开、读写

使用open()打开文件,如果打开成功则返回文件描述符,如果打开失败则返回最近的错误码errno,errno是linux内置的错误号。可以通过perror查看erron对应的错误描述。
使用open()也可以创建一个新的文件,在open函数中多一个O_CREAT参数即可
当一个文件被open函数打开后可以通过read和write函数进行读写操作
下面的代码是将一个文件中的内容复制到一个本来不存在的文件中:

#include<unistd.h>
#include<stdio.h>
#include<sys/stat.h>
#include<sys/types.h>
#include<fcntl.h>
using namespace std;
int main()
{//1、读取待复制的文件int srcfd=open("/home/lanpangzi/Desktop/Linux/lession02/test01.cpp",O_RDONLY);if(srcfd==-1){perror("open");return -1;}printf("文件打开成功\n");//2、创建一个待粘贴的新的空文件,用来写入int targetfd=  open("cpy.cpp",O_WRONLY|O_CREAT,0664);if(targetfd==-1){perror("open");return -1;}printf("文件创建成功\n");//3、循环读写将文件完全拷贝进去char buff[1024]{};int read_re = 0;while((read_re=read(srcfd,buff,sizeof(buff)))>0){write(targetfd, buff,read_re);}printf("文件复制成功\n");//4、关闭所有打开的文件close(srcfd);close(targetfd);printf("文件关闭成功\n");return 0;
}

2、 Linux 多进程开发

2.1 创建和使用子进程

子进程注意需要使用wait()进行回收,否则容易产生僵尸进程

#include<sys/types.h>
#include<unistd.h>
#include<stdio.h>int main(){printf("公共段1\n"); //这一句只会执行两遍// 创建子进程pid_t n_pid = fork(); // fork之后的语句父子进程都会执行printf("公共段2\n"); //这一句会执行两遍// 原来的父进程和新创建的子进程pid不同,通过判断n_pid来判断当前是父进程还是子进程if(n_pid>0){printf("当前是父进程!\n");printf("当前进程的pid: %d, 当前进程的父进程pid: %d\n", getpid(),getppid());}else if(n_pid==0){printf("当前是子进程!\n");printf("当前进程的pid: %d, 当前进程的父进程pid: %d\n", getpid(),getppid());}printf("卧槽真牛逼!\n"); //这一句会执行两遍return 0;
}

2.2 GDB多进程调试

GDB只能跟踪一个进程,但是可以在fork之前设置GDB调试工具是跟踪父进程还是跟踪子进程,以及在跟踪这个进程时另一个进程的状态

2.3 调用其他程序

使用execl() 函数可以直接跳出当前程序直接去执行其他的程序,exec函数族是几个长得长不多的函数,功能也都是调用一个程序,只是参数传递方式或环境变量设置不同

2.4 进程间的通信

2.4.1 使用匿名pipe管道进行通信

#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
int main(){// 创建管道标识,用于接收但会的两个文件描述符// pipefd[0]->读取pipefd[1]->写入int pipefd[2];// 创建管道,返回值用来判断是否创建成功int ret = pipe(pipefd);if(ret==-1){perror("pipe");exit(0);}pid_t pid = fork();if(pid>0){// 父进程,向管道中写入信息char * str = "我是父进程写入的信息";char buf[1024]={0};while (1){read(pipefd[0],buf,sizeof(buf));printf("这里是父进程,读取的信息为:%s\n",buf);write(pipefd[1],str,strlen(str));printf("这里是父进程, pid:%d,正在写入文件\n", getpid());sleep(1);}}else{// 子进程// 从管道中读取信息char * str = "我是子进程写入的信息";char buf[1024]={0};while (1){write(pipefd[1],str,strlen(str));printf("这里是子进程, pid:%d,正在写入文件\n", getpid());sleep(1);read(pipefd[0],buf,sizeof(buf));printf("这里是子进程,读取的信息为:%s\n",buf);}}return 0;
}   

2.4.2 使用有名管道 FIFO进行通信

写端:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>int main()
{// 创建之前需要先判断文件是否已经存在int ret = access("fifo1",F_OK);if(ret==-1){// 如果不存在则创建管道ret = mkfifo("fifo1",0664);if (ret==-1){perror("fifo");exit(0);}}//打开管道准备写入int fd=open("fifo1",O_WRONLY);if(fd==-1){perror("open");exit(0);}for(int i=0;i<100;i++){char buf[1024];sprintf(buf, "hello, %d\n", i);printf("write data : %s\n", buf);write(fd, buf, strlen(buf));}close(fd);return 0;
}

读端:

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>int main()
{int fd = open("fifo1",O_RDONLY);if(fd==-1){perror("read");exit(0);}while(1){char buf[1024]={0};int len = read(fd,buf,sizeof(buf));if(len==0){printf("写端断开连接\n");break;}printf("read buf: %s\n", buf);}return 0;
}

2.4.3 使用内存映射进行通信

#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags,int fd, off_t offset);
int munmap(void *addr, size_t length);

内存映射就是将磁盘中的文件映射到内存中,直接进行修改,效率比较高

2.4.4 使用信号通信

和QT那个SIGNAL差不多,就是告诉一个进程发生了什么事,也称为软件中断,信号是由内核执行的,信号使用之前需要先注册一下,这样当信号发生时可以执行对应的函数。
信号集是一个存储一组信号的数据结构,可以使用一系列的函数来操作信号集

2.4.5 共享内存通信

多个进程通过唯一的内存标识符访问同一块内存,可以通过ftok()根据key生成标识符,然后通过这个标识符创建或者访问某一块共享内存。

2.4.6 守护进程

了解守护进程之前先需要了解 终端、进程组(一堆进程)、会话(一堆进程组)。守护进程就是一个一直在后台运行的进程,断掉了它与控制台一切联系,就连输入输出都是重定向到指定的文件的。

3、Linux多线程开发

子线程就像是在主线程里面运行一个新的函数,但是这个函数的执行不会阻塞主线程。由于和主线程是一个cpp文件,所以它们的变量啥的都是共享的,如果主线程是由pthread_exit()退出主线程的话,不会影响其他正常运行的线程。

其他的术语:线程分离(pthread_detach())、线程取消(pthread_cancel())、线程属性(pthread_attr_***)、线程同步(), 互斥锁、死锁、读写锁、生产者消费者模型、信号量(sem_t)

4、网络编程

4.1 网络结构模式

C/S, B/S

4.2 MAC地址、IP地址、端口

4.3 网络模型

4.3.1 七层网络模型

OSI

4.3.1 TCP/IP四层模型

常见的协议 (protocol)
应用层协议:FTP, HTTP, NFS
传输层协议:TCP, UDP
网络层协议:IP, ICMP, IGMP
网络接口层协议:ARP, RARP

协议最终体现在传输的数据的格式

4.4 socket

socket(套接字),直译为插座,在linux中用于表示进程间进行网络通信的特殊文件类型,也就是说他是一个文件,用来实现进程间的通信,但是进行通信的进程在不同的主机中,主机之间通过网络连接。

4.5 字节序

就跟阅读文字一样,计算机处理数据的单位是字节,就像是一个个的字,那么处理的时候是从左往右处理还是从右往左处理呢?这个就是字节序,一个字节就没有字节序的说法。字节序有两种,大端字节序(Big-Endian)和小端字节序(Little-Endian),不同的计算机可能采用不同的字节序,因此当收到文件后需要判断字节序与本机字节序是否相同。
注意,高位字节序低位字节序是相对与一个字节来说的,所以判断的时候不是看数的增长方向,而是一个int型变量中不同位置放的是什么,另外可以借助数组中索引越小内存地址越小的特性。

    union {short value;char bytes[sizeof(short)];} test;test.value=0x0102;// 01是这个数的高位,02是这个数的低位// 01(高位)在内存中地址更小,所以是大端字节序,整数的高位在内存的低处if(test.bytes[0]==1 && test.bytes[1]==2) printf("大端字节序\n");// 01(高位)在内存中地址更大,所以是小端字节序,整数的高位在内存的高处else if (test.bytes[0]==2 && test.bytes[1]==1) printf("小端字节序\n");

为了防止字节序不同导致的问题,将数据上传到网络之前统统转换为大端字节序,然后进入网络,这样接收方每次接收到文件时都是大端的,然后根据自己的字节序判断是否需要转换

4.6 socket地址

socket地址封装了ip和端口,方便进行通信

4.7 TCP通信流程

在这里插入图片描述

4.8 本地创建服务器和客户端进行通信

一个可以双向通信的本地server和client,但是只能一人一句,而且第一句话必须由client发起
server.cpp

// TCP通信服务器端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<iostream>int main(){// 1、创建监听的socketint lfd = socket(AF_INET, SOCK_STREAM,0);if (lfd==-1){perror("socket");exit(-1);}// 2、绑定本地的IP和端口,为了统一ip和端口的格式,将其封装在 sockaddr_in类的结构体中struct sockaddr_in saddr;saddr.sin_family = AF_INET;// ip可以是任意的,这样这个主机才可以接受到来自任意ip的客户端的连接saddr.sin_addr.s_addr = INADDR_ANY; saddr.sin_port=htons(9999); // 端口为9999,通过htons转换为网络字节序int bind_ret = bind(lfd, (sockaddr *)&saddr, sizeof(saddr));if (bind_ret==-1){perror("bind");exit(-1);}// 3、监听,监听这个socket上有无连接进来,8是未连接和已连接的客户端数量之和的最大值// 相当于开启一个后开进程,不会阻塞当前程序int listen_ret=listen(lfd,8);if(listen_ret==-1){perror("listen");exit(-1);}// 4、接收客户端连接struct sockaddr_in clientaddr; // 定义客户端的地址,将来接收到客户端相关信息后放到这个里面socklen_t len=sizeof(clientaddr);//   accept是阻塞函数,直到有客户端连接上,否则一直阻塞int cfd = accept(lfd,(struct sockaddr*)&clientaddr, &len);if(cfd==-1){perror("accept");exit(-1);}// 输出客户端信息char client_ip[16];// 将字符串转换为可以192.168.1.1 的字符串形式inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, client_ip, sizeof(client_ip));unsigned short clientport = ntohs(clientaddr.sin_port);printf("connected! client ip is %s, port is %d\n", client_ip, clientport);while(1){// 5、获取客户端的数据并发送数据char rec_buf[1024]={0};//  read是阻塞函数,连接上之后accept继续往下走,但是如果客户端没有信息发送过来read会阻塞住int read_ret = read(cfd, rec_buf, sizeof(rec_buf));if (read_ret==-1){perror("read");exit(-1);} else if (read_ret>0){printf("cliend: %s", rec_buf);}else if(read_ret==0){printf("client is closed!\n");break;}// 给客户端发送数据char data[1024];fgets(data, sizeof(data),stdin);write(cfd, data,strlen(data));}// 6、关闭所有打开的文件描述符close(cfd);close(lfd);return 0;}

client.cpp

// TCP通信客户端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<iostream>int main(){// 1、创建socket,int fd = socket(AF_INET, SOCK_STREAM,0);if (fd==-1){perror("socket");exit(-1);}// 2、设置服务器的 IP 192.168.18.128和端口 9999,这样客户端才知道要去哪里找struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;inet_pton(AF_INET, "192.168.18.128",&serveraddr.sin_addr.s_addr);serveraddr.sin_port = htons(9998);// 3、连接服务器int con_ret = connect(fd, (struct sockaddr*)&serveraddr,sizeof(serveraddr));if (con_ret==-1){perror("connect");exit(-1);}//   如果进行到这里说明客户端已经成功连接到了主机printf("connected!\n");while(1){// 4、向服务器端发送数据,客户端是先发送send,再接受recvchar data[1024];fgets(data, sizeof(data),stdin);write(fd,data,strlen(data));// 写进去了socket会自动发送// 5、获取服务器发送回来的数据char recv_buf[1024]={0};int read_ret = read(fd, recv_buf, sizeof(recv_buf));// 返回的是读取到的文件的长度if (read_ret==-1){perror("read");exit(-1);} else if (read_ret>0){printf("server: %s", recv_buf);}else if(read_ret==0){printf("server is closed!\n");break;}}// 6、关闭所有打开的文件描述符close(fd);return 0;
}

4.9 使用多进程实现并发

#include<iostream>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<wait.h>
#include<error.h>void recyleChild(int arg){while(1){int wait_ret = waitpid(-1,NULL,WNOHANG);if(wait_ret==-1){// 所有在子进程都已回收完毕,没有还活着的了break;}else if(wait_ret==0){// 所有能回收的子进程都已经回收了,没有需要回收的了,但是还有子进程还活着break;} else if(wait_ret>0){// 有子进程被回收了printf("子进程%d被回收了\n",wait_ret);}}
}int main(){struct sigaction act;act.sa_flags = 0;sigemptyset(&act.sa_mask);act.sa_handler = recyleChild;// 注册信号捕捉,用来回收结束了的子进程// 如果子进程exit会向父进程发送一个SIGCHLD信号,这个注册的函数act就是用来处理这个信号的sigaction(SIGCHLD,&act,NULL);int lfd = socket(AF_INET,SOCK_STREAM,0);if(lfd==-1){perror("socket");exit(-1);}struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = INADDR_ANY;saddr.sin_port=htons(9999);int bind_ret = bind(lfd,(sockaddr *) &saddr,sizeof(saddr));if(bind_ret==-1){perror("bind");exit(-1);}// 开启监听的开关,不会阻塞,就相当于打开了一个开关int listen_ret = listen(lfd,8);if(listen_ret==-1){perror("listen");exit(-1);}// 将accept放在循环里面,每一次连接都创建一个子进程来进行通讯while(1){struct sockaddr_in clientaddr;socklen_t clientaddr_len = sizeof(clientaddr);int cfd = accept(lfd, (struct sockaddr*)&clientaddr, &clientaddr_len);if(cfd==-1){// 如果accept被信号打断会报EINTR错误,直接continue即可// 因为这个程序会需要使用信号来回收结束的子进程,但是子进程会打断accept的阻塞状态if(errno==EINTR){continue;}perror("accept");exit(-1);}// 有连接到的客户端,创建一个新的子进程进行通讯pid_t pid=fork();if(pid==0){// 进入子进程printf("服务器接收到客户端连接,并创建了我这个子进程,我的pid: %d\n", getpid());// 输出客户端信息,并进行通讯char client_ip[16];inet_ntop(AF_INET,&clientaddr.sin_addr,client_ip,sizeof(client_ip));unsigned short clientport = ntohs(clientaddr.sin_port);printf("connected! client ip is %s, port is %d\n", client_ip,clientport);// 开始进行通讯while(1){// 获取客户端的数据并发送数据char rec_buf[1024]={0};//  read是阻塞函数只有当cfd中有数据过来时才会继续,否则while直接循环起飞了int read_ret = read(cfd, rec_buf, sizeof(rec_buf));if (read_ret==-1){perror("read");exit(-1);} else if (read_ret>0){printf("cliend: %s", rec_buf);}else if(read_ret==0){printf("client is closed!\n");break;}// 给客户端发送数据char data[1024];fgets(data, sizeof(data),stdin);write(cfd, data,strlen(data));}close(cfd);exit(0);// 退出当前子进程,如果不进行其他的操作这个子进程的资源就无法回收了 }}close(lfd);// 关闭服务器socketreturn 0;
}

4.10 使用多线程实现并发

#include<iostream>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<pthread.h>
#include<thread>struct sockInfo
{int fd;pthread_t tid;struct sockaddr_in addr;
};// 为了能让working函数使用那些局部变量,将这个局部变量统统存放在这个数组中
// 128表示最多连接128个客户端,并将它们的数据存在在这个数组中
struct sockInfo sockinfos[128];void* working(void * arg){// 进入子线程// 子线程函数,用于和客户端通信,需要传入相关的参数struct sockInfo * pinfo=(struct sockInfo *) arg;std::cout<<"服务器接收到客户端连接,并创建了我这个子线程,我的id:"<<std::this_thread::get_id()<<std::endl;// 输出客户端信息,并进行通讯char client_ip[16];inet_ntop(AF_INET,&pinfo->addr.sin_addr,client_ip,sizeof(client_ip));unsigned short clientport = ntohs(pinfo->addr.sin_port);printf("connected! client ip is %s, port is %d\n", client_ip,clientport);// 开始进行通讯while(1){// 获取客户端的数据并发送数据char rec_buf[1024]={0};//  read是阻塞函数只有当cfd中有数据过来时才会继续,否则while直接循环起飞了int read_ret = read(pinfo->fd, rec_buf, sizeof(rec_buf));if (read_ret==-1){perror("read");exit(-1);} else if (read_ret>0){printf("cliend: %s", rec_buf);}else if(read_ret==0){printf("client is closed!\n");break;}// 给客户端发送数据char data[1024];fgets(data, sizeof(data),stdin);write(pinfo->fd, data,strlen(data));}close(pinfo->fd);pinfo->fd=-1;return NULL;
}int main(){int lfd = socket(AF_INET,SOCK_STREAM,0);if(lfd==-1){perror("socket");exit(-1);}struct sockaddr_in saddr;saddr.sin_family = AF_INET;saddr.sin_addr.s_addr = INADDR_ANY;saddr.sin_port=htons(9999);int bind_ret = bind(lfd,(sockaddr *) &saddr,sizeof(saddr));if(bind_ret==-1){perror("bind");exit(-1);}// 开启监听的开关,不会阻塞,就相当于打开了一个开关int listen_ret = listen(lfd,8);if(listen_ret==-1){perror("listen");exit(-1);}// 初始化数据,每一个,addr初始化成0,fd和tid初始化成-1int max = sizeof(sockinfos)/ sizeof(sockinfos[0]);for(int i=0;i<max;i++){bzero(&sockinfos[i],sizeof(sockinfos[i]));sockinfos[i].fd=-1;sockinfos[i].tid=-1;}while(1){struct sockaddr_in client_addr;socklen_t len = sizeof(client_addr);int cfd=accept(lfd, (struct sockaddr*)&client_addr, &len);// 寻找一个全部变量去放接受到的客户端的相关信息struct sockInfo *pinfo;for(int i=0;i<max;i++){if(sockinfos[i].fd==-1){pinfo=&sockinfos[i];break;}if(i==max-1){// 128个位置全部满了,等待一秒再从头开始sleep(1);i=-1;// for这个循环体结束后会自动+1}}// 这个时候pinfo里面对应的就是一个全局的变量的pinfo->fd=cfd;memcpy(&pinfo->addr, &client_addr,len);// 结构体不能直接相等,要使用memcpy拷贝内存// 创建子线程用于通信// 有个问题,创建子线程的时候传入的参数是定义在这个while函数中的,但是working函数是在main函数之外的//  因此working函数并不能使用这些参数,因为working函数执行的时候这个主线程的这个while已经结束了,这些//  局部变量全部都已经释放了pthread_create(&pinfo->tid,NULL,working,pinfo);// 设置线程分离,再不阻塞的情况下实现线程的自动回收pthread_detach(pinfo->tid);}close(lfd); return 0;
}

4.11 TCP状态转换

三次握手建立连接、数据传输、四次挥手断开连接

4.12 半关闭、端口复用

半关闭可以用来进行单向传输,可以使用函数 int shutdown();实现
端口复用可以通过setsockpt函数来实现,端口复用可以解决当前程序要使用的端口仍然被其他程序占用的问题,就是可以让大家同时使用一个端口进行通讯

4.13 I/O多路复用(I/O多路转接)

I/O输入输出指的是对内存的操作

I/O多路复用使得程序可以同时监听多个文件描述符,能够提高程序的性能,Linux中实现多路复用的方式主要有select、epoll、和poll

4.13.1 select的使用

server.cpp

#include<iostream>
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/select.h>int main(){int lfd=socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family=AF_INET;saddr.sin_addr.s_addr = INADDR_ANY;int bind_ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));if(bind_ret==-1){perror("bind");exit(-1);}int listen_ret = listen(lfd,8);if(listen_ret==-1){perror("listen");exit(-1);}// 创建fd_set,用来存放要监测的文件描述符fd_set rdset,tmp; // 一个用来保存原来的状态,一个用来送给内核FD_ZERO(&rdset);FD_SET(lfd,&rdset);int maxfd=lfd;while(1){tmp=rdset;// 调用select,让内核来检测哪些文件描述符有数据int select_ret = select(maxfd+1,&tmp,NULL,NULL,NULL);if(select_ret==-1){perror("select");exit(-1);}else if(select_ret==0){continue;}else if(select_ret>0){// 有select_ret个文件描述符产生了变化// 这个if用来判断有没有新的连接产生,如果有则创建新的cfd用于通信if (FD_ISSET(lfd,&tmp)){// lfd这个文件描述符专门用来检测是否有新的客户端链接进来了,如果有// lfd就会发生变化,但是lfd并不进行后续的通信,通信交给下面创建的cfdprintf("client connected!\n");struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len);FD_SET(cfd,&rdset); // 将新的文件描述符添加进集合中FD_SET(cfd,&tmp);maxfd = maxfd>cfd?maxfd:cfd;}// 遍历所有的fd,从lfd+1开始,因为lfd只是用来判断是否有新的连接,不用来进行通信printf("start detect fd\n");for(int i=lfd+1;i<=maxfd;++i){printf("now fd is:%d\n",i);if(FD_ISSET(i,&tmp)){// 进了这个if说明文件描述符i发生了变化,可以进行通讯char buf[1024]={0};// read会阻塞住,直到client向server发送数据int len =read(i,buf,sizeof(buf));if(len==-1){perror("read");exit(-1);}else if(len==0){printf("client closed!\n");close(i);FD_CLR(i,&rdset);}else if(len>0){printf("read buf: %s\n",buf);write(i,buf,strlen(buf)+1); // 回射}}}}}close(lfd);return 0;
}

4.13.2 poll的使用

poll与select相比,好像只解决了1024个的个数问题,其他的问题比如遍历、从用户态到内核态的转换都还是存在的。poll只是比select更灵活更容易管理

#include<iostream>
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/poll.h>int main(){int lfd=socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family=AF_INET;saddr.sin_addr.s_addr = INADDR_ANY;int bind_ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));if(bind_ret==-1){perror("bind");exit(-1);}int listen_ret = listen(lfd,8);if(listen_ret==-1){perror("listen");exit(-1);}// 定义一个存放所有pollfd的数组,这个数组和select中的文件描述符不同的是我可以设置的很大,可以超过1024struct pollfd pfds[2048];// 初始化for(int i=0;i<2048;++i){pfds[i].fd=-1; // 初始化为-1表示还没有监听pfds[i].events=POLLIN; // 默认监听读时间  }pfds[0].fd=lfd; // 将最开始的监听描述符放进去,这玩意仅仅用来监听是否有连接进来,不用于通信int nfds = 0;  // 和select一样,需要记录最大的那个文件描述符的索引while(1){// 调用poll,让内核来检测哪些文件描述符有数据int poll_rect = poll(pfds, nfds+1, -1);if(poll_rect==-1){perror("poll");exit(-1);}else if(poll_rect==0){continue;}else if(poll_rect>0){// 有poll_ret个文件描述符产生了变化// 这个if用来判断有没有新的连接产生,如果有则创建新的cfd用于通信// 注意是将监听描述符与检测的状态进行与运算if (pfds[0].revents&POLLIN){// lfd这个文件描述符专门用来检测是否有新的客户端链接进来了,如果有// lfd就会发生变化,但是lfd并不进行后续的通信,通信交给下面创建的cfdprintf("client connected!\n");struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len);// 将新的文件描述符添加进集合中for(int i=1;i<2048;++i){if (pfds[i].fd==-1){pfds[i].fd=cfd;pfds[i].events=POLLIN;if(nfds<i){nfds=i;}break;}}}// 遍历所有的fd,从lfd+1开始,因为lfd只是用来判断是否有新的连接,不用来进行通信printf("start detect fd\n");for(int i=1;i<=nfds;++i){printf("now fd is:%d\n",pfds[i].fd);if(pfds[i].revents&POLLIN){// 进了这个if说明文件描述符pfds[i].fd发生了变化,可以进行通讯char buf[1024]={0};// read会阻塞住,直到client向server发送数据int len =read(pfds[i].fd,buf,sizeof(buf));if(len==-1){perror("read");exit(-1);}else if(len==0){printf("client closed!\n");close(pfds[i].fd);pfds[i].fd=-1;}else if(len>0){printf("read buf: %s\n",buf);write(pfds[i].fd,buf,strlen(buf)+1); // 回射}else{printf("不知道什么原因,信息未能发送\n");}}}}}close(lfd);return 0;
}

4.13.3 epoll的使用

epoll有两种工作模式,分别为:
LT模式(水平触发)和ET模式(边沿触发) ,LT模式就是如果内核通知你有一个文件描述符已就绪但是你未处理那么内核就会一直通知你,而ET则相反,只会通知你一次,不管你有没有处理这个文件操作符。

LT模式是epoll默认的触发模式
使用ET模式时需要注意read是默认阻塞的,需要将read读取的文件描述符的属性设置为非阻塞

epoll_server.cpp

#include<iostream>
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<stdlib.h>
#include<string.h>
#include<sys/time.h>
#include<sys/types.h>
#include<sys/epoll.h>int main(){int lfd=socket(PF_INET,SOCK_STREAM,0);struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family=AF_INET;saddr.sin_addr.s_addr = INADDR_ANY;int bind_ret = bind(lfd,(struct sockaddr *)&saddr,sizeof(saddr));if(bind_ret==-1){perror("bind");exit(-1);}int listen_ret = listen(lfd,8);if(listen_ret==-1){perror("listen");exit(-1);}// 使用epoll检测哪些文件描述符发生了变化// 使用create创建epoll实例int epfd = epoll_create(1);// 将监听描述符lfd加入到epfd中struct epoll_event epev;epev.events = EPOLLIN;epev.data.fd = lfd;epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&epev);// epevs用来保存epoll检测到的发生变化的文件描述符struct epoll_event epevs[1024]; while(1){// epoll_wait_ret表示有几个文件描述符发生了变化,具体是哪些发生了变化见epevs数组// 1024表示最大检测的个数是1024,-1 表示阻塞int epoll_wait_ret =  epoll_wait(epfd,epevs,1024,-1);if (epoll_wait_ret==-1){perror("epoll_wait");exit(-1);}for(int i=0;i<epoll_wait_ret;++i){if (epevs[i].data.fd==lfd){// 如果发生变化的文件描述符中包括lfd,表示有新的客户端连接进来了printf("client connected!\n");struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int cfd=accept(lfd,(struct sockaddr*)&clientaddr,&len);epev.events = EPOLLIN;epev.data.fd=cfd;epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&epev); // 将cfd添加进epoll实例中}else{// 文件描述符epevs[i].data.fd发生了变化,表示有文件过来了char buf[1024]={0};// read会阻塞住,直到client向server发送数据int len =read(epevs[i].data.fd,buf,sizeof(buf));if(len==-1){perror("read");exit(-1);}else if(len==0){printf("client closed!\n");// 将epevs[i].data.fd这个文件描述符移出epoll实例epoll_ctl(epfd,EPOLL_CTL_DEL,epevs[i].data.fd,NULL);close(epevs[i].data.fd);}else if(len>0){printf("read buf: %s\n",buf);write(epevs[i].data.fd,buf,strlen(buf)+1); // 回射}else{printf("不知道什么原因,信息未能发送\n");}}}}close(lfd);close(epfd); //注意创建的epoll实例对应的文件描述符也需要关闭return 0;
}

4.14 UDP通信

UDP通信的流程,比tcp的握手挥手啥的要简单很多,直接创建socket然后一个发送一个接受就好了
在这里插入图片描述
udp_server.cpp

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string>
#include<arpa/inet.h>
#include<string.h>int main(){// UDP使用的是SOCK_DGRAM不是TCP的SOCK_STREAMint fd = socket(PF_INET,SOCK_DGRAM,0);if (fd==-1){perror("socket");exit(-1);}struct sockaddr_in saddr;saddr.sin_port = htons(9999);saddr.sin_family=AF_INET;saddr.sin_addr.s_addr = INADDR_ANY;// 绑定int bind_ret = bind(fd,(struct sockaddr *)&saddr,sizeof(saddr));if(bind_ret==-1){perror("bind");exit(-1);}// 通信while(1){// 接收数据char recvbuf[128];char ipbuf[16];struct sockaddr_in clientaddr;socklen_t len = sizeof(clientaddr);int num = recvfrom(fd,recvbuf,sizeof(recvbuf),0,(struct sockaddr *)&clientaddr, &len);if(num==-1){perror("recvfrom");exit(-1);}else{inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ipbuf,sizeof(ipbuf));unsigned short clientport = ntohs(clientaddr.sin_port);printf("client IP: %s,port: %d\n",ipbuf,clientport);printf("client: %s\n",recvbuf);}// 发送数据sendto(fd,recvbuf,strlen(recvbuf)+1,0,(struct sockaddr *)&clientaddr, sizeof(clientaddr));}close(fd);return 0;
}

udp_client.cpp

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string>
#include<arpa/inet.h>
#include<string.h>int main(){// UDP使用的是SOCK_DGRAM不是TCP的SOCK_STREAMint fd = socket(PF_INET,SOCK_DGRAM,0);if (fd==-1){perror("socket");exit(-1);}// 2、设置服务器的 IP 192.168.18.128和端口 9999,这样客户端才知道要去哪里找struct sockaddr_in serveraddr;serveraddr.sin_port = htons(9999);serveraddr.sin_family=AF_INET;inet_pton(AF_INET, "192.168.18.128",&serveraddr.sin_addr.s_addr);int num=0;// 通信while(1){// 发送数据char sendbuf[128];sprintf(sendbuf,"hello I'm cliend, this is %d\n",num++);  // 格式化数据sendto(fd,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr *)&serveraddr, sizeof(serveraddr));// 接受数据char recvbuf[128];socklen_t len = sizeof(serveraddr);// 接受数据是通过fd接受的,和ip啥的没有关系了,直接设置为NULL也行int num = recvfrom(fd,recvbuf,sizeof(recvbuf),0,NULL, NULL);if(num==-1){perror("recvfrom");exit(-1);}else{printf("server: %s\n",recvbuf);}sleep(1);}close(fd);return 0;
}

4.14 广播

1、广播就是主机向子网中的所有计算机发送消息,广播消息包含一个特殊的ip地址,这个ip地址的主机标志部分二进制全部为1
2、什么是ip地址的主机标志部分: 192.168.23.21,前面的192.168.23是网络id后面的21是主机id,主机标志部分全部为1即255。当然,前几位是网络id后几位是主机id是由子网掩码决定的
3、需要注意,广播只能在局域网中使用,而且客户端必须要已经绑定了服务器广播使用的端口才能收到广播消息,因为虽然主机是向局域网内的所有计算机发送的消息,但是你不监听这个port当然是收不到的

注意:要想一个socket可以发送广播需要使用setsockopt()进行设置,就像设置端口复用那样

broad_server.cpp

// TCP通信服务器端
#include<stdio.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<iostream>int main(){// 1、创建监听的socket,注意使用的是UDP不是TCP,因此是SOCK_DGRAM,实际上广播使用哪个都可以,只是通常时UDPint lfd = socket(PF_INET,SOCK_DGRAM,0);if (lfd==-1){perror("socket");exit(-1);}// 2、要想进行广播,需要设置socket的广播属性int op=1;setsockopt(lfd,SOL_SOCKET,SO_BROADCAST,&op,sizeof(op));// 3、创建广播的地址struct sockaddr_in saddrs;saddrs.sin_family = AF_INET;saddrs.sin_port=htons(9999); // 端口为9999,通过htons转换为网络字节序inet_pton(AF_INET, "192.168.18.255",&saddrs.sin_addr.s_addr); // 255是特殊的,表示向192.168.18.内所有设备广播// 服务端只是发送广播,不需要bind,每次sendto时操作系统会自己指定一个网络端口进行发送// 4、通信,发送广播int num=0;while(1){char brosend[128];sprintf(brosend,"hello I'm server, this is %d brocast\n",num++);  // 格式化数据// 直接向saddrs对应的地址发送消息,因为最后一位是255,所以是广播// 打印我广播的目标ipchar ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &(saddrs.sin_addr.s_addr), ip, INET_ADDRSTRLEN);printf("Bro IP: %s\n", ip);printf("Bro Port: %d\n", ntohs(saddrs.sin_port));// 发送广播信息,由于最后一位是255,因为这个消息会发送给这个网络id下所有的设备sendto(lfd,brosend,strlen(brosend)+1,0,(struct sockaddr *)&saddrs, sizeof(saddrs));sleep(1);}// 5、关闭所有打开的文件描述符close(lfd);return 0;}

broatclient.cpp

#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string>
#include<arpa/inet.h>
#include<string.h>// 这个客户端只接受来自服务器的广播消息,不发送信息
int main(){// UDP使用的是SOCK_DGRAM不是TCP的SOCK_STREAMint fd = socket(PF_INET,SOCK_DGRAM,0);if (fd==-1){perror("socket");exit(-1);}// 2、设置可能发送消息过来的ip地址和端口,才可以接收到这个ip主机发送的广播信息struct sockaddr_in addr;addr.sin_port = htons(9999);addr.sin_family=AF_INET;// 注意,这里是将网络接口设置为这个计算机上的任意网络接口,表示我接受来自这台计算机上所有接口的数据addr.sin_addr.s_addr = INADDR_ANY; // 因为要在同一台主机上进行测试但是每一次运行这个程序都会使用bind相同的addr// 因此都会绑定到0.0.0.0,虽然这个ip很特殊,但是也不能被绑定两次,除非设置成可以复用int reuse = 1;// 设置为可以复用if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1) {perror("setsockopt");exit(-1);}// 绑定int bind_rect = bind(fd,(struct sockaddr*)&addr,sizeof(addr));if(bind_rect==-1){perror("bind");exit(-1);}// 查看这个进程绑定的ip和端口struct sockaddr_in taddr;socklen_t addrlen = sizeof(taddr);// 获取套接字绑定的本地地址if (getsockname(fd, (struct sockaddr *)&taddr, &addrlen) == 0) {char ip[INET_ADDRSTRLEN];inet_ntop(AF_INET, &(taddr.sin_addr.s_addr), ip, INET_ADDRSTRLEN);printf("Local IP: %s\n", ip);printf("Local Port: %d\n", ntohs(taddr.sin_port));} else {perror("getsockname");}// 不需要监听listen,listen通常时服务端使用,用于监听是否有新的连接请求,广播一般使用的时UDP,不需要建立连接// 即使是tcp连接,客户端也不需要listen,因为client是发起连接的一方,监测有无新的连接请求是服务器干的事情// 通信while(1){// 接受数据char recvbuf[128];// recvfrom默认是阻塞的,因此不用sleepint num=recvfrom(fd,recvbuf,sizeof(recvbuf),0,NULL,NULL);printf("server broadcast message: %s\n",recvbuf);}close(fd);return 0;
}

4.15 组播(多播)

1、与广播类似,但是目标是一组特定的客户端,而且组播既可以应用于局域网也可以用于广域网
2、只有加入了多播组的客户端才会接收到组播的数据
3、组播也有特定的ip限制

4.16 本地套接字实现进程间通信

使用本地套接字进行本地进程间的通信
注意:创建本地套接字之前应该先使用unlink删除可能存在的且无人使用的这个本地套接字文件,创建的本地套接字文件并不会自动删除

ipc_server.cpp

// 使用tcp进行本地进程间通讯
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<sys/un.h>int main(){// 上来先清楚可能重名的本地套接字unlink("server.sock");// 1、 创佳套接字int lfd = socket(AF_LOCAL, SOCK_STREAM,0);if(lfd==-1){perror("socket");exit(-1);}// 2、创建并绑定本地的套接字地址struct sockaddr_un addr;addr.sun_family=AF_LOCAL;strcpy(addr.sun_path,"server.sock");int bind_rect = bind(lfd, (struct sockaddr*)&addr,sizeof(addr));if(bind_rect== -1){perror("bind");exit(-1);}// 3、 监听int listen_rect=listen(lfd,100);if (listen_rect==-1){perror("listen");exit(-1);}// 4、等待客户端连接struct sockaddr_un clientaddr;socklen_t len=sizeof(clientaddr);int cfd = accept(lfd,(struct sockaddr*)&clientaddr,&len);if(cfd==-1){perror("accept");exit(-1);}printf("client socket filename:%s\n",clientaddr.sun_path);// 5、通信while(1){char buf[128];int recv_rect = recv(cfd,buf,sizeof(buf),0);if (recv_rect==-1){perror("recv");exit(-1);}else if(recv_rect==0){printf("client is closed\n");close(cfd);break;}else{printf("client: %s\n", buf);send(cfd,buf,len,0);}}close(lfd);return 0;
}

ipc_client.cpp

// 使用tcp进行本地进程间通讯
#include<stdio.h>
#include<string.h>
#include<unistd.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<sys/un.h>int main(){// 上来先清楚可能重名的本地套接字unlink("client.sock");// 1、 创佳套接字int cfd = socket(AF_LOCAL, SOCK_STREAM,0);if(cfd==-1){perror("socket");exit(-1);}// 2、创建并绑定本地的套接字地址struct sockaddr_un addr;addr.sun_family=AF_LOCAL;strcpy(addr.sun_path,"client.sock");int bind_rect = bind(cfd, (struct sockaddr*)&addr,sizeof(addr));if(bind_rect== -1){perror("bind");exit(-1);}// 3、指定服务器并连服务器,因此客户端需要提前知道服务器生成的本地的套接字的名称struct sockaddr_un serveraddr;serveraddr.sun_family=AF_LOCAL;strcpy(serveraddr.sun_path,"server.sock");// connect默认是阻塞的,直到连接建立int con_rect = connect(cfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr));if(con_rect==-1){perror("connect");exit(-1);}printf("connected!\n");// 4、通信int num=0;while(1){// 先发送数据char buf[128];sprintf(buf,"hello I am client %d\n",num++);send(cfd,buf,strlen(buf)+1,0);// 接受数据int recv_rect = recv(cfd,buf,sizeof(buf),0);if (recv_rect==-1){perror("recv");exit(-1);}else if(recv_rect==0){printf("server is closed\n");close(cfd);break;}else{printf("server: %s\n", buf);}sleep(1);}return 0;
}

5、项目实战

5.1 阻塞/非阻塞、同步/异步 (网络IO)

1、异步io是非阻塞的,同步io是阻塞的。教程一直以来使用的都是同步io,在使用recv等这个函数从cfd中读取信息时其实进程都是阻塞的,哪怕cfd中已经有了数据,只不过读取的速度很快,没有察觉而已。异步的话数据在读取时进程不会阻塞,当读取完了在通知进程可以使用了,进程直接去使用就好了
2、异步io需要使用特殊的系统调用函数,比如aio_read()、aio_write(),异步io使用起来很麻烦,一般不会使用
3、一个典型的网络io接口调用分为两个阶段,“数据就绪”(等待数据过来)和"数据读取"(从tcp缓冲区搬到内存)这两个阶段都是需要花费时间的

5.2 5种IO模型

  1. 阻塞 blocking,常规的read、recv,进程停下来等待文件的到达
  2. 非阻塞 Non-blocking,非阻塞的调用,结果总是返回,根据返回的结果判断文件是否已经到达
  3. IO复用 IO multiplexing, select、poll、epoll实现IO多路复用,同时检测多个文件描述符有无数据到达
  4. 信号驱动 signal-driven, 当io事件准备就绪后发送SIGIO信号告诉进程去处理
  5. 异步 asynchronous,使用异步io接口去处理文件

5.3 web服务器及HTTP协议

web server( 网页服务器),是一个服务器软件,主要功能就是对客户端发来的http请求做出回应

5.4 服务器编程的基本框架

服务器的程序种类繁多,但是基本的额框架都是一样的,不同的只是逻辑处理
一般有4块:

  1. I/O处理单元,等待并处理客户发送过来的请求,读写网络数据
  2. 逻辑处理单元,根据解析的信息进行一些逻辑处理,就是判断该干啥
  3. 网络存储单元,就是数据库啥的
  4. 请求队列,各个单元之间的通信方式

5.5 事件处理模式

服务器干的活主要是三种:I/O,信号和定时事件,有两种高效的事件处理模式,分别为Reactor和Proactor

1、Reactor模式:
主线程只负责监听文件描述符上是否有事件发生,有的话就通知其他工作线程进行处理,除此之外主线程不干其他事情。

2、proactor模式:
将所有的I/O操作都交给主线程来处理,工作线程只负责业务逻辑。

这两种模式都可以使用同步或者异步io实现,但是linux中由于异步io接口并不完善所以一般都是使用同步io去模拟proactor和reactor

5.6 线程池

线程池就是一系列的线程,当没有任务时所有线程睡眠,来一个唤醒一个去执行任务,因为唤醒一个线程比创建一个新的线程要快
特点:

  1. 空间换时间
  2. 是一组静态资源
  3. 所有的资源在创建时就已经分配好了,不需要动态分配
  4. 当服务器处理完一个客户连接后可以直接将资源放回池中,无需执行系统调用释放资源

http://chatgpt.dhexx.cn/article/OQdAXxnV.shtml

相关文章

Web 服务器的搭建

1.下载Nginx源码&#xff1a; wget http://nginx.org/download/nginx-1.19.4.tar.gz2.解压Nginx源码&#xff1a; tar -zxvf nginx-1.19.4.tar.gz 3.安装相关依赖 sudo apt-get install openssl libssl-dev libpcre3 libpcre3-dev zlib1g-dev –y4. 进入到nginx-1.19.4目录…

Tomcat服务器和Web开发介绍

Tomcat服务器和Web开发介绍 一、开启Web开发 什么是web开发 WEB&#xff0c;即网页的意思&#xff0c;它用于表示Internet主机上供外界访问的资源。 Internet上供外界访问的Web资源分为&#xff1a; 静态web资源&#xff08;如html 页面&#xff09;&#xff1a;指web页面中供…

C#开发自己的Web服务器

下载源代码 介绍 我们将学习如何写一个简单的web服务器&#xff0c;用于响应知名的HTTP请求&#xff08;GET和POST)&#xff0c;用C#发送响应。然后&#xff0c;我们从网络访问这台服务器&#xff0c;这次我们会说“Hello world!” 背景 HTTP协议 HTTP是服务器和客户机之间的通…

Web开发介绍

Web开发介绍 1 什么是web开发 Web&#xff1a;全球广域网&#xff0c;也称为万维网(www World Wide Web)&#xff0c;能够通过浏览器访问的网站。 所以Web开发说白了&#xff0c;就是开发网站的&#xff0c;例如下图所示的网站&#xff1a;淘宝&#xff0c;京东等等 那么我们…

搭建web服务器

1.要求搭建web服务器&#xff0c;能够访问到网页内容为“小胖&#xff0c;你咋这么胖呢&#xff01;” 2.要求搭建web服务器&#xff0c;创建基于域名的虚拟主机&#xff0c;能够使用www.xiaopang.com和www.dapang.com访问各自的网站网站存放路径分别为/xiaopang和/dapang,内容…

Web开发及服务器

转载自https://www.cnblogs.com/xdp-gacl/p/3729033.html。 一、基本概念 1.1、WEB开发的相关知识 WEB&#xff0c;在英语中web即表示网页的意思&#xff0c;它用于表示Internet主机上供外界访问的资源。 Internet上供外界访问的Web资源分为&#xff1a; 静态web资源&#x…

EI数据库免费检索入口

转载自&#xff1a;http://www.ei-istp.com/New_691.html 具体查询方式&#xff0c;详看链接。

数据库搜索与索引

索引是对数据库表中一列或多列的值进行排序的一种结构&#xff0c;使用索引可快速访问数据库表中的特定信息。如果想按特定职员的姓来查找他或她&#xff0c;则与在表中搜索所有的行相比&#xff0c;索引有助于更快地获取信息。 索引的一个主要目的就是加快检索表中数据&#x…

数据库 索引

多数数据库&#xff0c;使用 B 树&#xff08;Balance Tree&#xff09;的结构来保存索引。 B 树&#xff0c; 最上层节点&#xff1a;根节点 最下层节点&#xff1a;叶子节点 两者之间的节点&#xff1a;中间节点 B 树&#xff0c;显著特征&#xff1a;从根节点&#xff0c;到…

mysql全库搜索关键字_数据库 全文检索

一、概述 MySQL全文检索是利用查询关键字和查询列内容之间的相关度进行检索,可以利用全文索引来提高匹配的速度。 二、语法 MATCH (col1,col2,...) AGAINST (expr [search_modifier]) search_modifier: { IN BOOLEAN MODE | WITH QUERY EXPANSION } 例如:SELECT * FROM tab_n…

人文社科类文献去哪些数据库检索下载

查找下载人文社科类文献的数据库大盘点&#xff1a; 1、文献党下载器&#xff08;wxdown.org&#xff09; 大型文献馆&#xff0c;几乎整合汇集了所有中外文献数据库资源&#xff0c;可附带权限进入文献数据库查找下载文献&#xff0c;覆盖全科包括查找下载人文社科类文献的众…

数据库索引的实现原理

强烈建议参阅链接:http://www.linezing.com/blog/?p=798#nav-1 说白了,索引问题就是一个查找问题。。。 数据库索引,是数据库管理系统中一个排序的数据结构,以协助快速查询、更新数据库表中数据。索引的实现通常使用B树及其变种B+树。 在数据之外,数据库系统还维护着满足…

WoS数据库使用及检索示例

目录 快速了解一个领域的情况 1. 核心合集检索和所有数据库检索的区别 2. 检索结果分析 2.1 排序方式&#xff08;日期&#xff0c;被引频次&#xff09; 2.2 分析检索结果 2.3 精炼检索结果&#xff08;二次检索&#xff09; 2.4 创建引文报告 2.5 具体某一篇文献 快…

【MySQL】检索数据

每日鸡汤 &#xff1a; —— 若你困于无风之地&#xff0c;我将奏响高空之歌 要和我一起花 10 min 学一会 SQL 嘛&#xff1f; - 当然愿意&#xff0c;我美丽的小姐 &#xff08;封寝期间练就的自言自语能力越来越炉火纯青了~~~&#xff09; 前言&#xff1a; 本实验中所用数据…

数据库的检索(select)

今天我们学习一下数据库检索语句&#xff0c;由于经常用到&#xff0c;有需求的小伙伴欢迎来查看哦&#xff01; 一、简单的查询 --获取所以列 select * from T_table --获取部分列 select id, title from T_table 效果展示&#xff1a; 在...之间&#xff1a;Between.. a…

《MySQL必知必会》学习笔记之“数据库的检索”

文章目录 第一章 SQL与MySQL1 数据库基础2 什么是SQL3 客户机—服务器软件4 MySQL工具mysql命令行实用程序&#xff08;使用最多的实用程序之一&#xff09;MySQL AdministratorMySQL Query Browser 第二章 使用MySQL1 连接2选择数据库3了解数据库和表4 注释 第三章 检索数据1 …

最全最易理解的数据库查询教程

数据库查询 检索数据表中一个字段的内容检索数据表中多个字段的内容检索数据表中所有字段的内容带限制条件的查询表达式查询使用 WHERE 语句和逻辑表达式使用WHERE语句检索单范围数据使用WHERE语句检索双范围数据使用WHERE语句检索不匹配某条件的语句使用通配符[]模糊匹配数据内…

数据库基础知识——SELECT 语句(检索数据)

SQL使用 SQL&#xff08;发音为字母S-Q-L或sequel&#xff09;是结构化查询语言&#xff08;Structured Query Language&#xff09;的缩写。SQL是一种专门用来与数据库通信的语言。 SQL 语言特点&#xff1a; SQL 语言不区分大小写&#xff1b;在命令行窗口的 SQL 语句要以…

Python爬虫获取数据保存到数据库中(超级详细,保证一看就会)

1.简介介绍 -网络爬虫&#xff08;又称为网页蜘蛛&#xff0c;网络机器人&#xff0c;在FOAF社区中间&#xff0c;更经常的称为网页追逐者&#xff09;&#xff0c;是一种按照一定的规则&#xff0c;自动地抓取万维网信息的程序或者脚本。另外一些不常使用的名字还有蚂蚁、自动…

python数据爬取---简单页面的爬取

1、准备Requests和User Agent python中能实现爬虫功能的库有若干个&#xff0c;而最简单最容易上手的&#xff0c;要数Requests库&#xff0c;它是一个常用的http请求库&#xff0c;首先用pip install requests 进行安装才能使用。 User Agent一般翻译为用户代理&#xff0c;…