WebRTC 实现P2P音视频通话——搭建信令服务器

该文详细记录了如何使用Node.js和Socket.IO搭建一个简单的信令服务器,以支持WebRTC的P2P音视频通话。首先介绍了安装Node.js和npm的过程,接着在服务器端实现信令服务,包括引入相关库、创建服务器实例、处理客户端连接和房间管理。客户端部分则展示了HTML界面和JavaScript交互代码,实现了界面元素和事件监听。文章以一个聊天室为例,为后续加入音视频通话功能奠定了基础。
摘要由CSDN通过智能技术生成

WebRTC 实现P2P音视频通话——搭建信令服务器


前言

WebRTC 实现P2P音视频通话系列记录了从零->搭建信令服务器->搭建stun/turn P2P穿透和转发服务器->WebRTC P2P音视频通话,
WebRTC 实现P2P音视频通话——搭建信令服务器 本文将在公网服务器主机上使用node.js搭建一个简单的聊天室

一、安装NodeJS,npm

通过SSH进入服务器主机

ssh 用户名@IP地址 例如:ssh root@192.168.1.1

通过apt 搜索一下nodejs相关信息

apt search nodejs

通过apt进行安装(也可以通过源码进行编译安装,源码地址:http://nodejs.cn/)

apt install nodejs

安装完成,查看nodejs版本

nodejs --version

二、服务器端实现

1.引入库

通过npm安装接下来需要的第三库
//serve-index 将文件夹中文件列表显示到浏览器中

npm install serve-index 

//express 目前最流行的基于Node.js的Web开发框架

npm install express

//socket.io 网络通信开发框架

npm install socket.io

👇socket.io安装失败,需要更新nodejs,npm版本 (更新前nodejs版本为4.2.6,npm版本为3.5.2)在这里插入图片描述
更新nodejs到最新版本

npm install -g n
n stable

更新后的nodejs在/usr/local/bin/node路径下需要通过MV指令移到/usr/bin/node路径下,替换原有的nodejs在这里插入图片描述

mv /usr/local/bin/node /usr/bin/node

更新npm

curl -qL https://www.npmjs.com/install.sh | sh

更新完后执行

npm install socket.io

2.代码实现

新建webserver目录并进入

mkdir webserver

在webserver目录下新建server.js

vi server.js

server.js文件中实现信令服务代码👇

'use strict'
//导入需要使用的相关库
var http  = require('http');
var https = require('https');
var fs    = require('fs');
//serve-index
var serveIndex = require('serve-index');
//express
var express = require('express');
//socket.is
var socketIo = require('socket.io');
//控制房间内用户个数
var USERCOUNT = 3;

var app = express();
//顺序不能换
app.use(serveIndex('./public'));
app.use(express.static('./public'));

//加载本地路径下的ssl证书,专用于https, 没相关的域名以及证书,https将无法访问,有证书在webserver路径下新建cert目录,将证书拷入
var options = {
	key  : fs.readFileSync('./cert/XXXXXXXX.cn.key'),
	cert : fs.readFileSync('./cert/XXXXXXXX.cn_bundle.pem')
}

//https_server 有证书就直接拷贝到cert路径下,在填入options中,没有就填null,https服务将无法访问
var https_server = https.createServer(null, app);
//http_server
var http_server = http.createServer(app);

//bind socket.io with https_server
var io = socketIo.listen(https_server);
var sockio = socketIo.listen(http_server);

//connection
io.sockets.on('connection', (socket)=>{

	socket.on('message', (room, data)=>{
		socket.to(room).emit('message', room, data)//房间内所有人,除自己外,转发消息
	});
	socket.on('join', (room)=> {

		socket.join(room);//先加入房间,后面在进行判断,加入房间或者踢出房间

		var myRoom = io.sockets.adapter.rooms[room];
		var users = Object.keys(myRoom.sockets).length;

		//在这里可以控制进入房间的人数,现在一个房间最多 2个人
		//为了便于客户端控制,如果是多人的话,应该将目前房间里
		//人的个数当做数据下发下去。
		if(users < USERCOUNT) {
			socket.emit('joined', room, socket.id);	//给自己反馈一个成功加入房间的消息
			if (users > 1) {//房间有其他用户,通知其他用户有新的用户加入房间
				socket.to(room).emit('otherjoin', room);//除自己之外,房间内其他用户反馈一个有其他用户加入的消息
			}
		}else {
			socket.leave(room);//踢出房间
			socket.emit('full', room, socket.id);//反馈一个房间满员消息	
		}
	 	//socket.to(room).emit('joined', room, socket.id);//除自己之外,房间内的人都发一遍
		//io.in(room).emit('joined', room, socket.id)//房间内所有人
	 	//socket.broadcast.emit('joined', room, socket.id);//除自己,全部站点	
	});

	socket.on('leave', (room)=> {//退出房间的消息

		var myRoom = io.sockets.adapter.rooms[room];
		var users = (myRoom)? Object.keys(myRoom.sockets).length:0;

		//socket.leave(room);
		socket.to(room).emit('bye', room, socket.id);//房间内所有人,除自己外,
		socket.emit('leaved',room, socket.id);//退出房间成功,反馈退出成功消息
		socket.leave(room);
		//socket.to(room).emit('leaved', room, socket.id);//出自己外
		//io.in(room).emit('leaved', room, socket.id);//房间内所有人
		//socket.broadcast.emit('leaved', room, socket.id);//除自己,全部站点
	});

});

//connection
sockio.sockets.on('connection', (socket)=>{

	socket.on('message', (room, data)=>{
		socket.to(room).emit('message', room, data)//转发消息给房间内所有人,除自己外
	});

	socket.on('join', (room)=> {

		socket.join(room);//先加入房间,之后在进行判断
		var myRoom = sockio.sockets.adapter.rooms[room];//根据房间名称获取到房间
		var users = (myRoom)?Object.keys(myRoom.sockets).length:0;//判断房间是否存在,存在在获取到房间内的用户个数
		if(users < USERCOUNT){//已条件限制房间内用户数量
			socket.emit('joined', room, socket.id);//反馈房间加入成功
			if(users > 1){
				socket.to(room).emit('otherJoin',room,socket.id);//除自己之外,房间内其他用户反馈一个有其他用户加入的消息
			}
		}else{
			socket.leave(room);//退出房间
			socket.emit('full',room,socket.id);//反馈房间已满的消息
		}
	 	//socket.to(room).emit('joined', room, socket.id);//除自己之外
		//io.in(room).emit('joined', room, socket.id)//房间内所有人
	 	//socket.broadcast.emit('joined', room, socket.id);//除自己,全部站点	
	});

	socket.on('leave', (room)=> {//客户端退出房间的消息
		var myRoom = sockio.sockets.adapter.rooms[room];
		var users = Object.keys(myRoom.sockets).length;
		//users - 1;

		//socket.leave(room);
		socket.to(room).emit('bye',room,socket.id);//通知房间内其他人有人退出房间
	 	socket.emit('leaved', room, socket.id);	//反馈退出成功
	 	//socket.to(room).emit('joined', room, socket.id);//除自己之外
		//io.in(room).emit('joined', room, socket.id)//房间内所有人
	 	//socket.broadcast.emit('joined', room, socket.id);//除自己,全部站点	
	});
});
http_server.listen(80, '0.0.0.0');//主机所有网口监听80端口,如果无法访问需要排查一下公网服务器主机防火墙是否将80端口开放出来
https_server.listen(443, '0.0.0.0');//主机所有网口监听443端口,如果无法访问需要排查一下公网服务器主机防火墙是否将443端口开放出来

3.nodejs启动方式

1.直接通过 nodejs 运行

nodejs server.js (关闭窗口将会停止服务)

2.通过第三工具 forever,先通过npm 安装 forever工具

npm install  forever
forever start  server.js //启动服务器
forever stop  server.js //停止服务器

启动过程中出现socketIo.listen is not a function找不到函数,是因为socketIO版本过高,需要将原有的卸载再重新安装指定版本
在这里插入图片描述
卸载原有

npm uninstall socket.io

安装指定版本

npm install socket.io@2.0.3

三、客户端实现

1.代码实现

webserver路径下新建public公开目录,访问ip:80就能放访问到public目录下的所有文件及文件夹

mkdir public

进入public目录,在新建一个chatRoomDemo目录并进入

mkdir chatRoomDemo

chatRoomDemo目录下新建index.html

vi index.html

2.index.html 中实现界面代码

<html>
	<head>
		<title>Chat Room </title>
		<link rel="stylesheet" href="./css/main.css"></link>
	</head>
	<body>
		<table align="center">
			<tr>
				<td>
					<label>UesrName:</label>
					<input type=text id="username"></input>
				</td>
			</tr>
			<tr>
                                <td>
					<label>room:</label>
                                        <input type=text id="room"></input>
					<button id="connect">Connect</button>
					<button id="leave" disabled>Leave </button>
                                </td>
                        </tr>
			<tr>
                                <td>
					<label>Content: </label><br>
					<textarea disables style="line-height:1.5;" id="output" rows="10" cols="100"></textarea>
                                </td>
                        </tr>
			<tr>
                                <td>
					<label>Input :</label><br>
					<textarea disabled id="input" rows="3" cols="100"></textarea>
                                </td>
                        </tr>
			<tr>
                                <td>
					<button id="send">Send</button>
                                </td>
                        </tr>

		</table>
		
		<script src="https://cdnjs.cloudflare.com/ajax/libs/socket.io/2.0.3/socket.io.js"></script>
		<script src="./js/client.js"></script>
	</body>
</html>

在chatRoomDemo目录下新建css目录,进入css目录在新建main.css

mkdiar css 
cd css 
vi  main.css

3.main.css 中界面样式布局代码

button {
  margin: 0 20px 25px 0;
  vertical-align: top;
  width: 134px;
}

div#getUserMedia {
  padding: 0 0 8px 0;
}

