sip开发从理论到实践,让你快速入门sip

news2024/11/25 22:48:26

目录

引言:

 sip协议是什么?

 sip的网络结构(重点)

 sip的特点

sip使用的url

sip协议的应用领域

sip协议基本的消息类型

请求消息

响应消息

sip协议的消息结构(这个是重点)

sip的常见会话流程(重点)

注册

注销

基于sip协议实现简单的网络视频通话

步骤:

效果:

代码:


  • 引言:

最近,公司让我去了解sip协议,自己花了些的时间去了解了相关的知识,但我发现网上的资料比较乱,没有的对sip协议做一个很好的总结,而且实践很少,对于新手学习sip而言,需要花费大量的时间去找相关的资料,所以我在这里总结一下我自己的学习心得,让大家能花较短的时间也可以对sip协议快速入门。


  •  sip协议是什么?

SIP(Session Initiation Protocol,即初始会话协议)是 IETF 提出的基于文本编码的 IP 电话/多媒体会议协议。用于建立、修改并终止多媒体会话。

SIP 协 议可用于发起会话,也可以用于邀请成员加入已经用其它方式建立的会话。 多媒体会话可以是点到点的话音通信或视频通信,也可以是多点参与的话音 或视频会议等。SIP 协议透明地支持名字映射和重定向服务,便于实现 ISDN, 智能网以及个人移动业务。

SIP 协议可以用多点控制单元(MCU)或全互连的方式代替组播发起多方呼 叫。与 PSTN 相连的 IP 电话网关也可以用 SIP 协议来建立普通电话用户之间 的呼叫。

     

总结:sip协议是一种基于文本编码的协议,用于建立、修改并终止多媒体会话,多使用在IP通话(基于网络来实现通话,类似与微信通话)

扩展知识:ip电话和传统的电话有什么区别呢?如何进行区分呢,分别都是使用了那些协议和技术呢?

区别:ip电话是一个统称,只要是基于互联网来实现实时通话,都可以成为ip电话,想我们生活中的微信通话,qq通话等都是属于ip电话的一种,而基于sip协议来实现的sip话机通话也是IP电话的一种。

区分:在生活中,向我们日常手机那种需要手机卡才能拨打电话的称为传统通话,而只需要有网络就可以拨打电话的就是ip电话了

协议和技术:传统的话机主要是基于PSTN来实现的,使用电路交换技术来传输数据;而qq,微信等是有自己定义的协议来实现,sip话机是基于sip协议来实现,使用分组交换技术传输数据。


  •  sip的网络结构(重点)

sip的网络结构主要有5部分组成

用户代理:SIP 用户代理(UA) 是终端用户设备,如用于创建和管理 SIP 会话的移动电话、多媒体手持设备、PC、PDA 等。用户代理客户机发出消息。用户代理服务器对消息进行响应(常见的用户代理就有sip话机,实现了sip协议的软件换设备(app,网页,其他软件));

代理服务器:用于转发sip请求,作为 User Agent Client 和 User Agent Server 间的中间媒体, 它转发 User Agent Client 来的的邀请,在转发之前,根据被叫标识请求位置 服务器获得被叫的可能位置,然后分别向它们发出邀请;

重定向服务器:接受 User Agent Client 来的邀请,根据被叫标识请求位置服 务器获得被叫的可能位置,把这些信息返回给邀请的发起者(User Agent Client),和 Proxy Server 的不同之处就在于它不转发邀请,邀请由主叫终端 自己完成;

本地服务器和注册服务器:主要用于登记分组终端的当前位置和位置服务的原始数据

如果还需要连接传统的PSTN,则需要添加一个网关,将SIp转化成PSTN信号

sip的网络结构基本组成

在同一个局域网的

 在不同局域网的


  •  sip的特点

协议简单:

