본문 바로가기

네트워크

[네트워크/C#] SocketAsyncEventArgs를 이용한 비동기 TCP 통신

728x90

# SocketAsyncEventArgs 클래스

SocketAsyncEventArgs 클래스는 비동기 소켓 통신을 할 때 

송신과 수신 자체 작업을 담당하는 클래스입니다.

 

Socket 통신을 하기 때문에 Socket이고,

비동기로 작업을 하기 때문에 Async입니다.

작업을 완료하면 실행되는 완료 콜백 함수를 가질 수 있어 Event이고,

Socket 클래스의 비동기 함수에 인수로 들어가기 때문에 Args라고 생각하시면 좋겠습니다.

 

Socket 클래스의 함수 중 뒤에 Async가 붙는 함수들은 모두

public bool ***Async(SocketAsyncEventArgs e);

의 형태로 선언되어 있습니다.

 

그래서 SocketAsyncEventArgs를 활용한 비동기 통신을 할 때의 대략적인 순서는

SocketAsyncEventArgs 객체를 만들어 필요한 설정을 하고

Socket 함수에 인수로 넣어 호출하는 방식으로 진행됩니다.

 

 

 

# SocketAsyncEventArgs 멤버

목차 설명
Completed 이벤트 핸들러. 작업이 완료되면 이 곳에 추가된 함수가 실행됩니다.
SetBuffer(byte[], int, int) 함수. 객체에 담을 데이터의 크기를 설정합니다.
RemoteEndPoint 프러퍼티. IP와 포트번호를 갖는 EndPoint 클래스를 갖습니다.
AcceptSocket 프러퍼티. 현재 작업과 연결된 소켓를 갖습니다. Socket 리턴.
UserToken 프러퍼티. 현재 작업과 연결된 사용자(소켓) 또는 애플리케이션 개체를 갖습니다. object 리턴.
SocketError 프러퍼티. 소켓의 각종 에러 상태를 갖는데, Success도 있어 에러가 없음을 알 수 있습니다.
Buffer 프러퍼티. byte[]. 송수신될 데이터를 갖습니다.
BytesTransferred 프러퍼티. 소켓에서 전송된 바이트 수를 리턴합니다.
offset 프러퍼티. Buffer가 참조할 데이터의 오프셋입니다.

* 이 외 멤버들은 SocketAsyncEventArgs 정의부를 참조바랍니다.

 

 

# SocketAsyncEventArgs TCP Server

- 먼저 서버를 생성하고 비동기로 클라이언트의 요청을 받을 준비를 합니다.

    void CreateServer ()
    {
        string host = Dns.GetHostName();
        IPHostEntry ipHost = Dns.GetHostEntry(host);
        IPAddress ipAddress = ipHost.AddressList[0];
        IPEndPoint endPoint = new IPEndPoint(ipAddress, 80);
        server = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);

        server.Bind(endPoint);
        server.Listen(1);

        // 클라이언트의 승인 요청을 받는 작업을 할 비동기소켓작업 객체를 생성하고,
        // 작업이 끝나면 OnAcceptCompleted()를 호출하도록 이벤트핸들러에 등록합니다.
        SocketAsyncEventArgs eventArgs = new SocketAsyncEventArgs();
        eventArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnAcceptCompleted);

        RegisterAccept(eventArgs);
    }

 

 

 

- 클라이언트의 승인 요청이 들어오면 처리할 수 있도록 준비합니다.

AcceptAsync()는 비동기로 승인하는 함수이기 때문에 넘어온 SocketAsyncEventArgs를 가진 체

승인이 들어올 때까지 대기하다가 승인 요청이 들어오면 승인 처리 한 후

SocketAsyncEventArgs가 가지고 있는 콜백함수인 OnAcceptCompleted 함수를 호출할 것입니다.

void RegisterAccept(SocketAsyncEventArgs args)
{
    args.AcceptSocket = null;

    // 서버는 승인요청을 받는 작업을 할 SocketAsyncEventArgs 객체와 함께
    // 클라이언트의 요청이 들어오면 승인 작업을 하도록 처리합니다.
    bool pending = server.AcceptAsync(args);
    if (pending == false) {
        OnAcceptCompleted(null, args);
    }
}

 

 

 

