[C#] 비동기 소켓 접속

개발 노트/C#  2013.07.28 18:10


안녕하세요. 명월입니다.

최근 개인 일이 너무 많아서 블로그 포스팅을 전혀.. 못하고 있네요.. ㅠㅜ..
그래서 질문 메일 보내주시면 주말에 틈틈히 짬을 내서 답변해주는 정도밖에 못하네요..

이번 포스팅에서는 비동기 소켓 통신입니다... 이 부분에서는 구글링을 해도 내용이 별로 없고. MSDN도 정확한 예제(?)가 없어서 제가 정리를 해 보았습니다.

보통 우리가 프로그램에서 사용하는 소켓통신 방식은 동기 방식이겠습니다. 말 그대로 프로그램과 같은 프로세스 안에서 돌려서 처리하는 방식입니다. 동기 방식은 계산이 비교적 간단하고 빠른 개발이 가능하나. 조금 설계가 복잡해지면 다루기가 많이 까다로워 집니다. 그럼 결국엔 쓰레드를 통한 비동기 방식( 다른 프로세스 안의 통신)을 이용하는데 이것을 굳히 쓰레드 선언해서 구현 할 필요가 없고 .Net Api에서 제공받으면 되겠습니다.

그럼 먼저 서버 단에서의 비동기 소켓 소스 예제 입니다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;

namespace AsyncSocketServer
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private Socket m_ServerSocket;
        private List m_ClientSocket;
        private byte[] szData;
        private void Form1_Load(object sender, EventArgs e)
        {
            m_ClientSocket = new List();

            m_ServerSocket = new Socket(
                                AddressFamily.InterNetwork, 
                                SocketType.Stream, 
                                ProtocolType.Tcp);
            IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 10000);
            m_ServerSocket.Bind(ipep);  
            m_ServerSocket.Listen(20);  

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.Completed 
                += new EventHandler(Accept_Completed);
            m_ServerSocket.AcceptAsync(args);
        }

        private void Accept_Completed(object sender, SocketAsyncEventArgs e)
        {
            Socket ClientSocket = e.AcceptSocket;
            m_ClientSocket.Add(ClientSocket);

            if (m_ClientSocket != null)
            {
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                szData = new byte[1024]; 
                args.SetBuffer(szData, 0, 1024);
                args.UserToken = m_ClientSocket;
                args.Completed 
                    += new EventHandler(Receive_Completed);
                ClientSocket.ReceiveAsync(args);
            }
            e.AcceptSocket = null;
            m_ServerSocket.AcceptAsync(e);
        }
        private void Receive_Completed(object sender, SocketAsyncEventArgs e)
        {
            Socket ClientSocket = (Socket)sender;
            if (ClientSocket.Connected && e.BytesTransferred > 0)
            {
                byte[] szData = e.Buffer;    // 데이터 수신
                string sData = Encoding.Unicode.GetString(szData);

                string Test = sData.Replace("\0", "").Trim();
                SetText(Test);
                for (int i = 0; i < szData.Length; i++)
                {
                    szData[i] = 0;
                }
                e.SetBuffer(szData, 0, 1024);
                ClientSocket.ReceiveAsync(e);
            }
            else
            {
                ClientSocket.Disconnect(false);
                ClientSocket.Dispose();
                m_ClientSocket.Remove(ClientSocket);
            }
        }
        private delegate void SetTextCallback(string text);
        private void SetText(string text)
        {
            if (richTextBox1.InvokeRequired)
            {
                SetTextCallback d = new SetTextCallback(SetText);
                this.Invoke(d, new object[] { text });
            }
            else
            {
                if (richTextBox1.TextLength > 0)
                {
                    richTextBox1.AppendText("\n");
                }
                richTextBox1.AppendText(text);
                richTextBox1.ScrollToCaret();
            }
        }
        protected override void Dispose(bool disposing)
        {
            foreach (Socket pBuffer in m_ClientSocket)
            {
                if(pBuffer.Connected)
                    pBuffer.Disconnect(false);
                pBuffer.Dispose();
            }
            m_ServerSocket.Dispose();
            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}


서버 소스입니다. 



다음은 클라이언트 입니다.

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Net;
using System.Net.Sockets;

