2011-01-31 6 views
6

Tôi thích ứng với bài đăng dài này. Tôi đã làm cho nó càng nhỏ càng tốt trong khi vẫn truyền tải được vấn đề.Rất lạ Vấn đề gửi dữ liệu qua Ổ cắm trong C#

Ok điều này khiến tôi phát điên. Tôi có một khách hàng và một chương trình máy chủ, cả trong C#. Máy chủ gửi dữ liệu đến máy khách thông qua Socket.Send(). Máy khách nhận dữ liệu qua Socket.BeginReceive và Socket.Receive. Giao thức giả của tôi như sau: Máy chủ gửi một giá trị song song (ngắn) cho biết độ dài của dữ liệu thực tế được theo sau bởi dữ liệu thực tế. Máy khách đọc hai byes đầu tiên một cách không đồng bộ, chuyển đổi các byte thành một đoạn ngắn và ngay lập tức đọc nhiều byte từ socket một cách đồng bộ.

Bây giờ điều này hoạt động tốt cho một chu kỳ mỗi vài giây hoặc lâu hơn nhưng khi tôi tăng tốc độ, mọi thứ trở nên kỳ lạ. Dường như khách hàng sẽ đọc ngẫu nhiên dữ liệu thực tế khi nó đang cố gắng đọc từ độ dài hai byte. Sau đó nó sẽ cố gắng chuyển đổi hai byte tùy ý này thành một đoạn ngắn dẫn đến giá trị hoàn toàn không chính xác, gây ra sự cố. Đoạn mã sau là từ chương trình của tôi nhưng được cắt bớt để chỉ hiển thị các dòng quan trọng.

phương pháp phía máy chủ để gửi dữ liệu:

private static object myLock = new object(); 
private static bool sendData(Socket sock, String prefix, byte[] data) 
{ 
    lock(myLock){ 
     try 
     { 
      // prefix is always a 4-bytes string 
      // encoder is an ASCIIEncoding object  
      byte[] prefixBytes = encoder.GetBytes(prefix); 
      short length = (short)(prefixBytes.Length + data.Length); 

      sock.Send(BitConverter.GetBytes(length)); 
      sock.Send(prefixBytes); 
      sock.Send(data); 

      return true; 
     } 
     catch(Exception e){/*blah blah blah*/} 
    } 
} 

phương pháp Client-side để tiếp nhận dữ liệu:

private static object myLock = new object(); 
private void receiveData(IAsyncResult result) 
{ 
    lock(myLock){ 
     byte[] buffer = new byte[1024]; 
     Socket sock = result.AsyncState as Socket; 
     try 
     { 
      sock.EndReceive(result); 
      short n = BitConverter.ToInt16(smallBuffer, 0); 
      // smallBuffer is a 2-byte array 

      // Receive n bytes 
      sock.Receive(buffer, n, SocketFlags.None); 

      // Determine the prefix. encoder is an ASCIIEncoding object 
      String prefix = encoder.GetString(buffer, 0, 4); 

      // Code to process the data goes here 

      sock.BeginReceive(smallBuffer, 0, 2, SocketFlags.None, receiveData, sock); 
     } 
     catch(Exception e){/*blah blah blah*/} 
    } 
} 

server-side code để tái tạo vấn đề đáng tin cậy:

byte[] b = new byte[1020]; // arbitrary length 

for (int i = 0; i < b.Length; i++) 
    b[i] = 7; // arbitrary value of 7 

while (true) 
{ 
    sendData(socket, "PRFX", b); 
    // socket is a Socket connected to a client running the same code as above 
    // "PRFX" is an arbitrary 4-character string that will be sent 
} 

Nhìn vào mã ở trên, người ta có thể xác định rằng máy chủ sẽ mãi gửi số 1024, độ dài của tổng số dữ liệu, bao gồm tiền tố, dưới dạng ngắn (0x400), tiếp theo là "PRFX" trong mã nhị phân ASCII theo sau là một loạt 7 (0x07). Máy khách sẽ đọc vĩnh viễn hai byte đầu tiên (0x400), giải thích rằng là 1024, lưu trữ giá trị đó dưới dạng n và sau đó đọc 1024 từ luồng.

Đây thực sự là những gì nó thực hiện trong 40 lần lặp đầu tiên, nhưng, một cách tự nhiên, khách hàng sẽ đọc hai byes đầu tiên và giải thích chúng là 1799, không phải 1024! 1799 trong hex là 0x0707 là hai liên tiếp 7's !!! Đó là dữ liệu chứ không phải độ dài! Điều gì đã xảy ra với hai byte đó? Điều này xảy ra với bất kỳ giá trị nào tôi đặt trong mảng byte, tôi chỉ chọn 7 vì nó rất dễ thấy mối tương quan với 1799.

Nếu bạn vẫn đọc vào thời điểm này, tôi hoan nghênh sự cống hiến của bạn.

Một số quan sát quan trọng:

  • Giảm chiều dài của b sẽ làm tăng số lần lặp trước khi vấn đề xảy ra nhưng không ngăn cản các vấn đề xảy ra
  • Thêm một sự chậm trễ đáng kể giữa mỗi lần lặp của vòng lặp có thể ngăn sự cố xảy ra
  • Điều này KHÔNG xảy ra khi sử dụng cả máy khách và máy chủ trên cùng một máy chủ và kết nối qua địa chỉ lặp lại.

Như đã đề cập, điều này khiến tôi phát điên! Tôi luôn có thể giải quyết các vấn đề lập trình của mình nhưng vấn đề này đã hoàn toàn làm tôi bối rối. Vì vậy, tôi ở đây cầu xin cho bất kỳ tư vấn hoặc kiến ​​thức về chủ đề này.