div.input {
  display: inline-block;
  margin: 0 4px 0 0;
  vertical-align: top;
  width: 310px;
}

div.input > div {
  margin: 0 0 20px 0;
  vertical-align: top;
}

div.output {
  background-color: #eee;
  display: inline-block;
  font-family: 'Inconsolata', 'Courier New', monospace;
  font-size: 0.9em;
  padding: 10px 10px 10px 25px;
  position: relative;
  top: 10px;
  white-space: pre;
  width: 270px;
}

section#statistics div {
  display: inline-block;
  font-family: 'Inconsolata', 'Courier New', monospace;
  vertical-align: top;
  width: 308px;
}

section#statistics div#senderStats {
  margin: 0 20px 0 0;
}

section#constraints > div {
  margin: 0 0 20px 0;
}

section#video > div {
  display: inline-block;
  margin: 0 20px 0 0;
  vertical-align: top;
  width: calc(50% - 22px);
}

section#video > div div {
  font-size: 0.9em;
  margin: 0 0 0.5em 0;
  width: 320px;
}

h2 {
  margin: 0 0 1em 0;
}

section#constraints label {
  display: inline-block;
  width: 156px;
}

section {
  margin: 0 0 20px 0;
  padding: 0 0 15px 0;
}

section#video {
  width: calc(100% + 20px);
}

