当前位置:编程学习 > C#/ASP.NET >>

异步TCP调用BeginReceive和EndAccept有时候会产生异常

首先说一下我的代码的大概结构
1.程序是CS架构的。客户端负责把有硬件采集到的数据发到服务器,当服务器接收到某个客户端发来的数据(结构体来的),把接收到的数据推进一个队列,有专门的线程循环检测队列中是否有数据,有数据就取数据更新图形界面,并且再分发到其他客户端,客户端接收到来自服务器发来的数据会更新本客户端的图形界面,从而能看到别的客户端的实时情况。目前有16个客户端同时接入服务器。
2.服务器和客户端都是用.Net2.0和C#开发,通信部分都采用异步Socket,代码的结构大致相同,下面会给出代码。
3.服务器端代码主要的函数有:
(1)initListener(),初始化listen的Socket,并启动异步接收客户端连接(这里没有采用while循环来Accept,而是在BeginAccept的回调函数中再次调用BeginAccept)。
(2)OnConnectReuqest(),作为BeginAccept的回调函数,当异步接收到一个客户端连接时调用该函数,在该函数中再次调用lietener.BeginAccept(),继续接受下一个连接
(3)OnReceiveData()函数接收来自客户端的操作并且执行一系列的操作
4.一些自定义类型的解释
TrackStateStr是我自定义的结构体类型
SocketChatClient类封装了一个Socket和接收缓冲区,当接受了一个客户端的连接时候,把与客户端通信的Socket封装到该类中,该类提供了SetupReceiveCallback方法调用BeginReceive异步接收数据(回调函数就是OnReceiveData)

出现的问题:
1.程序运行久了(大概一天不到),会在OnConnectRequest()函数的SockChatClient client = new SockChatClient(listener.EndAccept(ar), this)这一句出现异常,提示只能对一个异步操作调用一次EndAccept,不知道是由什么引起的。
2.运行了一段时间(也是大概一天不到)在SockChatClient类的SetupReceiveCallback()会出现异常,应该是调用BeginReceive失败了,提示要 访问的资源已经释放,应该是在调用的时候Socket已经被释放了。不知道是不是由于客户端的关闭引起的。但是我在OnReceiveData函数里面有捕获客户端关闭的异常。
3.出现了一个异常提示:集合已修改;可能无法执行枚举操作。应该是在SetTrackStateCall()这个函数中出现的异常,不知道是因为什么。SetTrackStateCall()是循环检测接收队列里面是否有数据并进行相应处理的函数。

主要就是以上3个问题,请各位对Socket方面有丰富经验的大大门帮我看看问题是在哪,关键代码我都贴在下面。

