janus模块介绍-SIP Gateway

news2024/10/6 18:21:24

模块启动

默认的SIP GateWay也是https协议,端口为8088或者8089
如果需要在自己搭建的测试服务上测试SIP GateWay模块,则也需要修改为wss
具体改动如下: 找到/opt/janus/share/janus/demos/siptest.js

var server = "wss://" + window.location.hostname + ":8989";

在这里插入图片描述
然后重启janus服务,执行

/opt/janus/bin/janus --debug-level=5 --log-file=$HOME/janus-log

打开测试网页,在demos中找到SIP Gateway,点击start开始运行。
在这里插入图片描述
在这里插入图片描述
点击f12可以看到demo中Using WebSockets to contact Janus: wss://49.232.250.45:8989
在这里插入图片描述

模块基本说明

这是Janus的一个简单SIP插件,允许WebRTC对等方在SIP服务器(例如Asterisk)注册,并通过Janus实例调用SIP用户代理。具体地,当连接到插件时,请求对等方提供其SIP服务器证书,即SIP服务器的地址和其用户名/密码。这导致插件在SIP服务器处注册,并代表网络对等方充当SIP客户端。大多数SIP状态和生存期都被插件屏蔽,只有相关事件(例如INVITE和BYE)和功能(呼叫、挂断)可供网络对等方使用:对等方可以在SIP服务器上呼叫扩展或等待传入的INVITE,并且在呼叫期间可以发送DTMF音调。调用可以执行纯RTP或SDES-SRTP。
该插件背后的概念是允许与同一对等方相关联的不同网页,从而允许同一SIP用户同时连接到插件,但只进行一次SIP REGISTER。这同样适用于呼叫:虽然传入的呼叫将被通知给与对等方相关的所有web UI,但只有一个能够接听和接听,这与SIP分叉的工作方式非常相似,但不需要在同一个地方分叉。然而,这一特定功能尚未实现。

SIP Plugin API

我们可以在SIP插件API中发送的所有请求都是异步的,这意味着所有响应(成功和错误)都将作为同一事务的事件传递。
支持的请求包括注册、注销、调用、接受、拒绝、信息、消息、dtmf_info、订阅、取消订阅、传输、录制、保留、取消保留、更新和挂断。顾名思义,register可以用于在SIP注册器处注册用户名以调用和被调用,而unregister则注销该用户名;call用于通过插件向不同的SIP URI发送INVITE,而accept和dence用于在邀请而不是邀请的情况下接受或拒绝呼叫;接送负责有人接送和无人接送(更多详细信息,请参阅有人接送或无人接送);hold和unhold可以分别用于将呼叫挂起和恢复呼叫;info允许您发送一个通用的SIP info请求,而dtmf_info则专注于将info用于dtmf;message是用于向另一个对等方发送SIP消息的方法;subscribe和unsubscribe用于处理SIP事件,即发送subscribe请求,该请求将导致NOTIFY异步事件;相反,录音用于将对话记录到一个或多个.mjr文件中(取决于您想要录制的方向);更新允许您更新现有会话(例如,进行重新协商或强制ICE重新启动);最后,挂断可以用于在任何时候终止通信,或者挂断(BYE)正在进行的呼叫,或者取消/拒绝(cancel/BYE)尚未开始的呼叫。
无论请求如何,错误响应或事件的格式始终如下:

{
        "sip" : "event",
        "error_code" : <numeric ID, check Macros below>,
        "error" : "<error description as a string>"
}

请注意,上面的错误语法指的是插件API消息传递,而不是响应于SIP请求而获得的SIP错误代码,这些错误代码是使用不同的语法通知的:

{
        "sip" : "event",
        "result" : {
                "event" : "<name of the error event>",
                "code" : <SIP error code>,
                "reason" : "<SIP error reason>",
                "reason_header" : "<Reason header text; optional>",
                "reason_header_protocol" : "<Reason header protocol; optional>",
                "reason_header_cause" : "<Reason header cause code; optional>"
        }
}