Cảm ơn bạn.

Trả lời

3

Tôi nghi ngờ bạn cho rằng toàn bộ thư được gửi trong một cuộc gọi đến receiveData. Đây không phải là, nói chung, trường hợp. Sự phân mảnh, sự chậm trễ, v.v. có thể kết thúc có nghĩa là dữ liệu đến trong hình ảnh nhỏ giọt, để bạn có thể nhận được hàm receiveData được gọi khi chỉ có, ví dụ: 900 byte đã sẵn sàng. Cuộc gọi của bạn sock.Receive(buffer, n, SocketFlags.None); cho biết "Cho tôi dữ liệu vào bộ đệm, tối đa n byte" - bạn có thể nhận được ít hơn và số byte thực sự đọc được trả về bởi Receive.

Điều này giải thích tại sao giảm b, tăng thêm độ trễ hoặc sử dụng cùng một máy chủ có vẻ như "khắc phục" sự cố - tỷ lệ cược được tăng lên đáng kể khi toàn bộ thư sẽ đến bằng một phương pháp này (nhỏ hơn b, dữ liệu nhỏ hơn; thêm độ trễ có nghĩa là có ít dữ liệu tổng thể hơn trong đường ống; không có mạng nào trong đường dẫn cho địa chỉ cục bộ).

Để xem đây có phải là sự cố hay không, hãy ghi lại giá trị trả lại là Receive. Nó đôi khi sẽ ít hơn 1024 nếu chẩn đoán của tôi là chính xác. Để khắc phục nó, bạn cần đợi cho đến khi vùng đệm của mạng cục bộ có tất cả dữ liệu của bạn (không lý tưởng), hoặc chỉ nhận dữ liệu khi nó đến, lưu trữ cục bộ cho đến khi toàn bộ thông báo sẵn sàng và xử lý nó.

+0

Đây là vấn đề tôi gặp phải tại một thời điểm. Hãy thử đọc dữ liệu từ mạng trong một vòng lặp while cho đến khi bạn đọc lượng dữ liệu được chỉ định trong tham số "length" – Kurru

+0

Đăng nhập lại 1024; nó có thể tất nhiên không chỉ khi đọc tiêu đề 2 byte –

+0

@Marc - tôi có nghĩa là thông qua việc ghi giá trị trả về của Receive, bạn sẽ thấy ít hơn 1024 trong trường hợp thử nghiệm nhất định nếu mạng cơ bản cung cấp ít hơn toàn bộ thông điệp trong một Nhận. –

0

Đoán của tôi là smallBuffer được khai báo bên ngoài phương thức nhận và đang được sử dụng lại từ nhiều luồng. Nói cách khác, đó không phải là chủ đề an toàn.

Việc viết mã ổ cắm tốt là khó. Vì bạn đang viết cả máy khách và máy chủ, bạn có thể muốn xem Windows Communication Foundation (WCF) cho phép bạn gửi và nhận toàn bộ các đối tượng với ít rắc rối hơn nhiều.

+0

Cảm ơn bạn đã trả lời. Tôi quên đề cập rằng cả hai phương pháp đều nằm trong một khối khóa. Tôi sẽ cập nhật mã một cách thích hợp để những người khác có thể thấy nếu tôi thậm chí còn khóa mã một cách chính xác. –

1

Khi bạn sử dụng ổ cắm, bạn phải dự đoán rằng ổ cắm có thể truyền ít byte hơn bạn mong đợi. Bạn phải lặp lại phương thức .Receive để nhận phần còn lại của các byte.

Điều này cũng đúng khi bạn gửi byte qua ổ cắm. Bạn phải kiểm tra xem có bao nhiêu byte được gửi đi và lặp lại cho đến khi tất cả các byte được gửi đi.

Hành vi này là do các lớp mạng tách các thư thành nhiều gói. Nếu tin nhắn của bạn ngắn, thì bạn ít có khả năng gặp phải điều này. Nhưng bạn nên luôn viết mã cho nó.

Với nhiều byte cho mỗi bộ đệm, bạn rất có thể thấy thông báo của người gửi nhổ vào nhiều gói. Mỗi hoạt động đọc sẽ nhận được một gói, đó chỉ là một phần của thông điệp của bạn. Nhưng các bộ đệm nhỏ cũng có thể được chia nhỏ.

2

Nghe có vẻ như tôi gặp vấn đề chính ở đây là không kiểm tra kết quả của EndReceive là số byte đã đọc. Nếu ổ cắm mở, điều này có thể là bất cứ thứ gì > 0<= giá trị tối đa bạn chỉ định. Nó không phải là an toàn để giả định rằng bởi vì bạn yêu cầu 2 byte, 2 byte đã được đọc. Ditto khi đọc dữ liệu thực tế.

Bạn phải luôn lặp lại, tích lũy số lượng dữ liệu bạn mong đợi (và giảm số lượng còn lại và tăng mức bù trừ, tương ứng).

Sự pha trộn của sync/async sẽ không giúp làm cho nó dễ dàng, một trong hai:

£
2
sock.Receive(buffer, n, SocketFlags.None); 

Bạn không kiểm tra giá trị trả về của hàm. Ổ cắm sẽ trả về tối đa 'n' byte nhưng sẽ chỉ trả về những gì có sẵn. Cơ hội là bạn không nhận được toàn bộ 'dữ liệu' với đọc này.Vì vậy, khi bạn thực hiện socket tiếp theo đọc, bạn đang thực sự nhận được hai byte đầu tiên của phần còn lại của dữ liệu và không phải là độ dài của lần truyền tiếp theo.