Tôi đã viết một cái gì đó tương tự như thế này trong quá khứ. Từ nghiên cứu của tôi nhiều năm trước cho thấy rằng việc thực hiện ổ cắm của riêng bạn là đặt cược tốt nhất, sử dụng các ổ cắm không đồng bộ. Điều này có nghĩa là khách hàng không thực sự làm bất cứ điều gì thực sự đòi hỏi tương đối ít tài nguyên. Bất cứ điều gì xảy ra đều được xử lý bởi nhóm luồng .net.
Tôi đã viết nó như là một lớp quản lý tất cả các kết nối cho các máy chủ.
Tôi chỉ đơn giản là sử dụng một danh sách để giữ tất cả các kết nối máy khách, nhưng nếu bạn cần tra cứu nhanh hơn cho danh sách lớn hơn, bạn có thể viết nó theo cách bạn muốn.
private List<xConnection> _sockets;
Ngoài ra, bạn cần các ổ cắm thực sự lắng nghe cho các kết nối không liên quan.
private System.Net.Sockets.Socket _serverSocket;
Phương thức start thực sự khởi động socket máy chủ và bắt đầu lắng nghe mọi kết nối không liên quan.
public bool Start()
{
System.Net.IPHostEntry localhost = System.Net.Dns.GetHostEntry(System.Net.Dns.GetHostName());
System.Net.IPEndPoint serverEndPoint;
try
{
serverEndPoint = new System.Net.IPEndPoint(localhost.AddressList[0], _port);
}
catch (System.ArgumentOutOfRangeException e)
{
throw new ArgumentOutOfRangeException("Port number entered would seem to be invalid, should be between 1024 and 65000", e);
}
try
{
_serverSocket = new System.Net.Sockets.Socket(serverEndPoint.Address.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
}
catch (System.Net.Sockets.SocketException e)
{
throw new ApplicationException("Could not create socket, check to make sure not duplicating port", e);
}
try
{
_serverSocket.Bind(serverEndPoint);
_serverSocket.Listen(_backlog);
}
catch (Exception e)
{
throw new ApplicationException("Error occured while binding socket, check inner exception", e);
}
try
{
//warning, only call this once, this is a bug in .net 2.0 that breaks if
// you're running multiple asynch accepts, this bug may be fixed, but
// it was a major pain in the ass previously, so make sure there is only one
//BeginAccept running
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (Exception e)
{
throw new ApplicationException("Error occured starting listeners, check inner exception", e);
}
return true;
}
Tôi chỉ muốn lưu ý mã xử lý ngoại lệ có vẻ tệ, nhưng lý do là tôi có mã loại trừ ngoại lệ trong đó để mọi ngoại lệ sẽ bị loại bỏ và trả về false
nếu tùy chọn cấu hình được đặt, nhưng tôi muốn xóa nó cho lợi ích ngắn gọn.
_ServerSocket.BeginAccept (AsyncCallback mới (acceptCallback)), _serverSocket) ở trên về cơ bản đặt ổ cắm máy chủ của chúng tôi để gọi phương thức acceptCallback mỗi khi người dùng kết nối. Phương thức này chạy từ luồng xử lý .Net, tự động xử lý việc tạo các luồng công nhân bổ sung nếu bạn có nhiều thao tác chặn. Điều này sẽ tối ưu xử lý bất kỳ tải trên máy chủ.
private void acceptCallback(IAsyncResult result)
{
xConnection conn = new xConnection();
try
{
//Finish accepting the connection
System.Net.Sockets.Socket s = (System.Net.Sockets.Socket)result.AsyncState;
conn = new xConnection();
conn.socket = s.EndAccept(result);
conn.buffer = new byte[_bufferSize];
lock (_sockets)
{
_sockets.Add(conn);
}
//Queue recieving of data from the connection
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
//Queue the accept of the next incomming connection
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (SocketException e)
{
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
{
_sockets.Remove(conn);
}
}
//Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
catch (Exception e)
{
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
{
_sockets.Remove(conn);
}
}
//Queue the next accept, think this should be here, stop attacks based on killing the waiting listeners
_serverSocket.BeginAccept(new AsyncCallback(acceptCallback), _serverSocket);
}
}
Đoạn mã trên về cơ bản vừa hoàn thành việc chấp nhận kết nối đi vào, hàng đợi BeginReceive
là cuộc gọi lại sẽ chạy khi máy khách gửi dữ liệu, và sau đó xếp hàng tiếp theo acceptCallback
sẽ chấp nhận kết nối máy khách tiếp theo.
Các BeginReceive
gọi phương thức là những gì cho ổ cắm biết phải làm gì khi nhận được dữ liệu từ máy khách. Đối với BeginReceive
, bạn cần cung cấp cho nó một mảng byte, đó là nơi nó sẽ sao chép dữ liệu khi máy khách gửi dữ liệu. Các ReceiveCallback
phương pháp sẽ được gọi, đó là cách chúng tôi xử lý dữ liệu nhận được.
private void ReceiveCallback(IAsyncResult result)
{
//get our connection from the callback
xConnection conn = (xConnection)result.AsyncState;
//catch any errors, we'd better not have any
try
{
//Grab our buffer and count the number of bytes receives
int bytesRead = conn.socket.EndReceive(result);
//make sure we've read something, if we haven't it supposadly means that the client disconnected
if (bytesRead > 0)
{
//put whatever you want to do when you receive data here
//Queue the next receive
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
}
else
{
//Callback run but no data, close the connection
//supposadly means a disconnect
//and we still have to close the socket, even though we throw the event later
conn.socket.Close();
lock (_sockets)
{
_sockets.Remove(conn);
}
}
}
catch (SocketException e)
{
//Something went terribly wrong
//which shouldn't have happened
if (conn.socket != null)
{
conn.socket.Close();
lock (_sockets)
{
_sockets.Remove(conn);
}
}
}
}
EDIT: Trong mẫu này tôi quên đề cập rằng trong lĩnh vực mã này:
//put whatever you want to do when you receive data here
//Queue the next receive
conn.socket.BeginReceive(conn.buffer, 0, conn.buffer.Length, SocketFlags.None, new AsyncCallback(ReceiveCallback), conn);
Những gì tôi thường làm là trong bất cứ điều gì bạn muốn mã, là thực hiện việc sắp xếp lại các gói thành tin nhắn, và sau đó tạo chúng như các công việc trên nhóm luồng. Bằng cách này, BeginReceive của khối tiếp theo từ máy khách không bị trì hoãn trong khi bất kỳ mã xử lý tin nhắn nào đang chạy.
Cuộc gọi lại chấp nhận kết thúc việc đọc ổ cắm dữ liệu bằng cách gọi kết thúc nhận. Điều này sẽ điền vào bộ đệm được cung cấp trong hàm bắt đầu nhận. Khi bạn làm bất cứ điều gì bạn muốn nơi tôi để lại nhận xét, chúng tôi gọi BeginReceive
phương thức tiếp theo sẽ chạy lại cuộc gọi lại nếu khách hàng gửi thêm dữ liệu. Bây giờ đây là phần thực sự khó khăn, khi khách hàng gửi dữ liệu, cuộc gọi lại nhận được của bạn chỉ có thể được gọi với một phần của tin nhắn. Việc lắp lại có thể trở nên rất rất phức tạp. Tôi đã sử dụng phương pháp của riêng mình và tạo ra một loại giao thức độc quyền để làm điều này. Tôi đã để nó ra, nhưng nếu bạn yêu cầu, tôi có thể thêm nó vào. Trình xử lý này thực sự là đoạn mã phức tạp nhất tôi từng viết.
public bool Send(byte[] message, xConnection conn)
{
if (conn != null && conn.socket.Connected)
{
lock (conn.socket)
{
//we use a blocking mode send, no async on the outgoing
//since this is primarily a multithreaded application, shouldn't cause problems to send in blocking mode
conn.socket.Send(bytes, bytes.Length, SocketFlags.None);
}
}
else
return false;
return true;
}
Phương thức gửi ở trên thực sự sử dụng đồng bộ Send
cuộc gọi , đối với tôi nó vẫn ổn do kích thước tin nhắn và tính chất đa luồng của ứng dụng của tôi. Nếu bạn muốn gửi cho mọi khách hàng, bạn chỉ cần lặp qua Danh sách _sockets.
Lớp xConnection mà bạn thấy được tham chiếu ở trên về cơ bản là một trình bao bọc đơn giản cho một socket để bao gồm bộ đệm byte và trong triển khai của tôi một số tính năng bổ sung.
public class xConnection : xBase
{
public byte[] buffer;
public System.Net.Sockets.Socket socket;
}
Ngoài ra để tham khảo ở đây là những cái using
tôi bao gồm vì tôi luôn cảm thấy khó chịu khi chúng không được bao gồm.
using System.Net.Sockets;
Tôi hy vọng điều đó hữu ích, nó có thể không phải là mã sạch nhất, nhưng nó hoạt động. Cũng có một số sắc thái của mã mà bạn nên mệt mỏi khi thay đổi. Đối với một, chỉ có mộtBeginAccept
gọi bất cứ lúc nào. Đã từng có một lỗi .net rất khó chịu xung quanh vấn đề này, cách đây nhiều năm nên tôi không nhớ lại chi tiết.
Ngoài ra, trong ReceiveCallback
mã, chúng tôi xử lý mọi thứ nhận được từ ổ cắm trước khi chúng tôi xếp hàng nhận tiếp theo. Điều này có nghĩa là đối với một ổ cắm duy nhất, chúng ta chỉ thực sự từng có ReceiveCallback
một lần tại bất kỳ thời điểm nào và chúng ta không cần phải sử dụng đồng bộ hóa luồng. Tuy nhiên, nếu bạn sắp xếp lại thứ này để gọi nhận tiếp theo ngay sau khi lấy dữ liệu, có thể nhanh hơn một chút, bạn sẽ cần đảm bảo rằng bạn đã đồng bộ hóa đúng các luồng.
Ngoài ra, tôi đã hack rất nhiều mã của mình, nhưng để lại bản chất của những gì đang diễn ra. Đây sẽ là một khởi đầu tốt cho thiết kế của bạn. Để lại một bình luận nếu bạn có thêm bất kỳ câu hỏi xung quanh này.