uni-app利用renderjs实现安卓App上jssip+freeswitch+webrtc音视频通话功能

效果图

在这里插入图片描述

前置知识

利用renderjs在app端加载for web库
JsSIP+FreeSwitch+Vue实现WebRtc音视频通话

原始模块

<template>
  <view
		class="test-sip"
		:userExtension="userExtension"
		:change:userExtension="JsSIP.handleUserExtenSionChange"
		:targetExtension="targetExtension"
		:change:targetExtension="JsSIP.handleTargetExtensionChange"
		:logFlag="logFlag"
		:change:logFlag="JsSIP.handleLogFlagChange"
		:jsSipTestLocalStream="jsSipTestLocalStream"
		:change:jsSipTestLocalStream="JsSIP.handleTestLocalStreamChange"
		:jsSipIsRegisted="jsSipIsRegisted"
		:change:jsSipIsRegisted="JsSIP.handleSipRegistedChange"
		:jsSipCallByAudio="jsSipCallByAudio"
		:change:jsSipCallByAudio="JsSIP.handleCallByAudio"
		:jsSipCallByVideo="jsSipCallByVideo"
		:change:jsSipCallByVideo="JsSIP.handleCallByVideo"
		:jsSipHangup="jsSipHangup"
		:change:jsSipHangup="JsSIP.handleJsSipHangupChange"
	>
		<view class="log-box">
			<view class="log-item" :style="`color: ${!logFlag?'#2979ff':'#333'}`">关闭日志</view>
			<u-switch class="log-item" v-model="logFlag"></u-switch>
			<view class="log-item" :style="`color: ${logFlag?'#2979ff':'#333'}`">打开日志</view>
		</view>
    <view class="step">
      <view class="step-title">步骤 1:输入自己的分机号(1001-1019)</view>
			<u--input v-model="userExtension" border="surround" placeholder="请输入自己的分机号(1001-1019)"
				:disabled="hasLocalStream" class="mb-10" :customStyle="{border: '1px solid #e0e0e0'}"
			/>
			<u-button type="primary" :disabled="!userExtension || isRegisted" @click="registerUser">注册</u-button>
    </view>

    <view class="step">
      <view class="step-title">步骤 2:输入要呼叫的分机号(1001-1019)</view>
			<u--input 
				v-model="targetExtension" border="surround" placeholder="请输入要呼叫的分机号(1001-1019)" :disabled="!isRegisted"
				class="mb-10" :customStyle="{border: '1px solid #e0e0e0'}"
			/>
			<u-button type="primary" class="mb-10" :disabled="!targetExtension || hasCurrentSession" @click="startCall(false)">拨打语音电话</u-button>
			<u-button type="primary" :disabled="!targetExtension || hasCurrentSession" @click="startCall(true)">拨打视频电话</u-button>
    </view>

    <view class="step">
      <view class="step-title">其他操作</view>
			<u-button type="primary" class="mb-10" :disabled="!hasCurrentSession" @click="jsSipHangup=true">挂断</u-button>
			<u-button type="primary" class="mb-10" :disabled="!isRegisted" @click="jsSipIsRegisted=false">取消注册</u-button>
			<u-button type="primary" v-if="!jsSipTestLocalStream" :disabled="hasCurrentSession" @click="jsSipTestLocalStream=true">测试本地设备</u-button>
			<u-button type="primary" v-else :disabled="hasCurrentSession" @click="jsSipTestLocalStream=false">停止测试本地设备</u-button>
    </view>
		
		<view class="step" id="audio-container">
			<!-- <view class="step-title">音频</view> -->
		</view>
		
		<view class="step" id="video-container">
			<view class="step-title">视频</view>
		</view>
		
		<u-notify ref="uNotify"></u-notify>
  </view>
	
</template>

