ฉันเคยเขียนบางสิ่งที่คล้ายกับสิ่งนี้ในอดีต จากงานวิจัยของฉันเมื่อหลายปีก่อนแสดงให้เห็นว่าการเขียนการติดตั้งซ็อกเก็ตของคุณเองเป็นทางออกที่ดีที่สุดโดยใช้ซ็อกเก็ตอะซิงโครนัส นั่นหมายความว่าลูกค้าไม่ได้ทำสิ่งใดต้องการทรัพยากรที่ค่อนข้างน้อย สิ่งใดที่เกิดขึ้นได้รับการจัดการโดย. เธรดพูล
ฉันเขียนเป็นคลาสที่จัดการการเชื่อมต่อทั้งหมดสำหรับเซิร์ฟเวอร์
ฉันใช้ลิสต์เพื่อหยุดการเชื่อมต่อไคลเอนต์ทั้งหมด แต่ถ้าคุณต้องการการค้นหาที่เร็วขึ้นสำหรับลิสต์ที่ใหญ่ขึ้นคุณสามารถเขียนได้ตามที่คุณต้องการ
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 threadpool ซึ่งจัดการการสร้างเธรดผู้ทำงานเพิ่มเติมโดยอัตโนมัติหากคุณมีการดำเนินการบล็อกจำนวนมาก สิ่งนี้ควรจัดการโหลดบนเซิร์ฟเวอร์อย่างเหมาะสมที่สุด
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);
}
}
}
}
แก้ไข: ในรูปแบบนี้ฉันลืมที่จะพูดถึงว่าในรหัสพื้นที่นี้:
//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 ของบล็อกถัดไปจากไคลเอ็นต์จะไม่ล่าช้าในขณะที่รหัสการประมวลผลข้อความกำลังทำงานอยู่
การเรียกกลับยอมรับเสร็จสิ้นการอ่านซ็อกเก็ตข้อมูลโดยการเรียกรับสิ้นสุด สิ่งนี้จะเติมบัฟเฟอร์ที่มีให้ในฟังก์ชั่นเริ่มต้นรับ เมื่อคุณทำสิ่งที่คุณต้องการในที่ที่ฉันได้แสดงความคิดเห็นเราจะเรียก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 ที่คุณเห็นอ้างอิงข้างต้นนั้นเป็น wrapper ง่ายๆสำหรับซ็อกเก็ตที่จะรวมบัฟเฟอร์ไบต์และในการใช้งานของฉันพิเศษบางอย่าง
public class xConnection : xBase
{
public byte[] buffer;
public System.Net.Sockets.Socket socket;
}
นอกจากนี้สำหรับการอ้างอิงที่นี่เป็นสิ่งusing
ที่ฉันรวมไว้เพราะฉันมักจะรำคาญเมื่อพวกเขาไม่รวม
using System.Net.Sockets;
ฉันหวังว่าจะเป็นประโยชน์อาจไม่ได้เป็นรหัสที่สะอาดที่สุด แต่ใช้งานได้ นอกจากนี้ยังมีความแตกต่างบางอย่างของรหัสที่คุณควรจะเบื่อหน่ายเกี่ยวกับการเปลี่ยนแปลง สำหรับหนึ่งมีเพียงหนึ่งเดียวที่BeginAccept
เรียกในเวลาใดก็ได้ เคยมีข้อผิดพลาด. net ที่น่ารำคาญรอบ ๆ สิ่งนี้ซึ่งเป็นเมื่อหลายปีก่อนดังนั้นฉันจึงไม่จำรายละเอียดได้
นอกจากนี้ในReceiveCallback
รหัสเราประมวลผลทุกอย่างที่ได้รับจากซ็อกเก็ตก่อนที่เราจะได้รับคิวต่อไป ซึ่งหมายความว่าสำหรับซ็อกเก็ตเดียวเราจริง ๆ แล้วได้ตลอด ReceiveCallback
เวลาในเวลาใดก็ได้และเราไม่จำเป็นต้องใช้การซิงโครไนส์เธรด อย่างไรก็ตามหากคุณเรียงลำดับสิ่งนี้เพื่อเรียกรับครั้งต่อไปทันทีหลังจากดึงข้อมูลซึ่งอาจเร็วขึ้นเล็กน้อยคุณจะต้องตรวจสอบให้แน่ใจว่าคุณซิงโครไนซ์เธรดอย่างถูกต้อง
นอกจากนี้ฉันแฮ็คโค้ดจำนวนมาก แต่ทิ้งสาระสำคัญของสิ่งที่เกิดขึ้น นี่ควรเป็นการเริ่มต้นที่ดีสำหรับการออกแบบของคุณ แสดงความคิดเห็นหากคุณมีคำถามเพิ่มเติมเกี่ยวกับเรื่องนี้