>  기사  >  백엔드 개발  >  .NET을 사용하여 WebSocket 서버 측 인스턴트 메시징을 구현하는 예제에 대한 자세한 설명

.NET을 사용하여 WebSocket 서버 측 인스턴트 메시징을 구현하는 예제에 대한 자세한 설명

Y2J
Y2J원래의
2017-05-04 11:12:176621검색

이 글에서는 인스턴트 메시징과 WebSocket 서버 예제를 구현하기 위한 .NET을 주로 소개합니다. 편집자는 이것이 꽤 좋다고 생각하므로 이제 공유하고 참고용으로 제공하겠습니다. 편집자를 따라 살펴보겠습니다.

인스턴트 메시징의 일반적인 방법

1. 구글 인스턴트 메신저(Google Instant Messenger)가 무료인 텐센트 환신(Tencent Huanxin)은 무료지만 무료라는 뜻은 무료이고 사용하기 쉽지 않다는 뜻이다. 일부 다른 제3자는 일반적으로 수수료를 부과하며, 사용 요구 사항에 따라 흐름(1초/한도 x 메시지)이 제한되거나 사용자 수가 제한됩니다.

하지만 안정성은 나쁘지 않고 서비스 부담을 덜어줄 수 있습니다

2. System.Net.Sockets.Socket을 사용하면 더 좋은 서버를 작성할 수도 있습니다. .NET 4.5 이전에 더 많이 사용되었으며 사용하기가 번거로웠습니다. 데이터 패킷의 파싱과 기타 작업이 필요한데 (인터넷에 초장거리 패킷을 처리하는 방법이 있는 것 같습니다)

3. System.Net.WebSockets.WebSocket, 이게 나온 겁니다. .NET 4.5 이상이며 서버 환경에 매우 유용합니다. IIS8 이상의 요구 사항도 있습니다. 즉, Windows Server2008R2와 함께 제공되는 IIS는 이를 지원하지 않지만 Windows 8 및 Server2012 이상과 함께 제공되는 IIS는 지원합니다. 이번 글에서는 주로 이 방법의 예시를 설명합니다

전체 프로세스

1) 클라이언트 요청 연결

코드는 다음과 같습니다:

ws = new WebSocket('ws://' + window.location.hostname + ':' + window.location.port + '/Handler1.ashx?user=' + $ ("#user" ).val());

2) 서버는 연결 개체 를 가져와 연결 풀

CONNECT_POOL.Add(user, socket);

에 저장합니다. 3) 연결 개체가 수신 대기를 시작합니다(각 클라이언트와 서버는 긴 링크를 저장합니다)

코드는 다음과 같습니다.

WebSocketReceiveResult result = wait 소켓.ReceiveAsync(buffer, CancellationToken.None);

4) 클라이언트 A는 B

ws.send($("#to").val() + "|" + $('#content').val());

5)에게 메시지를 보냅니다. 서버 A의 연결 개체는 A

string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);

6)로부터 메시지를 수신합니다. 메시지 본문(B|안녕하세요 저는 A입니다) 수신자 ID를 가져오고, 수신자 ID를 기반으로 연결 풀에서 B의 서버 연결 개체를 검색하고, B의 연결 개체

WebSocket destSocket = CONNECT_POOL[descUser];
await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);

를 통해 B 클라이언트에 메시지를 푸시합니다. 7) 서버 A는 계속해서 수신을 합니다

코드는 다음과 같습니다:

WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);

8) 클라이언트 B는 푸시된 메시지를 받습니다

