Linux系统中的信号

news2024/11/20 2:07:55

信号是由用户、系统或者进程发送给目标进程的信息,以通知目标进程某个状态的改变或系统异常。Linux信号可由如下条件产生:

  • 对于前台进程,用户可以通过输入特殊的终端字符来给它发送信号。比如输入Ctrl+C通常会给进程发送一个中断信号;
  • 系统异常。比如浮点异常和非法内存段访问;
  • 系统状态变化。比如alarm定时器到期将引起SIGALRM信号;
  • 运行kill命令或调用kill函数。

服务器程序必须处理(或至少忽略)一些常见的信号,以免异常终止。

发送信号

Linux下,一个进程给其他进程发送信号的API是kill函数。其定义如下

#include<sys/types.h>
#include<signal.h>
int kill(pid_t pid,int sig);

该函数把信号sig发送给目标进程;目标进程由pid参数指定。如果sig取值为0,则kill函数不发送任 何信号。但将sig设置为0可以用来检测目标进程或进程组是否存在,因为检查工作总是在信号发送之前就执行。不过这种检测方式是不可靠的。一方面由于进程PID的回绕,可能导致被检测的PID不是我们期望的进程的PID;另一方面,这种检测方法不是原子操作。

信号的处理方式

目标进程在收到信号时,需要定义一个接收函数来处理之。信号处理函数的原型如下

#include<signal.h>
typedef void(*__sighandler_t)(int);

信号处理函数只带有一个整型参数,该参数用来指示信号类型

除了用户自定义信号处理函数外,bits/signum.h头文件中还定义了信号的两种其他处理方式——SIG_IGN和SIG_DEL:

#include<bits/signum.h>
#define SIG_DFL((__sighandler_t)0)
#define SIG_IGN((__sighandler_t)1)

SIG_IGN表示忽略目标信号,SIG_DFL表示使用信号的默认处理方式。信号的默认处理方式有如下几种:结束进程(Term)、忽略信号 (Ign)、结束进程并生成核心转储文件(Core)、暂停进程(Stop),以及继续进程(Cont)。

中断系统调用

如果程序在执行处于阻塞状态的系统调用时接收到信号,并且为该信号设置了信号处理函数,则默认情况下系统调用将被中断,并且 errno被设置为EINTR。使用sigaction函数为信号设置SA_RESTART标志以自动重启被该信号中断的系统调用。 对于默认行为是暂停进程的信号(比如SIGSTOP、SIGTTIN),如果我们没有为它们设置信号处理函数,则它们也可以中断某些系统调用 (比如connect、epoll_wait)。POSIX没有规定这种行为,这是Linux独有的。

signal系统调用

为一个信号设置处理函数,可以使用下面的signal系统调用:

#include<signal.h>
_sighandler_t signal(int sig,_sighandler_t _handler)

sig参数指出要捕获的信号类型_handler参数是_sighandler_t类型的函数指针,用于指定信号sig的处理函数。 signal函数成功时返回一个函数指针,该函数指针的类型也是 _sighandler_t。这个返回值是前一次调用signal函数时传入的函数指针, 或者是信号sig对应的默认处理函数指针SIG_DEF(如果是第一次调用signal的话)。 signal系统调用出错时返回SIG_ERR,并设置errno。

sigaction系统调用 

设置信号处理函数的更健壮的接口是如下的系统调用:

#include<signal.h>
int sigaction(int sig,const struct sigaction*act,struct sigaction*oact);

sig参数指出要捕获的信号类型act参数指定新的信号处理方式oact参数则输出信号先前的处理方式。act和oact都是sigaction结构体类型的指针,sigaction结构体描述了信号处理的细节,其定义如下: 

struct sigaction
{
    #ifdef__USE_POSIX199309
    union
    {
        _sighandler_t sa_handler;
        void(*sa_sigaction)(int,siginfo_t*,void*);
    }
    _sigaction_handler;
    #define sa_handler__sigaction_handler.sa_handler
    #define sa_sigaction__sigaction_handler.sa_sigaction
    #else
    _sighandler_t sa_handler;
    #endif
    _sigset_t sa_mask;
    int sa_flags;
    void(*sa_restorer)(void);
};