服务端异步TCP的主要代码:
Socket listener;
        int listenPort;
        public bool isRun;
        Type trackStateType = (new TrackStateStr()).GetType();
        public ArrayList m_clientSockList = new ArrayList();
        Dictionary<string, int> m_IPIndex;
        object curAllTrackStateLocker = new object();
        public Queue<TrackStateStr> recvTrackStateQueue = new Queue<TrackStateStr>(100);
        public ManualResetEvent manualResetEvent = new ManualResetEvent(true);
        AutoResetEvent writeHandler = new AutoResetEvent(true);
        Thread setTrackTrackThread;

        public void initListener()
        {
            this.listenPort = (int)Port.ServerListenPort;
            IPEndPoint iep = new IPEndPoint(IPAddress.Parse(MyIP), this.listenPort);
            this.listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            this.listener.Bind(iep);
            this.listener.Listen(16);
            this.listener.BeginAccept(new AsyncCallback(OnConnectRequest), this.listener);
            this.isRun = true;
        }

        private void OnConnectRequest(IAsyncResult ar)
        {
            if (!isRun)
                return;

            Socket listener = (Socket)ar.AsyncState;
            SockChatClient client = new SockChatClient(listener.EndAccept(ar), this);
            if (this.IsHandleCreated)
            {
                int stationIndex = 0;
                foreach (KeyValuePair<int, string> kvp in IPArray)
                {
                    if (kvp.Value == ((IPEndPoint)client.Sock.RemoteEndPoint).Address.ToString())
                    {
                        stationIndex = kvp.Key;
                        break;
                    }
                }
                if (stationIndex != 0)
                {
                    Invoke(this.m_AddToMsgTips, new string[] { this.stationName[stationIndex] + " 成功连接到服务器" });
                    client.StationName = this.stationName[stationIndex];
                    client.StationId = stationIndex;
                    Invoke(this.m_SetZColor, new object[] { _ZArray[stationIndex], _connectedColor });
                    m_clientSockList.Add(client);
                    client.SetupReceiveCallBack();
                }
                else
                {
                    Invoke(this.m_AddToMsgTips, new string[] { "unknown client join!" });
                    client.Sock.Close();
                    //client.StationName = "unknow client";
                    //client.StationId = 0;
                }

            }
            listener.BeginAccept(new AsyncCallback(OnConnectRequest), listener);
        }

        object queueLocker = new object();
        public void OnReceiveData(IAsyncResult ar)
        {
            if (!isRun)
                return;

            SockChatClient client = (SockChatClient)ar.AsyncState;
            byte[] recvData = client.GetReceiveData(ar);
            try
            {

                if (recvData.Length < 1)
                {
                    //收到的字节数为0,远程机器可能已经断开连接
                    if (this.IsHandleCreated)
                    {
                        this.Invoke(this.m_AddToMsgTips, new string[] { "与 " + client.StationName + " 的连接断开" });
                        if (client.StationId != 0)
                        {
                            if (this.stationCtrlState[client.StationId] == 2)
                                this._twinklingTimerArr[client.StationId].Stop();
                            this.Invoke(this.m_SetZColor, new object[] { _ZArray[client.StationId], _disconnectedColor });
                        }
                    }
                    this.stationCtrlState[client.StationId] = 0;
                    client.Sock.Close();
                    this.m_clientSockList.Remove(client);
                    return;
                }

                TrackStateStr newTrackState = (TrackStateStr)this.BytesToStruct(recvData, this.trackStateType);
                Invoke(m_AddToMsgTips, new string[] { "from " + client.StationName });

                //writeHandler.WaitOne();
                lock (queueLocker)
                {
                    recvTrackStateQueue.Enqueue(newTrackState);
                }
                //writeHandler.Set();
            }
            catch (Exception er)
            {
                this.Invoke(this.m_AddToMsgTips, new string[] { er.Message });
            }

            client.SetupReceiveCallBack();
        }

        int j = 0;
        private void SetTrackStateCall()
        {
            while (true)
            {
                manualResetEvent.WaitOne();
                //manualResetEvent.Reset();
                lock (queueLocker)
                {
                    if (recvTrackStateQueue.Count > 0)
                    {
                        try
                        {
                            TrackStateStr newTrackState = recvTrackStateQueue.Dequeue();
                            //更新当前状态
                            Invoke(m_UpdateCurTrackState, new object[] { newTrackState });
                            //回发到各个车站
                            SendBackToCli();
                            //设置控件状态
                            Invoke(m_SetTrackState, new object[] { newTrackState });
                            for (int i = 0; i < 16; i++)
                            {
                                this._curAllTrackState.Z[i].modified = false;
                            }
                        }
                        catch (Exception ex)
                        {
                            MessageBox.Show(ex.Message, DateTime.Now + ":From SetTrackStateCall");
                        }
                    }
                }
                //manualResetEvent.Set();
                Thread.Sleep(10);
            }
        }

        /// <summary>
        /// 回发全线状态到所有客户端
        /// </summary>
        public void SendBackToCli()
        {
            byte[] sendData = StructToBytes(this._curAllTrackState);
            foreach (SockChatClient cli in this.m_clientSockList)
            {
                try
                {
                    cli.Sock.Send(sendData);
                }
                catch
                {
                    cli.Sock.Close();
                    m_clientSockList.Remove(cli);
                    //return;
                }
            }
        }
        #endregion