video {
  --width: 90%;
  display: inline-block;
  width: var(--width);
  height: calc(var(--width) * 0.75);
  margin: 0 0 10px 0;
}

@media screen and (max-width: 720px) {
  button {
    font-weight: 500;
    height: 56px;
    line-height: 1.3em;
    width: 90px;
  }

  div#getUserMedia {
    padding: 0 0 40px 0;
  }

  section#statistics div {
    width: calc(50% - 14px);
  }

  video {
    display: inline-block;
    width: var(--width);
    height: 96px;
  }
}

在chatRoomDemo目录下新建js目录,进入js目录在新建client.js

mkdiar js
cd js
vi  client.js

3.client.js 中界面交互代码

'use strict'

//获取到组件
var userName = document.querySelector('input#username');
var inputRoom = document.querySelector('input#room');
var btnConnect = document.querySelector('button#connect');
var btnLeave = document.querySelector('button#leave');
var outputArea = document.querySelector('textarea#output');
var inputArea = document.querySelector('textarea#input');
var btnSend = document.querySelector('button#send');

var socket;
var room;

//连接按钮的点击事件
btnConnect.onclick = ()=>{
	//socket connect
	socket = io.connect();

	//recieve message
	socket.on('joined', (room, id)=>{//成功加入房间
		btnConnect.disabled = true;//关闭连接按钮事件
		btnLeave.disabled = false;//开启断开连接按钮事件
		inputArea.disabled = false;//开启消息输入框事件
		btnSend.sidabled = false;//开启发送消息按钮事件
	});

	socket.on('leaved', (room, id) =>{
		btnConnect.disabled = false;//开启连接按钮事件
                btnLeave.disabled = true;//关闭断开连接按钮事件
                inputArea.disabled = true;//关闭消息输入框事件
                btnSend.sidabled = true;//关闭发送消息按钮事件

		socket.disconnect();
	});

	socket.on('message', (room, data)=>{
		outputArea.scrollTop = outputArea.scrollHeight;//窗口总是显示最后的内容
		outputArea.value = outputArea.value+data+'\r';

	});

	socket.on('disconnect', (socket)=>{
		btnConnect.disabled = false;//开启连接按钮事件
                btnLeave.disabled = true;//关闭断开连接按钮事件
                inputArea.disabled = true;//关闭消息输入框事件
                btnSend.sidabled = true;//关闭发送消息按钮事件
	});

	socket.on('full',(socket)=>{
		console.log('不好意思!满员了');
		outputArea.value = '不好意思!满员了';

	});

	//send message
	room = inputRoom.value;
	socket.emit('join', room);

}

//发送按钮的点击事件
btnSend.onclick = ()=>{
	var data = inputArea.value;
	data = userName.value+" : "+data;
	socket.emit('message', room, data);
	inputArea.value = '';

}

//断开按钮的点击事件
btnLeave.onclick = ()=>{
	room = inputRoom.value;
	socket.emit('leave', room);

}

//消息输入框键盘消息
inputArea.onkeypress = (event)=>{
	if(event.keyCode == 13){//回车发送消息
		var data = inputArea.value;
        	data = userName.value+" : "+data;
        	scoket.emit('message', room, data);
        	inputArea.value = '';
		event.preventDefault();//阻止默认行为
	}
}

效果

在这里插入图片描述
以上只是记录简单实现了一个聊天室的功能,后续将加入音视频通话。

  • 0
    点赞
  • 12
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值