Để thực thi hiệu quả các ổ cắm không đồng bộ, mỗi socket sẽ cần nhiều hơn 1 SocketAsyncEventArgs. Ngoài ra còn có một vấn đề với bộ đệm byte [] trong mỗi SocketAsyncEventArgs. Tóm lại, các bộ đệm byte sẽ được ghim bất cứ khi nào một chuyển đổi được quản lý - bản địa xảy ra (gửi/nhận). Nếu bạn phân bổ bộ đệm SocketAsyncEventArgs và byte khi cần thiết, bạn có thể chạy vào OutOfMemoryExceptions với nhiều máy khách do phân đoạn và không có khả năng của GC để nén bộ nhớ nhỏ gọn.
Cách tốt nhất để xử lý việc này là tạo một lớp SocketBufferPool sẽ phân bổ một số lượng lớn byte và SocketAsyncEventArgs khi ứng dụng được khởi động lần đầu, cách này bộ nhớ được ghim sẽ tiếp giáp. Sau đó, chỉ đơn giản là tái sử dụng các bộ đệm từ hồ bơi khi cần thiết.
Trong thực tế, tôi đã tìm thấy nó tốt nhất để tạo ra một lớp bao bọc xung quanh SocketAsyncEventArgs và một lớp SocketBufferPool để quản lý việc phân phối tài nguyên.
Như một ví dụ, đây là mã cho một phương pháp BeginReceive:
private void BeginReceive(Socket socket)
{
Contract.Requires(socket != null, "socket");
SocketEventArgs e = SocketBufferPool.Instance.Alloc();
e.Socket = socket;
e.Completed += new EventHandler<SocketEventArgs>(this.HandleIOCompleted);
if (!socket.ReceiveAsync(e.AsyncEventArgs)) {
this.HandleIOCompleted(null, e);
}
}
Và đây là phương pháp HandleIOCompleted:
private void HandleIOCompleted(object sender, SocketEventArgs e)
{
e.Completed -= this.HandleIOCompleted;
bool closed = false;
lock (this.sequenceLock) {
e.SequenceNumber = this.sequenceNumber++;
}
switch (e.LastOperation) {
case SocketAsyncOperation.Send:
case SocketAsyncOperation.SendPackets:
case SocketAsyncOperation.SendTo:
if (e.SocketError == SocketError.Success) {
this.OnDataSent(e);
}
break;
case SocketAsyncOperation.Receive:
case SocketAsyncOperation.ReceiveFrom:
case SocketAsyncOperation.ReceiveMessageFrom:
if ((e.BytesTransferred > 0) && (e.SocketError == SocketError.Success)) {
this.BeginReceive(e.Socket);
if (this.ReceiveTimeout > 0) {
this.SetReceiveTimeout(e.Socket);
}
} else {
closed = true;
}
if (e.SocketError == SocketError.Success) {
this.OnDataReceived(e);
}
break;
case SocketAsyncOperation.Disconnect:
closed = true;
break;
case SocketAsyncOperation.Accept:
case SocketAsyncOperation.Connect:
case SocketAsyncOperation.None:
break;
}
if (closed) {
this.HandleSocketClosed(e.Socket);
}
SocketBufferPool.Instance.Free(e);
}
Đoạn mã trên được chứa trong một lớp học TcpSocket rằng sẽ nâng cao DataReceived & Sự kiện DataSent. Một điều cần lưu ý là trường hợp SocketAsyncOperation.ReceiveMessageFrom: block; nếu socket không có lỗi, nó sẽ bắt đầu ngay lập tức một BeginReceive() khác sẽ phân bổ một SocketEventArgs khác từ pool.
Một lưu ý quan trọng khác là thuộc tính SocketEventArgs SequenceNumber được đặt trong phương thức HandleIOComplete. Mặc dù các yêu cầu không đồng bộ sẽ hoàn thành theo thứ tự xếp hàng đợi, bạn vẫn phải tuân theo các điều kiện chủng tộc khác. Kể từ khi mã gọi BeginReceive trước khi nâng cao sự kiện DataReceived có khả năng là thread phục vụ IOCP gốc sẽ chặn sau khi gọi BeginReceive nhưng trước khi rasing sự kiện trong khi async thứ hai nhận được hoàn thành trên một luồng mới làm tăng sự kiện DataReceived trước. Mặc dù đây là một trường hợp khá hiếm có thể xảy ra và thuộc tính SequenceNumber cung cấp cho ứng dụng tiêu thụ khả năng đảm bảo rằng dữ liệu được xử lý theo đúng thứ tự.
Một khu vực khác cần lưu ý là không đồng bộ gửi. Thông thường, các yêu cầu gửi không đồng bộ sẽ hoàn thành đồng bộ (SendAsync sẽ trả về false nếu cuộc gọi hoàn thành đồng bộ) và có thể làm giảm hiệu suất nghiêm trọng. Các chi phí bổ sung của cuộc gọi async quay trở lại trên IOCP có thể trong thực tế gây ra hiệu suất tồi tệ hơn chỉ đơn giản bằng cách sử dụng cuộc gọi đồng bộ. Cuộc gọi async yêu cầu hai cuộc gọi hạt nhân và phân bổ đống trong khi cuộc gọi đồng bộ xảy ra trên ngăn xếp.
Hope this helps, Bill