<script>
import { requestCameraPermission, requestRecordAudioPermission } from '@/utils/request-android-permission.js'
export default {
  data() {
    return {
      userExtension: "", // 当前用户分机号
      targetExtension: "", // 目标用户分机号
			logFlag: false,
			hasCurrentSession: false,
			jsSipTestLocalStream: false,
			hasLocalStream: false,
			jsSipIsRegisted: false,
			isRegisted: false,
			jsSipCallByAudio: false,
			jsSipCallByVideo: false,
			jsSipHangup: false
    };
  },
	mounted() {
		requestRecordAudioPermission(() => {
			requestCameraPermission()
		})
	},
  methods: {
    isValidExtension(extension) {
      const extNumber = parseInt(extension, 10);
      return extNumber >= 1001 && extNumber <= 1019;
    },
    registerUser() {
      if (!this.isValidExtension(this.userExtension)) {
        this.showError("分机号无效,请输入1001-1019之间的分机号");
        return;
      }
			this.jsSipIsRegisted = true
    },
    startCall(flag) {
      if (!this.isValidExtension(this.targetExtension)) {
        this.showError("分机号无效,请输入1001-1019之间的分机号");
        return;
      }
			flag ? this.jsSipCallByVideo = true : this.jsSipCallByAudio = true
    },
		/* 接收 renderjs 传过来的数据 */
		reciveMessage(msgObj) {
			console.log('view reciveMsg:', msgObj);
			const { msg, data } = msgObj
			switch (msg) {
				case 'notify': 
					this.$refs.uNotify[data.type](data.message)
					break
				case 'changeViewData':
					this[data.key] = data.value === 'true' ? true : data.value === 'false' ? false : data.value
			}
		}
  },
};
</script>

<script module="JsSIP" lang="renderjs">
import renderjs from './jsSipRender.js'
export default renderjs
</script>

<style lang="scss" scoped>
.test-sip {
  padding: 30px;
	.log-box {
		display: flex;
		margin-bottom: 10px;
		
		.log-item {
			margin-right: 10px;
		}
	}
	.step {
	  margin-bottom: 20px;
		
		.mb-10 {
			margin-bottom: 10px;
		}
		.step-title {
			margin-bottom: 10px;
		}
	}
	

}



</style>

renderjs 模块

import JsSIP from 'jssip'
const testMp3 = './static/media/test.mp3'
const testMp4 = './static/media/test.mp4'

