HTML5 WebSocket
ASP.NET Core 中的 WebSocket 支持
WS: WebSocket
WSS: WebSocket Secure
WS是非安全的,WSS是安全的。非安全的没有证书,安全的需要SSL证书。
WS的体现形式是TCP + WS AS WS ,WSS的体现形式是TCP + TLS + WS AS WSS。
WS一般默认是80端口,而WSS默认是443端口,大多数网站用的就是80和433端口。
http协议下使用ws,https协议下使用wss。
webSocket = new WebSocket("ws://localhost:port/hub");
webSocket = new WebSocket("wss://localhost:port/hub");
一、WebSocket
WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议
● 支持双向通信,实时性更强
● 可以发送文本,也可以发送二进制数据
● 减少通信量:只要建立起WebSocket连接,就希望一直保持连接状态。和HTTP相比,不但每次连接时的总开销减少,而且由于WebSocket的首部信息很小,通信量也相应减少了
二、双向通信
HTTP 协议有一个缺陷:通信只能由客户端发起。举例来说,我们想了解今天的天气,只能是客户端向服务器发出请求,服务器返回查询结果。HTTP 协议做不到服务器主动向客户端推送信息。这种单向请求的特点,注定了如果服务器有连续的状态变化,客户端要获知就非常麻烦。
在WebSocket协议之前,有三种双向通信的方式:轮询(polling)、长轮询(long-polling)和iframe流(streaming)。
1、轮询(polling)
轮询是客户端和服务器之间会一直进行连接,每隔一段时间就询问一次。其缺点也很明显:连接数会很多,一个接受,一个发送。而且每次发送请求都会有Http的Header,会很耗流量,也会消耗CPU的利用率。
● 优点:实现简单,无需做过多的更改
● 缺点:轮询的间隔过长,会导致用户不能及时接收到更新的数据;轮询的间隔过短,会导致查询请求过多,增加服务器端的负担
2、长轮询(long-polling)
长轮询是对轮询的改进版,客户端发送HTTP给服务器之后,看有没有新消息,如果没有新消息,就一直等待。当有新消息的时候,才会返回给客户端
● 优点:比 Polling 做了优化,有较好的时效性
● 缺点:保持连接会消耗资源; 服务器没有返回有效数据,程序超时。
3、iframe流(streaming)
iframe流方式是在页面中插入一个隐藏的iframe,利用其src属性在服务器和客户端之间创建一条长连接,服务器向iframe传输数据(通常是HTML,内有负责插入信息的javascript),来实时更新页面。
● 优点:消息能够实时到达;浏览器兼容好
● 缺点:服务器维护一个长连接会增加开销;IE、chrome、Firefox会显示加载没有完成,图标会不停旋转。
三、Socket
Socket 是一个网络通信的套接字(接口);分为 服务端 和 客户端;使用 TCP/IP协议 或 UDP 协议;
服务端
private void Start_Click(object sender, EventArgs e)
{
try
{
var _ip = txtIP.Text;
var _port = txtPort.Text;
Validate(_ip, _port);
socketWatch = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socketSend = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
IPAddress ip = IPAddress.Parse(_ip);
//将IP地址和端口号绑定到网络节点endpoint上
//IPEndPoint point = new IPEndPoint(ip, Convert.ToInt32(_port));
IPEndPoint point = new IPEndPoint(IPAddress.Any, Convert.ToInt32(_port));//自动获取ip可使用此方法
socketWatch.Bind(point);
ShowMsg("开始监听 Ln 60...");
LogUtils.GetInstance().Info("Ln 61...");
//设置监听 - 将套接字的监听队列长度限制为10
socketWatch.Listen(10);
ShowMsg("开始监听 Ln 65...");
LogUtils.GetInstance().Info("Ln 66...");
Thread thread = new Thread(Listen);
thread.IsBackground = true;
thread.Start();
Start.Enabled = false;
btnStop.Enabled = true;
ShowMsg("开始监听客户端传来的信息 Ln 82...");
}
catch (Exception ex)
{
MessageBox.Show("启动服务失败,请联系管理员!");
LogUtils.GetInstance().Error(ex.Message);
}
}
客户端
using CoreLibrary;
using CoreLibrary.UMeeting;
using Lawyer.Api.RequestModel;
using Lawyer.Entity;
using Newtonsoft.Json;
using System;
using System.Configuration;
using System.Net;
using System.Net.Sockets;
using System.Text;
using System.Threading;
using System.Web.Mvc;
namespace Lawyer.Api.Controllers
{
public class DefaultController : Controller
{
log4net.ILog logger = log4net.LogManager.GetLogger(typeof(ConferenceController));
string ClassName = string.Empty;
string MaxCallTime = string.Empty;
ManualResetEvent connectDone = new ManualResetEvent(false);
ManualResetEvent sendDone = new ManualResetEvent(false);
ManualResetEvent receiveDone = new ManualResetEvent(false);
String response = String.Empty;
Socket tcpClient = null;
public DefaultController()
{
ClassName = "Api.Controllers.ConferenceController";
MaxCallTime = StringPlus.GetWebConfigKey("UMMaxCallTime");
IPAddress ipaddress = IPAddress.Parse("101.201.44.11");
EndPoint point = new IPEndPoint(ipaddress, 8803);
tcpClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
tcpClient.BeginConnect(point, new AsyncCallback(ConnectCallback), tcpClient);//建立连接
connectDone.WaitOne();
string sendMsg = "{\"key\":\"verify\",\"command\":9,\"userid\":\"8888888\",\"password\":\"8888888888\"}";
Send(tcpClient, sendMsg, 9);
sendDone.WaitOne();
}
/// <summary>
/// 双向回呼
/// </summary>
/// <param name="data"></param>
/// <returns></returns>
[HttpPost]
public ActionResult CallBack(string conferenceid)
{
try
{
Thread thread = new Thread(new ParameterizedThreadStart(Monitor));
thread.IsBackground = true;
thread.Start(conferenceid);
return ResultJsonMessage.Tool.SuccessResult();
}
catch (Exception ex)
{
logger.Info("Entered CallBack() " + ex.Message);
return ResultJsonMessage.Tool.ExceptionResult(ex);
}
}
/// <summary>
/// 修改订单状态
/// </summary>
void UpdateOrder(object conferenceid)
{
try
{
logger.Info("Entered UpdateOrder 开始订单修改操作");
OrderCallback o = biz.GetModelByCallSid(conferenceid.ToString());
if (o != null)
{
logger.Info("Entered UpdateOrder 执行业务逻辑");
}
}
catch (Exception ex)
{
logger.Info("Entered UpdateOrder(" + conferenceid.ToString() + ") " + ex.Message);
}
}
/// <summary>
/// 监听会议
/// </summary>
/// <param name="conferenceid"></param>
void Monitor(object conferenceid)
{
string monitor = "{\"key\":\"" + conferenceid + "\",\"command\":13,\"regphoneid\":\"" + ConfigurationManager.AppSettings["UMregphoneid"] + "\",\"conferenceid\":\"" + conferenceid + "\"}";
Send(tcpClient, monitor, 13);
sendDone.WaitOne();
Receive(tcpClient);
receiveDone.WaitOne();
}
private void ConnectCallback(IAsyncResult ar)
{
try
{
// Retrieve the socket from the state object.
Socket client = (Socket)ar.AsyncState;
// Complete the connection.
client.EndConnect(ar);
///Console.WriteLine("Socket connected to {0}", client.RemoteEndPoint.ToString());
logger.Info(string.Format("Socket connected to {0}", client.RemoteEndPoint.ToString()));
// Signal that the connection has been made.
connectDone.Set();
}
catch (Exception e)
{
//Console.WriteLine(e.ToString());
logger.Info("ConnectCallback Exception Ln 304" + e.ToString());
}
}
private void Send(Socket client, String data, int type)
{
// Convert the string data to byte data using ASCII encoding.
byte[] byteData = Encoding.UTF8.GetBytes(data);
#region
byte[] abuff = new byte[4];
byte[] bbuff = new byte[1];
ConvertIntToByteArray(data.Length + abuff.Length + bbuff.Length, ref abuff);
ConvertIntToByte(type, ref bbuff);
byte[] d = new byte[2048];
abuff.CopyTo(d, 0);
bbuff.CopyTo(d, abuff.Length);
byteData.CopyTo(d, abuff.Length + bbuff.Length);
int dlength = byteData.Length + 5;
#endregion
client.BeginSend(d, 0, dlength, 0, new AsyncCallback(SendCallback), client);
}
private void SendCallback(IAsyncResult ar)
{
try
{
// Retrieve the socket from the state object.
Socket client = (Socket)ar.AsyncState;
// Complete sending the data to the remote device.
int bytesSent = client.EndSend(ar);
//Console.WriteLine("Sent {0} bytes to server.", bytesSent);
logger.Info("SendCallback " + string.Format("Sent {0} bytes to server.", bytesSent));
// Signal that all bytes have been sent.
sendDone.Set();
}
catch (Exception e)
{
logger.Info("SendCallback Exception Ln 347 " + e.ToString());
}
}
private void Receive(Socket client)
{
try
{
// Create the state object.
StateObject state = new StateObject();
state.workSocket = client;
// Begin receiving the data from the remote device.
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
catch (Exception e)
{
//Console.WriteLine(e.ToString());
logger.Info("Receive Exception Ln 364 " + e.ToString());
}
}
private void ReceiveCallback(IAsyncResult ar)
{
//logger.Info("Entered ReceiveCallback Begin Ln 370");
try
{
// Retrieve the state object and the client socket
// from the asynchronous state object.
StateObject state = (StateObject)ar.AsyncState;
Socket client = state.workSocket;
// Read data from the remote device.
int bytesRead = client.EndReceive(ar);
if (bytesRead > 0)
{
// There might be more data, so store the data received so far.
state.sb.Append(Encoding.UTF8.GetString(state.buffer, 0, bytesRead));
//int headerLen = BitConverter.ToInt32(state.buffer.Take(4).ToArray(), 0);
//logger.Info("Entered header长度 " + headerLen);
string log = "";
if (bytesRead > 5)
{
log = Encoding.UTF8.GetString(state.buffer, 5, bytesRead);
}
else
{
log = Encoding.UTF8.GetString(state.buffer, 0, bytesRead);
}
if (bytesRead > 50)
{
ConvertObj(log);
}
//log = log.Substring(0, log.LastIndexOf("}") + 1);
//logger.Info("Entered Ln 400 " + log);
// Get the rest of the data.
client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
}
else
{
// All the data has arrived; put it in response.
if (state.sb.Length > 1)
{
response = state.sb.ToString();
logger.Info("Entered response Ln 410 " + response);
}
// Signal that all bytes have been received.
receiveDone.Set();
}
//logger.Info("Entered state.sb Ln 415 " + state.sb.ToString());
}
catch (Exception e)
{
//Console.WriteLine(e.ToString());
logger.Info("ReceiveCallback Exception Ln 420 " + e.ToString());
}
//logger.Info("Entered ReceiveCallback End Ln 422");
}
bool ConvertIntToByteArray(Int32 m, ref byte[] arry)
{
if (arry == null) return false;
if (arry.Length < 4) return false;
arry[0] = (byte)(m & 0xFF);
arry[1] = (byte)((m & 0xFF00) >> 8);
arry[2] = (byte)((m & 0xFF0000) >> 16);
arry[3] = (byte)((m >> 24) & 0xFF);
return true;
}
bool ConvertIntToByte(Int32 m, ref byte[] arry)
{
if (arry == null) return false;
arry[0] = (byte)(m & 0xFF);
return true;
}
void ConvertObj(string log)
{
logger.Info("Entered ConvertObj Begin");
logger.Info("Entered 原始字符串 " + log);
try
{
string first = log.Substring(0, 1);
string last = log.Substring(log.Length - 1, 1);
if (first == "{" && last != "}")
{
logger.Info("Entered 字符串截取操作 Ln 457 " + log);
log = log.Substring(0, log.LastIndexOf("}") + 1);
}
UMPushInfo umPush = (UMPushInfo)JsonConvert.DeserializeObject(log, typeof(UMPushInfo));
if (umPush.command == 15 && umPush.phonelist != null)
{
logger.Info("Entered phonelist总数:" + umPush.phonelist.Count.ToString());
if (umPush.phonelist[0].callstate == 2 || umPush.phonelist[0].callstate == 3)
{
//logger.Info("Entered 15 " + umPush.phonelist[0].callstate);
//logger.Info("Entered 结束会议");
//string command = "{\"key\":\"\",\"command\":12,\"client\":\"\",\"conferenceid\":\"" + umPush.conferenceid + "\",\"commandno \":9,\"memberid\":0,\"address\":\"\",\"phone\":\"\"}";
//Send(tcpClient, command, 12);
}
}
if (umPush.command == 15 && umPush.phonelist[0].callstate == 0)
{
logger.Info("Entered 15 0 呼叫中");
}
if (umPush.command == 15 && umPush.phonelist[0].callstate == 1 && umPush.phonelist[0].role == 0)
{
logger.Info("Entered 15 1 主持人接通");
}
if (umPush.command == 15 && umPush.phonelist[0].callstate == 1 && umPush.phonelist[0].role == 3)
{
logger.Info("Entered 15 1 参会人接通");
Thread thread = new Thread(new ParameterizedThreadStart(UpdateOrder));
thread.IsBackground = true;
thread.Start(umPush.conferenceid);
}
if (umPush.command == 15 && umPush.phonelist[0].callstate == 2)
{
logger.Info("Entered 15 2 未接通");
}
if (umPush.command == 15 && umPush.phonelist[0].callstate == 3)
{
logger.Info("Entered 15 3 挂断");
}
if (umPush.command == 18 && umPush.conferencestate == 0)
{
logger.Info("Entered 18 0 会议还未开始");
}
if (umPush.command == 18 && umPush.conferencestate == 1)
{
logger.Info("Entered 18 1 会议正在召开");
}
if (umPush.command == 18 && umPush.conferencestate == 2)
{
logger.Info("Entered 18 2 会议成功关闭");
}
}
catch (Exception ex)
{
logger.Info("Entered Exception Ln 504 " + ex.Message);
logger.Info("Entered " + log);
}
logger.Info("Entered ConvertObj End");
}
}
}
*
*
*
*
*