- 승인 작업이 완료되면 실행되는 완료 콜백 함수입니다.

승인된 클라이언트와 매핑될 서버 측 소켓을 추가합니다.

데이터 수신용 SocketAsyncEventArgs 객체를 생성해 데이터 수신을 위해 보냅니다.

승인 정보를 넘겨준 승인용 SocketAsyncEventArgs 객체는 

새로운 승인을 위해 다시 돌아갑니다.

 

void OnAcceptCompleted( object sender, SocketAsyncEventArgs args )
{
    if( args.SocketError == SocketError.Success )
    {
        clients.Add(args.AcceptSocket);

        // 데이터 수신용 SocketAsyncEventArgs 객체
        SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
        recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
        recvArgs.SetBuffer(new byte[1024], 0, 1024);
        recvArgs.UserToken = args.AcceptSocket;
  
        // 데이터 수신을 기다립니다.
        // 데이터 수신용 SocketAsyncEventArgs 객체가 인수로 넘어갑니다.
        RegisterRecv(recvArgs);
    }
    else
    {
        print(args.SocketError.ToString());
    }
    
    // 새로운 승인을 기다립니다.
    // 승인작업을 가지고 온 SocketAsyncEventArgs 객체가 작업을 마치고
    // 다시 승인작업을 하러 넘어갑니다.
    RegisterAccept(args);
}

 

 

- 데이터가 수신되면 처리할 수 있도록 준비합니다.

데이터 수신을 위해 생성된 소켓 client가

넘어온 데이터 수신용 SocketAsyncEventArgs 객체를 인수로 가진체

비동기로 데이터 수신을 준비합니다.

void RegisterRecv(SocketAsyncEventArgs args )
{
    Socket client = args.UserToken as Socket;
    bool pending = client.ReceiveAsync(args);
    if ( pending == false )
    {
        OnRecvCompleted(null, args);
    }
}

 

 

 

- 데이터 수신이 완료되면 호출되는 완료 콜백 함수입니다.

데이터를 받으면 다시 모든 클라이언트에게 데이터를 전송해준 뒤,

다시 데이터를 수신하기 위해 준비합니다.

void OnRecvCompleted( object sender, SocketAsyncEventArgs args )
{
    if( args.BytesTransferred > 0 && args.SocketError == SocketError.Success )
    {
        string recvData = System.Text.Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
        byte[] sendArray =  System.Text.Encoding.UTF8.GetBytes(recvData);

        sendQueue.Enqueue(sendArray);
        
        // 받은 데이터를 연결된 모든 클라이언트에게 전송하는 함수입니다.
        RegisterSend();
    }
    
    // 새로운 데이터 수신을 위해 준비합니다.
    RegisterRecv(args);
}

 

 

 

- 데이터 송신을 준비합니다.

사실 이 서버는 채팅 서버이기 때문에

한 클라이언트에서 채팅 데이터가 들어오면

그대로 모든 클라이언트에게 다시 전송해주면 됩니다.

 

void RegisterSend()
{
    byte[] buff = sendQueue.Dequeue();
    
    // 연결된 모든 클라이언트들에게 데이터를 보냅니다.
    // 역시 데이터 송신용 SocketAsyncEventArgs 객체를 만들어 담은 후
    // 소켓 클래스의 SendAsync() 함수에 인수로 담아 전달합니다.
    for( int i = 0; i< clients.Count; ++ i )
    {
        SocketAsyncEventArgs sendArgs = new SocketAsyncEventArgs();
        sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);
        sendArgs.SetBuffer(buff, 0, buff.Length);
		
        
        bool pending = clients[i].SendAsync(sendArgs);

        if (pending == false)
        {
            OnSendCompleted(null, sendArgs);
        }
    }
}

 

 

 

- 데이터 송신이 완료된 후 호출되는 완료 콜백 함수입니다.

에러가 있거나, 전송할 데이터가 남았을 경우의 예외처리 등을 하게됩니다.

이 부분은 다음에 다루겠습니다.

void OnSendCompleted( object sender, SocketAsyncEventArgs args ){
    if( args.BytesTransferred > 0 && args.SocketError == SocketError.Success )    {
        if( sendQueue.Count > 0 ){
        }
    }
}

 

 

 

 