SIP协议被设计为非常简单,具有有限的命令集。它也是基于文本的,因此任何人都可以读取在SIP会话中的端点之间传递的SIP消息,SIP协议是一个Client/Sever协议,因此SIP消息分两种:请求消息和响应消息。请求消息是SIP客户端为了激活特定操作而发给服务器端的消息。

可读性强:sip协议是基于文本编码的,在网络中获取后可以直接阅读。所以协议传输的数据可读性强。

扩展性强:sip协议的网络组成是基于分布式部署的,用户可以根据自己的需要部署一台或多台网络组件,如果想连接PSTN网,则可以通过使用id网关来实现异构网的搭建,所以扩展性强。


  • sip使用的url

结构:name@domian

案例:

alice@atlanta.com

bob@biloxi.com

1001@192.168.8.248

1002@192.168.8.248


  • sip协议的应用领域

  

SIP(Session Initiation Protocol)是一种应用层协议,用于建立、修改和终止多媒体会话,如电话呼叫、视频会议等。SIP协议的应用领域包括以下几个方面:

  1. 互联网电话(VoIP):SIP是VoIP中最常用的协议之一,它可以用于建立和管理Internet上的语音通信。通过SIP,用户可以通过互联网进行电话呼叫,无需传统电话线路。

  2. 多媒体会议:SIP可以用于建立和管理多媒体会议,包括音频、视频和文本等形式的通信。通过SIP,多个参与者可以在不同的地理位置之间进行实时的多媒体会话。

  3. 即时消息和在线聊天:SIP可以用于发送即时消息和进行在线聊天。它支持文本消息的传输和处理,并可以实现用户之间的实时通信。

  4. 网络电话交换(Softswitch):SIP可以作为网络电话交换的控制协议,用于连接不同的电话网络和设备。通过SIP,电话呼叫可以在不同的网络之间进行转接和路由。

  5. 增强业务通信:SIP还可以用于增强业务通信,如语音信箱、呼叫转移、呼叫等待等功能。它提供了一种灵活的方式来定制和管理通信服务。

总的来说,SIP协议在VoIP、多媒体会议、即时消息和业务通信等领域都有广泛的应用。它为实现实时通信和多媒体交互提供了一种标准化和可扩展的解决方案。


  • sip协议基本的消息类型

SIP 协议是以层协议的形式组成的,就是说它的行为是以一套相对独立的处理阶段来描述的,每个阶段之间的关系不是很密切。

SIP 协议将 Server 和 User Agent 之间的通讯的消息分为两类:请求消息和响应消息。

  • 请求消息

  • INVITE:表示主叫用户发起会话请求,邀请其他用户加入一个会话。也可以用在呼叫建立后用于更新会话(此时该INVITE又称为Re-invite)。

  • ACK:客户端向服务器端证实它已经收到了对INVITE请求的最终响应。

  • PRACK:表示对1xx响应消息的确认请求消息。

  • BYE:表示终止一个已经建立的呼叫。

  • CANCEL:表示在收到对请求的最终响应之前取消该请求,对于已完成的请求则无影响。

  • REGISTER:表示客户端向SIP服务器端注册列在To字段中的地址信息。

  • OPTIONS:表示查询被叫的相关信息和功能。

  • 以上方法以外,还有其他扩展的方法,如INFO、NOTIFY等等.

响应消息

服务器向客户反馈对应请求的处理结果的 SIP 消息,包括 1xx、2xx、 3xx、4xx、5xx、6xx 响应。

常用的一些响应消息:

  • 100试呼叫(Trying)

  • 180振铃(Ringing)

  • 181呼叫正在前转(Call is Being Forwarded)

  • 200成功响应(OK)

  • 302临时迁移(Moved Temporarily)

  • 400错误请求(Bad Request)

  • 401未授权(Unauthorized)

  • 403禁止(Forbidden)

  • 404用户不存在(Not Found)

  • 408请求超时(Request Timeout)

  • 480暂时无人接听(Temporarily Unavailable)

  • 486线路忙(Busy Here)

  • 504服务器超时(Server Time-out)

  • 600全忙(Busy Everywhere)


  • sip协议的消息结构(这个是重点)

