草庐IT

【局域网音频实时传输、屏幕单播及广播】

夕日_- 2024-05-08 原文

局域网音频实时传输、屏幕单播及广播

程序源代码及可执行程序链接: https://gitee.com/azhezhezhezhe/DesktopSharing/tree/master/

设计思路

教师端

主界面加载时,会调用方法对与本机ip网络地址段相同的一系列地址进行ping操作,能收到返回信息的说明此ip地址活跃于本局域网,这些ip地址会被添加到单选模式的ip选择下拉框和多播模式的ip复选框中。
模式选择完毕后,点击开始共享,当当前模式为多播或广播时将选中的ip地址以a字符为间隔连接,并使用udpclient广播,当为单播模式时,广播的信息为单个0字符,这样操作的目的时使学生端根据收到的信息判断是否需要接收教师端发送的音频信息。与此同时使用定时器每隔固定时间调用获取屏幕图像的方法,将图像转换成数据流,并使用udpclient发送给指定ip或多播组ip,由于数据量过大,需要分包,按每1400字节分割数据,并在首部加两位的头,第一位取值A、B、C,分别代表数据流开始、中间、结束,第二位取值为分包的个数。
开始共享后,点击开启声音,使用NAudio库,开始采集麦克风的声音信息,不断存入缓存,同时读指针不断读取缓存,当达到指定大小后,触发事件,执行委托,将读取的缓存数据使用udpclient发送到指定多播组ip。

学生端

主界面加载时,点击开始,首先进行接收来自教师端的广播信息,若接收信息为0字符,则为单播模式,若接收信息为IP字符串,则将其中IP与本端IP依次对比,若存在相同,则将本端接收图像数据的udpclient加入多播组,接收图像数据时,依次查询数据包的头部,从开始标识数据包开始接收,直到结尾标识数据包,然后整合数据,将其复原成图片,显示到端界面。
当成功接收图像信息后才可点击开启声音,用于接收声音数据的udpclient加入多播组,接收教师端发送的数据,存入待播放缓存,播放声音。

主要步骤代码

获取局域网IP

private List<String> ipItems = new List<String>();//存通过ping探测到的ip
        private List<String> PingIp()//通过ping的方式找到局域网中的活动ip
        {
            var hostName = Dns.GetHostName();//得到本机主机名
            var ipEntry = Dns.GetHostEntry(hostName);//根据主机名得到IPHostEntry实例
            var ips = ipEntry.AddressList;//获得本机的ip地址列表
            string sIP = null;
            foreach (var ipa in ips)
            {
                if (ipa.AddressFamily == AddressFamily.InterNetwork)
                {
                    sIP = ipa.ToString();//得到列表中最后一个ip,转字符串
                }
            }
            if (sIP != null)
            {
                for (int i = 1; i < 255; i++)
                {
                    var ip = sIP.Substring(0, sIP.LastIndexOf(".")) + "." + i;//截取出网络地址段+i
                    if (ip == sIP)//跳过本机ip地址
                    {
                        continue;
                    }
                    var ping = new Ping();
                    ping.PingCompleted += ping_completed;//事件触发执行委托
                    ping.SendAsync(ip, 1000, null);//分别ping这些ip地址
                }
                Thread.Sleep(1000);
            }
            return ipItems;
        }