该结构体中的sa_hander成员指定信号处理函数。sa_mask成员设置进程的信号掩码,以指定哪些信号不能发送给本进程。sa_mask是信号集sigset_t类型,该类型指定一组信号。sa_flags成员用于设置程序收到信号时的行为 。

信号集

        Linux使用数据结构sigset_t来表示一组信号。其定义如下:

#include<bits/sigset.h>
#define_SIGSET_NWORDS(1024/(8*sizeof(unsigned long int)))
typedef struct
{
    unsigned long int__val[_SIGSET_NWORDS];
}__sigset_t;

sigset_t实际上是一个长整型数组,数组的每个元素的每个位表示一个信号。这种定义方式和文件描述符集fd_set类似,Linux提供了如下一组函数来设置、修改、删除和查询信号集:

#include<signal.h>
int sigemptyset(sigset_t*_set)/*清空信号集*/
int sigfillset(sigset_t*_set)/*在信号集中设置所有信号*/
int sigaddset(sigset_t*_set,int_signo)/*将信号_signo添加至信号集中*/
int sigdelset(sigset_t*_set,int_signo)/*将信号_signo从信号集中删除*/
int sigismember(_const sigset_t*_set,int_signo)/*测试_signo是否在信号集中*/

        前文提到可以利用sigaction结构体的sa_mask成员来设置进程的信号掩码。此外,如下函数也可以用于设置或查看进程的信号掩码:

#include<signal.h>
int sigprocmask(int_how,_const sigset_t*_set,sigset_t*_oset);

_set参数指定新的信号掩码,_oset参数则输出原来的信号掩码。如果_set参数不为NULL,则_how参数指定设置进程信号掩码的方式,其可选值如表所示

如果_set为NULL,则进程信号掩码不变,仍然可以利用 _oset参数来获得进程当前的信号掩码。

        设置进程信号掩码后,被屏蔽的信号将不能被进程接收。如果给进程发送一个被屏蔽的信号,则操作系统将该信号设置为进程的一个被挂起的信号。如果我们取消对被挂起信号的屏蔽,则它能立即被进程接收到。如下函数可以获得进程当前被挂起的信号集

#include<signal.h>
int sigpending(sigset_t*set);

set参数用于保存被挂起的信号集。

统一事件源

        信号是一种异步事件:信号处理函数和程序的主循环是两条不同的执行路线。很显然,信号处理函数需要尽可能快地执行完毕,以确保该信号不被屏蔽(为了避免一些竞态条件,信号在处理期间,系统不会再次触发它)太久。一种典型的解决方案是:把信号的主要处理逻辑放到程序的主循环中,当信号处理函数被触发时,它只是简单地通知主循环程序接收到信号,并把信号值传递给主循环,主循环再根据接收到的信号值执行目标信号对应的逻辑代码。信号处理函数通常使用管道来将信号“传递”给主循环:信号处理函数往管道的写端写入信号值,主循环则从管道的读端读出该信号值。那么主循环怎么知道管道上何时有数据可读呢?这很简单,我们只需要使用I/O复用系统调用来监听管道的读端文件描述符上的可读事件。如此一来,信号事件就能和其他I/O事件一样被处理,即统一事件源。 很多优秀的I/O框架库和后台服务器程序都统一处理信号和I/O事 件,比如Libevent I/O框架库和xinetd超级服务。下面代码给出了统一事件源的一个简单实现:

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <pthread.h>

