Geçmişte buna benzer bir şey yazmıştım. Yıllar önce yaptığım araştırmadan, Asenkron prizleri kullanarak kendi soket uygulamanızı yazmanın en iyi bahis olduğunu gösterdi. Bu, istemcilerin gerçekten hiçbir şey yapmamalarının aslında nispeten az kaynak gerektirdiği anlamına geliyordu. Meydana gelen her şey .net iş parçacığı havuzu tarafından işlenir.
Sunucular için tüm bağlantıları yöneten bir sınıf olarak yazdım.
Tüm istemci bağlantılarını tutmak için bir liste kullandım, ancak daha büyük listeler için daha hızlı aramalara ihtiyacınız varsa, istediğiniz gibi yazabilirsiniz.
private List<xConnection> _sockets;
Ayrıca aslında gelen bağlantıları dinlemek için sokete ihtiyacınız var.
private System.Net.Sockets.Socket _serverSocket;
Start yöntemi aslında sunucu soketini başlatır ve gelen bağlantıları dinlemeye başlar.
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;
}
Sadece istisna işleme kodunun kötü göründüğünü belirtmek isterim, ama bunun nedeni, bir istisna bastırılmış ve false
bir yapılandırma seçeneği ayarlanmışsa geri dönecek şekilde istisna bastırma kodu vardı , ancak bunun için kaldırmak istedim. kısalık aşkına.
Yukarıdaki _serverSocket.BeginAccept (yeni AsyncCallback (acceptCallback)), _serverSocket), sunucu soketimizi kullanıcı bağladığında acceptCallback yöntemini çağırmak için ayarlar. Bu yöntem, birçok engelleme işleminiz varsa ek çalışan iş parçacıkları oluşturmayı otomatik olarak işleyen .Net iş parçacığı havuzundan çalışır. Bu, sunucudaki yükleri en iyi şekilde işlemelidir.
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);
}
}
Yukarıdaki kod aslında gelen bağlantıyı kabul etmeyi tamamladı BeginReceive
, istemci veri gönderdiğinde çalışacak bir geri arama olan kuyruklar ve sonra acceptCallback
gelen bir sonraki istemci bağlantısını kabul edecek bir sonraki kuyrukları sıralar .
BeginReceive
Yöntem çağrısı istemciden veri aldığında ne yapacağını soketi söyler budur. Bunun için BeginReceive
, bir bayt dizisi vermeniz gerekir; bu, istemci veri gönderdiğinde verileri kopyalayacağı yerdir. ReceiveCallback
Yöntem alıyoruz verilere uygulayabileceğiniz olan çağrılır alacak.
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);
}
}
}
}
DÜZENLEME: Bu modelde bu kod alanında şunu belirtmeyi unuttum:
//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);
Ne genel olarak ne yapmak istiyorum ne kodu, mesajların içine paketleri yeniden montaj yapmak ve daha sonra iş parçacığı havuzunda işler olarak oluşturmaktır. Bu şekilde, istemciden sonraki bloğun BeginReceive değeri, ileti işleme kodu çalışıyorsa gecikmez.
Geri arama kabulü, arama sonu alma işlemini yaparak veri soketini okumayı bitirir. Bu, başlangıç alma işlevinde sağlanan arabelleği doldurur. Yorumumu bıraktığım yerde istediğiniz her şeyi yaptıktan sonra BeginReceive
, istemci daha fazla veri gönderirse geri aramayı yeniden çalıştıracak bir sonraki yöntemi çağırırız . Şimdi gerçekten zor olan kısım, istemci veri gönderdiğinde, geri alma numaranız yalnızca mesajın bir kısmı ile çağrılabilir. Yeniden montaj çok karmaşık hale gelebilir. Kendi yöntemimi kullandım ve bunu yapmak için bir tür özel protokol oluşturdum. Dışarıda bıraktım, ancak talep ederseniz, ekleyebilirim. Bu işleyici aslında yazdığım en karmaşık kod parçasıydı.
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;
}
Yukarıdaki gönderme yöntemi aslında Send
benim için mesaj boyutları ve benim uygulama çok iş parçacıklı doğası nedeniyle iyi bir zaman uyumlu çağrı kullanır . Her istemciye göndermek istiyorsanız, _sockets Listesinde dolaşmanız yeterlidir.
Yukarıda referans olarak gördüğünüz xConnection sınıfı temel olarak bir soketin bayt arabelleğini içermesi için basit bir sarmalayıcıdır ve benim uygulamada bazı ekstralar içerir.
public class xConnection : xBase
{
public byte[] buffer;
public System.Net.Sockets.Socket socket;
}
Ayrıca referans için burada using
ben dahil değildir zaman her zaman rahatsız olsun ben dahil s vardır.
using System.Net.Sockets;
Umarım bu yardımcı olur, en temiz kod olmayabilir, ama işe yarıyor. Ayrıca, kodda, değiştirme konusunda yorgun olmanız gereken bazı nüanslar da vardır. Birincisi, BeginAccept
herhangi bir anda sadece bir tane çağırın. Yıllar önce bu konuda çok can sıkıcı bir .net hatası vardı, bu yüzden ayrıntıları hatırlamıyorum.
Ayrıca, ReceiveCallback
kodda, bir sonraki alma sırasına geçmeden önce soketten alınan her şeyi işleriz. Bu, tek bir soket için, ReceiveCallback
herhangi bir zamanda sadece bir kerede olduğumuzu ve iş parçacığı senkronizasyonunu kullanmamız gerekmediği anlamına gelir. Ancak, veri çekildikten hemen sonra bir sonraki alımı çağırmak için bunu yeniden sıralarsanız, bu biraz daha hızlı olabilir, iş parçacıklarını doğru şekilde senkronize ettiğinizden emin olmanız gerekir.
Ayrıca, kodumun bir sürü kesmek, ama yerinde neler olduğunu özü bıraktı. Bu, tasarımınız için iyi bir başlangıç olmalıdır. Bu konuyla ilgili başka sorularınız varsa yorum bırakın.