发送与接收模式信息

 public void sendScreen()//发送窗口截图
        {
            IPEndPoint iep1 = null;
            IPEndPoint iep2 = null;
            byte[] ipSelected = null;//被选中的目的主机ip
            string ipstr = null;
            UdpClient udpClient1 = new UdpClient(2001);//用于发送模式信息
            UdpClient udpClient2 = new UdpClient(2002);//用于发送图片
            byte[] data = screenData();//执行窗口截图函数
            if (form2.radioButton1.Checked == true)//单播被选中
            {
                if (form2.comboBox1.SelectedItem != null)//已有选中ip
                {
                    iep1 = new IPEndPoint(IPAddress.Parse(form2.comboBox1.SelectedItem.ToString()), 2001);
                    iep2 = new IPEndPoint(IPAddress.Parse(form2.comboBox1.SelectedItem.ToString()), 2002);
                    ipSelected = Encoding.Default.GetBytes("0");
                    udpClient1.Send(ipSelected, ipSelected.Length, iep1);//给目的ip发送长度为1的数据包//以告知发送端当前为单播模式
                }
            }
            else if (form2.radioButton2.Checked == true)//多播被选中
            {
                iep1 = new IPEndPoint(IPAddress.Parse("192.168.1.255"), 2001);//直接广播地址及端口
                iep2 = new IPEndPoint(IPAddress.Parse("224.1.2.3"), 2002); //多播地址及端口            
                udpClient1.EnableBroadcast = true;//允许发送广播
                //将两个多选框中被选中项转换为字符串,以a为间隔
                for (int j = 0; j < form2.checkedListBox1.SelectedItems.Count; j++)
                {
                    ipstr += form2.checkedListBox1.SelectedItems[j].ToString() + "a";
                }
                for (int j = 0; j < form2.checkedListBox2.SelectedItems.Count; j++)
                {
                    ipstr += form2.checkedListBox2.SelectedItems[j].ToString() + "a";
                }
                ipSelected = Encoding.Default.GetBytes(ipstr);//字符串转byte数组
                udpClient1.Send(ipSelected, ipSelected.Length, iep1);//广播存有被选ip的数据包//以告知发送端为多播模式

            }
            else if (form2.radioButton3.Checked == true)//广播被选中
            {
                iep1 = new IPEndPoint(IPAddress.Parse("192.168.1.255"), 2001);//直接广播地址及端口
                iep2 = new IPEndPoint(IPAddress.Parse("224.1.2.3"), 2002); //多播地址及端口   
                udpClient1.EnableBroadcast = true;//允许发送广播
                for (int i = 0; i < ipItems.Count; i++)//将所有ip转为字符串,a为间隔
                {
                    ipstr += ipItems[i] + "a";
                }
                ipSelected = Encoding.Default.GetBytes(ipstr);//字符串转byte数组
                udpClient1.Send(ipSelected, ipSelected.Length, iep1);//广播存有被选ip的数据包//以告知发送端为多播模式

            }
 public void Listen1()//监听模式信息//单播或多播及ip
        {
            udpClient1 = new UdpClient(2001);
            iep1 = new IPEndPoint(IPAddress.Any, 2001);
            //获得本机ip
            var hostname = Dns.GetHostName();
            var ipEntry = Dns.GetHostEntry(hostname);
            var ips = ipEntry.AddressList;
            string sIP = null;
            foreach (var ipa in ips)
            {
                if (ipa.AddressFamily == AddressFamily.InterNetwork)
                {
                    sIP = ipa.ToString();
                }
            }
            bool flag = true;
            bool flag2 = false;//TRUE为多播//FALSE为单播
            while (flag)
            {
                byte[] receive = udpClient1.Receive(ref iep1);
                int count = receive.Length;
                if (count > 0)
                {
                    if (count == 1)//收到字节数为1时意味着单播
                    {
                        break;
                    }
                    string[] str = Encoding.Default.GetString(receive).Split('a');//分割收到的ip数据
                    for (int i = 0; i < str.Length; i++)//依次与本机ip地址对比
                    {
                        if (str[i].Equals(sIP))
                        {
                            flag2 = true;
                            break;
                        }
                    }
                    flag = false;
                }
                receive = null;
            }
            Listen2(flag2);
        }

获取屏幕图像

        private byte[] screenData()//截取窗口
        {
            MemoryStream ms = new MemoryStream();
            try
            {
                byte[] by;
                Bitmap bmp = new Bitmap(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height);
                Graphics gp = Graphics.FromImage(bmp);
                //将屏幕图像复制到bmp中
                gp.CopyFromScreen(0, 0, 0, 0, new Size(Screen.PrimaryScreen.Bounds.Width, Screen.PrimaryScreen.Bounds.Height));
                bmp.Save(ms, ImageFormat.Jpeg);
                by = ms.GetBuffer();
                pictureBox1.Image = bmp.GetThumbnailImage(320, 256, null, IntPtr.Zero);//返回缩略图
                ms.Close();
                ms.Dispose();
                return by;
            }
            catch
            {
                ms.Close();
                ms.Dispose();
                return null;
            }
        }

发送与接收图像

        public void sendScreen()//发送窗口截图
        {
            IPEndPoint iep1 = null;
            IPEndPoint iep2 = null;
            byte[] ipSelected = null;//被选中的目的主机ip
            string ipstr = null;
            UdpClient udpClient1 = new UdpClient(2001);//用于发送模式信息
            UdpClient udpClient2 = new UdpClient(2002);//用于发送图片
            byte[] data = screenData();//执行窗口截图函数
            if (form2.radioButton1.Checked == true)//单播被选中
            {
                if (form2.comboBox1.SelectedItem != null)//已有选中ip
                {
                    iep1 = new IPEndPoint(IPAddress.Parse(form2.comboBox1.SelectedItem.ToString()), 2001);
                    iep2 = new IPEndPoint(IPAddress.Parse(form2.comboBox1.SelectedItem.ToString()), 2002);
                    ipSelected = Encoding.Default.GetBytes("0");
                    udpClient1.Send(ipSelected, ipSelected.Length, iep1);//给目的ip发送长度为1的数据包//以告知发送端当前为单播模式
                }
            }
            else if (form2.radioButton2.Checked == true)//多播被选中
            {
                iep1 = new IPEndPoint(IPAddress.Parse("192.168.1.255"), 2001);//直接广播地址及端口
                iep2 = new IPEndPoint(IPAddress.Parse("224.1.2.3"), 2002); //多播地址及端口            
                udpClient1.EnableBroadcast = true;//允许发送广播
                //将两个多选框中被选中项转换为字符串,以a为间隔
                for (int j = 0; j < form2.checkedListBox1.SelectedItems.Count; j++)
                {
                    ipstr += form2.checkedListBox1.SelectedItems[j].ToString() + "a";
                }
                for (int j = 0; j < form2.checkedListBox2.SelectedItems.Count; j++)
                {
                    ipstr += form2.checkedListBox2.SelectedItems[j].ToString() + "a";
                }
                ipSelected = Encoding.Default.GetBytes(ipstr);//字符串转byte数组
                udpClient1.Send(ipSelected, ipSelected.Length, iep1);//广播存有被选ip的数据包//以告知发送端为多播模式

            }
            else if (form2.radioButton3.Checked == true)//广播被选中
            {
                iep1 = new IPEndPoint(IPAddress.Parse("192.168.1.255"), 2001);//直接广播地址及端口
                iep2 = new IPEndPoint(IPAddress.Parse("224.1.2.3"), 2002); //多播地址及端口   
                udpClient1.EnableBroadcast = true;//允许发送广播
                for (int i = 0; i < ipItems.Count; i++)//将所有ip转为字符串,a为间隔
                {
                    ipstr += ipItems[i] + "a";
                }
                ipSelected = Encoding.Default.GetBytes(ipstr);//字符串转byte数组
                udpClient1.Send(ipSelected, ipSelected.Length, iep1);//广播存有被选ip的数据包//以告知发送端为多播模式

            }

            udpClient2.Client.SendBufferSize = 1024 * 1024 * 5;//发送端缓存大小
            int size = 0;
            int count = 1399;
            int length = (int)Math.Ceiling((double)data.Length / 1400);//分包数量
            while (size < data.Length)
            {
                byte[] temp = null;
                //数据分包//给数据添加特殊头//分别表示属于哪部分(开始、中间、结束)、分包数
                if (size == 0)//数据开始部分
                {
                    temp = SetFirst(data.Skip(size).Take(count).ToArray(), length);
                }
                else if (size + count > data.Length)//数据中间部分
                {
                    temp = SetLast(data.Skip(size).Take(count).ToArray(), length);
                }
                else//数据结尾部分
                {
                    temp = SetMiddle(data.Skip(size).Take(count).ToArray(), length);
                }
                if (iep2!=null)
                {
                    udpClient2.Send(temp, temp.Length, iep2);//发送数据
                }
                size += count;//取缓存的位置移动
            }
            udpClient1.Close();//释放资源
            udpClient2.Close();
        }
        private static byte[] SetFirst(byte[] arr, int length)
        {
            byte[] res = new byte[arr.Length + 2];
            arr.CopyTo(res, 2);//原本数据后移2位
            res[0] = (byte)'A';//数据属于哪部分
            res[1] = (byte)length;//分包数
            return res;
        }
        private static byte[] SetMiddle(byte[] arr, int length)
        {
            byte[] res = new byte[arr.Length + 2];
            arr.CopyTo(res, 2);
            res[0] = (byte)'B';
            res[1] = (byte)length;
            return res;
        }
        private static byte[] SetLast(byte[] arr, int length)
        {
            byte[] res = new byte[arr.Length + 2];
            arr.CopyTo(res, 2);
            res[0] = (byte)'C';
            res[1] = (byte)length;
            return res;
        }
 public void Listen2(bool flag)
        {
            if (udpClient2 == null)
            {
                udpClient2 = new UdpClient(2002);
                iep2 = new IPEndPoint(IPAddress.Any, 2002);
                if (flag)
                {
                    udpClient2.JoinMulticastGroup(IPAddress.Parse("224.1.2.3"), 0);
                }
                ReceiveThread = new Thread(new ThreadStart(ListenThread));
                ReceiveThread.IsBackground = true;
                ReceiveThread.Start();
            }
        }
        private void ListenThread()
        {
            try
            {
                bool flag = false;
                int length = 0;
                while (true)
                {
                    byte[] receive = udpClient2.Receive(ref iep2);
                    int count = receive.Length;
                    if (receive[0] == 'A')//数据开始
                    {
                        flag = true;
                    }
                    if (count > 0 && flag)
                    {
                        if (receive[0] == 'C')//数据结尾
                        {
                            length++;
                            if (length != receive[1])//分包书与接收数不符
                            {
                                TempData.Clear();
                            }
                            else
                            {
                                TempData.Add(receive.Take(count).ToArray());
                                var res = GetAllData(TempData);
                                MemoryStream ms = new MemoryStream(res);
                                pictureBox1.Image = Image.FromStream(ms);
                                Action action = () => { button3.Enabled = true; };
                                Invoke(action);
                                ms.Close();
                                TempData.Clear();
                                length = 0;
                            }
                        }
                        else
                        {
                            length++;
                            TempData.Add(receive);
                        }
                    }
                }
            }
            catch (Exception)
            {
                TempData.Clear();
            }
        }
        private static byte[] GetAllData(List<byte[]> list)//将头部之外的数据整合
        {
            List<byte> data = new List<byte>();
            for (int i = 0; i < list.Count; i++)
            {
                for (int j = 2; j < list[i].Length; j++)
                {
                    data.Add(list[i][j]);
                }
            }
            return data.ToArray();
        }

发送与接收声音

使用了Naudio开源库,在VS2022项目右键-管理NuGet程序包里搜索安装,就可以在代码里Using了,NAudio库的链接:https://github.com/naudio/NAudio

 private WaveIn waveIn;
        private UdpClient udpClient3;//用于发送声音数据
        private void toolStripMenuItem3_Click(object sender, EventArgs e)//开启关闭声音
        {
            if (toolStripMenuItem3.Text.Equals("开启声音"))
            {
                toolStripMenuItem3.Text = "关闭声音";
                udpClient3 = new UdpClient(2003);
                waveIn = new WaveIn();//生成音频输入设备
                waveIn.WaveFormat = new WaveFormat(16000, 16, 1); //设置录音格式
                //绑定声音输入处理事件
                waveIn.DataAvailable += new EventHandler<WaveInEventArgs>(DataAvailable);
                waveIn.StartRecording();//开始采集声音
            }
            else
            {
                toolStripMenuItem3.Text = "开启声音";
                waveIn.StopRecording();//停止采集声音
                CloseUdpCliend();//关闭组播
            }
        }
        private void DataAvailable(object sender, WaveInEventArgs e)//处理事件的方法
        {
            SendMessage(e.Buffer, 2003);
        }
        public async void SendMessage(byte[] message, int destport)//发送声音数据
        {
            if (udpClient3 == null) return;
            byte[] buffer = message;
            int len = 0;
            try
            {
                len = await udpClient3.SendAsync(buffer, buffer.Length, new IPEndPoint(IPAddress.Parse("224.1.2.4"), destport));
            }
            catch (Exception)
            {
                len = 0;
            }
            if (len <= 0)
            { 
                Thread.Sleep(100);
            }
        }
        public void CloseUdpCliend()
        {
            if (udpClient3 == null)
            { throw new ArgumentNullException("udpClient cant not null"); }

            try
            {
                udpClient3.Client.Shutdown(SocketShutdown.Both);
            }
            catch (Exception)
            {
            }
            udpClient3.Close();
            udpClient3 = null;
        }
private WaveOut waveOut;//音频输出
        private BufferedWaveProvider buffered;//接收声音缓冲
        private UdpClient udpClient3;//接收

        private void button3_Click(object sender, EventArgs e)
        {
            if (button3.Text.Equals("开启声音"))
            {
                //接收udp组播数据用
                udpClient3 = new UdpClient(2003);
                //生成默认声音播放对象
                waveOut = new WaveOut();
                //设置声音格式
                WaveFormat wf = new WaveFormat(16000, 16, 1);
                //初始化待播放声音缓存对象
                buffered = new BufferedWaveProvider(wf);
                //初始化播放设备
                waveOut.Init(buffered);
                Start();//开始接收数据
                button3.Text = "关闭声音";
                waveOut.Play();//开始播放声音
            }
            else
            {
                button3.Text = "开启声音";
                waveOut.Stop();//停止播放声音
                CloseUdpCliend();//关闭接收           
            }
        }
        public void Start()
        {
            while (true)
            {
                try
                {
                    udpClient3.JoinMulticastGroup(IPAddress.Parse("224.1.2.4"));
                    ReceiveMessage();
                    break;
                }
                catch (Exception)
                {
                    Thread.Sleep(100);
                }
            }
        }

        private async void ReceiveMessage()//接收声音数据
        {
            while (true)
            {
                if (udpClient3 == null)return;
                try
                {
                    UdpReceiveResult udpReceiveResult = await udpClient3.ReceiveAsync();
                    byte[] message = udpReceiveResult.Buffer;
                    buffered.AddSamples(message, 0, message.Length);//添加到待播放缓存中
                }
                catch (Exception)
                {
                }
            }
        }
        public void CloseUdpCliend()
        {
            if (udpClient3 == null)
                throw new ArgumentNullException("udpClient cant not null");

            try
            {
                udpClient3.Client.Shutdown(SocketShutdown.Both);
            }
            catch (Exception)
            {
            }
            udpClient3.Close();
            udpClient3 = null;
        }

运行截图

发送端





接收端

存在的明显缺陷

1.传输图像中没有鼠标
2.图像成功传输二十几秒后就出问题了,推测与发送端或接收端缓存有关,还未解决

有关【局域网音频实时传输、屏幕单播及广播】的更多相关文章

  1. 屏幕录制为什么没声音?检查这2项,轻松解决 - 2

    相信很多人在录制视频的时候都会遇到各种各样的问题,比如录制的视频没有声音。屏幕录制为什么没声音?今天小编就和大家分享一下如何录制音画同步视频的具体操作方法。如果你有录制的视频没有声音,你可以试试这个方法。 一、检查是否打开电脑系统声音相信很多小伙伴在录制视频后会发现录制的视频没有声音,屏幕录制为什么没声音?如果当时没有打开音频录制,则录制好的视频是没有声音的。因此,建议在录制前进行检查。屏幕上没有声音,很可能是因为你的电脑系统的声音被禁止了。您只需打开电脑系统的声音,即可录制音频和图画同步视频。操作方法:步骤1:点击电脑屏幕右下侧的“小喇叭”图案,在上方的选项中,选择“声音”。 步骤2:在“声

  2. (附源码)vue3.0+.NET6实现聊天室(实时聊天SignalR) - 2

    参考文章搭建文章gitte源码在线体验可以注册两个号来测试演示图:一.整体介绍  介绍SignalR一种通讯模型Hub(中心模型,或者叫集线器模型),调用这个模型写好的方法,去发送消息。  内容有:    ①:Hub模型的方法介绍    ②:服务器端代码介绍    ③:前端vue3安装并调用后端方法    ④:聊天室样例整体流程:1、进入网站->调用连接SignalR的方法2、与好友发送消息->调用SignalR的自定义方法 前端通过,signalR内置方法.invoke()  去请求接口3、监听接受方法(渲染消息)通过new signalR.HubConnectionBuilder().on

  3. ruby-on-rails - 在 Rails 应用程序的前端获取实时日志 - 2

    在Rails3.x应用程序中,我正在使用net::ssh并向远程pc运行一些命令。我想向用户的浏览器显示实时日志。比如,如果两个命令在net中运行::ssh执行即echo"Hello",echo"Bye"被传递然后"Hello"应该在执行后立即显示在浏览器中。这是代码我在ruby​​onrails应用程序中使用ssh连接和运行命令Net::SSH.start(@servers['local'],@machine_name,:password=>@machine_pwd,:timeout=>30)do|ssh|ssh.open_channeldo|channel|channel.requ

  4. ruby - 使用 Ruby 将 HTTP GET 的响应主体流式传输到 HTTP POST - 2

    我正在尝试下载一个大文件,然后使用Ruby将该文件发布到REST端点。该文件可能非常大,即超过可以存储在内存中甚至磁盘上的临时文件中的容量。我一直在用Net::HTTP尝试这个,但我愿意接受任何其他库(rest-client等)的解决方案,只要他们做我想做的事情。这是我尝试过的:require'net/http'source_uri=URI("https://example.org/very_large_file")source_request=Net::HTTP::Get.new(source_uri)source_http=Net::HTTP.start(source_uri.ho

  5. ruby-on-rails - 将 Heroku 环境变量传输到 Docker 实例 - 2

    我在Heroku上构建了一个必须在Docker容器内运行的RoR应用程序。为此,我使用officialDockerfile.因为它在Heroku中很常见,所以我需要一些附加组件才能使这个应用程序完全运行。在生产中,变量DATABASE_URL在我的应用程序中可用。但是,如果我尝试其他一些使用环境变量(在我的例子中是Mailtrap)的加载项,变量不会在运行时复制到实例中。所以我的问题很简单:如何让docker实例在Heroku上执行时知道环境变量?您可能会问,我已经知道我们可以在docker-compose.yml中指定一个environment指令。我想避免这种情况,以便能够通过项目

  6. micropython复现经典单片机项目(二)可视化音频 频谱解析(基本搞定) - 2

    本人是音乐爱好者,从小就特别喜欢那个随着音乐跳动的方框效果,就是这个:arduino上一大把对,我忍你很久了,我就想用mpy做,全网没有,行我自己研究。果然兴趣是最好的老师,我之前有篇博客专门讲音频,有兴趣的可以回顾一下。提到可视化频谱,必然绕不开fft,大学学过这玩意,当时一心玩,老师讲的一个字都么听进去,网上教程简略扫了一下,大该就是把时域转频域的工具,我大mpy居然没有fft函数,奶奶的,先放着。音频信息如何收集?第一种傻瓜式的ADC,模拟转数字,原始粗暴,第二种,I2S库,我之前博客有讲过,数据是PCM编码。然后又去学PCM编码,一学豁然开朗,舒服,以代码为例:audio_in=I2S

  7. ruby - Capistrano 3.0 文件传输? - 2

    似乎Capistrano曾经有anextensivefiletransferpackage.但是在3.0版本重写后好像没有了。知道是否还有其他方法可以将文件传输到服务器/从服务器传输文件吗? 最佳答案 下载:desc"downloadfoobar.txtintolocal_dir/"task:foobardodownload!"some_remote_path/foobar.txt","local_dir/"end我知道这从Cap3.2.1开始有效,因为我现在正在使用它。 关于ruby-

  8. ruby-on-rails - 在服务器端检测屏幕尺寸和像素密度? - 2

    我一直在做一些研究,我想我已经知道答案了,但我想知道是否有任何方法可以在不使用javascript或依赖CSS3媒体的情况下获得设备的屏幕尺寸和像素密度查询。本质上,我正在研究如何获取屏幕分辨率和像素密度,以便服务器可以决定在URI请求中为服务器提供哪个图像。到目前为止,我还没有发现任何证据表明这是可能的,但我想嘿,为什么不问问呢? 最佳答案 我不完全同意上面的正确答案。实际上,这个答案在很多情况下都是正确的……但理论上并非如此。通常向Web服务器发出的请求包含一个User-Agent字段,从理论上讲,该字段可用于识别有关设备屏幕分

  9. ruby-on-rails - 如何将数据从我的生产数据库传输到 heroku 中的登台数据库? - 2

    我正在尝试将数据从我的生产数据库传输到我的登台数据库,但没有成功。我正在关注heroku的相关文档:http://devcenter.heroku.com/articles/pgbackups#transfers这些是我运行的命令...$herokuaddons:addpgbackups--remotestaging$herokuaddons:addpgbackups--remoteproduction$herokupgbackups:capture--remoteproduction$herokupgbackups:restoreDATABASE`herokupgbackups:ur

  10. ruby-on-rails - Ruby on Rails 3 - 公共(public)实时聊天 - 2

    我想使用Rails3创建一个公共(public)实时聊天应用程序。我在rails2上找到了一些例子。任何人都可以告诉你一个很好的例子/教程来使用rails3开发一个实时聊天应用程序。 最佳答案 当我试图在我的Rails3应用程序中实现一个公共(public)和私有(private)聊天系统时,我遇到了几个障碍。我查看了faye、juggernaut、node.js等。最终在尝试了几种方法之后,我能够实现一个运行良好的系统:1)我开始关注Railscast260中的faye消息传递视频指南。正如DevinM所提到的,我能够快速设置一个

随机推荐