#define MAX_EVENT_NUMBER 1024
static int pipefd[2];
//非阻塞socket
int setnonblocking( int fd )
{
    int old_option = fcntl( fd, F_GETFL );
    int new_option = old_option | O_NONBLOCK;
    fcntl( fd, F_SETFL, new_option );
    return old_option;
}
//将事件添加注册表
void addfd( int epollfd, int fd )
{
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET;
    epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );
    setnonblocking( fd );
}
/*信号处理函数*/
void sig_handler( int sig )
{
    /*保留原来的errno,在函数最后恢复,以保证函数的可重入性*/
    int save_errno = errno;
    int msg = sig;
    send( pipefd[1], ( char* )&msg, 1, 0 );
    errno = save_errno;
}
/*设置信号的处理函数*/
void addsig( int sig )
{
    struct sigaction sa;
    memset( &sa, '\0', sizeof( sa ) );
    sa.sa_handler = sig_handler;//指定信号处理函数
    sa.sa_flags |= SA_RESTART;//设置程序接收信号时行为(重新调用被该信号终止的系统调用)
    sigfillset( &sa.sa_mask );//设置信号掩码
    assert( sigaction( sig, &sa, NULL ) != -1 );//设置信号处理函数
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    int ret = 0;
    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

    int listenfd = socket( PF_INET, SOCK_STREAM, 0 );
    assert( listenfd >= 0 );

    //int nReuseAddr = 1;
    //setsockopt( listenfd, SOL_SOCKET, SO_REUSEADDR, &nReuseAddr, sizeof( nReuseAddr ) );
    ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );
    if( ret == -1 )
    {
        printf( "errno is %d\n", errno );
        return 1;
    }
    //assert( ret != -1 );

    ret = listen( listenfd, 5 );
    assert( ret != -1 );

    //创建注册表
    epoll_event events[ MAX_EVENT_NUMBER ];
    int epollfd = epoll_create( 5 );
    assert( epollfd != -1 );
    addfd( epollfd, listenfd );
    /*使用socketpair创建管道,注册pipefd[0]上的可读事件*/
    /*socketpair相当于创建一对套接字(pipefd[2]),可用于全双工通信,一方负责写,一方负责读*/
    ret = socketpair( PF_UNIX, SOCK_STREAM, 0, pipefd );
    assert( ret != -1 );
    setnonblocking( pipefd[1] );
    addfd( epollfd, pipefd[0] );
    /*设置一些信号的处理函数*/
    // add all the interesting signals here
    addsig( SIGHUP );//控制终端挂起
    addsig( SIGCHLD );//子进程状态发生变化(退出或者暂停)
    addsig( SIGTERM );//终止进程
    addsig( SIGINT );//键盘输入以中断进程(Ctrl+C)
    bool stop_server = false;

    while( !stop_server )
    {
        int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );
        if ( ( number < 0 ) && ( errno != EINTR ) )
        {
            printf( "epoll failure\n" );
            break;
        }
    
        for ( int i = 0; i < number; i++ )
        {
            int sockfd = events[i].data.fd;
            /*如果就绪的文件描述符是listenfd,则处理新的连接*/
            if( sockfd == listenfd )
            {
                struct sockaddr_in client_address;
                socklen_t client_addrlength = sizeof( client_address );
                int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );
                addfd( epollfd, connfd );
            }
            /*如果就绪的文件描述符是pipefd[0],则处理信号*/
            else if( ( sockfd == pipefd[0] ) && ( events[i].events & EPOLLIN ) )
            {
                int sig;
                char signals[1024];
                ret = recv( pipefd[0], signals, sizeof( signals ), 0 );
                if( ret == -1 )
                {
                    continue;
                }
                else if( ret == 0 )
                {
                    continue;
                }
                else
                {
                /*因为每个信号值占1字节,所以按字节来逐个接收信号。
                我们以SIGTERM为例,来说明如何安全地终止服务器主循环*/
                    for( int i = 0; i < ret; ++i )
                    {
                        //printf( "I caugh the signal %d\n", signals[i] );
                        switch( signals[i] )
                        {
                            case SIGCHLD:
                            case SIGHUP:
                            {
                                continue;
                            }
                            case SIGTERM:
                            case SIGINT:
                            {
                                stop_server = true;
                            }
                        }
                    }
                }
            }
            else
            {
            }
        }
    }

    printf( "close fds\n" );
    close( listenfd );
    close( pipefd[1] );
    close( pipefd[0] );
    return 0;
}

网络编程相关信号

