System.Net.WebSockets.ClientWebSocket没有可靠的工作?

时间:2015-09-23 20:18:06

标签: c# sockets asp.net-web-api websocket

我正在实现根据此URL在我们的api中传回一个Web套接字;

http://blogs.msdn.com/b/youssefm/archive/2012/07/17/building-real-time-web-apps-with-asp-net-webapi-and-websockets.aspx

现在的想法是用户将注册一个Web套接字。这样做并使用以下代码工作;

    [HttpGet]
    [Route("getsocket")]
    public HttpResponseMessage GetWebSocket()
    {
        HttpContext.Current.AcceptWebSocketRequest(new TestWebSocketHandler());
        return Request.CreateResponse(HttpStatusCode.SwitchingProtocols);
    }

然后他们调用api开始做一些特定的函数,然后将消息报告回同一个websocket。

    private static WebSocketWrapper _socket;

    [HttpGet]
    [Route("begin")]
    public async Task<IHttpActionResult> StartRunning(string itemid)
    {
        try
        {
            if (_socket == null ||
                _socket.State() == WebSocketState.Aborted ||
                _socket.State() == WebSocketState.Closed ||
                _socket.State() == WebSocketState.None)
            {
                _socket = WebSocketWrapper.Create("wss://localhost:44301/api/v1/testcontroller/getsocket");

                _socket.OnConnect(OnConnect);
                _socket.OnDisconnect(OnDisconnect);
                _socket.OnMessage(OnMessage);

                await _socket.ConnectAsync();
            }
            //builds first message to be sent and sends it
            _socket.QueueMessage(firstTest);
        }
        catch(Exception ex)
        {
            //logs error
        }

        return Ok();
    }

因此,客户端有效地创建了一个连接到服务器的新websocket。然后,他们调用第二条消息来触发服务器在传递的设备上启动一些测试。服务器启动测试并将消息广播回套接字(json消息模型包含deviceid,因此客户端可以过滤相关消息)。

当他们收到消息时,客户端将对其进行确认并完成下一次测试等。

现在它第一次运行它(编译后)。但是,我希望能够让多个客户端连接到websocket列表(解决方案列表并且它将运行的测试可能需要一段时间,因此可能会在任何时间运行多个测试)。所以我认为它与静态WebSocketWrapper实例有关。

但是,他们要求在服务器上使用单个websocket,并列出正在监听的设备列表。因此,实际上所有消息都从一个服务器连接发送到所有客户端。然后,客户端根据他们传递的设备ID过滤掉他们想要收听的消息。

当我尝试重新运行时,运行第二个测试,基本上调用getwebsocket然后调用begin方法,代码运行时没有错误,但onopen方法永远不会被调用?就好像插座没有启动一样?

不幸的是我们不能使用signalr,因为没有指定

作为参考,socket包装类是

