[상단] C#고성능 대용량 SOCKET 동시 실행(둘): SocketAsyncEventArgs 패키지
SocketAsyncEventArgs는 마이크로소프트가 제공하는 고성능 비동기 Socket 실현 클래스로 주로 고성능 네트워크 서버 응용 프로그램을 위해 설계된 것으로 비동기 플러그인 입출력량이 매우 많을 때 중복되는 대상 분배와 동기화를 피하기 위한 것이다.비동기 소켓 작업을 수행하는 이러한 모드에는 다음 단계가 포함됩니다. 1.새 SocketAsyncEventArgs 컨텍스트 객체를 할당하거나 응용 프로그램 풀에서 빈 객체를 가져옵니다.2. 이 상하문 대상의 속성을 실행할 작업으로 설정한다(예를 들어 리셋 방법, 데이터 버퍼, 버퍼 편이량 및 전송할 최대 데이터량).3. 비동기 동작을 시작하려면 적절한 소켓 방법(xxxAsync)을 호출합니다.4. 비동기 소켓 방법(xxxAsync)이 true로 되돌아오면 리셋에서 상하문 속성을 조회하여 완성 상태를 가져옵니다.5. 비동기 소켓 방법(xxxAsync)이false를 반환하면 동기화가 완료된 것입니다.작업 결과를 얻기 위해 상하문 속성을 조회할 수 있습니다.6. 이 상하문을 다른 작업에 다시 사용하고 응용 프로그램 풀에 다시 넣거나 버려라.
2, SocketAsyncEventArgs 패키지
SocketAsyncEventArgs를 사용하기 전에 다음과 같은 코드를 사용하여 Socket 스니퍼 객체를 만들어야 합니다.
public void Start(IPEndPoint localEndPoint)
{
listenSocket = new Socket(localEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
listenSocket.Bind(localEndPoint);
listenSocket.Listen(m_numConnections);
Program.Logger.InfoFormat("Start listen socket {0} success", localEndPoint.ToString());
//for (int i = 0; i < 64; i++) // AcceptAsync, 8000
StartAccept(null);
m_daemonThread = new DaemonThread(this);
}
그리고 연결을 받기 시작합니다. SocketAsyncEventArgs가 연결될 때Completed 이벤트를 통해 외부에 알릴 수 있기 때문에 연결을 받는 코드는 다음과 같습니다. public void StartAccept(SocketAsyncEventArgs acceptEventArgs)
{
if (acceptEventArgs == null)
{
acceptEventArgs = new SocketAsyncEventArgs();
acceptEventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(AcceptEventArg_Completed);
}
else
{
acceptEventArgs.AcceptSocket = null; // Socket, Socket
}
m_maxNumberAcceptedClients.WaitOne(); //
bool willRaiseEvent = listenSocket.AcceptAsync(acceptEventArgs);
if (!willRaiseEvent)
{
ProcessAccept(acceptEventArgs);
}
}
접속 응답 이벤트 코드를 수락합니다. void AcceptEventArg_Completed(object sender, SocketAsyncEventArgs acceptEventArgs)
{
try
{
ProcessAccept(acceptEventArgs);
}
catch (Exception E)
{
Program.Logger.ErrorFormat("Accept client {0} error, message: {1}", acceptEventArgs.AcceptSocket, E.Message);
Program.Logger.Error(E.StackTrace);
}
}
private void ProcessAccept(SocketAsyncEventArgs acceptEventArgs)
{
Program.Logger.InfoFormat("Client connection accepted. Local Address: {0}, Remote Address: {1}",
acceptEventArgs.AcceptSocket.LocalEndPoint, acceptEventArgs.AcceptSocket.RemoteEndPoint);
AsyncSocketUserToken userToken = m_asyncSocketUserTokenPool.Pop();
m_asyncSocketUserTokenList.Add(userToken); //
userToken.ConnectSocket = acceptEventArgs.AcceptSocket;
userToken.ConnectDateTime = DateTime.Now;
try
{
bool willRaiseEvent = userToken.ConnectSocket.ReceiveAsync(userToken.ReceiveEventArgs); //
if (!willRaiseEvent)
{
lock (userToken)
{
ProcessReceive(userToken.ReceiveEventArgs);
}
}
}
catch (Exception E)
{
Program.Logger.ErrorFormat("Accept client {0} error, message: {1}", userToken.ConnectSocket, E.Message);
Program.Logger.Error(E.StackTrace);
}
StartAccept(acceptEventArgs); // ,
}
연결을 받은 후 현재 Socket 버퍼 탱크인 AsyncSocketUserTokenPool에서 사용자 대상인 AsyncSocketUserToken을 획득하고 AsyncSocketUserToken은 비동기적인 이벤트를 수신하는 m 를 포함한다.receive EventArgs, 비동기 이벤트 보내기 msendEventArgs, 수신 데이터 버퍼 mreceiveBuffer, 전송 데이터 버퍼 msendBuffer, 프로토콜 논리 호출 객체 masyncSocketInvokeElement, 서비스 대상을 구축한 후 수신과 발송의 이벤트 응답 함수를 실현해야 합니다. void IO_Completed(object sender, SocketAsyncEventArgs asyncEventArgs)
{
AsyncSocketUserToken userToken = asyncEventArgs.UserToken as AsyncSocketUserToken;
userToken.ActiveDateTime = DateTime.Now;
try
{
lock (userToken)
{
if (asyncEventArgs.LastOperation == SocketAsyncOperation.Receive)
ProcessReceive(asyncEventArgs);
else if (asyncEventArgs.LastOperation == SocketAsyncOperation.Send)
ProcessSend(asyncEventArgs);
else
throw new ArgumentException("The last operation completed on the socket was not a receive or send");
}
}
catch (Exception E)
{
Program.Logger.ErrorFormat("IO_Completed {0} error, message: {1}", userToken.ConnectSocket, E.Message);
Program.Logger.Error(E.StackTrace);
}
}
Completed 이벤트에서 전송과 수신의 구체적인 논리 코드를 처리해야 하는데 그 중에서 수신의 논리는 다음과 같다. private void ProcessReceive(SocketAsyncEventArgs receiveEventArgs)
{
AsyncSocketUserToken userToken = receiveEventArgs.UserToken as AsyncSocketUserToken;
if (userToken.ConnectSocket == null)
return;
userToken.ActiveDateTime = DateTime.Now;
if (userToken.ReceiveEventArgs.BytesTransferred > 0 && userToken.ReceiveEventArgs.SocketError == SocketError.Success)
{
int offset = userToken.ReceiveEventArgs.Offset;
int count = userToken.ReceiveEventArgs.BytesTransferred;
if ((userToken.AsyncSocketInvokeElement == null) & (userToken.ConnectSocket != null)) // Socket , ,
{
BuildingSocketInvokeElement(userToken);
offset = offset + 1;
count = count - 1;
}
if (userToken.AsyncSocketInvokeElement == null) // ,
{
Program.Logger.WarnFormat("Illegal client connection. Local Address: {0}, Remote Address: {1}", userToken.ConnectSocket.LocalEndPoint,
userToken.ConnectSocket.RemoteEndPoint);
CloseClientSocket(userToken);
}
else
{
if (count > 0) //
{
if (!userToken.AsyncSocketInvokeElement.ProcessReceive(userToken.ReceiveEventArgs.Buffer, offset, count))
{ // ,
CloseClientSocket(userToken);
}
else //
{
bool willRaiseEvent = userToken.ConnectSocket.ReceiveAsync(userToken.ReceiveEventArgs); //
if (!willRaiseEvent)
ProcessReceive(userToken.ReceiveEventArgs);
}
}
else
{
bool willRaiseEvent = userToken.ConnectSocket.ReceiveAsync(userToken.ReceiveEventArgs); //
if (!willRaiseEvent)
ProcessReceive(userToken.ReceiveEventArgs);
}
}
}
else
{
CloseClientSocket(userToken);
}
}
우리가 제정한 협의의 첫 번째 바이트는 협의 표지이기 때문에 첫 번째 바이트를 받을 때 협의 해석 대상을 귀속시켜야 한다. 구체적인 코드는 다음과 같다. private void BuildingSocketInvokeElement(AsyncSocketUserToken userToken)
{
byte flag = userToken.ReceiveEventArgs.Buffer[userToken.ReceiveEventArgs.Offset];
if (flag == (byte)SocketFlag.Upload)
userToken.AsyncSocketInvokeElement = new UploadSocketProtocol(this, userToken);
else if (flag == (byte)SocketFlag.Download)
userToken.AsyncSocketInvokeElement = new DownloadSocketProtocol(this, userToken);
else if (flag == (byte)SocketFlag.RemoteStream)
userToken.AsyncSocketInvokeElement = new RemoteStreamSocketProtocol(this, userToken);
else if (flag == (byte)SocketFlag.Throughput)
userToken.AsyncSocketInvokeElement = new ThroughputSocketProtocol(this, userToken);
else if (flag == (byte)SocketFlag.Control)
userToken.AsyncSocketInvokeElement = new ControlSocketProtocol(this, userToken);
else if (flag == (byte)SocketFlag.LogOutput)
userToken.AsyncSocketInvokeElement = new LogOutputSocketProtocol(this, userToken);
if (userToken.AsyncSocketInvokeElement != null)
{
Program.Logger.InfoFormat("Building socket invoke element {0}.Local Address: {1}, Remote Address: {2}",
userToken.AsyncSocketInvokeElement, userToken.ConnectSocket.LocalEndPoint, userToken.ConnectSocket.RemoteEndPoint);
}
}
발송 응답 함수 실현은 주의해야 한다. 우리는 발송 데이터를 목록에 넣고 이전 발송 이벤트가 응답Completed 이벤트를 완성하면 발송 대기열에 발송하지 않은 데이터가 있는지 확인하고 존재하면 계속 발송해야 한다. private bool ProcessSend(SocketAsyncEventArgs sendEventArgs)
{
AsyncSocketUserToken userToken = sendEventArgs.UserToken as AsyncSocketUserToken;
if (userToken.AsyncSocketInvokeElement == null)
return false;
userToken.ActiveDateTime = DateTime.Now;
if (sendEventArgs.SocketError == SocketError.Success)
return userToken.AsyncSocketInvokeElement.SendCompleted(); //
else
{
CloseClientSocket(userToken);
return false;
}
}
SendCompleted는 다음에 보내야 할 데이터를 리셋하는데 구체적인 실현 과정은 다음과 같다. public virtual bool SendCompleted()
{
m_activeDT = DateTime.UtcNow;
m_sendAsync = false;
AsyncSendBufferManager asyncSendBufferManager = m_asyncSocketUserToken.SendBuffer;
asyncSendBufferManager.ClearFirstPacket(); //
int offset = 0;
int count = 0;
if (asyncSendBufferManager.GetFirstPacket(ref offset, ref count))
{
m_sendAsync = true;
return m_asyncSocketServer.SendAsyncEvent(m_asyncSocketUserToken.ConnectSocket, m_asyncSocketUserToken.SendEventArgs,
asyncSendBufferManager.DynamicBufferManager.Buffer, offset, count);
}
else
return SendCallback();
}
// ,
public virtual bool SendCallback()
{
return true;
}
SocketAsyncEventArgs가 끊긴 후에 우리는 대응하는 Socket 연결을 끊고 대응하는 자원을 방출해야 한다. 구체적인 실현 함수는 다음과 같다. public void CloseClientSocket(AsyncSocketUserToken userToken)
{
if (userToken.ConnectSocket == null)
return;
string socketInfo = string.Format("Local Address: {0} Remote Address: {1}", userToken.ConnectSocket.LocalEndPoint,
userToken.ConnectSocket.RemoteEndPoint);
Program.Logger.InfoFormat("Client connection disconnected. {0}", socketInfo);
try
{
userToken.ConnectSocket.Shutdown(SocketShutdown.Both);
}
catch (Exception E)
{
Program.Logger.ErrorFormat("CloseClientSocket Disconnect client {0} error, message: {1}", socketInfo, E.Message);
}
userToken.ConnectSocket.Close();
userToken.ConnectSocket = null; // , ,
m_maxNumberAcceptedClients.Release();
m_asyncSocketUserTokenPool.Push(userToken);
m_asyncSocketUserTokenList.Remove(userToken);
}
3. SocketAsyncEventArgs 패키지와 MSDN의 차이점
MSDNhttp://msdn.microsoft.com/zh-cn/library/system.net.sockets.socketasynceventargs(v=vs.110).ppx는 예시 코드를 실현했고 초보적인 연못화 처리를 실현했다. 우리는 이를 바탕으로 수신 데이터 버퍼링, 발송 데이터 대기열을 확장했다. 그리고 SocketAsync Event Args와 수신 SocketAsync Event Args를 분리하고 프로토콜 해석 단원을 실현했다. 이런 장점은 후속 논리로 파일의 업로드, 다운로드와 로그 출력을 실현하는 데 편리하다는 것이다.
DEMO 다운로드 주소:http://download.csdn.net/detail/sqldebug_fan/7467745면책 성명: 이 코드는 C#의 포트 프로그래밍 완료를 보여주기 위한 것일 뿐 학습 및 연구용으로만 사용되며 상업용으로는 사용되지 않습니다.수준이 제한되어 있고 C#도 초학에 속하기 때문에 실수가 불가피합니다. 시정과 지도를 환영합니다.메일주소:[email protected].
이 내용에 흥미가 있습니까?
현재 기사가 여러분의 문제를 해결하지 못하는 경우 AI 엔진은 머신러닝 분석(스마트 모델이 방금 만들어져 부정확한 경우가 있을 수 있음)을 통해 가장 유사한 기사를 추천합니다:
Java 다중 스레드를 순차적으로 실행하는 몇 가지 방법 요약Java 다중 스레드를 순차적으로 실행하는 몇 가지 방법 요약 동료는 무심결에 이 문제를 제기하고 두 가지 방법을 직접 실천했다.물론 더 좋은 방법이 있을 거야. 방법 1 이런 방법은 비교적 흔히 볼 수 있는 해결 ...
텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
CC BY-SA 2.5, CC BY-SA 3.0 및 CC BY-SA 4.0에 따라 라이센스가 부여됩니다.