SIGHUP

        当挂起进程的控制终端时,SIGHUP信号将被触发。对于没有控制终端的网络后台程序而言,它们通常利用SIGHUP信号来强制服务器重读配置文件。一个典型的例子是xinetd超级服务程序。

SIGPIPE

        默认情况下,往一个读端关闭的管道或socket连接中写数据将引发 SIGPIPE信号。我们需要在代码中捕获并处理该信号,或者至少忽略它,因为程序接收到SIGPIPE信号的默认行为是结束进程,而我们绝对不希望因为错误的写操作而导致程序退出。引起SIGPIPE信号的写操作将设置errno为EPIPE。

我们可以使用send函数的MSG_NOSIGNAL标志来禁止写操作触发SIGPIPE信号。在这种情况下,我们应该使用send函数反馈的errno值来判断管道或者socket连接的读端是否已经关闭。 此外,我们也可以利用I/O复用系统调用来检测管道和socket连接的读端是否已经关闭。以poll为例,当管道的读端关闭时,写端文件描述符上的POLLHUP事件将被触发当socket连接被对方关闭时,socket上的POLLRDHUP事件将被触发

SIGURG

在Linux环境下,内核通知应用程序带外数据到达主要有两种方法:之前介绍的I/O复用技术,select等系统调用在接收到带外数据时将返回,并向应用程序报告socket上的异常事件;另外一种方法就是使用SIGURG信号,代码如下所示:

#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <signal.h>
#include <fcntl.h>

#define BUF_SIZE 1024

static int connfd;
/*SIGURG信号的处理函数*/
void sig_urg( int sig )
{
    int save_errno = errno;
    
    char buffer[ BUF_SIZE ];
    memset( buffer, '\0', BUF_SIZE );
    int ret = recv( connfd, buffer, BUF_SIZE-1, MSG_OOB );/*接收带外数据*/
    printf( "got %d bytes of oob data '%s'\n", ret, buffer );

    errno = save_errno;
}
/*设置信号的处理函数*/
void addsig( int sig, void ( *sig_handler )( int ) )
{
    struct sigaction sa;
    memset( &sa, '\0', sizeof( sa ) );
    sa.sa_handler = sig_handler;//指定信号处理函数
    sa.sa_flags |= SA_RESTART;//设置程序接收信号时行为(重新调用被该信号终止的系统调用)
    sigfillset( &sa.sa_mask );//设置信号掩码
    assert( sigaction( sig, &sa, NULL ) != -1 );//设置信号处理函数
}

int main( int argc, char* argv[] )
{
    if( argc <= 2 )
    {
        printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );
        return 1;
    }
    const char* ip = argv[1];
    int port = atoi( argv[2] );

    struct sockaddr_in address;
    bzero( &address, sizeof( address ) );
    address.sin_family = AF_INET;
    inet_pton( AF_INET, ip, &address.sin_addr );
    address.sin_port = htons( port );

    int sock = socket( PF_INET, SOCK_STREAM, 0 );
    assert( sock >= 0 );

    int ret = bind( sock, ( struct sockaddr* )&address, sizeof( address ) );
    assert( ret != -1 );

    ret = listen( sock, 5 );
    assert( ret != -1 );

    struct sockaddr_in client;
    socklen_t client_addrlength = sizeof( client );
    connfd = accept( sock, ( struct sockaddr* )&client, &client_addrlength );
    if ( connfd < 0 )
    {
        printf( "errno is: %d\n", errno );
    }
    else
    {   /*设置一些信号的处理函数*/
        addsig( SIGURG, sig_urg );//控制终端挂起
        fcntl( connfd, F_SETOWN, getpid() );/*根据文件描述词来操作文件(设置异步I/O所有权)*/

        char buffer[ BUF_SIZE ];
        while( 1 )
        {
            memset( buffer, '\0', BUF_SIZE );
            ret = recv( connfd, buffer, BUF_SIZE-1, 0 );
            if( ret <= 0 )
            {
                break;
            }
            printf( "got %d bytes of normal data '%s'\n", ret, buffer );
        }

        close( connfd );
    }

    close( sock );
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/693889.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