- 소켓 닫기

 

사용이 끝난 클라이언트 소켓과 서버 소켓을 모두 닫습니다.

private void OnApplicationQuit()
{
    for( int i = 0; i < clients.Count; ++ i )
    {
        clients[i].Shutdown(SocketShutdown.Both);
        clients[i].Close();
    }
    if (server != null)
        server.Close();
}

 

 

# SocketAsyncEventArgs TCP Client

- 연결 요청용 SocketAsyncEventArgs 객체에 서버의 식별 정보를 담아 연결 요청을 보냅니다.

public void Connect( int port )
{
    string host = Dns.GetHostName();
    IPHostEntry ipHost = Dns.GetHostEntry(host);
    IPAddress ipAddress = ipHost.AddressList[0];
    IPEndPoint endPoint = new IPEndPoint(ipAddress, 80);

    client = new Socket(endPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp );
	
    // 연결 요청용 SocketAsyncEventArgs 객체입니다.
    // 완료 콜백 함수로 OnConnectCompleted()함수를 추가하고
    // 서버의 식별정보를 가진 IPEndPoint로 넣어줍니다.
    SocketAsyncEventArgs args = new SocketAsyncEventArgs();
    args.Completed += OnConnectCompleted;
    args.RemoteEndPoint = endPoint;
        
    // 연결 요청용 SocketAsyncEventArgs 객체를 인수로 보내 비동기로 연결 요청을 합니다.
    client.ConnectAsync(args);
    isConnect = true;
    }

 

 

 

- 연결이 완료되면 호출되는 완료 콜백 함수입니다.

연결이 완료되면

데이터 송신용 SocketAsyncEventArgs 객체와

데이터 수신용 SocketAsyncEventArgs 객체를 준비합니다.

데이터 수신용 SocketAsyncEventArgs 객체는 데이터 수신 준비를 하러 갑니다.

public void OnConnectCompleted( object obj, SocketAsyncEventArgs args )
{
    if (args.SocketError == SocketError.Success)
    {

        // 데이터 송신용 SocketAsyncEventArgs 객체
        sendArgs = new SocketAsyncEventArgs();
        sendArgs.Completed += OnSendCompleted;

        // 데이터 수신용 SocketAsyncEventArgs 객체
        SocketAsyncEventArgs receiveArgs = new SocketAsyncEventArgs();
        receiveArgs.SetBuffer(new byte[1024], 0, 1024);
        receiveArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);

        // 데이터 수신 준비를 합니다.
        bool pending = client.ReceiveAsync(receiveArgs);
        if (pending == false)
            OnRecvCompleted(null, receiveArgs);
    }
}

 

 

 

- 데이터 수신 작업이 완료되면 실행되는 콜백 함수입니다.

채팅 프로그램이기 때문에 채팅 UI에 text를 뿌려주면 됩니다.

public void OnRecvCompleted(object obj, SocketAsyncEventArgs args )
{
    if( args.BytesTransferred > 0 && args.SocketError == SocketError.Success )
    {
        string recvData = System.Text.Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
        
        // .. 받은 데이터를 채팅 UI에 표현합니다 ..

        // 새로운 데이터 수신을 준비합니다.
        bool pending = client.ReceiveAsync(args);
        if (pending == false)
            OnRecvCompleted(null, args);
    }
}

 

 

 

- 데이터 송신 함수

채팅 프로그램이기 때문에 데이터 송신용 SocketAsyncEventArgs 객체의 버퍼에 데이터를 담고

소켓의 SendAsync 함수 인수로 보내면 비동기로 송신됩니다.

public void SendMsg( string message )
{
    if( message.Equals(string.Empty) == false )
    {
       byte[] buffer = System.Text.Encoding.UTF8.GetBytes(message);
        sendArgs.SetBuffer(buffer, 0, buffer.Length);
        client.SendAsync(sendArgs);
    }
}

 

 

- 데이터 송신 콜백 함수

데이터 송신용 SocketAsyncEventArgs 객체 Buffer를 비워줬습니다.

public void OnSendCompleted(object obj, SocketAsyncEventArgs args)
{
    if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
    {
        sendArgs.BufferList = null;
    }
}
728x90