注意:这个看起来很繁琐,感觉不需要记,但实际上在日常开发中,如果需要要抓包去查看信息的话,这个是必须要懂得,每个字段代表什么意思要记下来

请求行:

       请求行分为请求和响应二种类型:

        请求:请求方法         请求路径         协议版本号

         INVITE sip:bob@192.0.2.4 SIP/2.0

         INVITE sip:bob@biloxi.com SIP/2.0

        响应:协议版本号       响应码          原因短语 

        SIP/2.0 100 Trying

        SIP/2.0 180 Ringing


 请求头:


Via: SIP/2.0/UDP pc33.atlanta.com
;branch=z9hG4bKnashds8
Max-Forwards: 70
To: Bob <sip:bob@biloxi.com>
From: Alice <sip:alice@atlanta.com>
;tag= 1928301774
Call-ID: a84b4c76e66710
CSeq: 314159 INVITE
Contact: <sip:alice@pc33.atlanta.com> 
Content-Type: application/sdp
Content-Length: 142


    请求体:

    主要由SDP协议组成

请求类型

 


  • sip的常见会话流程(重点)

注册

 

注销

          

 

                                                        基本得呼叫流程

 

主动呼叫        

 

 

  • 基于sip协议实现简单的网络视频通话

这里是基于sip.js来实现得一个简单得web在线呼叫

步骤:

  1.   从github上下载一个打包号得sip.js文件(这里需要注意,我使用得是15.10版本,然后现在更新到了2.1版了,从1.6版本之后,变化比较大,所以如果要使用我的代码的话,版本不能超过1.6)
  2. 安装一个sip服务器,现在sip.js支持三种类型的服务器,分别是freeswich,onsip,asterisk,他们之间的差别我就不介绍了,现在国内主流的是使用freeswich这个作为sip服务器,本案例就是选用freeswich作为sip服务器
  3. 在运行freeswich服务器后,完成注册就可以实现视频通话了(这里需要注意现在必须要在同一个类型的浏览器才能实现正常的通话)

freeswich的安装教程:Windows下FreeSWITCH的安装及使用_freeswitch管理工具_rivercoder的博客-CSDN博客

效果:

代码:

<!DOCTYPE html>
<html lang="en">

<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>sip练习</title>
</head>
<!-- 引入sipjs -->
<script src="./js/sip-0.15.10.js"></script>

<body>
    <div style="
    width: 300px;
    height: 250px;
    margin: 0 auto;
    background-color: antiquewhite;