export default {
	data() {
	  return {
	    userAgent: null, // 用户代理实例
	    incomingSession: null,
			currentSession: null,
			outgoingSession: null,
	    password: "xxxxx", // 密码
	    serverIp: "xxxxxxx", // 服务器ip
			audio: null,
			meVideo: null,
			remoteVideo: null,
			localStream: null,
			constraints: {
				audio: true,
				video: {
					width: { max: 1280 },
					height: { max: 720 },
				},
			},
			myHangup: false,
	  }
	},
	computed: {
		ws_url() {
		  return `ws://${this.serverIp}:5066`;
		}
	},
	mounted() {
		this.audio = document.createElement('audio')
		this.audio.autoplay = true
		// this.audio.src = testMp3
		document.getElementById('audio-container').appendChild(this.audio)
		this.meVideo = document.createElement('video')
		this.meVideo.autoplay = true
		this.meVideo.playsinline = true
		// this.meVideo.src = testMp4
		document.getElementById('video-container').appendChild(this.meVideo)
		this.remoteVideo = document.createElement('video')
		this.remoteVideo.autoplay = true
		this.remoteVideo.playsinline = true
		// this.remoteVideo.src = testMp4
		document.getElementById('video-container').appendChild(this.remoteVideo)
		const styleObj = {
			width: '150px',
			'background-color': '#333',
			border: '2px solid blue',
			margin: '0 5px'
		}
		Object.keys(styleObj).forEach(key => {
			this.meVideo.style[key] = styleObj[key]
			this.remoteVideo.style[key] = styleObj[key]
		})
	},
	methods: {
		handleLogFlagChange(nV, oV) {
			nV ? JsSIP.debug.enable("JsSIP:*") : JsSIP.debug.disable("JsSIP:*");
			// this.log('logFlag', nV, oV)
			/* if(oV !== undefined) {
				this.log('logFlag', nV, oV)
			} */
		},
		handleUserExtenSionChange(nV, oV) {
			if(oV !== undefined) {
				// this.log('userExtenSion', nV, oV)
			}
		},
		handleTargetExtensionChange(nV, oV) {
			if(oV !== undefined) {
				// this.log('targetExtenSion', nV, oV)
			}
		},
		handleTestLocalStreamChange(nV, oV) {
			if(oV !== undefined) {
				// this.log('jsSipTestLocalStream', nV, oV)
				if(nV) {
					this.captureLocalMedia(() => {
						this.sendMsg('changeViewData', {
							key: 'hasLocalStream',
							value: true
						})
					}, (e) => {
						this.sendMsg('changeViewData', {
							key: 'jsSipTestLocalStream',
							value: false
						})
						this.sendMsg('notify', {
							type: 'error',
							message: "getUserMedia() error: " + e.name
						})
					})
				} else {
					this.stopLocalMedia()
					this.sendMsg('changeViewData', {
						key: 'jsSipTestLocalStream',
						value: false
					})
				}
			}
		},
		handleSipRegistedChange(nV, oV) {
			if(oV !== undefined) {
				if(nV) {
					this.registerUser()
				} else {
					this.unregisterUser()
				}
			}
		},
		handleCallByAudio(nV, oV) {
			if(oV !== undefined) {
				if(nV) {
					this.startCall(false)
				}
			}
		},
		handleCallByVideo(nV, oV) {
			if(oV !== undefined) {
				if(nV) {
					this.startCall(true)
				}
			}
		},
		handleJsSipHangupChange(nV, oV) {
			if(oV !== undefined) {
				if(nV) {
					this.hangUpCall()
				}
			}
		},
		captureLocalMedia(successCb, errCb) {
			console.log("Requesting local video & audio");
			navigator.mediaDevices
				.getUserMedia(this.constraints)
				.then((stream) => {
					console.log("Received local media stream");
					this.localStream = stream;

					// 连接本地麦克风
					if ("srcObject" in this.audio) {
						this.audio.srcObject = stream;
					} else {
						this.audio.src = window.URL.createObjectURL(stream);
					}
					// 如果有视频流,则连接本地摄像头
					if (stream.getVideoTracks().length > 0) {
						if ("srcObject" in this.meVideo) {
							this.meVideo.srcObject = stream;
						} else {
							this.meVideo.src = window.URL.createObjectURL(stream);
						}
					}
					successCb()
				})
				.catch((e) => errCb(e));
		},
		stopLocalMedia() {
			if (this.localStream) {
				this.localStream.getTracks().forEach((track) => track.stop());
				this.localStream = null;
				// 清空音频和视频的 srcObject
				this.clearMedia("audio");
				this.clearMedia("meVideo");
			}
		},
		clearMedia(mediaNameOrStream) {
			let mediaSrcObject = this[mediaNameOrStream].srcObject;
			if (mediaSrcObject) {
				let tracks = mediaSrcObject.getTracks();
				for (let i = 0; i < tracks.length; i++) {
					tracks[i].stop();
				}
			}
			this[mediaNameOrStream].srcObject = null;
		},
		registerUser() {
			const configuration = {
				sockets: [new JsSIP.WebSocketInterface(this.ws_url)],
				uri: `sip:${this.userExtension}@${this.serverIp};transport=ws`,
				password: this.password,
				contact_uri: `sip:${this.userExtension}@${this.serverIp};transport=ws`,
				display_name: this.userExtension,
				register: true, //指示启动时JsSIP用户代理是否应自动注册
				session_timers: false, //关闭会话计时器(根据RFC 4028)
			};
			this.userAgent = new JsSIP.UA(configuration);
			
			this.userAgent.on("connecting", () => console.log("WebSocket 连接中"));
			this.userAgent.on("connected", () => console.log("WebSocket 连接成功"));
			this.userAgent.on("disconnected", () =>
				console.log("WebSocket 断开连接")
			);
			this.userAgent.on("registered", () => {
				console.log("用户代理注册成功");
				this.sendMsg('changeViewData', { key: 'isRegisted', value: true })
			});
			this.userAgent.on("unregistered", () => {
				console.log("用户代理取消注册");
				this.sendMsg('changeViewData', { key: 'isRegisted', value: false })
			});
			this.userAgent.on("registrationFailed", (e) => {
				this.sendMsg('notify', { type: 'error', message: '注册失败' })
			});
			
			this.userAgent.on("newRTCSession", (e) => {
				console.log("新会话: ", e);
				if (e.originator == "remote") {
					console.log("接听到来电");
					this.incomingSession = e.session;
					this.sipEventBind(e);
				} else {
					console.log("打电话");
					this.outgoingSession = e.session;

					this.outgoingSession.on("connecting", (data) => {
						console.info("onConnecting - ", data.request);
						this.currentSession = this.outgoingSession;
						this.sendMsg('changeViewData', { key: 'hasCurrentSession', value: true })
						this.outgoingSession = null;
					});
					
					this.outgoingSession.connection.addEventListener("track", (event) => {
						console.info("Received remote track:", event.track);
						this.trackHandle(event.track, event.streams[0]);
					});

					//连接到信令服务器,并恢复以前的状态,如果以前停止。重新开始时,如果UA配置中的参数设置为register:true,则向SIP域注册。
					this.userAgent.start();
					console.log("用户代理启动");
				}
			})
			
			//连接到信令服务器,并恢复以前的状态,如果以前停止。重新开始时,如果UA配置中的参数设置为register:true,则向SIP域注册。
			this.userAgent.start();
			console.log("用户代理启动");
		},
		startCall(isVideo = false) {
			if (this.userAgent) {
				try {
					const eventHandlers = {
						progress: (e) => console.log("call is in progress"),
						failed: (e) => {
							console.error(e);
							this.sendMsg('notify', {
								type: 'error',
								message: `call failed with cause: ${e.cause}`
							})
						},
						ended: (e) => {
							this.endedHandle();
							console.log(`call ended with cause: ${e.cause}`);
						},
						confirmed: (e) => console.log("call confirmed"),
					};
					console.log("this.userAgent.call");
					this.outgoingSession = this.userAgent.call(
						`sip:${this.targetExtension}@${this.serverIp}`, // :5060
						{
							mediaConstraints: { audio: true, video: isVideo },
							eventHandlers,
						}
					);
				} catch (error) {
					this.sendMsg('notify', {
						type: 'error',
						message: '呼叫失败'
					})
					console.error("呼叫失败:", error);
				}
			} else {
				this.sendMsg('notify', {
					type: 'error',
					message: '用户代理未初始化'
				})
			}
		},
		sipEventBind(remotedata, callbacks) {
			//接受呼叫时激发
			remotedata.session.on("accepted", () => {
				console.log("onAccepted - ", remotedata);
				if (remotedata.originator == "remote" && this.currentSession == null) {
					this.currentSession = this.incomingSession;
					this.sendMsg('changeViewData', { key: 'hasCurrentSession', value: true })
					this.incomingSession = null;
					console.log("setCurrentSession:", this.currentSession);
				}
			});

			//在将远程SDP传递到RTC引擎之前以及在发送本地SDP之前激发。此事件提供了修改传入和传出SDP的机制。
			remotedata.session.on("sdp", (data) => {
				console.log("onSDP, type - ", data.type, " sdp - ", data.sdp);
			});

			//接收或生成对邀请请求的1XX SIP类响应(>100)时激发。该事件在SDP处理之前触发(如果存在),以便在需要时对其进行微调,甚至通过删除数据对象中响应参数的主体来删除它
			remotedata.session.on("progress", () => {
				console.log(remotedata);
				console.log("onProgress - ", remotedata.originator);
				if (remotedata.originator == "remote") {
					console.log("onProgress, response - ", remotedata.response);
					//answer设置的自动接听
					//RTCSession 的 answer 方法做了自动接听。实际开发中,你需要弹出一个提示框,让用户选择是否接听

					const isVideoCall = remotedata.request.body.includes("m=video");
					const flag = confirm(`检测到${remotedata.request.from.display_name}${isVideoCall ? "视频" : "语音"}来电,是否接听?`);
					if(!flag) {
						this.hangUpCall();
						return;
					} else {
						//如果同一电脑两个浏览器测试则video改为false,这样被呼叫端可以看到视频,两台电脑测试让双方都看到改为true
						remotedata.session.answer({
							mediaConstraints: { audio: true, video: isVideoCall },
							// mediaStream: this.localStream,
						});
					}
				}
			});

			//创建基础RTCPeerConnection后激发。应用程序有机会通过在peerconnection上添加RTCDataChannel或设置相应的事件侦听器来更改peerconnection。
			remotedata.session.on("peerconnection", () => {
				console.log("onPeerconnection - ", remotedata.peerconnection);

				if (remotedata.originator == "remote" && this.currentSession == null) {
					//拿到远程的音频流
					/* remotedata.session.connection.addEventListener(
						"addstream",
						(event) => {
							console.info("Received remote stream:", event.stream);
							this.streamHandle(event.stream);
						}
					); */
					remotedata.session.connection.addEventListener("track", (event) => {
						console.info("Received remote track:", event.track);
						this.trackHandle(event.track, event.streams[0]);
					});
				}
			});

			//确认呼叫后激发
			remotedata.session.on("confirmed", () => {
				console.log("onConfirmed - ", remotedata);
				if (remotedata.originator == "remote" && this.currentSession == null) {
					this.currentSession = this.incomingSession;
					this.sendMsg('changeViewData', { key: 'hasCurrentSession', value: true })
					this.incomingSession = null;
					console.log("setCurrentSession - ", this.currentSession);
				}
			});

			// 挂断处理
			remotedata.session.on("ended", () => {
				this.endedHandle();
				console.log("call ended:", remotedata);
			});

			remotedata.session.on("failed", (e) => {
				this.sendMsg('notify', { type: 'error', message: '会话失败' })
				console.error("会话失败:", e);
			});
		},
		unregisterUser() {
			console.log("取消注册");
			this.userAgent.unregister();
			this.sendMsg('changeViewData', { key: 'isRegisted', value: false })
			this.sendMsg('changeViewData', { key: 'userExtension', value: '' })
			this.sendMsg('changeViewData', { key: 'targetExtension', value: '' })
		},
		trackHandle(track, stream) {
			const showVideo = () => {
				navigator.mediaDevices
					.getUserMedia({
						...this.constraints,
						audio: false, // 不播放本地声音
					})
					.then((stream) => {
						this.meVideo.srcObject = stream;
					})
					.catch((error) => {
						this.sendMsg('notify', {
							type: 'error',
							message: `${error.name}${error.message}`
						})
					});
			};
			// 根据轨道类型选择播放元素
			if (track.kind === "video") {
				// 使用 video 元素播放视频轨道
				this.remoteVideo.srcObject = stream;
				showVideo();
			} else if (track.kind === "audio") {
				// 使用 audio 元素播放音频轨道
				this.audio.srcObject = stream;
			}
		},
		endedHandle() {
			this.clearMedia("meVideo");
			this.clearMedia("remoteVideo");
			this.clearMedia("audio");
			if (this.myHangup) {
				this.sendMsg('notify', { type: 'success', message: '通话结束' })
			} else {
				this.sendMsg('notify', { type: 'warning', message: '对方已挂断!' })
			}
			this.myHangup = false;

			this.currentSession = null;
			this.sendMsg('changeViewData', { key: 'hasCurrentSession', value: false })
			this.sendMsg('changeViewData', { key: 'jsSipCallByVideo', value: false })
			this.sendMsg('changeViewData', { key: 'jsSipCallByAudio', value: false })
		},
		hangUpCall() {
			this.myHangup = true;
			this.outgoingSession = this.userAgent.terminateSessions();
			this.currentSession = null;
			this.sendMsg('changeViewData', { key: 'hasCurrentSession', value: false })
			this.sendMsg('changeViewData', { key: 'jsSipHangup', value: false })
		},
		// 日志
		log(key, nV, oV) {
			console.log(`renderjs:${key} 改变`);
			console.log(`${key} 新值:`, nV);
			console.log(`${key} 旧值:`, oV);
		},
		// 向视图层发送消息
		sendMsg(msg, data) {
			// 向页面传参
			// console.log('renderjs sendMsg:');
			// console.log(msg, data);
			
			this.$ownerInstance.callMethod('reciveMessage', {
				msg,
				data
			})
		},
	}
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

1+x(Java)中级题库易混淆理论题(五)

Java 语言具有许多优点和特点&#xff0c;多线性反映了 Java 程序并行机制的特点 字符流与字节流的区别在于每次读写的字节数不同 如果需要从文件中读取数据&#xff0c;则可以在程序中创建FileInputStream的对象 void 的含义是方法没有返回值 设 x1&#xff0c;y2&#xf…

SpringBoot集成slf4j日志配置

目录 前言 1、slf4j概述 2、pom.xml的日志依赖 3、application.yml的日志配置 4、logback.xml配置文件定义 5、logback.xml配置文件解析 5.1 定义日志的存储路径 5.2 定义日志的输出格式 5.3 定义控制台输出 5.4 定义日志相关参数 5.5 定义日志的输出级别 6、测试日…

AI大模型时代:一线大厂为何竞相高薪招揽AI产品经理?

前言 在当今日新月异的科技浪潮中&#xff0c;人工智能&#xff08;AI&#xff09;技术已经渗透至各行各业&#xff0c;成为推动社会进步的重要力量。在这样的背景下&#xff0c;AI产品经理这一新兴职位逐渐崭露头角&#xff0c;成为各大企业竞相争夺的稀缺人才。那么&#xf…

​带三维重建和还原的PACS源码 医院PACS系统源码 PACS系统源码医院PACS系统源码 C/S架构 带三维重建和还原​

带三维重建和还原的PACS源码 医院PACS系统源码 PACS系统源码医院PACS系统源码 C/S架构 带三维重建和还原 ​ 主要的任务就是把日常产生的各种医学影像&#xff08;包括核磁&#xff0c;CT&#xff0c;超声&#xff0c;各种X光机&#xff0c;各种红外仪、显微仪等设备产生的图…

cleanmymacX和腾讯柠檬到底哪个好用 2024最新使用测评

CleanMyMac X和腾讯柠檬都是Mac系统清理软件&#xff0c;各有其特点和优势&#xff0c;选择哪个更好用取决于用户的具体需求和使用习惯。 经常有新关注的粉丝问&#xff0c;同样做为垃圾清理软件&#xff0c;付费CleanMyMac和免费的柠檬清理哪个更好用&#xff1f;其实&#xf…

【AI绘画】Stable Diffusion 3开源

Open Release of Stable Diffusion 3 Medium 主要内容 Stable Diffusion 3是Stability AI目前为止最先进的文本转图像开放源代码算法。 这款模型的小巧设计使其完美适合用于消费级PC和笔记本电脑&#xff0c;以及企业级图形处理单元上运行。它已经满足了标准化的文字转图像模…

HTML静态网页成品作业(HTML+CSS)—— 家乡山西介绍网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有6个页面。 二、作品演示 三、代…

使用Python保护或加密Excel文件的7种方法

目录 安装Python Excel库 Python 使用文档打开密码保护 Excel 文件 Python 使用文档修改密码保护 Excel 文件 Python 将 Excel 文件标记为最终版本 Python 保护 Excel 工作表 Python 在保护 Excel 工作表的同时允许编辑某些单元格 Python 锁定 Excel 工作表中的特定单元…

移植fatfs制作内存文件系统

本文目录 1、引言2、环境准备2.1 下载源码2.2 创建一个工程 3、移植3.1 修改配置3.2 修改diskio.c3.3 编写RAM驱动3.4 编写验证代码 4、验证 文章对应视频教程&#xff1a; 暂无&#xff0c;可以关注我的B站账号等待更新。 点击图片或链接访问我的B站主页~~~ 1、引言 在嵌入式…

GaN VCSEL:工艺革新引领精准波长控制新纪元

日本工程师们凭借精湛的技艺&#xff0c;开创了一种革命性的生产工艺&#xff0c;让VCSEL的制造达到了前所未有的高效与精准。这一成果由名城大学与国家先进工业科学技术研究所的精英们联手铸就&#xff0c;将氮化镓基VCSELs的商业化进程推向了新的高峰。它们将有望成为自适应前…

【Effective Web】常见的css居中方式

CSS居中方式 水平居中 text-align:center 适用范围&#xff1a;容器中都是行内元素 缺点&#xff1a;容器内所有元素都会居中&#xff0c;如果是文本描述需要左对齐&#xff0c;需要增加text-align:left覆盖 margin: 0 auto 适用范围&#xff1a;容器宽度固定。子元素宽度…

Linux-黑马程序员

目录 一、前言二、初识Linux1、操作系统&#xff08;1&#xff09;硬件和软件&#xff08;2&#xff09;操作系统 2、Linux3、虚拟机4、FinalShell5、WSL6、虚拟机快照 三、Linux基础命令1、Linux的目录结构2、Linux命令入门&#xff08;1&#xff09;Linux命令基础格式&#x…

优雅谈大模型11:Mistral

大模型技术论文不断&#xff0c;每个月总会新增上千篇。本专栏精选论文重点解读&#xff0c;主题还是围绕着行业实践和工程量产。若在某个环节出现卡点&#xff0c;可以回到大模型必备腔调或者LLM背后的基础模型新阅读。而最新科技&#xff08;Mamba,xLSTM,KAN&#xff09;则提…

tcp协议机制的总结(可靠性,提高性能),基于tcp的应用层协议,用udp如何实现可靠传输

目录 总结 引入 可靠性 ​编辑 分析 三次握手 提高性能 其他 常见的基于tcp应用层协议 用udp实现可靠传输 总结 引入 为什么tcp要比udp复杂的多? 因为它既要保证可靠性,又要兼顾性能 可靠性 分析 其中,序列号不止用来排序,还可以用在重传时去重 确认应答是机制中的…

618有什么值得推荐?2024数码产品推荐,轻松拿捏选购!

随着618购物节即将来临&#xff0c;你是否已被琳琅满目的商品所吸引&#xff0c;难以抉择&#xff1f;团团特意为你筛选出一系列经过亲身试验的优质好物&#xff0c;旨在帮助你在这场购物盛宴中迅速锁定心仪之选。这些推荐不仅走在时尚的前沿&#xff0c;更能满足你日常生活的各…

AUTOSAR学习

文章目录 前言1. 什么是autosar&#xff1f;1.1 AP&#xff08;自适应平台autosar&#xff09;1.2 CP&#xff08;经典平台autosar)1.3 我的疑问 2. 为什么会有autosar3.autosar的架构3.1 CP的架构3.1.1 应用软件层3.1.2 运行时环境3.1.3 基础软件层 3.2 AP的架构 4. 参考资料 …

软件测试分类介绍

大家好&#xff0c;软件测试是确保软件质量的关键环节之一&#xff0c;通过对软件系统的各个方面进行测试&#xff0c;可以发现和解决潜在的问题&#xff0c;提高软件的稳定性、可靠性和用户满意度。在软件测试领域&#xff0c;根据测试的目的、方法和对象的不同&#xff0c;可…

LLM大模型的挑战与未来,挑战大但是机遇更大!

大模型必然是未来很长一段时间我们工作生活的一部分&#xff0c;而对于这样一个与我们生活高度同频互动的“大家伙”&#xff0c;除了性能、效率、成本等问题外&#xff0c;大规模语言模型的安全问题几乎是大模型所面对的所有挑战之中的重中之重&#xff0c;机器幻觉是大模型目…

揭秘!wifi贴项目市场到底怎么样??

在共享经济市场中WiFi贴这个看似微小的项目&#xff0c;正逐渐散发出它独特的光芒。它的出现既充满了希望又伴随着疑惑&#xff1a;WiFi贴项目的真正面貌究竟如何&#xff1f;让我们一同揭开这神秘面纱。 首先&#xff0c;我们必须理解WiFi贴的本质&#xff1a;它由微火的罗经理…

【每日刷题】Day63

【每日刷题】Day63 &#x1f955;个人主页&#xff1a;开敲&#x1f349; &#x1f525;所属专栏&#xff1a;每日刷题&#x1f34d; &#x1f33c;文章目录&#x1f33c; 1. 414. 第三大的数 - 力扣&#xff08;LeetCode&#xff09; 2. 2265. 统计值等于子树平均值的节点数…