对于可用的请求,您可以使用注册请求发送一个SIP REGISTER。更准确地说,注册请求可能会导致SIP register,因为这种方法实际上提供了无需注册即可开始使用SIP帐户的方法。例如,所谓的访客注册就是这样的情况:如果你注册为访客,这意味着你将使用From标头中提供的SIP URI进行呼叫,但实际上你不会发送SIP register;这对于向不需要注册的服务(例如,IVR系统或会议桥)发出的呼叫特别有用,但也意味着除非对等方知道您的私人SIP地址,否则您将无法接收呼叫。注册为帮助者时也不会发送SIP REGISTER:正如我们稍后将解释的,帮助者会话仅用于方便使用同一帐户设置同时SIP呼叫。
也就是说,注册请求的格式必须如下:

{
        "request" : "register",
        "type" : "<if guest or helper, no SIP REGISTER is actually sent; optional>",
        "send_register" : <true|false; if false, no SIP REGISTER is actually sent; optional>,
        "force_udp" : <true|false; if true, forces UDP for the SIP messaging; optional>,
        "force_tcp" : <true|false; if true, forces TCP for the SIP messaging; optional>,
        "sips" : <true|false; if true, configures a SIPS URI too when registering; optional>,
        "rfc2543_cancel" : <true|false; if true, configures sip client to CANCEL pending INVITEs without having received a provisional response first; optional>,
        "username" : "<SIP URI to register; mandatory>",
        "secret" : "<password to use to register; optional>",
        "ha1_secret" : "<prehashed password to use to register; optional>",
        "authuser" : "<username to use to authenticate (overrides the one in the SIP URI); optional>",
        "display_name" : "<display name to use when sending SIP REGISTER; optional>",
        "user_agent" : "<user agent to use when sending SIP REGISTER; optional>",
        "proxy" : "<server to register at; optional, as won't be needed in case the REGISTER is not goint to be sent (e.g., guests)>",
        "outbound_proxy" : "<outbound proxy to use, if any; optional>",
        "headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP REGISTER; optional>",
        "contact_params" : "<array of key/value objects, to specify custom Contact URI params to add to the SIP REGISTER; optional>",
        "incoming_header_prefixes" : "<array of strings, to specify custom (non-standard) headers to read on incoming SIP events; optional>",
        "refresh" : "<true|false; if true, only uses the SIP REGISTER as an update and not a new registration; optional>",
        "master_id" : "<ID of an already registered account, if this is an helper for multiple calls (more on that later); optional>",
        "register_ttl" : "<integer; number of seconds after which the registration should expire; optional>"
}

注册事件将被发回,因为这是一个异步请求。
如果需要,此请求将向具有正确凭据的指定服务器发起SIP REGISTER。401和407响应将被自动处理,因此错误将不会被通知回呼叫者,除非它们是决定性的(例如,错误的凭证)。注册失败将返回名称为registration_failed的错误。相反,成功注册会在注册事件中得到通知,格式如下:

{
        "sip" : "event",
        "result" : {
                "event" : "registered",
                "username" : <SIP URI username>,
                "register_sent" : <true|false, depending on whether a REGISTER was sent or not>,
                "master_id" : <unique ID of this registered session in the plugin, if a potential master>
        }
}

要注销,只需发送一个不带其他参数的注销请求:

{
        "request" : "unregister"
}

与以前一样,将发回一个注销事件。和以前一样,这也会发送一个SIP REGISTER,以防它最初被发送。在未注册的事件中通知成功注销:

{
        "sip" : "event",
        "result" : {
                "event" : "unregistered",
                "username" : <SIP URI username>,
                "register_sent" : <true|false, depending on whether a REGISTER was sent or not>
        }
}

注册后,您可以拨打电话或等待来电:请注意,如果您选择从不发送REGISTER,您将无法接听来电。
要发送SIP INVITE,您可以使用呼叫请求,其格式如下:

{
        "request" : "call",
        "call_id" : "<user-defined value of Call-ID SIP header used in all SIP requests throughout the call; optional>",
        "uri" : "<SIP URI to call; mandatory>",
        "refer_id" : <in case this is the result of a REFER, the unique identifier that addresses it; optional>,
        "headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP INVITE; optional>",
        "srtp" : "<whether to mandate (sdes_mandatory) or offer (sdes_optional) SRTP support; optional>",
        "srtp_profile" : "<SRTP profile to negotiate, in case SRTP is offered; optional>",
        "secret" : "<password to use to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
        "ha1_secret" : "<prehashed password to use to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
        "authuser" : "<username to use to authenticate as to call, only needed in case authentication is needed and no REGISTER was sent; optional>",
        "autoaccept_reinvites" : <true|false, whether we should blindly accept re-INVITEs with a 200 OK instead of relaying the SDP to the application; optional, TRUE by default>
}

调用事件将被发回,因为这是一个异步请求。
请注意,此请求必须与JSEP优惠相关联:无法通过SIP插件发送无优惠INVITE。这将生成一个SIP INVITE,并根据指示进行发送。虽然100秒Trying不会通知回用户,但在振铃事件中,180秒将会响铃:

{
        "sip" : "event",
        "call_id" : "<value of SIP Call-ID header for related call>",
        "result" : {
                "event" : "ringing",
                "headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
        }
}

如果呼叫被拒绝或发生任何其他错误,则会发回挂断错误事件。如果呼叫被接受,则接受的事件将与被叫方发起的JSEP应答一起发送回用户:

{
        "sip" : "event",
        "call_id" : "<value of SIP Call-ID header for related call>",
        "result" : {
                "event" : "accepted",
                "username" : "<SIP URI of the callee>",
                "headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
        }
}

在这一点上,除了PeerConnection相关的考虑因素外,可以认为该调用已建立。SIP插件会自动发送SIP ACK,因此不需要应用程序手动执行任何操作。
请注意,SIP插件通过183个响应响应支持早期媒体。在接收到183响应的情况下,在进度事件中,它将与被叫方发起的JSEP应答一起发送回用户:

{
        "sip" : "event",
        "call_id" : "<value of SIP Call-ID header for related call>",
        "result" : {
                "event" : "progress",
                "username" : "<SIP URI of the callee>",
                "headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
        }
}

如果呼叫者收到进度事件,以下接受的事件将不包含JSEP应答,因为在“会话进度”事件中收到的应答将作为会话的SDP应答。
请注意,您只使用call来开始对话,即第一次INVITE。要通过重新邀请更新会话,例如,重新协商会话以添加/删除流或强制ICE重新启动,您不使用调用,而是使用另一个名为update的请求。这个请求不需要任何参数,因为整个上下文都是从会话的当前状态派生的。不过,作为重新谈判的一部分,它确实需要新的JSEP报价。

{
        "request" : "update"
}

更新事件将被发回,因为这是一个异步请求。
虽然呼叫请求允许您发送SIP INVITE(更新请求允许您更新现有会话),但也有一种方法可以对SIP INVITEs做出反应,即处理传入呼叫。传入呼叫通过传入呼叫事件通知应用程序:

{
        "sip" : "event",
        "call_id" : "<value of SIP Call-ID header for related call>",
        "result" : {
                "event" : "incomingcall",
                "username" : "<SIP URI of the caller>",
                "displayname" : "<display name of the caller, if available; optional>",
                "callee" : "<SIP URI that was called (in case the user is associated with multiple public URIs)>",
                "referred_by" : "<SIP URI header conveying the identity of the transferor, if this is a transfer; optional>",
                "replaces" : "<call-ID of the call that this is supposed to replace, if this is an attended transfer; optional>",
                "srtp" : "<whether the caller mandates (sdes_mandatory) or offers (sdes_optional) SRTP support; optional>",
                "headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
        }
}

来电可能会也可能不会附带JSEP报价,这取决于来电者发送的是无报价邀请还是常规邀请。无论哪种方式,您都可以通过接受请求来接受来电:

{
        "request" : "accept",
        "srtp" : "<whether to mandate (sdes_mandatory) or offer (sdes_optional) SRTP support; optional>",
        "headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP OK; optional>"
        "autoaccept_reinvites" : <true|false, whether we should blindly accept re-INVITEs with a 200 OK instead of relaying the SDP to the browser; optional, TRUE by default>
}