" id="registerDiv">
        </form>
        <p align="center">
            <tr>
                <td>用户名:<input type="text" name="username" id="username" value="" placeholder="请输入sip账号"></td>
            </tr><br />

            <tr>
                <td>密 &nbsp;&nbsp;码:<input type="password" id="password" name="password" value="" placeholder="请输入密码">
                </td>
            </tr><br />
        </p>

        <p align="center">
            <td><input type="submit" name="register" value="register" id="register" "></td>
        </p>
        <form>
    </div>


    <div style="
        width: 500px;
        height: 450px;
        margin: 0 auto;
        background-color: antiquewhite;
    " id="mainDiv">
    <audio id="ringtone" loop src="./sounds/ringtone.wav"></audio> <!--//来电提示音-->
    <audio id="ringbacktone" loop src="./sounds/ringbacktone.wav"></audio> <!--//电话呼叫后声音-->
    <audio id="dtmfTone" src="./sounds/dtmf.wav"></audio>

    

    
        </form>
        <p align="center">
            <tr>
                <td>账号:<input type="text" name="username" id="number" value="" placeholder="请输入sip账号"></td>
            </tr><br />
    
            <tr>
                <td>消息:<input type="text" id="msg" name="msg" value="hello" placeholder="要发送的消息">
                </td>
            </tr><br />
        </p>
            <button id="startCall">Start Call</button>
            <button id="endCall">End Call</button>
            <button id="transfer">transfer</button>
            <button id="hold">hold</button>
            <button id="unhold">unhold</button>
            <button id="mute">mute</button>
            <button id="unmute">unmute</button>
            <button id="sendMessage">Send Message</button>
            
            
            <form>
			
			<div style="width: 90%; height: 100%;background-color: #f6f2f2; border: 2px solid blue; padding:0px; margin-top: 4px;">
				<video id="remoteView" style="width: 200px; height: 200px; background-color: #c5e5cd; margin-left: 20px;" autoplay></video>
                <video id="localVideo" autoplay="autoplay" muted="muted"
                    style="width: 200px; height: 200px; background-color: #d8d0d0; margin-left: 20px;">
                </video>
			</div>
            
    </div>
		
    <script>
        var ua;

        //初始化按钮变量 
        var sessionall;
        var startCall = document.getElementById('startCall');
        var endCall = document.getElementById('endCall');
        var transfer = document.getElementById('transfer');
        var hold = document.getElementById('hold');
        var unhold = document.getElementById('unhold');
        var sendMessage = document.getElementById('sendMessage');
        var mute = document.getElementById('mute');
        var unmute = document.getElementById('unmute');
        var remoteView = document.getElementById('remoteView');
        var localVideo = document.getElementById('localVideo');
        var currentSession=null;        //当前会话


        <!--  实现注册功能 -->
        var test =document.getElementById("register").addEventListener('click', function (e) {
            var username = document.getElementById("username").value; 
			var password = document.getElementById("password").value; 
		   //定义一个config 
		   var configuration={ 
           uri: 'sip:' +username + '@192.168.8.248:5060',
		   displayName: username, 
		   password: password, 
		   transportOptions: {
                //freeswich会运行websocket服务器,一个是ws类型,端口对应5066,用于本地测试,一个是wss类型,端口对应7443,用于实际开发,相当于加入了ssl的ws
                wsServers: ['ws://192.168.8.248:5066']
            },
            
            register: true // 启用注册功能 
			}; 
			//开始注册sip 
			ua=new SIP.UA(configuration); 
			//登录成功后进行回调
			ua.on('registered', function () {
				alert('登录成功'); 
             document.getElementById("registerDiv").style.display = "none";
             document.getElementById("mainDiv").style.display="block";
			});		


            // 接听来电
            ua.on('invite', function (session) {
                // 开始振铃
                Ring.startRingTone();
                var url = session.remoteIdentity.uri.toString() + "来电了,是否接听";
                //弹出是否接听对话框
                 var isaccept = confirm(url);
                if (isaccept) {
                    //接受来电
                    session.accept({
                        sessionDescriptionHandlerOptions: {
                            constraints: {
                                audio: true,
                                video: true
                            }
                        }
                    });
                

                session.on("terminated", function (message, cause) {
                    Ring.stopRingTone();

                })

                /**
                 *
                 */
                session.on('accepted', function (response, cause) {
                    console.error(response);
                    console.error(session);
                    Ring.stopRingTone();
                    // If there is a video track, it will attach the video and audio to the same element
                    //
                    // Gets remote tracks
                    var pc = session.sessionDescriptionHandler.peerConnection;
                    var x=pc.getReceivers();
                    if(pc.getReceivers()){
                        var remoteStream = new MediaStream();
                        //接听会话
                        pc.getReceivers().forEach(function (receiver) {
                            remoteStream.addTrack(receiver.track);
                        });
                        remoteView.srcObject = remoteStream;
                        remoteView.play();
                    }
                    
                    var y=pc.getSenders();
                    //发起会话
                    if (pc.getSenders()) {
                        var localStream = new MediaStream();
                        pc.getSenders().forEach(function (sender) {
                            localStream.addTrack(sender.track);
                        });
                        localVideo.srcObject = localStream;
                        localVideo.play();
                    }

                })


                session.on('bye', function (resp, cause) {
                    Ring.stopRingTone();
                });

                }
                else {
                    //拒绝来电
                    session.reject();
                }
            }); //接听会话结束

        }, 
        false); 

        // 页面加载时调用
        window.onload = function () {
              document.getElementById("mainDiv").style.display = "none"; 
        };

        /**
        *  绑定方法
        * @param obj 要绑定事件的元素
        * @param ev 绑定的事件
        * @param fn  绑定事件的函数
        */
        function bindEvent(obj, ev, fn) {
            if (obj.attachEvent) {
                obj.attachEvent("on" + ev, fn);
            }
            else {
                obj.addEventListener(ev, fn, false);
            }
        }

       
       
        //发送消息
        bindEvent(sendMessage, 'click', function () {
            // Sends a new message
            var number = document.getElementById("number").value;
            var src=number+"@192.168.8.248"
            var message = document.getElementById("msg").value;
            ua.message(src, message);
            // When receiving a message, prints it out
            ua.on('message', function (message) {
                console.log(message.body);
            });
        })

        /**
        * 拨打电话
        */
        bindEvent(startCall, 'click', function () {
            var number = document.getElementById("number").value;
            var sippath=number+ "@192.168.8.248";
            //创建一个新的出站(用户代理客户端)会话
            currentSession = ua.invite(number, {
                sessionDescriptionHandlerOptions: {
                    constraints: {
                        audio: true,
                        video: true
                    },
                    alwaysAcquireMediaFirst: true // 此参数是sip.js官方修复在firefox遇到的bug所设置
                }
            });

                //开始嘟嘟嘟
                Ring.startRingbackTone();

                //每次收到成功的最终(200-299)响应时都会触发。
                currentSession.on("accepted", function (response, cause) {
                    console.log(response);
                    Ring.stopRingbackTone();
                    //获取seesion
                    
                    // Gets remote tracks
                    var pc = currentSession.sessionDescriptionHandler.peerConnection;
                   
                    //接听会话
                    if(pc.getReceivers()){
                         var remoteStream = new MediaStream();
                    pc.getReceivers().forEach(function (receiver) {
                        remoteStream.addTrack(receiver.track);
                    });
                    remoteView.srcObject = remoteStream;
                    remoteView.play();
                }
                    //发起会话
                    if (pc.getSenders()) {
                        var localStream = new MediaStream();
                        pc.getSenders().forEach(function (sender) {
                            localStream.addTrack(sender.track);
                        });
                        localVideo.srcObject = localStream;
                        localVideo.play();
                    }
                })

                //挂机时会触发
                currentSession.on("bye", function (response, cause) {
                    Ring.stopRingbackTone();
                    console.log(response);
                })

                //请求失败时触发,无论是由于最终响应失败,还是由于超时,传输或其他错误。
                currentSession.on("failed", function (response, cause) {
                    Ring.stopRingbackTone();
                    console.log(response);
                })

                /**
                 *
                 */
                currentSession.on("terminated", function (message, cause) {
                    Ring.stopRingbackTone();
                })

                /**
                 * 对方拒绝
                 */
                currentSession.on('rejected', function (response, cause) {
                    Ring.stopRingbackTone();
                })
            })


        

    
            


        //振铃  
        var Ring = {
                /**
                 * 振铃
                 */
                startRingTone: function () {
                    let play = document.getElementById("ringtone").play();
                    play.then(() => {

                    }).catch((err) => {

                    });

                },
                /**
                 * 停止振铃
                 */
                stopRingTone: function () {
                    document.getElementById("ringtone").pause();
                },

                startRingbackTone: function () {
                    let play = document.getElementById("ringbacktone").play();
                    play.then(() => {

                    }).catch((err) => {


                    });
                },
                stopRingbackTone: function () {
                    document.getElementById("ringbacktone").pause();
                }
            };
	</script>

