话不多说,先上图

背景:

微信聊天,经常会遇见视频发不了,嗯,还有聊天不方便的问题,于是我就自己买了服务器,部署了一套可以直接在微信打开的网页进行聊天,这样只需要发送个url给朋友,就能聊天了!

由于自己无聊弄着玩的,代码比较粗糙,各位多指正!

1、首先安装SignalR,这步我就不做过多说明了

安装好以后在根目录新建一个Hubs文件夹,做用户的注册和通知

MessageHub.cs 文件

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Hubs;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Web; namespace SignalR.Hubs
{
[HubName("MessageHub")]
public class MessageHub : Hub
{
private readonly ChatTicker ticker;
public MessageHub()
{
ticker = ChatTicker.Instance;
} public void register(string username, string group = "default")
{
var list = (List<SiginalRModel>)HttpRuntime.Cache.Get("msg_hs");
if (list == null)
{
list = new List<SiginalRModel>();
} if (list.Any(x => x.connectionId == Context.ConnectionId))
{
Clients.Client(Context.ConnectionId).broadcastMessage("已经注册,无需再次注册");
}
else if (list.Any(x => x.name == username))
{
var model = list.Where(x => x.name == username && x.group == group).FirstOrDefault();
if (model != null)
{
//注册到全局
ticker.GlobalContext.Groups.Add(Context.ConnectionId, group);
Clients.Client(model.connectionId).exit();
ticker.GlobalContext.Groups.Remove(model.connectionId, group);
list.Remove(model);
model.connectionId = Context.ConnectionId;
list.Add(model);
Clients.Group(group).removeUserList(model.connectionId);
Thread.Sleep(200);
var gourpList = list.Where(x => x.group == group).ToList();
Clients.Group(group).appendUserList(Context.ConnectionId, gourpList);
HttpRuntime.Cache.Insert("msg_hs", list);
// Clients.Client(model.connectionId).broadcastMessage("名称重复,只能注册一个");
}
//Clients.Client(Context.ConnectionId).broadcastMessage("名称重复,只能注册一个");
}
else
{
list.Add(new SiginalRModel() { name = username, group = group, connectionId = Context.ConnectionId }); //注册到全局
ticker.GlobalContext.Groups.Add(Context.ConnectionId, group);
Thread.Sleep(200); var gourpList = list.Where(x => x.group == group).ToList();
Clients.Group(group).appendUserList(Context.ConnectionId, gourpList);
HttpRuntime.Cache.Insert("msg_hs", list);
} } public void Say(string msg)
{
var list = (List<SiginalRModel>)HttpRuntime.Cache.Get("msg_hs");
if (list == null)
{
list = new List<SiginalRModel>();
}
var userModel = list.Where(x => x.connectionId == Context.ConnectionId).FirstOrDefault();
if (userModel != null )
{
Clients.Group(userModel.group).Say(userModel.name, msg);
}
} public void Exit()
{
OnDisconnected(true);
} public override Task OnDisconnected(bool s)
{
var list = (List<SiginalRModel>)HttpRuntime.Cache.Get("msg_hs");
if (list == null)
{
list = new List<SiginalRModel>();
}
var closeModel = list.Where(x => x.connectionId == Context.ConnectionId).FirstOrDefault(); if (closeModel != null)
{
list.Remove(closeModel); Clients.Group(closeModel.group).removeUserList(Context.ConnectionId); }
HttpRuntime.Cache.Insert("msg_hs", list); return base.OnDisconnected(s);
}
} public class ChatTicker
{
#region 实现一个单例 private static readonly ChatTicker _instance =
new ChatTicker(GlobalHost.ConnectionManager.GetHubContext<MessageHub>()); private readonly IHubContext m_context; private ChatTicker(IHubContext context)
{ m_context = context;
//这里不能直接调用Sender,因为Sender是一个不退出的“死循环”,否则这个构造函数将不会退出。
//其他的流程也将不会再执行下去了。所以要采用异步的方式。
//Task.Run(() => Sender());
} public IHubContext GlobalContext
{
get { return m_context; }
} public static ChatTicker Instance
{
get { return _instance; }
} #endregion
} public class SiginalRModel {
public string connectionId { get; set; } public string group { get; set; }
public string name { get; set; }
}
} 我把类和方法都写到一块了,大家最好是分开!