由于这是一个异步请求,将发回一个接受事件。
这将导致200 OK被发送回呼叫者。接受请求必须始终附有JSEP回复(如果incomingcall事件包含要约)或要约(如果是无要约邀请)。在前一种情况下,接受的事件将被发回,只是为了确认呼叫可以被视为已建立;相反,在后一种情况下,接受事件将被发送回,而接受的事件只会在稍后,只要在呼叫者发送回的SIP ACK中有JSEP应答可用,就紧随其后。
请注意,如果您在另一个呼叫中接到来电,您将不会收到incomingcall事件,而是收到missed_call事件,这只是一个通知,因为在SIP插件中,无法在同一句柄上同时进行两个呼叫:

{
        "sip" : "event",
        "call_id" : "<value of SIP Call-ID header for related call>",
        "result" : {
                "event" : "missed_call",
                "caller" : "<SIP URI of the caller>",
                "displayname" : "<display name of the caller, if available; optional>",
                "callee" : "<SIP URI that was called (in case the user is associated with multiple public URIs)>"
        }
}

此外,您只能使用accept来回答第一个INVITE。要接受重新邀请(将通过updatingcall事件通知),您不使用accept,而是使用之前引入的更新。这个请求不需要任何参数,因为整个上下文都是从会话的当前状态派生的。不过,作为重新谈判的一部分,它确实需要新的JSEP答案来提供。和以前一样,由于这是一个异步请求,因此会发回一个更新的事件。
关闭会话取决于调用状态。如果您有不想接听的来电,请使用拒绝请求;在所有其他情况下,请改用挂断请求。这两个请求都不需要额外的参数,因为整个上下文可以从插件中会话的当前状态中提取:

{
        "request" : "decline",
        "code" : <SIP code to be sent, if not set, 486 is used; optional>",
        "headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP request; optional>"
}
{
        "request" : "hangup",
        "headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP BYE; optional>"
}

由于这些都是异步请求,您将得到一个响应事件:如果使用了谢绝,则拒绝;如果使用了挂起,则挂起。
如前所述,当呼叫被拒绝或挂断时,会发送一个挂断事件,这基本上是一个SIP错误事件通知,因为它包括代码和原因。例如,常规BYE将被通知200和SIP BYE,尽管也可以提供更详细的描述。
建立会话后,可以使用不同的请求与会话进行交互。
首先,您可以使用挂起请求将呼叫挂起。默认情况下,此请求将向对等方发送一个新的INVITE,其中包含媒体的sendonly方向,但如果您想设置不同的方向(recvonly或inactive),也可以通过传递direction属性来实现:

{
        "request" : "hold",
        "direction" : "<sendonly, recvonly or inactive>"
}

持有者方不涉及WebRTC重新协商,因为这只会触发SIP方的重新INVITE。要从挂起状态移除调用,只需向插件发送一个取消挂起的请求,该请求不需要其他属性:

{
        "request" : "unhold"
}

并且将恢复在保持呼叫之前在SDP中设置的媒体方向。
消息请求允许您向对等方发送SIP message。默认情况下,在活动呼叫期间,它在对话框中发送。但是,如果用户已经注册,它也可能被发送到对话框之外。在这种情况下,uri参数是必需的。

{
        "request" : "message",
        "call_id" : "<user-defined value of Call-ID SIP header used to send the message; optional>",
        "content_type" : "<content type; optional>"
        "content" : "<text to send>",
        "uri" : "<SIP URI of the peer; optional; if set, the message will be sent out of dialog>",
        "headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP MESSAGE; optional>"
}

messagesend事件将被发回。相反,传入的SIP消息会在消息事件中得到通知:

{
        "sip" : "event",
        "result" : {
                "event" : "message",
                "sender" : "<SIP URI of the message sender>",
                "displayname" : "<display name of the sender, if available; optional>",
                "content_type" : "<content type of the message>",
                "content" : "<content of the message>",
                "headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
        }
}

传递后,消息传递事件将与SIP服务器响应一起发送回。用于跟踪邮件的传递状态。