公共类WebSocketWrapper         {             private const int ReceiveChunkSize = 2048;             private const int SendChunkSize = 2048;

        private readonly ClientWebSocket _ws;
        private readonly Uri _uri;
        private readonly CancellationTokenSource _cancellationTokenSource = new CancellationTokenSource();
        private readonly CancellationToken _cancellationToken;

        private Action<WebSocketWrapper> _onConnected;
        private Action<TestResultModel, WebSocketWrapper> _onMessage;
        private Action<WebSocketWrapper> _onDisconnected;

        //private static Queue<TestResultModel> _messageQueue = new Queue<TestResultModel>();
        private static BlockingCollection<TestResultModel> _messageQueue = new BlockingCollection<TestResultModel>();

        protected WebSocketWrapper(string uri)
        {
            _ws = new ClientWebSocket();
            _ws.Options.KeepAliveInterval = TimeSpan.FromSeconds(30);
            _uri = new Uri(uri);
            _cancellationToken = _cancellationTokenSource.Token;
        }

        /// <summary>
        /// Creates a new instance.
        /// </summary>
        /// <param name="uri">The URI of the WebSocket server.</param>
        /// <returns></returns>
        public static WebSocketWrapper Create(string uri)
        {
            return new WebSocketWrapper(uri);
        }

        /// <summary>
        /// Get the current state of the socket
        /// </summary>
        /// <returns>WebSocketState of the current _ws</returns>
        public WebSocketState State()
        {
            return _ws.State;
        }

        /// <summary>
        /// Disconnects from the websocket
        /// </summary>
        public async Task DisconnectAsync()
        {
            try
            {
                await _ws.CloseOutputAsync(
                    WebSocketCloseStatus.NormalClosure,
                    "Server has been closed by the disconnect method",
                    _cancellationToken);
                CallOnDisconnected();
            }
            catch(Exception ex)
            {
                throw ex;
            }
            finally
            {
                _ws.Dispose();
            }
        }

        /// <summary>
        /// Connects to the WebSocket server.
        /// </summary>
        /// <returns></returns>
        public async Task<WebSocketWrapper> ConnectAsync()
        {
            try
            {
                await _ws.ConnectAsync(_uri, _cancellationToken);
            }
            catch(Exception ex)
            {

            }

           CallOnConnected();

            RunInTask(() => ProcessQueueAsync());
            RunInTask(() => StartListen());

            return this;
        }

        /// <summary>
        /// Set the Action to call when the connection has been established.
        /// </summary>
        /// <param name="onConnect">The Action to call.</param>
        /// <returns></returns>
        public WebSocketWrapper OnConnect(Action<WebSocketWrapper> onConnect)
        {
            _onConnected = onConnect;
            return this;
        }

        /// <summary>
        /// Set the Action to call when the connection has been terminated.
        /// </summary>
        /// <param name="onDisconnect">The Action to call</param>
        /// <returns></returns>
        public WebSocketWrapper OnDisconnect(Action<WebSocketWrapper> onDisconnect)
        {
            _onDisconnected = onDisconnect;
            return this;
        }

        /// <summary>
        /// Adds a message to the queu for sending
        /// </summary>
        /// <param name="message"></param>
        public void QueueMessage(TestResultModel message)
        {
            //_messageQueue.Enqueue(message);
            _messageQueue.Add(message);
        }

        /// <summary>
        /// returns the size of the current message queue.
        /// Usefult for detemning whether or not an messages are left in queue before closing
        /// </summary>
        /// <returns></returns>
        public int QueueCount()
        {
            return _messageQueue.Count;
        }

        /// <summary>
        /// Processes the message queue in order
        /// </summary>
        public async Task ProcessQueueAsync()
        {
            try
            {
                foreach(var current in _messageQueue.GetConsumingEnumerable())
                {
                    await SendMessageAsync(current);
                }
            }
            catch(Exception ex)
            {
                //TODO
            }
        }

        /// <summary>
        /// Set the Action to call when a messages has been received.
        /// </summary>
        /// <param name="onMessage">The Action to call.</param>
        /// <returns></returns>
        public WebSocketWrapper OnMessage(Action<TestResultModel, WebSocketWrapper> onMessage)
        {
            _onMessage = onMessage;
            return this;
        }

        /// <summary>
        /// Send a message to the WebSocket server.
        /// </summary>
        /// <param name="message">The message to send</param>
        public async Task SendMessageAsync(TestResultModel result)
        {
            if (_ws.State != WebSocketState.Open)
            {
                throw new Exception("Connection is not open.");
            }

            var message = JsonConvert.SerializeObject(result);
            var messageBuffer = Encoding.UTF8.GetBytes(message);
            var messagesCount = (int)Math.Ceiling((double)messageBuffer.Length / SendChunkSize);

            for (var i = 0; i < messagesCount; i++)
            {
                var offset = (SendChunkSize * i);
                var count = SendChunkSize;
                var lastMessage = ((i + 1) == messagesCount);

                if ((count * (i + 1)) > messageBuffer.Length)
                {
                    count = messageBuffer.Length - offset;
                }

                await _ws.SendAsync(new ArraySegment<byte>(messageBuffer, offset, count), WebSocketMessageType.Text, lastMessage, _cancellationToken);
            }
        }

        private async Task StartListen()
        {
            var buffer = new byte[ReceiveChunkSize];
            //part of a big hack, temporary solution
            string prevResult = "";

            try
            {
                while (_ws.State == WebSocketState.Open)
                {
                    var stringResult = new StringBuilder();

                    WebSocketReceiveResult result;

                    do
                    {
                        result = await _ws.ReceiveAsync(new ArraySegment<byte>(buffer), _cancellationToken);


                        if (result.MessageType == WebSocketMessageType.Close)
                        {
                            await _ws.CloseAsync(WebSocketCloseStatus.NormalClosure, string.Empty, CancellationToken.None);
                            CallOnDisconnected();
                        }
                        else
                        {
                            var str = Encoding.UTF8.GetString(buffer, 0, result.Count);
                            stringResult.Append(str);
                        }
                    } while (!result.EndOfMessage);


                    if (!prevResult.Equals(stringResult.ToString()))
                    {
                        prevResult = stringResult.ToString();
                        CallOnMessage(stringResult);
                    }

                }
            }
            catch (Exception ex)
            {
                CallOnDisconnected();
            }
            finally
            {
                _ws.Dispose();
            }
        }

        private void CallOnMessage(StringBuilder stringResult)
        {
            if (_onMessage != null)
            {
                try
                {
                    var message = JsonConvert.DeserializeObject<TestResultModel>(stringResult.ToString());
                    RunInTask(() => _onMessage(message, this));
                }
                catch (Exception ex)
                {
                    //Ignore any other messages not TestResultModel
                }
            }
        }

        private void CallOnDisconnected()
        {
            if (_onDisconnected != null)
                RunInTask(() => _onDisconnected(this));
        }

        private void CallOnConnected()
        {
            if (_onConnected != null)
                RunInTask(() => _onConnected(this));
        }

        private static Task RunInTask(Action action)
        {
            return Task.Factory.StartNew(action);
        }
    }

作为更新,请参阅第二次尝试调用websocket时的调试屏幕。你可以看到它似乎处于流产状态? (在第一次运行时,显然是null)。有什么想法吗?

Screen Shot of Socket Code Running

1 个答案:

答案 0 :(得分:0)

您正在寻找的许多功能使用SignalR“轻松”实现。在您创建“Hub”的地方,您可以向所有客户广播或只选择一个客户。

我认为你以前有过websockets的经验,所以我觉得你应该看一看!它有点神奇,可以在更大的应用程序和服务中进行故障排除。

可以找到一个简单的聊天教程here 对于使用SignalR向单个客户发送通知有问题的人,您还可以查看here