接下来是控制器

HomeController.cs

using Microsoft.AspNet.SignalR;
using Microsoft.AspNet.SignalR.Client;
using SignalR.Hubs;
using SignalR.ViewModels;
using System;
using System.Collections;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using Newtonsoft.Json;
using System.Diagnostics;
using System.Text.RegularExpressions; namespace SignalR.Controllers
{
public class HomeController : Controller
{
public ActionResult Index()
{ return View();
} public ActionResult GetV(string v)
{
if (!string.IsNullOrEmpty(v))
{
string url = RedisHelper.Get(v)?.ToString();
if (!string.IsNullOrEmpty(url))
{
return Json(new { isOk = true, m = url }, JsonRequestBehavior.AllowGet);
}
return Json(new { isOk = false}, JsonRequestBehavior.AllowGet);
}
return Json(new { isOk = false }, JsonRequestBehavior.AllowGet);
} public ActionResult getkey(string url)
{
if (!string.IsNullOrEmpty(url))
{
var s = "v" + Util.GetRandomLetterAndNumberString(new Random(), 5).ToLower();
var dt = Convert.ToDateTime(DateTime.Now.AddDays(1).ToString("yyyy-MM-dd 04:00:00"));
int min = Convert.ToInt16((dt - DateTime.Now).TotalMinutes);
RedisHelper.Set(s, url, min);
return Json(new { isOk = true, m = s }, JsonRequestBehavior.AllowGet);
}
return Json(new { isOk = false }, JsonRequestBehavior.AllowGet);
} public ActionResult upfile()
{
try
{
if (Request.Files.Count > 0)
{
var file = Request.Files[0];
if (file != null)
{
var imgList = new List<string>() { ".gif", ".jpg", ".bmp", ".png" };
var videoList = new List<string>() { ".mp4" };
FileModel fmodel = new FileModel(); string name = Guid.NewGuid().ToString();
string fileExt = Path.GetExtension(file.FileName).ToLower();//上传文件扩展名
string path = Server.MapPath("~/files/") + name + fileExt;
file.SaveAs(path); string extension = new FileInfo(path).Extension; if (extension == ".mp4")
{
fmodel.t = 2;
}
else if (imgList.Contains(extension))
{
fmodel.t = 1;
}
else
{
fmodel.t = 0;
}
string url = Guid.NewGuid().ToString();
fmodel.url = "http://" + Request.Url.Host;
if (Request.Url.Port != 80)
{
fmodel.url += ":" + Request.Url.Port;
}
fmodel.url += "/files/" + name + fileExt;
GetImageThumb(Server.MapPath("~") + "files\\" + name + fileExt, name);
return Json(new { isOk = true, m = "file:" + JsonConvert.SerializeObject(fmodel) }, JsonRequestBehavior.AllowGet);
}
}
}
catch(Exception ex)
{
Log.Info(ex);
} return Content("");
} public string GetImageThumb(string localVideo,string name)
{
string path = AppDomain.CurrentDomain.BaseDirectory;
string ffmpegPath = path + "/ffmpeg.exe";
string oriVideoPath = localVideo;
int frameIndex = 5;
int _thubWidth;
int _thubHeight;
GetMovWidthAndHeight(localVideo, out _thubWidth, out _thubHeight);
int thubWidth = 200;
int thubHeight = _thubWidth == 0 ? 200 : (thubWidth * _thubHeight / _thubWidth ); string thubImagePath = path + "files\\" + name + ".jpg";
string command = string.Format("\"{0}\" -i \"{1}\" -ss {2} -vframes 1 -r 1 -ac 1 -ab 2 -s {3}*{4} -f image2 \"{5}\"", ffmpegPath, oriVideoPath, frameIndex, thubWidth, thubHeight, thubImagePath);
Cmd.RunCmd(command);
return name;
} /// <summary>
/// 获取视频的帧宽度和帧高度
/// </summary>
/// <param name="videoFilePath">mov文件的路径</param>
/// <returns>null表示获取宽度或高度失败</returns>
public static void GetMovWidthAndHeight(string videoFilePath, out int width, out int height)
{
try
{ //执行命令获取该文件的一些信息
string ffmpegPath = AppDomain.CurrentDomain.BaseDirectory + "/ffmpeg.exe";
string output;
string error;
ExecuteCommand("\"" + ffmpegPath + "\"" + " -i " + "\"" + videoFilePath + "\"", out output, out error);
if (string.IsNullOrEmpty(error))
{
width = 0;
height = 0;
} //通过正则表达式获取信息里面的宽度信息
Regex regex = new Regex("(\\d{2,4})x(\\d{2,4})", RegexOptions.Compiled);
Match m = regex.Match(error);
if (m.Success)
{
width = int.Parse(m.Groups[1].Value);
height = int.Parse(m.Groups[2].Value);
}
else
{
width = 0;
height = 0;
}
}
catch (Exception)
{
width = 0;
height = 0;
}
} public static void ExecuteCommand(string command, out string output, out string error)
{
try
{
//创建一个进程
Process pc = new Process();
pc.StartInfo.FileName = command;
pc.StartInfo.UseShellExecute = false;
pc.StartInfo.RedirectStandardOutput = true;
pc.StartInfo.RedirectStandardError = true;
pc.StartInfo.CreateNoWindow = true; //启动进程
pc.Start(); //准备读出输出流和错误流
string outputData = string.Empty;
string errorData = string.Empty;
pc.BeginOutputReadLine();
pc.BeginErrorReadLine(); pc.OutputDataReceived += (ss, ee) =>
{
outputData += ee.Data;
}; pc.ErrorDataReceived += (ss, ee) =>
{
errorData += ee.Data;
}; //等待退出
pc.WaitForExit(); //关闭进程
pc.Close(); //返回流结果
output = outputData;
error = errorData;
}
catch (Exception)
{
output = null;
error = null;
}
} } public class Util
{
public static string GetRandomLetterAndNumberString(Random random, int length)
{
if (length < 0)
{
throw new ArgumentOutOfRangeException("length");
}
char[] pattern = new char[] { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
'A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P',
'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z' };
string result = "";
int n = pattern.Length;
for (int i = 0; i < length; i++)
{
int rnd = random.Next(0, n);
result += pattern[rnd];
}
return result;
}
} class Cmd
{
private static string CmdPath = @"C:\Windows\System32\cmd.exe";
/// <summary>
/// 执行cmd命令 返回cmd窗口显示的信息
/// 多命令请使用批处理命令连接符:
/// <![CDATA[
/// &:同时执行两个命令
/// |:将上一个命令的输出,作为下一个命令的输入
/// &&:当&&前的命令成功时,才执行&&后的命令
/// ||:当||前的命令失败时,才执行||后的命令]]>
/// </summary>
/// <param name="cmd">执行的命令</param>
public static string RunCmd(string cmd)
{
cmd = cmd.Trim().TrimEnd('&') + "&exit";//说明:不管命令是否成功均执行exit命令,否则当调用ReadToEnd()方法时,会处于假死状态
using (Process p = new Process())
{
p.StartInfo.FileName = CmdPath;
p.StartInfo.UseShellExecute = false; //是否使用操作系统shell启动
p.StartInfo.RedirectStandardInput = true; //接受来自调用程序的输入信息
p.StartInfo.RedirectStandardOutput = true; //由调用程序获取输出信息
p.StartInfo.RedirectStandardError = true; //重定向标准错误输出
p.StartInfo.CreateNoWindow = true; //不显示程序窗口
p.Start();//启动程序 //向cmd窗口写入命令
p.StandardInput.WriteLine(cmd);
p.StandardInput.AutoFlush = true; //获取cmd窗口的输出信息
string output = p.StandardOutput.ReadToEnd();
p.WaitForExit();//等待程序执行完退出进程
p.Close(); return output;
}
}
}
} 我还是都写到一块了,大家记得分开!

