WCF -登录/注销聊天服务导致的问题

本文关键字:问题 服务 聊天 登录 注销 WCF | 更新日期: 2023-09-27 18:16:49

我有一个WCF服务托管在一个windows服务,我有客户端登录和注销的问题。

在客户端代码Login中,我有时会得到以下异常:

Exception thrown: System.ServiceModel.FaultException 1 in System.ServiceModel.dll

Additional information: The operation 'RefreshClients' could not be completed because the sessionful channel timed out waiting to receive a message. To increase the timeout, either set the receiveTimeout property on the binding in your configuration file, or set the ReceiveTimeout property on the Binding directly.

我已经收到了超时,如receiveTimeout="00:00:30",在绑定。但上述例外几乎是即时的。现在我不是专家,但对我来说,在超时发生之前有30秒,但我的异常几乎立即发生。

客户端代码-登录

private DuplexChannelFactory<IMessageComs> remoteFactory;
private IMessageComs remoteProxy;
private Client clientUser;
public bool Login()
        {
            ConnectionStatus = "Logging in";
            IsConnecting = true;
            try
            {
                var ctx = new InstanceContext(CallBacks);
                remoteFactory = new DuplexChannelFactory<IMessageComs>(ctx, "NetTcpBinding_IMessageComs");
            }
            catch (Exception ex)
            {
                ConnectionStatus = "Remote Factory Error";
                IsConnecting = false;
                return false;
            }
            remoteFactory.Closed += remoteFactory_Closed;
            remoteFactory.Opening += remoteFactory_Opening;
            CallBacks.ClientsChanged += CallBacks_ClientsChanged;
            try
            {
                remoteProxy = remoteFactory.CreateChannel();                 
            }
            catch (Exception ex)
            {
                ConnectionStatus = "Remote Proxy Error";
                IsConnecting = false;
                Console.WriteLine(ex.Message);
                return false;
            }
            try
            {
                clientUser = remoteProxy.ClientConnect(Environment.UserName.ToUpper()); << Exception Happens Here
            }
            catch (Exception ex)
            {
                IsConnecting = false;
                Console.WriteLine(ex.Message);
                return false;
            }
            if (clientUser != null)
            {
                ConnectionStatus = "Online";
                IsConnecting = false;
                LoggedIn = true;
                remoteFactory.Faulted += remoteFactory_Faulted;
                // Now get the online user list
                var users = remoteProxy.GetClientsAsync();
                var connectedUser = users.Result;
                if (connectedUser != null)
                {
                    foreach (Client c in connectedUser)
                        OnlineUsers.Add(c);
                }
            }
            else
            {
                IsConnecting = false;
                ConnectionStatus = "Offline";
                return false;
            }
            return true;
        }

服务端代码
public Client ClientConnect(string userName)
    {
        AddEvent(userName + " is attempting to connect");
        var exists = Clients.Where(x => x.Client.UserName == userName);
        if (exists.Count() == 0)
        {
            Client c = new Client();
            c.UserName = userName;
            var tmpNo = c.ID;   // Force ID generation?
            c.SessionID = OperationContext.Current.Channel.SessionId;
            ServerClient sc = new ServerClient(c, CurrentCallback);
            OperationContext.Current.Channel.Faulted += Channel_Faulted;
            Clients.Add(sc);
            AddEvent(userName + " now connected and clients being notified");
            if (Clients.Count > 0)
            {
                // Get clients list
                var Clientlist = new List<Client>();
                foreach (ServerClient s in Clients)
                {
                    Clientlist.Add(s.Client);
                }
                foreach (ServerClient s in Clients)
                {
                    s.CallBack.RefreshClients(Clientlist);
                }
            }               
            return c;
        }
        else
        {
            AddEvent(userName + " is already connected");
            return exists.First().Client;
        }
    }

EDIT 1: Stack trace

at System.ServiceModel.Channels.ServiceChannel.ThrowIfFaultUnderstood(Message reply, MessageFault fault, String action, MessageVersion version, FaultConverter faultConverter)