{
        "sip" : "event",
        "call_id" : "<value of SIP Call-ID header for related message>",
        "result" : {
                "event" : "messagedelivery",
                "code" : "<SIP error code>",
                "reason" : "<SIP error reason>",
        }
}

SIP INFO的工作方式几乎相同,只是您使用一个对一个对等体的信息请求:

{
        "request" : "info",
        "type" : "<content type>"
        "content" : "<message to send>",
        "headers" : "<object with key/value mappings (header name/value), to specify custom headers to add to the SIP INFO; optional>"
}

将发回一个infosend事件。相反,传入的SIP info会在信息事件中得到通知:

{
        "sip" : "event",
        "result" : {
                "event" : "info",
                "sender" : "<SIP URI of the message sender>",
                "displayname" : "<display name of the sender, if available; optional>",
                "type" : "<content type of the message>",
                "content" : "<content of the message>",
                "headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
        }
}

正如预期的那样,使用SUBSCRIBE和NOTIFY机制也支持SIP事件。要做到这一点,您需要使用订阅请求,该请求的格式如下:

{
        "request" : "subscribe",
        "call_id" : "<user-defined value of Call-ID SIP header used in all SIP requests throughout the subscription; optional>",
        "event" : "<the event to subscribe to, e.g., 'message-summary'; mandatory>",
        "accept" : "<what should be put in the Accept header; optional>",
        "to" : "<who should be the SUBSCRIBE addressed to; optional, will use the user's identity if missing>",
        "subscribe_ttl" : "<integer; number of seconds after which the subscription should expire; optional>",
        "headers" : "<array of key/value objects, to specify custom headers to add to the SIP SUBSCRIBE; optional>"
}

如果subscribe请求被接受,则会发回订阅事件,然后是subscribe_succeed,如果事务失败,则会返回subscribe_failed。相反,传入的SIP NOTIFY事件在通知事件中得到通知:

{
        "sip" : "event",
        "call_id" : "<value of SIP Call-ID header for related subscription>",
        "result" : {
                "event" : "notify",
                "notify" : "<name of the event that the user is subscribed to, e.g., 'message-summary'>",
                "substate" : "<substate of the subscription, e.g., 'active'>",
                "content-type" : "<content-type of the message>"
                "content" : "<content of the message>",
                "headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
        }
}

您还可以录制SIP调用,它的工作原理与VideoCall插件的工作原理几乎相同。具体来说,您可以使用录制请求来启动或停止录制,使用以下语法:

{
        "request" : "recording",
        "action" : "<start|stop, depending on whether you want to start or stop recording something>"
        "audio" : <true|false; whether or not our audio should be recorded>,
        "video" : <true|false; whether or not our video should be recorded>,
        "peer_audio" : <true|false; whether or not our peer's audio should be recorded>,
        "peer_video" : <true|false; whether or not our peer's video should be recorded>,
        "send_peer_pli" : <true|false; whether or not send PLI to request keyframe from peer>,
        "filename" : "<base path/filename to use for all the recordings>"
}

正如您所看到的,这意味着对话的双方是分开录制的,音频和视频流也是如此(如果可用)。您可以选择要记录的内容,以防您只对子集感兴趣。文件名部分只是一个前缀,指示将用于最多四个可能需要启用的录制的实际文件名。
如果请求成功,则会发回已记录的更新事件。

Simultaneous SIP calls using the same account