</body>

</html>

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

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

相关文章

OpenCV实例(九)基于深度学习的运动目标检测(三)YOLOv3识别物体

基于深度学习的运动目标检测&#xff08;三&#xff09;YOLOv3识别物体 1.基于YOLOv3识别物体2.让不同类别物体的捕捉框颜色不同3.不用Matplotlib实现目标检测 目标检测&#xff0c;粗略地说就是输入图片/视频&#xff0c;经过处理后得到目标的位置信息&#xff08;比如左上角和…

多线程线程池

# 线程池的学习笔记总结图 图1: 图2&#xff1a; # 多线程的三种实现方式&#xff1a; 1.继承Thread类 2.实现Runnable类 3.实现Callable接口 # 线程的常见成员变量 # 线程的生命周期 # 线程的状态 # 线程池 # 自定义线程池 # 自定义线程池的使用情况的图解 一开始定义好的线…

安装Vue_dev_tools

Vue控制台出现Download the Vue Devtools extension for a better development experience: 下载Vue_dev_tools,这里给出网盘链接&#xff0c;有Vue2和Vue3的&#xff0c;dev_tools 以Google浏览器为例 点击设置&#xff08;就是那三个点&#xff09;->扩展程序->管理扩…

HummingBird 基于 Go 开源超轻量级 IoT 物联网平台