ws.onmessage = function (evt) {

  $(&#39;#msg&#39;).append(&#39;<p>&#39; + evt.data + &#39;</p>&#39;);
}

다음은 완료됩니다. 코드

클라이언트 부분

클라이언트는 매우 간단합니다. 일반적인 상황에서는 WebSocket을 직접 사용한 다음 여러 이벤트를 수신합니다. WebSocket이고 괜찮습니다. 연결 시에는 현재 커넥터의 ID(사용자 번호)를 전달할 수 있고, 메시지를 보낼 때는 "A|Hello A, I am B! "

그러나 모바일 단말기를 사용할 때 처리해야 할 몇 가지 일반적인 시나리오는 여전히 있습니다

1: 휴대폰의 화면을 끄십시오. IOS가 화면을 끄면, WebSocket은 즉시 연결이 끊어집니다. Android연결이 끊어지기 전까지 잠시 기다립니다. 서버는 연결 끊김을 감지할 수 있습니다

2: 네트워크가 불안정하여 WebSocket은 네트워크 연결이 끊어져도 즉시 연결이 끊어지지 않으며 서버는 이를 알 수 없습니다. (서버 측에서 하트비트 메커니즘을 설계하여 연결 풀의 사용자에게 정기적으로 메시지를 보내 사용자가 연결을 유지하는지 감지할 수 있습니다.)

3: 기타 등... (갑자기 종료, 애플리케이션 종료 배경)

어느 쪽이든 클라이언트는 메시지를 보낼 때 ws의 상태를 먼저 확인할 수 있습니다(또는 네트워크 연결이 복원되거나 화면이 켜진 경우). 연결된 상태가 아니므로 다시 연결해야 합니다(새로 다운로드하세요)




 
 
 
 
 <script>
 var ws;
 $().ready(function () {
  $(&#39;#conn&#39;).click(function () {
  ws = new WebSocket(&#39;ws://&#39; + window.location.hostname + &#39;:&#39; + window.location.port + &#39;/Handler1.ashx?user=&#39; + $("#user").val());
  $(&#39;#msg&#39;).append(&#39;<p>正在连接</p>&#39;);

  ws.onopen = function () {
   $(&#39;#msg&#39;).append(&#39;<p>已经连接</p>&#39;);
  }
  ws.onmessage = function (evt) {
   $(&#39;#msg&#39;).append(&#39;<p>&#39; + evt.data + &#39;</p>&#39;);
  }
  ws.onerror = function (evt) {
   $(&#39;#msg&#39;).append(&#39;<p>&#39; + JSON.stringify(evt) + &#39;</p>&#39;);
  }
  ws.onclose = function () {
   $(&#39;#msg&#39;).append(&#39;<p>已经关闭</p>&#39;);
  }
  });

  $(&#39;#close&#39;).click(function () {
  ws.close();
  });

  $(&#39;#send&#39;).click(function () {
  if (ws.readyState == WebSocket.OPEN) {
   ws.send($(&quot;#to&quot;).val() + &quot;|&quot; + $(&amp;#39;#content&amp;#39;).val());
  }
  else {
   $(&#39;#tips&#39;).text(&#39;连接已经关闭&#39;);
  }
  });

 });
 </script>


 



目的用户

서버측 부분

서버측에서는 Handler(WebAPI도 사용할 수 있습니다), 주로 WebSocket 클래스를 사용합니다. 코드에는 비교적 자세한 설명이 있습니다. 주의가 필요한 몇 가지 문제는 다음과 같습니다.

1: Dictionary06f0454b88eb565f354426b6933e3e6e Handler를 요청하면 현재 커넥터의 사용자 ID가 전달됩니다. 서버는 연결된 모든 사용자 ID와 현재 사용자의 WebSocket 연결 개체를 유지 관리합니다

2: Dictionary>> ; MESSAGE_POOL: 오프라인 메시지 풀. A->B가 메시지를 보냈는데 B가 현재 어떤 이유로(갑작스러운 네트워크 연결 끊김/검은 화면 등) 온라인 상태가 아닌 경우 메시지가 먼저 저장되고(2일) B의 오프라인 메시지가 즉시 저장됩니다. B가 연결된 후 그에게 푸시하세요. (2: MessageInfo: 오프라인 엔터티. 현재 오프라인 메시지의 시간과 내용을 기록합니다.)

using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Web;
using System.Web.WebSockets;

namespace WebApplication1
{
 /// <summary>
 /// 离线消息
 /// </summary>
 public class MessageInfo
 {
 public MessageInfo(DateTime _MsgTime, ArraySegment<byte> _MsgContent)
 {
  MsgTime = _MsgTime;
  MsgContent = _MsgContent;
 }
 public DateTime MsgTime { get; set; }
 public ArraySegment<byte> MsgContent { get; set; }
 }

 /// <summary>
 /// Handler1 的摘要说明
 /// </summary>
 public class Handler1 : IHttpHandler
 {
 private static Dictionary<string, WebSocket> CONNECT_POOL = new Dictionary<string, WebSocket>();//用户连接池
 private static Dictionary<string, List<MessageInfo>> MESSAGE_POOL = new Dictionary<string, List<MessageInfo>>();//离线消息池

 public void ProcessRequest(HttpContext context)
 {
  if (context.IsWebSocketRequest)
  {
  context.AcceptWebSocketRequest(ProcessChat);
  } 
 }

 private async Task ProcessChat(AspNetWebSocketContext context)
 {
  WebSocket socket = context.WebSocket;
  string user = context.QueryString["user"].ToString();

  try
  {
  #region 用户添加连接池
  //第一次open时,添加到连接池中
  if (!CONNECT_POOL.ContainsKey(user))
   CONNECT_POOL.Add(user, socket);//不存在,添加
  else
   if (socket != CONNECT_POOL[user])//当前对象不一致,更新
   CONNECT_POOL[user] = socket;
  #endregion

  #region 离线消息处理
  if (MESSAGE_POOL.ContainsKey(user))
  {
   List<MessageInfo> msgs = MESSAGE_POOL[user];
   foreach (MessageInfo item in msgs)
   {
   await socket.SendAsync(item.MsgContent, WebSocketMessageType.Text, true, CancellationToken.None);
   }
   MESSAGE_POOL.Remove(user);//移除离线消息
  }
  #endregion

  string descUser = string.Empty;//目的用户
  while (true)
  {
   if (socket.State == WebSocketState.Open)
   {
   ArraySegment<byte> buffer = new ArraySegment<byte>(new byte[2048]);
   WebSocketReceiveResult result = await socket.ReceiveAsync(buffer, CancellationToken.None);
   
   #region 消息处理(字符截取、消息转发)
   try
   {
    #region 关闭Socket处理,删除连接池
    if (socket.State != WebSocketState.Open)//连接关闭
    {
    if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);//删除连接池
    break;
    }
    #endregion

    string userMsg = Encoding.UTF8.GetString(buffer.Array, 0, result.Count);//发送过来的消息
    string[] msgList = userMsg.Split(&#39;|&#39;);
    if (msgList.Length == 2)
    {
    if (msgList[0].Trim().Length > 0)
     descUser = msgList[0].Trim();//记录消息目的用户
    buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(msgList[1]));
    }
    else
    buffer = new ArraySegment<byte>(Encoding.UTF8.GetBytes(userMsg));

    if (CONNECT_POOL.ContainsKey(descUser))//判断客户端是否在线
    {
    WebSocket destSocket = CONNECT_POOL[descUser];//目的客户端
    if (destSocket != null && destSocket.State == WebSocketState.Open)
     await destSocket.SendAsync(buffer, WebSocketMessageType.Text, true, CancellationToken.None);
    }
    else
    {
    Task.Run(() =>
    {
     if (!MESSAGE_POOL.ContainsKey(descUser))//将用户添加至离线消息池中
     MESSAGE_POOL.Add(descUser, new List<MessageInfo>());
     MESSAGE_POOL[descUser].Add(new MessageInfo(DateTime.Now, buffer));//添加离线消息
    });
    }
   }
   catch (Exception exs)
   {
    //消息转发异常处理,本次消息忽略 继续监听接下来的消息
   }
   #endregion
   }
   else
   {
   break;
   }
  }//while end
  }
  catch (Exception ex)
  {
  //整体异常处理
  if (CONNECT_POOL.ContainsKey(user)) CONNECT_POOL.Remove(user);
  }
 }

 public bool IsReusable
 {
  get
  {
  return false;
  }
 }
 }
}

위 내용은 .NET을 사용하여 WebSocket 서버 측 인스턴트 메시징을 구현하는 예제에 대한 자세한 설명의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.