人类语言和机器语言

人类语言和机器语言是两种不同的语言形式&#xff0c;二者之间有很多异同点。人类语言是人们日常交流所使用的语言&#xff0c;也是一种自然语言&#xff0c;人类语言是非常复杂和多样化的&#xff0c;包括文字、口语、手语等等&#xff0c;而机器语言则是非常简单和规范化的一…

Maven项目,本地jar包导入手动导入到Maven库中

当你的项目&#xff0c;由于网络或者环境这些问题&#xff0c;无法从maven中央仓库更新jar包到本地的时候&#xff0c;可以尝试下面方法&#xff0c;手动添加jar包到Maven仓库&#xff1b; 方法一&#xff08;推荐&#xff09;&#xff1a; 1、需要先拿到你的jar包&#xff0…

Linux——3Linux用户和权限

目录 3.1 认识root用户 root用户&#xff08;超级管理员&#xff09; su 和 exit命令 sudo命令 3.2 用户、用户组 3.3 修改权限控制 - chmod 3.4 修改权限控制 - chown 3.1 认识root用户 root用户&#xff08;超级管理员&#xff09; 无论是Windows、MacOS、Linux均采…

8.10 TCP是如何实现可靠传输的

目录 TCP 最主要的特点 面向流的概念 Socket 有多种不同的意思 TCP是如何实现可靠传输的&#xff1f; A 如何知道 B 是否正确收到了 M1 呢&#xff1f; 确认丢失 确认迟到 连续 ARQ 协议 累计确认 TCP报文段的首部格式 TCP 最主要的特点 TCP 是面向连接的运输层协议&a…

基于Java理发店会员管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a;✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、Java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专…

深入探析CAN收发器NCV7342D10R2G各项参数

NCV7342D10R2G安森美深力科 CAN收发器是控制器局域网&#xff08;CAN&#xff09;协议控制器和物理总线之间的接口&#xff0c;可用于12V和24V系统。收发器向总线提供差分传输能力&#xff0c;向CAN控制器提供差分接收能力。由于接收器输入的共模电压范围很宽能够达到卓越的电磁…

charles unknown 问题和手机代理设置(iOS手机)

一、Charles下载 下载地址&#xff1a;https://www.charlesproxy.com/download/ 二、Charles配置代理 1.查看本机IP&#xff1a;help-->Local IP Address 2.查看或者设置访问端口&#xff1a;Proxy->Proxy Settings 3.设置不代理计算机的请求&#xff08;推荐&#xff0…

【Java】Java核心 76:XML解析 Dom4j (下)

文章目录 **3** **使用xpath技术结合DOM4J技术读取xml文件(了解)**1.概念介绍2.XPath使用步骤3.XPath语法(了解)3.1全文搜索路径表达式方式 掌握 3 使用xpath技术结合DOM4J技术读取xml文件(了解) 1.概念介绍 问题&#xff1a;通过上面的案例我们发现有个小问题.就是获取标签的…

Python程序设计期末作品完整版|代码和程序设计文档

python程序设计作品&#xff0c;希望对您有帮助&#xff0c;希望您的一键三连&#xff01; 程序设计报告 1.爬取数据的意义 富豪榜的出现&#xff0c;体现了人们思想的变化:由保守藏富向正向面对财富的转变;由保守向文明开放(–说明了大众媒体的进步与教育的普及等思想工具的极…

会网络爬虫能干什么?

网络爬虫是一种自动化程序&#xff0c;用于浏览互联网并从网页中获取数据。它可以执行以下任务&#xff1a; 数据采集&#xff1a;网络爬虫可以访问网站&#xff0c;并从中提取所需的数据&#xff0c;例如新闻文章、产品信息、用户评论等。这些数据可以用于各种目的&#xff0…

【数据结构导论】第 2 章:线性表

目录 一、线性表的基本概念 &#xff08;1&#xff09;线性表的基本概念 &#xff08;2&#xff09;线性表的逻辑结构特征 &#xff08;3&#xff09;线性表的基本运算 二、线性表的顺序存储 &#xff08;1&#xff09;线性表顺序存储的类型定义 &#xff08;2&…