如前几节所述,使用Janus句柄附加到SIP插件意味着代表用户或应用程序创建SIP堆栈:这通常意味着注册帐户,并能够启动或接收调用、处理订阅等。这也意味着,由于在Janus中,每个核心句柄只能与单个PeerConnection相关联,因此每个SIP帐户每次仅限于一个调用:如果用户已经在SIP会话中,并且另一个调用传入,则会自动拒绝,并显示486 Busy。
虽然通常不是什么大问题,但在某些用例中,支持多个并发调用是有意义的,并且可以从一个无缝地切换到另一个。这在使用所谓的助手会话的SIP插件中是可能的。具体来说,助手会话在假设存在正常注册的主会话(即“常规”SIP插件句柄)的情况下工作,并且这些助手会话可以简单地与之关联:任何时候需要另一个并发调用,如果主会话繁忙,则可以使用其中一个助手;可用的助手会话越多,可以建立的同时调用就越多。
其工作方式很简单:
您以通常的方式创建SIP会话,并在那里发送常规注册表;这将是主会话,并在成功注册时返回master_id;
对于要添加的每个助手,您将新的Janus句柄附加到SIP插件,并发送一个类型为“helper”的寄存器,并提供与主会话相同的用户名,以及引用主会话的master_id属性;
此时,新的助手与主机相关联,这意味着它可以用于启动新的调用或接收与主会话完全相同的调用,并使用相同的帐户信息、凭据等。
请注意,一旦主服务器注销,或者它所在的Janus句柄被分离,与它相关的所有助手会话也会自动删除。具体来说,插件将强制分离相关句柄。如果您需要再次注册,并希望在那里也有一些助手,您将不得不再次添加他们。
如果您想在实践中看到这一点,SIP插件演示有一个“隐藏”函数,您可以从JavaScript控制台调用该函数来玩助手:调用addHelper()函数将添加一个新的助手,并显示其他控件。您可以根据需要添加任意数量的辅助对象。

Attended and blind transfers

Janus SIP插件支持有人参与和盲传输,并且这样做主要依赖于多个调用功能:因此,请确保您已经阅读并熟悉关于使用相同帐户进行同步SIP调用的部分。
大多数与传输相关的功能都基于上一节中已经记录的现有消息和事件,但有几个方面需要注意。首先,如果您是传输者,则需要使用名为transfer的新请求,该请求允许您向受让者发送SIPREFER,以便到达不同的目标。传输请求的格式必须如下所示:

{
        "request" : "transfer",
        "uri" : "<SIP URI to send the transferee too>",
        "replace" : "<call-ID of the call this attended transfer is supposed to replace; default is none, which means blind/unattended transfer>"
}

无论是盲传输(无替换调用)还是有人参与传输,都将发回传输事件,因为这是一个异步请求。进一步的更新将以NOTIFY相关事件的形式出现,因为REFER隐式创建订阅。
相反,REFER的接收者将接收一个名为transfer的异步事件,其中包含它需要知道的信息。事实上,SIP插件不会自动执行任何操作:传入的REFER被通知给应用程序,以便它可以决定是否跟踪传输。事件的语法如下:

{
        "sip" : "event",
        "result" : {
                "event" : "transfer",
                "refer_id" : <unique ID, internal to Janus, of this referral>,
                "refer_to" : "<SIP URI to call>",
                "referred_by" : "<SIP URI SIP URI header conveying the identity of the transferor; optional>",
                "replaces" : "<call-ID of the call this transfer is supposed to replace; optional, and only present for attended transfers>",
                "headers" : "<object with key/value strings; custom headers extracted from SIP event based on incoming_header_prefix defined in register request; optional>"
        }
}

该列表中最重要的属性是refer_id,因为如果传输被接受,则该值必须包含在调用请求中以调用目标:事实上,这是SIP插件必须将新的传出调用与前一个传输请求相关联的唯一方法,从而能够通过notify事件通知传输者调用的进展情况。请注意,如果受让人决定跟踪转移请求,并且他们已经在通话中(例如,与转发器),则他们必须为此目的使用不同的句柄,例如,通过使用相同帐户部分的同步SIP呼叫中描述的助手。
传输目标将完全像前面讨论的那样接收调用,不同的是,出于信息目的,它可能包含或不包含referred_by属性。就像受让人一样,如果他们已经在调用中,则应由应用程序创建一个助手来设置一个新的Janus句柄以接受传输。
请注意,插件不会将涉及的调用置于等待状态,或自动关闭打算由传输替换的调用。所有这些都是应用程序的责任,因此开发人员应该相应地对事件做出反应。

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

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

相关文章

Python基础知识—运算符和if语句(二)