SockChatClient的代码
 class SockChatClient
    {
        private Socket m_sock;
        public Socket Sock
        {
            get { return m_sock; }
        }

        private string m_stationName;
        public string StationName
        {
            get { return m_stationName; }
            set { m_stationName = value; }
        }

        private int m_stationId;
        public int StationId
        {
            get { return m_stationId; }
            set { m_stationId = value; }
        }


        private byte[] m_buff;
        private int m_bufSize;

        ServerForm frmMain;
        AsyncCallback receiveData;

        public SockChatClient(Socket sock, ServerForm frmMain)
        {
            this.m_sock = sock;
            this.m_bufSize = Marshal.SizeOf((new TrackStateStr()).GetType());
            this.frmMain = frmMain;
            receiveData = new AsyncCallback(this.frmMain.OnReceiveData);
        }

        public void SetupReceiveCallBack()
        {
            try
            {
                m_buff = null;
                m_buff = new byte[m_bufSize];
                //AsyncCallback receiveData = new AsyncCallback(frmMain.OnReceiveData);
                m_sock.BeginReceive(m_buff, 0, m_buff.Length, SocketFlags.None, receiveData, this);
            }
            catch (Exception ex)
            {
                MessageBox.Show("Setup receive callback fail! " + ex.Message, DateTime.Now + ":From Sock " + this.m_stationId + "," + m_stationName);
            }
        }

        public byte[] GetReceiveData(IAsyncResult ar)
        {
            int nRecvBytes = 0;
            try
            {
                nRecvBytes = m_sock.EndReceive(ar);
            }
            catch
            {
            }

            byte[] bytesReturn = new byte[nRecvBytes];
            Array.Copy(m_buff, bytesReturn, nRecvBytes);
            return bytesReturn;
        }
    }
--------------------编程问答-------------------- 1.程序运行久了(大概一天不到),会在OnConnectRequest()函数的SockChatClient client = new SockChatClient(listener.EndAccept(ar), this)这一句出现异常,提示只能对一个异步操作调用一次EndAccept,不知道是由什么引起的。
----------------------------------------------------
那说明你在listener调用2次EndAccept,你把检查检查你的代码。

2.运行了一段时间(也是大概一天不到)在SockChatClient类的SetupReceiveCallback()会出现异常,应该是调用BeginReceive失败了,提示要访问的资源已经释放,应该是在调用的时候Socket已经被释放了。不知道是不是由于客户端的关闭引起的。但是我在OnReceiveData函数里面有捕获客户端关闭的异常。
--------------------------------------------------------------
那说明Socket已经关闭,那么你在调用之前,应该先判断一下是否为NULL,是否处于开打状态或者其他你需要的状态。

3.出现了一个异常提示:集合已修改;可能无法执行枚举操作。应该是在SetTrackStateCall()这个函数中出现的异常,不知道是因为什么。SetTrackStateCall()是循环检测接收队列里面是否有数据并进行相应处理的函数。
----------------------------------------------
那说明,你在某些foreach的地方,集合是被修改了。那么你应该改成for的形式。


以上是个人觉得可能出错的地方。你可以参考的找找问题。 --------------------编程问答-------------------- 首先先谢谢xray2005的回答

(1)代码我检查过了,按我的代码的执行流程,是不会对同一个BeginAccept执行两次EndAccept的
我的代码的执行流程是这样的
a. 程序开始先执行initListener(),对监听Socket进行初始化,并调用BeginAccept()
b. OnConnectRequest函数作为BeginAccept()的回调函数被调用,当有客户端接入,调用OnConnectRequest,在OnConnectRequest函数里会先调用一次EndAccept结束前一次的异步Accept,然后再次调用监听Socket的BeginAccept(监听的Socket是通过OnConnectRequest的IAsyncResult ar参数进行传递的)
c. 当又有客户端接入,会重复上述b的执行流程。

所以我觉得按这样的执行流程应该不会对同一个BeginAccept执行两次EndAccept操作的,不知道是什么引起调用两次了,求解答!

(2)如何判断一个Socket是否已经被释放了?我在释放的时候是调用Socket.Close()的,没有赋值为NULL,所以判断是否为NULL应该不行

(3)这个我似乎找到问题所在了

我是第一次用Socket来写程序,所以有很多地方都不懂,希望各位帮帮忙,项目比较急。。。。 --------------------编程问答-------------------- 没人吗?自己顶起啊
第三个问题应该解决了,还有前两个问题。
什么情况会引起Socket的连接断开呢,昨天测试有一个客户端与服务端的连接断开了,但是客户端程序是没有关掉的,就是突然间与服务端的连接断开了 --------------------编程问答-------------------- 没有其他人帮忙吗??
自己顶起 --------------------编程问答-------------------- 菜鳥,走過
补充:.NET技术 ,  C#
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,