SController.cs  这个是针对手机端单独拎出来的,里面不需要什么内容

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc; namespace SignalR.Controllers
{
public class SController : Controller
{
// GET: S
public ActionResult Index()
{
return View();
}
}
}

 根目录新建一个ViewModels文件夹,里面新建FileModel.cs文件

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web; namespace SignalR.ViewModels
{
public class FileModel
{
/// <summary>
/// 1 : 图片 2:视频
/// </summary>
public int t { get; set; } public string url { get; set; }
}
} 

RedisHelper.cs

using Microsoft.AspNet.SignalR.Messaging;
using StackExchange.Redis;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net;
using System.Runtime.Serialization.Formatters.Binary;
using System.Threading.Tasks;
using System.Web; namespace SignalR
{
public class RedisHelper
{
private static string Constr = "xxxx.cn:6379"; private static object _locker = new Object();
private static ConnectionMultiplexer _instance = null; /// <summary>
/// 使用一个静态属性来返回已连接的实例,如下列中所示。这样,一旦 ConnectionMultiplexer 断开连接,便可以初始化新的连接实例。
/// </summary>
public static ConnectionMultiplexer Instance
{
get
{
if (Constr.Length == 0)
{
throw new Exception("连接字符串未设置!");
}
if (_instance == null)
{
lock (_locker)
{
if (_instance == null || !_instance.IsConnected)
{
_instance = ConnectionMultiplexer.Connect(Constr);
}
}
}
//注册如下事件
_instance.ConnectionFailed += MuxerConnectionFailed;
_instance.ConnectionRestored += MuxerConnectionRestored;
_instance.ErrorMessage += MuxerErrorMessage;
_instance.ConfigurationChanged += MuxerConfigurationChanged;
_instance.HashSlotMoved += MuxerHashSlotMoved;
_instance.InternalError += MuxerInternalError;
return _instance;
}
} static RedisHelper()
{
} /// <summary>
///
/// </summary>
/// <returns></returns>
public static IDatabase GetDatabase()
{
return Instance.GetDatabase();
} /// <summary>
/// 这里的 MergeKey 用来拼接 Key 的前缀,具体不同的业务模块使用不同的前缀。
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
private static string MergeKey(string key)
{
return "SignalR:"+ key;
//return BaseSystemInfo.SystemCode + key;
} /// <summary>
/// 根据key获取缓存对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="key"></param>
/// <returns></returns>
public static T Get<T>(string key)
{
key = MergeKey(key);
return Deserialize<T>(GetDatabase().StringGet(key));
} /// <summary>
/// 根据key获取缓存对象
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static object Get(string key)
{
key = MergeKey(key);
return Deserialize<object>(GetDatabase().StringGet(key));
} /// <summary>
/// 设置缓存
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <param name="expireMinutes"></param>
public static void Set(string key, object value, int expireMinutes = 0)
{
key = MergeKey(key);
if (expireMinutes > 0)
{
GetDatabase().StringSet(key, Serialize(value), TimeSpan.FromMinutes(expireMinutes));
}
else
{
GetDatabase().StringSet(key, Serialize(value));
} } /// <summary>
/// 判断在缓存中是否存在该key的缓存数据
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Exists(string key)
{
key = MergeKey(key);
return GetDatabase().KeyExists(key); //可直接调用
} /// <summary>
/// 移除指定key的缓存
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static bool Remove(string key)
{
key = MergeKey(key);
return GetDatabase().KeyDelete(key);
} /// <summary>
/// 异步设置
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
public static async Task SetAsync(string key, object value)
{
key = MergeKey(key);
await GetDatabase().StringSetAsync(key, Serialize(value));
} /// <summary>
/// 根据key获取缓存对象
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static async Task<object> GetAsync(string key)
{
key = MergeKey(key);
object value = await GetDatabase().StringGetAsync(key);
return value;
} /// <summary>
/// 实现递增
/// </summary>
/// <param name="key"></param>
/// <returns></returns>
public static long Increment(string key)
{
key = MergeKey(key);
//三种命令模式
//Sync,同步模式会直接阻塞调用者,但是显然不会阻塞其他线程。
//Async,异步模式直接走的是Task模型。
//Fire - and - Forget,就是发送命令,然后完全不关心最终什么时候完成命令操作。
//即发即弃:通过配置 CommandFlags 来实现即发即弃功能,在该实例中该方法会立即返回,如果是string则返回null 如果是int则返回0.这个操作将会继续在后台运行,一个典型的用法页面计数器的实现:
return GetDatabase().StringIncrement(key, flags: CommandFlags.FireAndForget);
} /// <summary>
/// 实现递减
/// </summary>
/// <param name="key"></param>
/// <param name="value"></param>
/// <returns></returns>
public static long Decrement(string key, string value)
{
key = MergeKey(key);
return GetDatabase().HashDecrement(key, value, flags: CommandFlags.FireAndForget);
} /// <summary>
/// 序列化对象
/// </summary>
/// <param name="o"></param>
/// <returns></returns>
private static byte[] Serialize(object o)
{
if (o == null)
{
return null;
}
BinaryFormatter binaryFormatter = new BinaryFormatter();
using (MemoryStream memoryStream = new MemoryStream())
{
binaryFormatter.Serialize(memoryStream, o);
byte[] objectDataAsStream = memoryStream.ToArray();
return objectDataAsStream;
}
} /// <summary>
/// 反序列化对象
/// </summary>
/// <typeparam name="T"></typeparam>
/// <param name="stream"></param>
/// <returns></returns>
private static T Deserialize<T>(byte[] stream)
{
if (stream == null)
{
return default(T);
}
BinaryFormatter binaryFormatter = new BinaryFormatter();
using (MemoryStream memoryStream = new MemoryStream(stream))
{
T result = (T)binaryFormatter.Deserialize(memoryStream);
return result;
}
} /// <summary>
/// 配置更改时
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void MuxerConfigurationChanged(object sender, EndPointEventArgs e)
{
//LogHelper.SafeLogMessage("Configuration changed: " + e.EndPoint);
} /// <summary>
/// 发生错误时
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void MuxerErrorMessage(object sender, RedisErrorEventArgs e)
{
//LogHelper.SafeLogMessage("ErrorMessage: " + e.Message);
} /// <summary>
/// 重新建立连接之前的错误
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void MuxerConnectionRestored(object sender, ConnectionFailedEventArgs e)
{
//LogHelper.SafeLogMessage("ConnectionRestored: " + e.EndPoint);
} /// <summary>
/// 连接失败 , 如果重新连接成功你将不会收到这个通知
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void MuxerConnectionFailed(object sender, ConnectionFailedEventArgs e)
{
//LogHelper.SafeLogMessage("重新连接:Endpoint failed: " + e.EndPoint + ", " + e.FailureType +(e.Exception == null ? "" : (", " + e.Exception.Message)));
} /// <summary>
/// 更改集群
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void MuxerHashSlotMoved(object sender, HashSlotMovedEventArgs e)
{
//LogHelper.SafeLogMessage("HashSlotMoved:NewEndPoint" + e.NewEndPoint + ", OldEndPoint" + e.OldEndPoint);
} /// <summary>
/// redis类库错误
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private static void MuxerInternalError(object sender, InternalErrorEventArgs e)
{
//LogHelper.SafeLogMessage("InternalError:Message" + e.Exception.Message);
} //场景不一样,选择的模式便会不一样,大家可以按照自己系统架构情况合理选择长连接还是Lazy。
//建立连接后,通过调用ConnectionMultiplexer.GetDatabase 方法返回对 Redis Cache 数据库的引用。从 GetDatabase 方法返回的对象是一个轻量级直通对象,不需要进行存储。 /// <summary>
/// 使用的是Lazy,在真正需要连接时创建连接。
/// 延迟加载技术
/// 微软azure中的配置 连接模板
/// </summary>
//private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() =>
//{
// //var options = ConfigurationOptions.Parse(constr);
// ////options.ClientName = GetAppName(); // only known at runtime
// //options.AllowAdmin = true;
// //return ConnectionMultiplexer.Connect(options);
// ConnectionMultiplexer muxer = ConnectionMultiplexer.Connect(Coonstr);
// muxer.ConnectionFailed += MuxerConnectionFailed;
// muxer.ConnectionRestored += MuxerConnectionRestored;
// muxer.ErrorMessage += MuxerErrorMessage;
// muxer.ConfigurationChanged += MuxerConfigurationChanged;
// muxer.HashSlotMoved += MuxerHashSlotMoved;
// muxer.InternalError += MuxerInternalError;
// return muxer;
//}); #region 当作消息代理中间件使用 一般使用更专业的消息队列来处理这种业务场景 /// <summary>
/// 当作消息代理中间件使用
/// 消息组建中,重要的概念便是生产者,消费者,消息中间件。
/// </summary>
/// <param name="channel"></param>
/// <param name="message"></param>
/// <returns></returns>
public static long Publish(string channel, string message)
{
StackExchange.Redis.ISubscriber sub = Instance.GetSubscriber();
//return sub.Publish("messages", "hello");
return sub.Publish(channel, message);
} /// <summary>
/// 在消费者端得到该消息并输出
/// </summary>
/// <param name="channelFrom"></param>
/// <returns></returns>
public static void Subscribe(string channelFrom)
{
StackExchange.Redis.ISubscriber sub = Instance.GetSubscriber();
sub.Subscribe(channelFrom, (channel, message) =>
{
Console.WriteLine((string)message);
});
} #endregion /// <summary>
/// GetServer方法会接收一个EndPoint类或者一个唯一标识一台服务器的键值对
/// 有时候需要为单个服务器指定特定的命令
/// 使用IServer可以使用所有的shell命令,比如:
/// DateTime lastSave = server.LastSave();
/// ClientInfo[] clients = server.ClientList();
/// 如果报错在连接字符串后加 ,allowAdmin=true;
/// </summary>
/// <returns></returns>
public static IServer GetServer(string host, int port)
{
IServer server = Instance.GetServer(host, port);
return server;
} /// <summary>
/// 获取全部终结点
/// </summary>
/// <returns></returns>
public static EndPoint[] GetEndPoints()
{
EndPoint[] endpoints = Instance.GetEndPoints();
return endpoints;
}
}
}

  

 总体项目结构是这样的