&#x1f3ac; 秋野酱&#xff1a;《个人主页》 &#x1f525; 个人专栏:《Java专栏》 《Python专栏》 ⛺️心若有所向往,何惧道阻且长 文章目录 1.输入和输出函数1.1输出函数1.2输入函数 2.常见运算符2.1赋值运算符2.2比较运算符2.3逻辑运算符2.4and逻辑与2.5or逻辑或2.6not逻…

前端JS必用工具【js-tool-big-box】,防抖和节流的方法调用学习

这一小节&#xff0c;我们针对前端工具包&#xff08;npm&#xff09;js-tool-big-box的使用做一些讲解&#xff0c;主要是防抖和节流方面的。 目录 前言 1 安装和引入 2 防抖的调用学习 3 节流的调用学习 4 使用方法总结 前言 在前端项目中&#xff0c;经常涉及到防抖…

漏斗分析方法

目录 1.什么是漏斗分析方法 2.基本概念 3.漏斗步骤的构建 4.漏斗分析的意义 5.漏斗分析的挑战和限制 6.进行漏斗分析的步骤与方法 7.在数据分析中应用漏斗分析的策略 8.示例 1.什么是漏斗分析方法 漏斗分析方法是数据分析中一种常见的技术&#xff0c;专门用于优化和提…

Python 网络与并发编程(四)

文章目录 协程Coroutines协程的核心(控制流的让出和恢复)协程和多线程比较协程的优点协程的缺点 asyncio实现协程(重点) 协程Coroutines 协程&#xff0c;全称是“协同程序”&#xff0c;用来实现任务协作。是一种在线程中&#xff0c;比线程更加轻量级的存在&#xff0c;由程…

走进电线电缆行业龙头金杯电工,助推湖南“智赋万企”行动热潮

湖南省政府推动的“智赋万企”行动掀起千行百业万企的数智化浪潮&#xff0c;在企业、服务商、行业协会等多方共推下&#xff0c;湖南省的数字化生态越发繁荣。 4月23日&#xff0c;纷享销客举办的【走进数字化游学示范基地之金杯电工】活动在长沙顺利举行。本期活动走进电线电…

ThingsBoard远程RPC调用设备

使用 RPC 功能 客户端 RPC 从设备发送客户端 RPC 平台处理客户端RPC 服务器端 RPC 服务器端RPC结构 发送服务器端RPC 使用 RPC 功能 ThingsBoard 允许您从服务器端应用程序向设备发送远程过程调用 (RPC)&#xff0c;反之亦然。基本上&#xff0c;此功能允许您向设备发送命…

vue2项目升级到vue3经历分享1

依据vue官方文档&#xff0c;vue2在2023年12月31日终止维护。因此决定将原来的岁月云记账升级到vue3&#xff0c;预计工作量有点大&#xff0c;于是想着把过程记录下来。 原系统使用的技术栈 "dependencies": {"axios": "^0.21.1","babel-…

Qt配置CMake出错

一个项目需要在mingw环境下编译Opencv源码&#xff0c;当我用Qt配置opencv的CMakeLists.txt时&#xff0c;出现了以下配置错误&#xff1a; 首先我根据下述博文介绍&#xff0c;手动配置了CMake&#xff0c;但仍不能解决问题。 Qt(MinGW版本)安装 - 夕西行 - 博客园 (cnblogs.…

数之寻软件怎么样?

数之寻软件是一款功能强大的数据恢复和备份软件&#xff0c;以下是对其特点和功能的详细评价&#xff1a; 一、数据恢复方面&#xff1a; 高效的数据恢复能力&#xff1a;数之寻软件采用了先进的算法和数据恢复技术&#xff0c;能够快速有效地恢复丢失或损坏的数据。无论是文…

laravel视频对接aws

本次对接文件上传&#xff0c;目标是实现超级大文件的上传任务&#xff0c;可能就是4~5个g的视频文件&#xff0c;折腾了蛮久熟悉s3&#xff0c;因此记录一下。 大家要是对filesystem不清楚去看一下官方文档不然可能有点懵逼。 首先我先是对接了一个普通的s3存储文件的功能&a…

[解决] 为什么 App Inventor 扩展导入了,但是没啥反应?

