2017-10-16 66 views
0

我可能是错的,但在我看来,在这段代码中,一个名为_buffer的全局变量被分配给多个线程堆中的一个新对象,所以如果一个线程试图读取数据从函数中写入函数之后,但是在另一个线程将这个变量_buffer分配给堆上的另一个对象时,我会得到错误的数据。这是真的发生还是我错了?如果是这样,我该如何解决?C#多线程套接字 - 可能的并发访问

public class SocketServer 
{ 
    Socket _serverSocket; 
    List<Socket> _clientSocket = new List<Socket>(); 
    byte[] _buffer; 

    public SocketServer() 
    { 
     _serverSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp); 
    } 

    public void Bind(int Port) 
    { 
     Console.WriteLine("Setting up server..."); 
     _serverSocket.Bind(new IPEndPoint(IPAddress.Any, Port)); 
    } 

    public void Listen(int BackLog) 
    { 
     _serverSocket.Listen(BackLog); 
    } 

    public void Accept() 
    { 
     _serverSocket.BeginAccept(AcceptCallback, null); 
    } 

    private void AcceptCallback(IAsyncResult AR) 
    { 
     Socket socket = _serverSocket.EndAccept(AR); 
     _clientSocket.Add(socket); 
     Console.WriteLine("Client Connected"); 
     _buffer = new byte[1024]; 
     socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, socket); 
     Accept(); 
    } 

    private void ReceiveCallback(IAsyncResult AR) 
    { 
     Socket socket = AR.AsyncState as Socket; 
     int bufferSize = socket.EndReceive(AR); 

     string text = Encoding.ASCII.GetString(_buffer, 0, bufferSize); 
     Console.WriteLine("Text Received: {0}", text); 

     string response = string.Empty; 

     if (text.ToLower() != "get time") 
      response = $"\"{text}\" is a Invalid Request"; 
     else 
      response = DateTime.Now.ToLongTimeString(); 

     byte[] data = Encoding.ASCII.GetBytes(response); 
     socket.BeginSend(data, 0, data.Length, SocketFlags.None, SendCallback, socket); 

     _buffer = new byte[1024]; 
     socket.BeginReceive(_buffer, 0, _buffer.Length, SocketFlags.None, ReceiveCallback, socket); 
    } 

    private void SendCallback(IAsyncResult AR) 
    { 
     (AR.AsyncState as Socket).EndSend(AR); 
    } 
} 

回答

2

当涉及多个线程时,有一个data race,至少其中一个是作者。

Socket是线程安全的,但SocketServer不是。您在使用前立即写入_buffer。这绝对是多线程场景中的数据竞赛。在每次访问共享状态时都需要锁定机制。

如果在传递之前立即覆盖它,使用_buffer的字段没有意义。如果您需要使用一个缓冲区,请在初始化时将其分配一次。为了避免改变太多,你可以实现这样的:

class SocketServer 
{ 
    class Transaction 
    { 
     public readonly byte[] Data; 
     public readonly Socket Socket; 

     public Transaction(byte[] data, Socket socket) 
     { 
      Data = data; 
      Socket = socket; 
     } 
    } 

    private readonly object _syncObj = new object(); 
    private readonly List<Transaction> _received = new List<Transaction>(); 
    //... 

    //... 
    private void AcceptCallback(IAsyncResult AR) 
    { 
     //... 
     byte[] buffer = new byte[1024]; 
     socket.BeginReceive(
      buffer, 0, buffer.Length, SocketFlags.None, 
      ReceiveCallback, new Transaction(buffer, socket)); 
     //... 
    } 
    private void ReceiveCallback(IAsyncResult AR) 
    { 
     Transaction trans = (Transaction)AR.AsyncState; 
     Socket socket = trans.Socket; 
     int bufferSize = socket.EndReceive(AR); 
     lock (_syncObj) { 
      _received.Add(trans); 
     } 
     //... 
     byte[] buffer = new byte[1024]; 
     socket.BeginReceive(
      buffer, 0, buffer.Length, SocketFlags.None, 
      ReceiveCallback, new Transaction(buffer, socket)); 
    } 
    //... 

    // Call this to get all the received data. 
    // This will block ReceiveCallback until it completes. 
    public byte[] GetReceivedData() 
    { 
     int totalSize = 0; 
     lock (_syncObj) { 
      for (int i = 0; i < _received.Length; i++) { 
       totalSize += _received[i].Data.Length; 
      } 

      byte[] totalData = new byte[totalSize]; 
      int offset = 0; 
      for (int i = 0; i < _received.Length; i++) { 
       byte[] blockData = _received[i].Data; 
       Buffer.BlockCopy(blockData, 0, totalData, offset, blockData.Length); 
       offset += blockData.Length; 
      } 
      _received.Clear(); 
      return totalData; 
     } 
    } 
} 

另外,您可以创建IList<ArraySegment<byte>>线程安全的实现,并使用相应的overloads,但这不在这个答案的范围。

在不相关的说明中,您的命名约定不一致。你在字段中使用下划线驼峰大小写,混合使用大写字母& pascal case作为参数,局部变量使用camel case。使用你想要的任何约定,但请保持一致。我建议遵循general guidelines

2

_buffer不是线程安全的。我会使用ConcurrentBag而不是平面字节数组的并发集合。这将保证你的线程安全。 如果要将_buffer保存为数组,则必须使用适当的锁(例如,使用lock关键字)以确保多个线程不会同时尝试访问_buffer。 更多关于并发包:https://msdn.microsoft.com/en-us/library/dd381779(v=vs.110).aspx