मैंने अतीत में ऐसा ही कुछ लिखा है। मेरे शोध के वर्षों से पता चला है कि एसिंक्रोनस सॉकेट्स का उपयोग करके अपने स्वयं के सॉकेट कार्यान्वयन को लिखना सबसे अच्छा दांव था। इसका मतलब यह था कि ग्राहक वास्तव में कुछ भी नहीं कर रहे थे जो वास्तव में अपेक्षाकृत कम संसाधनों की आवश्यकता थी। जो कुछ भी घटित होता है उसे .net थ्रेड पूल द्वारा नियंत्रित किया जाता है।
मैंने इसे एक वर्ग के रूप में लिखा था जो सर्वरों के लिए सभी कनेक्शनों का प्रबंधन करता है।
मैंने बस सभी क्लाइंट कनेक्शन को होल्ड करने के लिए एक सूची का उपयोग किया था, लेकिन यदि आपको बड़ी सूची के लिए तेज़ लुकअप की आवश्यकता है, तो आप इसे अपनी इच्छानुसार लिख सकते हैं।
private List<xConnection> _sockets;
इसके अलावा आपको इनकमिंग कनेक्शन के लिए वास्तव में सुनने वाले सॉकेट की आवश्यकता है।
private System.Net.Sockets.Socket _serverSocket;
प्रारंभ विधि वास्तव में सर्वर सॉकेट शुरू करती है और किसी भी इनकमिंग कनेक्शन के लिए सुनना शुरू करती है।
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;
}
मैं सिर्फ यह नोट करना चाहूंगा कि अपवाद हैंडलिंग कोड खराब दिख रहा है, लेकिन इसका कारण यह है कि मेरे पास अपवाद दमन कोड था ताकि कोई भी अपवाद दबा दिया जाए और false
यदि कोई कॉन्फ़िगरेशन विकल्प सेट किया गया था, तो वापस आ जाए, लेकिन मैं इसे हटाना चाहता था संक्षिप्तता।
_ServerSocket.BeginAccept (नया AsyncCallback (acceptCallback)), _serverSocket ऊपर) अनिवार्य रूप से हमारे सर्वर सॉकेट को जब भी कोई उपयोगकर्ता जोड़ता है, तो AcceptCallback विधि को कॉल करने के लिए सेट करता है। यह विधि .Net थ्रेडपूल से चलती है, जो यदि आपके पास कई ब्लॉकिंग ऑपरेशन हैं, तो स्वचालित रूप से अतिरिक्त वर्कर थ्रेड बनाने का काम करती है। यह सर्वर पर किसी भी लोड को बेहतर तरीके से संभालना चाहिए।
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);
}
}
उपरोक्त कोड अनिवार्य रूप से बस आने वाले कनेक्शन को स्वीकार करने के लिए समाप्त हो गया है, कतारें BeginReceive
जो एक कॉलबैक है जो क्लाइंट को डेटा भेजने पर चलेगी, और फिर उस कतार को आगे बढ़ाएगा acceptCallback
जो अगले ग्राहक कनेक्शन को स्वीकार करेगी जो अंदर आता है।
BeginReceive
विधि कॉल क्या सॉकेट क्या जब यह ग्राहक से डेटा प्राप्त करने के लिए बताता है। इसके लिए BeginReceive
, आपको इसे एक बाइट सरणी देने की आवश्यकता है, जो क्लाइंट के डेटा भेजने पर डेटा को कॉपी करेगा। ReceiveCallback
विधि कहा जाता हो जाएगा, जो कि हम प्राप्त जानकारी का क्या।
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: इस पैटर्न में मैं यह बताना भूल गया कि कोड के इस क्षेत्र में:
//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);
मैं आमतौर पर जो कुछ भी आप कोड चाहते हैं, वह संदेश में पैकेट के पुनर्मूल्यांकन करना है, और फिर उन्हें थ्रेड पूल पर नौकरी के रूप में बनाना है। इस तरह क्लाइंट से अगले ब्लॉक की शुरुआत में देरी नहीं होती है, जबकि जो भी मैसेज प्रोसेसिंग कोड चल रहा है।
स्वीकार कॉलबैक अंतिम प्राप्त कॉल करके डेटा सॉकेट को पढ़ना समाप्त करता है। यह प्रारंभ प्राप्त फ़ंक्शन में प्रदान किए गए बफर को भरता है। एक बार जब आप वह कर लेते हैं जो आप चाहते हैं जहां मैंने टिप्पणी छोड़ दी है, तो हम अगली BeginReceive
विधि को कॉल करते हैं जो कॉलबैक को फिर से चलाएगा यदि क्लाइंट कोई और डेटा भेजता है। अब यहाँ वास्तव में मुश्किल हिस्सा है, जब ग्राहक डेटा भेजता है, तो आपका प्राप्त कॉलबैक केवल संदेश के भाग के साथ कहा जा सकता है। आश्वस्त करना बहुत जटिल हो सकता है। मैंने अपने तरीके का इस्तेमाल किया और ऐसा करने के लिए एक तरह का मालिकाना प्रोटोकॉल बनाया। मैंने इसे छोड़ दिया, लेकिन यदि आप अनुरोध करते हैं, तो मैं इसे जोड़ सकता हूं। यह हैंडलर वास्तव में कोड का सबसे जटिल टुकड़ा था जिसे मैंने कभी लिखा था।
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;
}
उपरोक्त भेजने की विधि वास्तव में एक तुल्यकालिक Send
कॉल का उपयोग करती है , मेरे लिए जो संदेश के आकार और मेरे आवेदन के बहुआयामी प्रकृति के कारण ठीक थी। यदि आप प्रत्येक ग्राहक को भेजना चाहते हैं, तो आपको बस _sockets सूची के माध्यम से लूप करना होगा।
ऊपर संदर्भित संदर्भित xConnection वर्ग मूल रूप से बाइट बफर को शामिल करने के लिए सॉकेट के लिए एक साधारण आवरण है, और मेरे कार्यान्वयन में कुछ अतिरिक्त।
public class xConnection : xBase
{
public byte[] buffer;
public System.Net.Sockets.Socket socket;
}
यहाँ संदर्भ के लिए using
मैं भी शामिल हूँ क्योंकि मैं हमेशा परेशान हो जाता हूँ जब वे शामिल नहीं हैं।
using System.Net.Sockets;
मुझे आशा है कि यह उपयोगी है, यह सबसे साफ कोड नहीं हो सकता है, लेकिन यह काम करता है। कोड के लिए कुछ बारीकियां भी हैं जिन्हें आपको बदलने के बारे में थका होना चाहिए। एक के लिए, BeginAccept
किसी भी एक समय में केवल एक को बुलाया जाता है। इस के आसपास एक बहुत कष्टप्रद .net बग हुआ करता था, जो वर्षों पहले था इसलिए मुझे विवरण याद नहीं है।
इसके अलावा, ReceiveCallback
कोड में, हम सॉकेट से प्राप्त किसी भी चीज़ को संसाधित करते हैं, इससे पहले कि हम अगला प्राप्त करें। इसका मतलब यह है कि एक ही सॉकेट के लिए, हम वास्तव में कभी ReceiveCallback
भी किसी भी समय एक बार में हैं, और हमें थ्रेड सिंक्रोनाइज़ेशन का उपयोग करने की आवश्यकता नहीं है। हालांकि, यदि आप डेटा को खींचने के तुरंत बाद अगले कॉल को कॉल करने के लिए इसे फिर से व्यवस्थित करते हैं, जो थोड़ा तेज़ हो सकता है, तो आपको यह सुनिश्चित करने की आवश्यकता होगी कि आप थ्रेड्स को ठीक से सिंक्रनाइज़ कर सकें।
इसके अलावा, मैंने अपने कोड का एक बहुत हैक कर लिया, लेकिन जो हो रहा है उसका सार छोड़ दिया। यह आप डिजाइन के लिए एक अच्छी शुरुआत होनी चाहिए। यदि आपके पास इसके आसपास कोई और प्रश्न है तो एक टिप्पणी छोड़ दें।