大概率是导入拓展后&#xff0c;没有拖动拓展到界面上&#xff01; 导入拓展后&#xff0c;别忘了拖动拓展到主界面上&#xff0c;这样才算真正创建了拓展对象&#xff0c;这时才能使用拓展的方法。 原文&#xff1a;为什么 App Inventor 扩展导入了&#xff0c;但是没啥反应&…

了解Cookie登录:原理、实践与安全指南

什么是Cookie登录&#xff1f; Cookie是什么 当你首次登录网站时&#xff0c;你会输入用户名和密码。在后台&#xff0c;网站的服务器验证这些凭据是否正确。一旦确认你的身份无误&#xff0c;服务器就会创建一个Cookie&#xff0c;并将其发送到你的浏览器。这了解Cookie登录…

量子+AI,实用还需多久?

生成式人工智能正在席卷全球。OpenAI的GPT-4能够通过律师资格考试&#xff0c;Midjourney的图像作品能够赢得艺术大奖&#xff0c;而Sora则能够根据文本创造出令人难以置信的逼真视频。 这些AI模型的成就预示着通用人工智能的曙光——一个曾经只存在于科幻小说中的概念。然而&a…

快速了解网站访问为什么提示存在安全隐患,该怎么解决

这通常是由于网站使用了不安全的HTTP协议进行通信&#xff0c;或者网站的SSL证书存在问题&#xff0c;或者网站被标记为危险&#xff0c;或者网页中混杂了非HTTPS的内容。 网站访问提示不安全通常是由于以下原因之一引起的&#xff0c;可以按照相应的解决方案进行排查和解决&…

Java集合相关的List、Set、Map基础知识

目录 一、集合介绍 二、List 三、Map HashMap的数据结构 如何理解红黑树 四、set 一、集合介绍 在Java中&#xff0c;集合是一种用于存储对象的数据结构&#xff0c;它提供了一种更加灵活和强大的方式来处理和操作数据。Java集合框架提供了一系列接口和类&#xff0c;用…

CyberData统一元数据服务

CyberData统一元数据服务功能完善&#xff0c;实现了湖仓平台元数据在整个平台的统一管理以及外部数据源元数据的主动发现和多计算引擎间元数据的互通互联。 同时&#xff0c;我们支持跨多元计算场景&#xff0c;以及在元数据基础上的统一数据权限管理和数据湖的自动化优化加速…

2024年好用又便宜的云手机!哪款性价比高?

随着科技的飞速发展&#xff0c;云计算技术也在不断演进&#xff0c;而云手机作为其创新之一&#xff0c;已经开始在我们的生活中崭露头角。它通过将手机的硬件和软件功能移到云端&#xff0c;让用户能够借助强大的云计算资源完成各种任务。2024年&#xff0c;哪款云手机性价比…

springboot整合rabbitMQ系列10 利用插件实现延时消息

插件的安装&#xff0c;本文就不做描述了&#xff0c;插件安装后如下&#xff0c;就说明安装成功了1 添加pom依赖&#xff0c;yml配置就不讲了2 核心类&#xff0c;定义交换机的代码改成如下&#xff0c;其它的定义队列&#xff0c;设置绑定关系&#xff0c;设置死信等&#xf…

WebStorm 2024 for Mac:前端开发的强大助手

WebStorm 2024 for Mac是一款专为前端开发者设计的集成开发环境&#xff08;IDE&#xff09;&#xff0c;以其强大的功能和出色的性能&#xff0c;为Mac平台上的开发者提供了高效、便捷的Web开发体验。 WebStorm 2024 for Mac v2024.1.1中文激活版下载 这款IDE支持多种编程语言…

深入理解GTK、Qt、AWTK:跨平台GUI框架对比

目录标题 GTK特性&#xff1a;优点&#xff1a;缺点&#xff1a; Qt特性&#xff1a;优点&#xff1a;缺点&#xff1a; AWTK特性&#xff1a;优点&#xff1a;缺点&#xff1a; 适用场景 在当今的软件开发领域&#xff0c;图形用户界面&#xff08;GUI&#xff09;的开发是不可…