namespace AsyncClient
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private Socket m_ClientSocket;

        private void Form1_Load(object sender, EventArgs e)
        {
            m_ClientSocket = new Socket(
                                     AddressFamily.InterNetwork,
                                     SocketType.Stream,
                                     ProtocolType.Tcp);
            IPEndPoint ipep = 
         new IPEndPoint(IPAddress.Parse("127.0.0.1"), 10000); 

            SocketAsyncEventArgs args = new SocketAsyncEventArgs();
            args.RemoteEndPoint = ipep;

            m_ClientSocket.ConnectAsync(args);
        }
        private void button1_Click(object sender, EventArgs e)
        {
            if(textBox1.Text.Length > 0)
            {
                SocketAsyncEventArgs args = new SocketAsyncEventArgs();
                byte[] szData = Encoding.Unicode.GetBytes(textBox1.Text);
                args.SetBuffer(szData, 0, szData.Length);
                m_ClientSocket.SendAsync(args);
                textBox1.Text = "";
                textBox1.Focus();
            }
        }

        private void textBox1_KeyDown(object sender, KeyEventArgs e)
        {
            if (e.KeyData == Keys.Enter)
            {
                button1_Click(null, null);
            }
        }
        protected override void Dispose(bool disposing)
        {
            if(m_ClientSocket.Connected)
                m_ClientSocket.Disconnect(false);
            m_ClientSocket.Dispose();

            if (disposing && (components != null))
            {
                components.Dispose();
            }
            base.Dispose(disposing);
        }
    }
}





테스트로 3개를 띄워서 통신 해 보았습니다.. 다중 접속도 잘 됩니다.



예제 소스입니다.


AsyncClient.zip


AsyncSocketServer.zip





