本帖最后由 Aricc 于 2009-06-23 10:27:39 编辑

解决方案 »

  1.   

    对集合上锁,尤其是使用 List 来保存的时候。而且使用的时候应该加上 Socket 是否为 Null, 或者 Connected 是否为 true 等判断
      

  2.   

    是不是可以这样:
    对集合删除的时候要加锁,但是遍历集合取其中的socket的时候,不对集合上锁,只对socket对象上锁?这样可行吗?
    是不是这样对删除加的锁也没意义了?
      

  3.   

    对单个socket加锁就行了,并且只在使用的时候加锁,不使用不用加锁.
      

  4.   

    我知道用Foreach遍历的话,这种情况肯定会抛出异常。
    用For也不行吗?
      

  5.   

    从我们写的代码上看, 用 for 来从尾部向头部遍历是不会出现异常的, 最多是遇到几个空的 item 或者是漏掉新加的项目而已。但是从线程的执行上看,由于无法预测线程什么时候执行,什么时候挂起,所以可能会有不明确的行为或者异常。例如:1. 线程 A 在遍历集合,判断 item[i] 不为空,准备发送广播消息。由于时间片用完,线程 A 挂起
    2. 线程 B 获得时间片,把 item[i] 删掉了
    3. 线程 A 再次获得时间片, 拿起刚才的item[i]发送广播信息....呃,有可能是空指针,也有可能是 item[i+1], 也有可能超出到了列表的末尾,抛个 IndexOutOfRange 给你。总之,一切皆有可能...就算不涉及线程挂起,只要是并行线程访问同一块内存都是危险的,不要被自己“看上去连贯”的代码骗了。
      

  6.   

    发个服务端的代码,希望帮得上楼主.(说明,这是我的早期模拟QQ服务器端代码,用的是同步,可支持多客户端连接.)
    using System;
    using System.Collections.Generic;
    using System.ComponentModel;
    using System.Data;
    using System.Drawing;
    using System.Text;
    using System.Windows.Forms;using System.Net;
    using System.Net.Sockets;
    using System.Threading;namespace ChatToolServer
    {
        public partial class Form1 : Form
        {
            const int connectionNum = 100;//最大链数接
            Socket[] clientSocket = new Socket[connectionNum];//server-用于处理客户端连接请求的socket
            Socket tempClient;//临时的客户端套接字对象
            public Form1()
            {
                InitializeComponent();
            }
            //server-侦听方法
            private void listen()
            {
                //获取服务器IP
                string hostName = Dns.GetHostName();
                IPAddress[] ip = Dns.GetHostAddresses(hostName);
                IPAddress HostIp = ip[0];            //创建一个网络端点
                IPEndPoint iep = new IPEndPoint(HostIp, 82);            //创建服务端服务端套接字
                Socket serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);            //将套接字与网络端点绑定
                serverSocket.Bind(iep);            //将套接字置为侦听状态,并设置最大队列数为10
                serverSocket.Listen(connectionNum);            //以同步方式从侦听套接字的连接请求队列中提取第一个挂起的连接请求,然后创建并返回新的 Socket
                //新的套接字:包含对方计算机的IP和端口号,可使用这个套接字与本机进行通信   
                int i = 0;
                while (i < connectionNum)
                {
                    tempClient = serverSocket.Accept();
                    if (clientSocket[i] == null)
                    {
                        clientSocket[i] = tempClient;
                        MessageBox.Show(string.Format("连接成功!(客户端套接字:{0})", clientSocket [i].RemoteEndPoint .ToString()));
                    }
                    i++;
                }
                
            }        private void send_Click(object sender, EventArgs e)
            {
                foreach (Socket s in clientSocket)
                {
                    if (s != null)
                    {
                        if (textBox1.Text != "")
                        {
                            try
                            {
                                //发送数据
                                string message = textBox1.Text;
                                byte[] sendbytes = System.Text.Encoding.UTF8.GetBytes(message);
                                int successSendBtyes = s.Send(sendbytes, sendbytes.Length, SocketFlags.None);
                            }
                            catch (Exception exp)
                            {
                                MessageBox.Show(exp.Message);
                            }
                            
                        }
                        else
                        {
                            MessageBox.Show("发送内容不能为空");
                        }
                       
                    }
                    
                }
                //将发送的数据显示到对话窗口并使对话窗口的滚动条一直停留在最下方
                this.textBox2.Text +="服务器:"+"\r\n" +textBox1.Text + "\r\n";//发完一条消息就换行显示
                this.textBox2.SelectionStart = this.textBox2.Text.Length;
                this.textBox2.ScrollToCaret();
                this.textBox1.Text = "";//将发送窗口清空         
            }        private void Form1_Load(object sender, EventArgs e)
            {
                //server-创建并运行侦听线程
                Thread threadListen = new Thread(new ThreadStart(listen));
                threadListen.Start();
            }        private void timer1_Tick(object sender, EventArgs e)
            {         
                    byte[] receiveBytes = new byte[1024];
                    //如果侦听后取得客户端连接,开始接收数据
                    foreach (Socket s in clientSocket)
                    {
                        if (s!=null && s.Poll(100, SelectMode.SelectRead))
                        {
                            //clientSocket[i].ReceiveTimeout=100;
                            int successReceiveBytes = s.Receive(receiveBytes);
                            this.textBox2.Text +="客户端"+"("+s.RemoteEndPoint .ToString()+")"+":\r\n"
                                +System.Text.Encoding.UTF8.GetString(receiveBytes, 0, successReceiveBytes)+ "\r\n";
                            this.textBox2.SelectionStart = this.textBox2.Text.Length;//使对话窗口的滚动条一直停留在最下方
                            this.textBox2.ScrollToCaret(); 
                        }
                    }
            }
        }
    }
      

  7.   

    我可喜欢用线程了!
    //用线程实时性要好些!
     private void timer1_Tick(object sender, EventArgs e) 
            {        
                    byte[] receiveBytes = new byte[1024]; 
                    //如果侦听后取得客户端连接,开始接收数据 
                    foreach (Socket s in clientSocket) 
                    { 
                        if (s!=null && s.Poll(100, SelectMode.SelectRead)) 
                        { 
                            //clientSocket[i].ReceiveTimeout=100; 
                            int successReceiveBytes = s.Receive(receiveBytes); 
                            this.textBox2.Text +="客户端"+"("+s.RemoteEndPoint .ToString()+")"+":\r\n" 
                                +System.Text.Encoding.UTF8.GetString(receiveBytes, 0, successReceiveBytes)+ "\r\n"; 
                            this.textBox2.SelectionStart = this.textBox2.Text.Length;//使对话窗口的滚动条一直停留在最下方 
                            this.textBox2.ScrollToCaret(); 
                        } 
                    } 
            } 
      

  8.   

    不管你使不使用线程锁,都是会产生不可预测的异常.做好catch处理就可以了.可能会遗漏掉某刚进来的用户,可能会catch掉刚退的用户.但是这些都是无法避免的.A刚群发,B就进来了.肯定是收不到的.
    A刚群发,C就正好退出那也肯定是会catch的.锁了,那后面的操作就延迟了。而谈到QQ,那么QQ是有离线操作的.通信一个字,要"快".
    foreach (string keys in _socketList.Keys)
    {
        try
        {
            _socketList[keys].ClientSocket.ASendTo(String.Format(SendCommand, "OutUser", SocketID, String.Empty), _ipEndPoint);
        }
        catch
        {
            continue;
        }
    }