蜂鸟&#xff08;HummingBird&#xff09; 是 Go 语言实现的超轻量级物联网开发平台&#xff0c;包含设备接入、产品管理、物模型、告警中心、规则引擎等丰富功能模块。系统采用GoLang编写&#xff0c;占用内存极低&#xff0c; 单物理机可实现百设备的连接。 在数据存储上&…

华为OD机试 - 秘钥格式化 - 字符串处理(Java 2023 B卷 100分)

目录 专栏导读一、题目描述二、输入描述三、输出描述四、解题思路五、Java算法源码六、效果展示1、输入2、输出3、说明 华为OD机试 2023B卷题库疯狂收录中&#xff0c;刷题点这里 专栏导读 本专栏收录于《华为OD机试&#xff08;JAVA&#xff09;真题&#xff08;A卷B卷&#…

Source Insight配置Cppcheck做静态测试(Windows)

1.安装cppcheck 先从cppcheck官方网站下载cppcheck的安装包。 注&#xff1a; &#xff08;1&#xff09;官网地址&#xff1a;https://sourceforge.net/projects/cppcheck &#xff08;2&#xff09;截止2023年8月&#xff0c;官方发布的最新版本是cppcheck-2.11-x64-Setup.…

解锁Spring AOP的神秘面纱

目录 Spring AOP的组成组成部分与常用注解举例理解 Spring AOP的实现添加 Spring AOP 框架⽀持定义切⾯和切点定义通知切点表达式说明 Spring AOP 实现原理JDK动态代理CGLIB动态代理 Spring AOP作为Spring框架的核心模块&#xff0c;为我们提供了一种优雅的方式来处理横切关注点…

Java-抽象类和接口(上)

如果一个类中没有包含足够的信息来描绘一个具体的对象&#xff0c;这样的类就是抽象类 在打印图形例子中, 我们发现, 父类 Shape 中的 draw 方法好像并没有什么实际工作, 主要的绘制图形都是由 Shape 的各种子类的 draw 方法来完成的. 像这种没有实际工作的方法, 我们可以把它…

OAuth2.0一 Spring Security OAuth2.0

这里主讲OAuth2.0 学习OAuth2前提&#xff1a; 掌握Spring Security Spring Security学习 一 OAuth2.0介绍 OAuth&#xff08;Open Authorization&#xff09;是一个关于授权&#xff08;authorization&#xff09;的开放网络标准&#xff0c;允许用户授权第三方应用访问他们…

数据结构<树和二叉树>顺序表存储二叉树实现堆排