下期我将把前端代码列出来,这个我只是为了实现功能,大神勿喷

仿微信 即时聊天工具 - SignalR (一)的更多相关文章

  1. Android 高仿微信即时聊天 百度云为基础的推

    转载请注明出处:http://blog.csdn.net/lmj623565791/article/details/38799363 ,本文出自:[张鸿洋的博客] 一直在仿微信界面,今天最终有幸利用百 ...

  2. 【手把手教程】uniapp + vue 从0搭建仿微信App聊天应用:腾讯云TXIM即时通讯的最佳实践

    基于uniapp + vue 实现仿微信App聊天应用实践,实现以下功能 1: 用户登陆 2: 聊天会话管理 3: 文本/图片/视频/定位消息收发 4: 贴图表情消息收发 5: 一对一语音视频在线通话 ...

  3. SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=》提升)

     SignalR快速入门 ~ 仿QQ即时聊天,消息推送,单聊,群聊,多群公聊(基础=>提升,5个Demo贯彻全篇,感兴趣的玩才是真的学) 官方demo:http://www.asp.net/si ...

  4. uniapp+nvue实现仿微信App聊天应用 —— 成功实现好友聊天+语音视频通话功能

    基于uniapp + nvue实现的uniapp仿微信App聊天应用 txim 实例项目,实现了以下功能. 1: 聊天会话管理 2: 好友列表 3: 文字.语音.视频.表情.位置等聊天消息收发 4: ...

  5. 高仿QQ即时聊天软件开发系列之三登录窗口用户选择下拉框

    上一篇高仿QQ即时聊天软件开发系列之二登录窗口界面写了一个大概的布局和原理 这一篇详细说下拉框的实现原理 先上最终效果图 一开始其实只是想给下拉框加一个placeholder效果,让下拉框在未选择未输 ...

  6. 高仿QQ即时聊天软件开发系列之二登录窗口界面

    继上一篇高仿QQ即时聊天软件开发系列之一开端之后,开始做登录窗口 废话不多说,先看效果,只有界面 可能还有一些细节地方没有做,例如那个LOGO嘛,不要在意这些细节 GIF虽短,可是这做起来真难,好吧因 ...

  7. 基于Nodejs开发的web即时聊天工具

    由于公司需要开发web即时聊天的功能,开始时我们主要的实施方法是用jquery的ajax定时(10秒)轮询向服务器请求,由于是轮询请求,对 服务器的压力比较大.我们网站上线的时间不长,访问量不是很大, ...

  8. h5移动端聊天室|仿微信界面聊天室|h5多人聊天室

    今年的FIFA世界杯甚是精彩,最近兴致高涨就利用HTML5开发了一个手机端仿微信界面聊天室,该h5聊天室采用750px全新伸缩flex布局,以及使用rem响应式配合fontsize.js,页面弹窗则是 ...

  9. 使用PHP+Swoole实现的网页即时聊天工具:PHPWebIM

    使用PHP+Swoole实现的网页即时聊天工具 全异步非阻塞Server,可以同时支持数百万TCP连接在线 同时支持websocket+comet2种兼容协议,可用于所有种类的浏览器包括IE 拥有完整 ...