异常详细堆栈跟踪-服务器堆栈跟踪:

   at System.ServiceModel.Channels.ServiceChannel.ThrowIfIdleAborted(ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannel.Call(String action, Boolean oneway, ProxyOperationRuntime operation, Object[] ins, Object[] outs, TimeSpan timeout)
   at System.ServiceModel.Channels.ServiceChannelProxy.InvokeService(IMethodCallMessage methodCall, ProxyOperationRuntime operation)
   at System.ServiceModel.Channels.ServiceChannelProxy.Invoke(IMessage message)
Exception rethrown at [0]: 
   at System.Runtime.Remoting.Proxies.RealProxy.HandleReturnMessage(IMessage reqMsg, IMessage retMsg)
   at System.Runtime.Remoting.Proxies.RealProxy.PrivateInvoke(MessageData& msgData, Int32 type)
   at EMS.Messaging.Service.IMessageComsCallBack.RefreshClients(List`1 clients)
   at EMS.Messaging.Service.ComsService.ClientConnect(String userName)
   at SyncInvokeClientConnect(Object , Object[] , Object[] )
   at System.ServiceModel.Dispatcher.SyncMethodInvoker.Invoke(Object instance, Object[] inputs, Object[]& outputs)
   at System.ServiceModel.Dispatcher.DispatchOperationRuntime.InvokeBegin(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage5(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage41(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage4(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage31(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage3(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage2(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage11(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.ImmutableDispatchRuntime.ProcessMessage1(MessageRpc& rpc)
   at System.ServiceModel.Dispatcher.MessageRpc.Process(Boolean isOperationContextSet)

Edit 2: Server Config

<?xml version="1.0" encoding="utf-8" ?>
<configuration>
  <startup>
    <supportedRuntime version="v4.0" sku=".NETFramework,Version=v4.5.1" />
  </startup>
  <system.web>
    <compilation debug="true" />
  </system.web>
  <system.serviceModel>
    <services>
      <service name="EMS.Messaging.Service.ComsService" behaviorConfiguration="MessagingServiceBehaviour">
        <endpoint address="" binding="netTcpBinding" contract="EMS.Messaging.Service.IMessageComs" bindingConfiguration="tcpBinding"/>
        <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" />
        <host>
          <baseAddresses>
            <add baseAddress="http://SERVERNAME:8000/MessagingService/service" />
            <add baseAddress="net.tcp://SERVERNAME:8001/MessagingService/service" />
          </baseAddresses>
        </host>
      </service>
    </services>
    <behaviors>
      <serviceBehaviors>
        <behavior name="MessagingServiceBehaviour">
          <serviceMetadata httpGetEnabled="false" httpsGetEnabled="false" />
          <serviceDebug includeExceptionDetailInFaults="true" />
          <serviceThrottling maxConcurrentSessions="100"/>
        </behavior>
      </serviceBehaviors>
    </behaviors>
    <bindings>
      <netTcpBinding>
        <binding name="tcpBinding" maxBufferSize="67108864" maxReceivedMessageSize="67108864" maxBufferPoolSize="67108864" transferMode="Buffered" closeTimeout="00:00:10"
                         openTimeout="00:00:10" receiveTimeout="00:20:00" sendTimeout="00:01:00" maxConnections="100" portSharingEnabled="true">
          <security mode="None"/>
          <readerQuotas maxArrayLength="67108864" maxBytesPerRead="67108864" maxStringContentLength="67108864" />
          <reliableSession enabled="true" inactivityTimeout="00:20:00" />
        </binding>
      </netTcpBinding>
      <customBinding>
        <binding name="mexBinding">
          <tcpTransport maxPendingConnections="100">
            <connectionPoolSettings groupName="default" maxOutboundConnectionsPerEndpoint="100"/>
          </tcpTransport>
        </binding>
      </customBinding>
    </bindings>
  </system.serviceModel>
</configuration>

WCF -登录/注销聊天服务导致的问题

问题出在服务器上的clients集合中的一个客户机上。在先前连接的一个客户端上,当您调用它们的回调方法RefreshClients时,会抛出一个异常。因为它没有被捕获和处理,所以它会返回到调用ClientConnect的客户端。

注册一个Faulted回调以将其从客户端集合中删除是不够的,原因有两个:首先,您可能会遇到竞争条件,当您在迭代客户端集合时,它会进入Faulted状态。还有一些故障模式直到您尝试拨打电话时才被发现。你需要把回调封装在try/catch块中。

你也有一些其他潜在的问题:如果两个客户端几乎同时调用clientconnect,当clientB调用clients . add时,你可能会在foreach块中迭代clientA的调用。当底层集合被修改时,这将导致迭代抛出异常。