✨Blog&#xff1a;&#x1f970;不会敲代码的小张:)&#x1f970; &#x1f251;推荐专栏&#xff1a;C语言&#x1f92a;、Cpp&#x1f636;‍&#x1f32b;️、数据结构初阶&#x1f480; &#x1f4bd;座右铭&#xff1a;“記住&#xff0c;每一天都是一個新的開始&#x1…

【音视频原理】图像相关概念 ① ( 像素 | 分辨率 | 位深 )

文章目录 一、图像相关概念1、像素2、分辨率3、位深 一、图像相关概念 图像相关概念 : 像素 : 图片基本单位 ;分辨率 : 图像的像素尺寸 ;位深 : 记录每个像素点颜色时使用的位数 ;帧率 : 一秒钟传输图片的帧数 , 单位 fps , Frame Per Second ;码率 : 单位时间内视频的数据流量…

博弈论 | 斐波那契博弈

斐波那契博弈 博弈论是二人或多人在平等的对局中各自利用对方的策略变换自己的对抗策略,达到取胜目标的理论。博弈论是研究互动决策的理论。博弈可以分析自己与对手的利弊关系,从而确立自己在博弈中的优势,因此有不少博弈理论,可以帮助对弈者分析局势,从而采取相应策略,最终达…

系统架构设计专业技能 · 信息安全技术

系列文章目录 系统架构设计专业技能 网络技术&#xff08;三&#xff09; 系统架构设计专业技能 系统安全分析与设计&#xff08;四&#xff09;【系统架构设计师】 系统架构设计高级技能 软件架构设计&#xff08;一&#xff09;【系统架构设计师】 系统架构设计高级技能 …

Maven介绍与配置+IDEA集成Maven+使用Maven命令

目录 一、Maven简介 二、配置环境变量 三、IDEA集成Maven 1.配置本地仓库地址 2.集成Maven 3. pom.xml文件介绍 四、Maven命令 jar包太多、jar包相互依赖、不方便管理、项目编译还需要jar包&#xff0c;Maven工具来帮你&#xff01; 一、Maven简介 Maven 是 Apache 软…

Python打包可执行文件-cx_Freeze

目录 简介 安装 问题处理 使用 打包 简介 之前有篇文章有使用pyinstaller打包.exe文件&#xff0c;但是没有详细介绍过&#xff0c;这次整理下 python入门-从安装环境配置(Anaconda)到做一个下班打卡提醒.exe小工具_瑶山的博客-CSDN博客 Python程序发布时&#xff0c;通常…

uniapp编写微信小程序和H5遇到的坑总结

uniapp编写微信小程序和H5遇到的坑总结 1、阻止事件冒泡2、二维码生成3、H5跨域配置4、H5时&#xff0c;地址栏上添加版本号5、H5时&#xff0c;tabBar遮挡部分内容6、uniapp使用webview通信6.1、uniapp编写的小程序嵌入h5之间的通信6.1.1、小程序向h5发送消息6.1.2、h5向小程序…

实验二 tftp 服务器环境搭建

tftp 服务器环境搭建 tftp&#xff08;Trivial File Transfer Protocol&#xff09;即简单文件传输协议是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议&#xff0c;提供不复杂、开销不大的文件传输服务。端口号为69 【实验目的】 掌握 tftp 环境搭…

【2023新教程】树莓派定时自动拍照并上传腾讯云对象存储COS

1 换源 仅适用于Release date: May 3rd 2023、Debian version: 11 (bullseye)这个树莓派OS版本&#xff0c;其他版本不保证有效。 首先使用如下命令&#xff0c;查看自己树莓派的架构。 uname -a结果如下&#xff1a; 如果红圈处显示为aarch64&#xff0c;使用命令sudo na…

Unity C# 引用池 ReferencePool

Unity C# 引用池 ReferencePool 1.目的 对于多次创建的数据使用new 关键字是十分消耗性能的&#xff0c;使用完成后由GC去自动释放&#xff0c;当一个类型的数据频繁创建可以使用引用池进行管理。 2.实现 项目目录 IReference 接口 要放入引用池的数据只需要继承这个接口…