PHP 旅游网站系统mysql数据库web结构apache计算机软件工程网页wamp

一、源码特点 PHP 旅游网站系统 是一套完善的web设计系统&#xff0c;对理解php编程开发语言有帮助&#xff0c;系统具有完整的源代码和数据库&#xff0c;系统主要采用B/S模式开发。开发环境为PHP APACHE&#xff0c;数据库为 mysql5.0&#xff0c;使用php语言开发。 代码…

Spark15-16

15. SparkOnYarn 15.1 Hadoop YARN回顾 15.1.1 YARN 的基本设计思想 将Hadoop 1.0中JobTracker拆分成两个独立的服务,一个全局的资源管理器ResourceManager(RM)和每个应用独有的ApplicationMaster(AM).其中RM负责整个系统的资源管理和分配,而AM负责单个的应用程序的管理 15.…

【面试题22】PHP通过Redis和MySQL实现商品秒杀功能

文章目录 一、前言二、系统架构三、技术栈四、系统设计4.1 商品设计4.2 用户设计4.3 抢单设计4.5 并发控制4.5 获取用户购买记录代码4.7 扣减商品库存代码4.8 获取商品锁代码4.9 添加订单记录代码 总结 一、前言 本文已收录于PHP全栈系列专栏&#xff1a;PHP面试专区。 计划将全…

element ui - el-table 设置表头背景颜色和字体颜色

element ui - el-table 设置表头背景颜色和字体颜色 场景代码效果 场景 在使用 elementui 中的 el-table 时&#xff0c;由于默认表格样式与设计稿不符&#xff0c;需要将表头的背景色和字体颜色设置为新颜色。 但是对 thead&#xff0c;thead tr&#xff0c;.el-table__cell…

go mod tidy 提示错误 go mod tidy -go=1.16 go mod tidy -go=1.17

错误概览 执行 go mod tidy 时&#xff0c;提示如下错误 > go mod tidy github.com/myrepo/myproj importsgo.k6.io/k6 importsgo.k6.io/k6/cmd importsgithub.com/fatih/color loaded from github.com/fatih/colorv1.12.0,but go 1.16 would select v1.13.0To upgrade to t…

通付盾发布WAAP白皮书,帮助企业应对数字化转型过程中日益高发的网络安全威胁

简介 企业数字化转型是数字经济发展的重要一环。面对企业数字化转型过程中的安全问题&#xff0c;WAAP白皮书将对攻击方式、攻击量、攻击来源、行业分布等维度对各类攻击进行详细解读&#xff0c;梳理传统Web应用防护能力的不足&#xff0c;分析日益增长的API防护&#xff0c;…

新手小白学JAVA_IDEA修改关键字和注释颜色

很多小白在刚刚使用IDEA的时候还不是很熟练 本文主要给大家提供一些使用的小技巧&#xff0c;希望能帮助到你 1.修改关键字的默认颜色 对于很多初学者而言&#xff0c;将Java的关键字设置成一个特殊的颜色有助于记忆 我们一起来试一试吧~ 2.修改注释的默认颜色 注释颜色默认都…

23JS14——Web APIs简介

文章目录 一、Web APIs和JS基础关联性1、JS组成2、基础阶段以及Web APIs阶段1、API2、Web APIs 二、API和Web APIs 目标&#xff1a; 1、Web APIs和JS基础关联性 2、API和Web APIs 一、Web APIs和JS基础关联性 1、JS组成 2、基础阶段以及Web APIs阶段 1、API 2、Web APIs 二…

elasticsearch7.17.3实现按terms传入内容排序,类似mysql中order by filed()的排序方式

现有一个需求&#xff0c;需要在elasticsearch中实现用terms筛选内容&#xff0c;并且按terms传入的内容顺序排列 类型于mysql中order by filed()的排序方式&#xff0c;具体实现如下 目录 一、需求二、整体思路三、es查询语句四、java生成es连接五、java调用es六、最终实现结果…