随机推荐

  1. spring cloud Ribbon的使用和实现原理

    转载链接:https://blog.csdn.net/qq_20597727/article/details/82860521 简介 这篇文章主要介绍一下ribbon在程序中的基本使用,在这里是单独拿 ...

  2. tomcat 7.0 免安装版配置

    转载请注明出处,欢迎邮件交流:1057449102@qq.com 注意:tomcat 运行是依赖jdk的,前提是要安装了jdk,配置好. 1.把下载好的 apache-tomcat-7.0.85.zi ...

  3. Tesseract引擎编译

    1. 工具包下载链接 libtiff 4.09 http://download.osgeo.org/libtiff/tiff-4.0.9.zip leptonica 1.76.0 http://www ...

  4. 如何做seo优化才能获取搜索引擎排名?

    现在网络上有很多网站,但是排名和流量都不理想,所以很多企业会很苦恼.所以我们经常思考如何使网站被搜索引擎喜欢,被用户喜欢,有一个良好的排名和流量? 在这个鱼龙混杂的网络中,seo优化实际上是seo网站 ...

  5. noip11 string

    这道题改题时我打了个玄学复杂度的暴力,然后我成功的造了一组数据hack掉了自己的代码.... 通过观察,我们可以很容易的发现在操作几次后,整个序列就会变成一块一块相同的字母. 于是我们可以对我们的暴力 ...

  6. 通过IDEA快速定位和排除依赖冲突

    前言 我们程序员在开发的时候经常会遇到各种各样的 BUG 问题,其中大部分是业务逻辑异常,还有一些是代码书写不规范造成的异常例如:NullPointException(NPE),IndexOutOfB ...

  7. 程序员学点xx 之 Redis

    程序员学点xx 之 Redis 概述 其实程序员也要和操作系统打交道, 比如最常见的,部署自己电脑上的开发环境. 当然有时某些牛人, 觉得运维或基础部门的同事不够给力, 亲自上手部署服务器或线上环境, ...

  8. 从0开始编写webpack插件

    1. 前言 插件(plugins)是webpack中的一等功臣.正是由于有了诸多插件的存在,才使得webpack无所不能.在webpack源码中也是使用了大量的内部插件,插件要是用的好,可以让你的工作 ...

  9. P3106 [USACO14OPEN]GPS的决斗(最短路)

    化简:够简的了.....但是!翻译绝对有锅. 这个最短路是从n到每个点的单源最短路,也就是最短路径树. 那么,思路就很明确了.建两个图,然后跑两边SPFA,记录下最短路径. 然后,对于两点之间的边,如 ...

  10. day 2 下午 骑士 基环树+树形DP

    #include<iostream> #include<cstdio> #include<cstring> #include<cstdlib> #inc ...