나는 과거에 이와 비슷한 것을 썼다. 몇 년 전 필자의 연구 결과에 따르면 비동기 소켓을 사용하여 자체 소켓 구현을 작성하는 것이 가장 좋습니다. 즉, 클라이언트가 실제로 아무것도하지 않으면 실제로 적은 리소스가 필요했습니다. 발생하는 모든 것은 .net 스레드 풀에 의해 처리됩니다.
서버의 모든 연결을 관리하는 클래스로 작성했습니다.
나는 단순히 모든 클라이언트 연결을 유지하기 위해 목록을 사용했지만 더 큰 목록을 위해 더 빠른 조회가 필요한 경우 원하는대로 작성할 수 있습니다.
private List<xConnection> _sockets;
또한 실제로 들어오는 연결을 수신 대기하는 소켓이 필요합니다.
private System.Net.Sockets.Socket _serverSocket;
start 메소드는 실제로 서버 소켓을 시작하고 들어오는 연결을 청취하기 시작합니다.
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 (new 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);
}
}
}
}
편집 :이 패턴 에서이 코드 영역에서 언급하는 것을 잊었습니다.
//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가 메시지 처리 코드가 실행되는 동안 지연되지 않습니다.
accept 콜백은 end receive를 호출하여 데이터 소켓 읽기를 완료합니다. 수신 시작 기능에 제공된 버퍼를 채 웁니다. 주석을 남긴 곳에서 원하는 것을 수행 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 메소드는 실제로 동기 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
한 번 에 한 번만 가능하므로 스레드 동기화를 사용할 필요가 없습니다. 그러나 데이터를 가져온 직후에 다음 수신을 호출하도록이 순서를 변경하면 약간 더 빠를 수 있으므로 스레드를 올바르게 동기화해야합니다.
또한 많은 코드를 해킹했지만 발생하는 내용의 본질을 남겼습니다. 디자인을 시작하기에 좋은 출발점이 될 것입니다. 이에 대해 더 궁금한 점이 있으면 의견을 남겨주십시오.