댓글 36개가 달렸습니다.
댓글쓰기
  1. 딸개이
    2013.11.16 20:28 신고 |  수정/삭제  댓글쓰기

    자료 받아갑니다. 수고하세요.

  2. 엄허이건써야돼
    2013.11.27 20:07 신고 |  수정/삭제  댓글쓰기

    항상 좋은 자료 잘 보고 갑니다.

  3. Tltiqchqh
    2014.01.06 19:53 신고 |  수정/삭제  댓글쓰기

    감사합니다. 하지만 매우 어렵네요 ㅠ.ㅠ
    좀더 열심히 공부해야 겠네요 저는..

  4. 와우
    2014.01.09 21:09 신고 |  수정/삭제  댓글쓰기

    좋은 자료 감사합니다. ^^

  5. kkk
    2014.01.24 10:43 신고 |  수정/삭제  댓글쓰기

    좋은 자료 감사합니다^^ 보면서 많이 공부하겠습니다.

  6. kimkj
    2014.04.01 17:51 신고 |  수정/삭제  댓글쓰기

    좋은자료 감사합니다.
    그런데, 서버프로그램 실행시키면 <각 소켓주소(프로토콜/네트워크 주소/포트)는 하나만 사용할 수 있습니다> 라는 경고가 나오고, 실제 실핼 후에 쿨라이언트에서 메시지를 보내도 서버에는 아무것도 보이지 않는데, 이것 왜 그런가요?

    • 明月 v명월v
      2015.01.12 11:29 신고 |  수정/삭제

      블로그 방문 감사합니다..
      다른 프로그램에서 같은 포트를 사용하고 있으면 그런 경고가 나옵니다.

  7. 김영훈
    2014.05.13 11:33 신고 |  수정/삭제  댓글쓰기

    이 프로그램에 서버에서 클라이언트로는 메시지를 보낼 수 없나요??

  8. 모가리
    2014.07.03 20:40 신고 |  수정/삭제  댓글쓰기

    안녕하세요 질문이 있는데요..

    메시지를 특정 소켓에서 받고 db처리 후에 응답 메시지를 보내야하는데요..

    클라이언트에서 10개의 쓰레드로 메시지를 날리고 서버는 10개중 만약에 1개를 실패했을시, 해당 쓰레드에 걸려있는 소켓만 실패매시지 날리고 나머진 성공 메시지를 날려야하는데..

    혹시 도움좀 받을수 있을까요?

    현재 db처리는 이 소스에 물려서 처리가 잘되는데 응답전문을 실패난 소켓에게만 전송하는 방법이 있을가요?ㅠㅠ

  9. 개발자
    2014.10.14 09:05 신고 |  수정/삭제  댓글쓰기

    c# 아무것도 모르는 상태에서 님 코드 보고 짰는데 여러모로 문제가 좀 있네요 ..
    그냥 ms문서에서 추천해주는 코드 보고 짰더니 잘되네요 ..

  10. 루나
    2015.01.09 16:56 신고 |  수정/삭제  댓글쓰기

    코드 한줄한줄 이해하며 잘 배웠습니다 ^-^ 감사합니다

  11. 明月 v명월v
    2015.01.12 18:27 신고 |  수정/삭제  댓글쓰기

    블로그방문 감사합니다 혹시 모르는 게있으면 같이 고민해봐요

  12. SeleneLuna
    2015.01.13 20:55 신고 |  수정/삭제  댓글쓰기

    질문하나드려도될까요 ㅠㅠ Receive부분에 (Socket)sender;는 sender에 뭐가들어오길래 저렇게해주는건가요?

    • 明月 v명월v
      2015.01.13 22:48 신고 |  수정/삭제

      안녕하세요 블로그 방문 감사합니다.
      C#은 보통 이벤트 object sender에 자기 자신(클래스 주소)가 옵니다.
      자세한 설명은 포스팅을 해 놓았으니 참고해 주시기 바랍니다.
      http://nowonbun.tistory.com/265

  13. 질문자
    2015.01.28 18:02 신고 |  수정/삭제  댓글쓰기

    안녕하세요 명월님 저는 닷넷2.0쓰고 있는데 Dispose부분이 보호수준때문에 system.net.sockets.socket.dispose(bool)에 액세스 할 수 없습니다. 에러가 나네요..
    닷넷 4.0client 버전으로 변경하면 이상 없는데... 2.0 에서 사용할 수 있는 방법 없을까요?

    • 明月 v명월v
      2015.01.30 16:51 신고 |  수정/삭제

      안녕하세요 명월입니다.
      Dispose 를 상속받지 말고...
      종료시점을 따로 정해 불러서 커넥션 종료 및 해지를 하세요..

  14. 박영수
    2015.02.26 14:53 신고 |  수정/삭제  댓글쓰기

    정말 감사합니다~!!!!!!!!!
    덕분에 문제 해결할수 있을거 같습니다!!!

    • 明月 v명월v
      2015.04.20 00:02 신고 |  수정/삭제

      안녕하세요. 블로그 방문 감사합니다...
      무슨 문제인지는 모르겠지만 해결되셨다니 저도 기쁘네요..ㅎㅎ

  15. a만듀
    2015.04.26 03:05 신고 |  수정/삭제  댓글쓰기

    명월님 감사합니다! 소켓프로그램에 대해서 공부하는 학생입니다! 열심히 보고 공부하겠습니다. 감사합니다!

  16. 초보개발자
    2015.04.30 01:04 신고 |  수정/삭제  댓글쓰기

    쌩초보인데 소스가 제가 만들려는 프로그램에 많은 도움이 되고 있습니다.
    감사합니다.

    그런데 한가지 궁금한것이 있습니다. 다중클라이언트가 접속이 되는데
    서버측에서 특정 클라이언트를 선택해서 메세지를 보내려면 어떻게 해야될까요?
    몇일을 고민하고 있는데 잘 풀리지 않네요.
    List<Socket>에 누적되는 소켓을 어떻게 불러와서 실제 소켓과 매칭시켜야 할지 감이 잡히질 않습니다.
    혹시 도움 주실 수 있다면 너무 감사하겠습니다.

    cgtoolz@naver.com

    • 明月 v명월v
      2015.05.09 22:39 신고 |  수정/삭제

      블로그 방문에 감사합니다.
      질문하신거 내용이 어떤 것인지 정확히는 파악하기는 힘들지만...
      특정 클라이언트를 구분하기 위해선 다른 클라인어트들과 구분이 되는 것(?)을 먼저 지정하시고..그 구분자로 검색을 해서 List에서 취득해서 send를 하면 될 것 같습니다만.. 그래도 혹시 이해가 안되시면 간단한 예제 만들어서 보내드리겠습니다.

  17. junddao
    2015.05.13 16:54 신고 |  수정/삭제  댓글쓰기

    위 초보개발자님과 동일한 문제로 고민중입니다.
    접속된 여러 클라이언트중 특정 클라이언트에게 메세지를 전달하고 싶은데..
    쉽지 않네요.
    도움 주실수 있으시면 너무 감사하겠습니다.

    • 明月 v명월v
      2015.05.14 22:50 신고 |  수정/삭제

      안녕하세요..먼저 블로그 방문 감사합니다.
      관련 예제를 작성해 놓았으니 확인 부탁드립니다.
      http://nowonbun.tistory.com/296


  18. 2015.07.15 14:13 |  수정/삭제  댓글쓰기

    비밀댓글입니다

    • 明月 v명월v
      2015.07.15 20:18 신고 |  수정/삭제

      안녕하세요. 먼저 블로그 방문 감사합니다.
      1.비동기 통신이라고 하면 기본적으로 본체의 큐에서 벗어나 독립된 쓰레드로 동작을 하는 형태를 비동기라고 합니다.
      어디에 정해진 패턴 형식이 아닙니다. 예전에 작성한것이라 자세히 기억은 안나지만.
      확실한건 이벤트를 이용한 비동기형식의 처리였던것은 확실했습니다.
      어떤 글을 보셨는지는 모르겠지만.. 프로그램은 어디 딱 정해진 답은 없습니다...
      제 글이든 다른 분의 글이든 본인이 좀 더 쉽게 이해되는 것을 채택해서 사용하시면 됩니다

      2. 비슷한 질문이 많아서 따로 포스팅을 했었습니다. 참고하세요.
      http://nowonbun.tistory.com/296