Microsoft MVP성태의 닷넷 이야기
비동기 소켓사용후 해당 스레드가 남아있을때 처리 방법 [링크 복사], [링크+제목 복사]
조회: 10221
글쓴 사람
유영태 (ytreyt at naver.com)
홈페이지
첨부 파일

서버는 블럭없이 대기하는 부분,블럭없이 자료수신하는 부분으로 구성하고 있습니다.
제 구상.
1. loopAccept메서드가 i/o 블럭없이 처리됨.
2. client 접속이 들어오면 TaskCompletionSource의 onComplete 메서드가 동작됩니다.
   여기서 접속된 클라이언트를 저장하고, 메시지를 받는 LoopReceive 메서드를 실행합니다.
3. 역시나 LoopReceive메서드 블럭없이 동작하기에 바로 다음 loopAccept 메서드가 반복됩니다.

4. LoopReceive는 클라이언트에서 자료를 보냈을때 완료되고 다음 loopReceive를 반복합니다.

문제점
.1 클라이언트를 5~6개 이상실행하였다가 종료하면 ,메인스레드이외에 작업스레드가 여러개 생기는데 어디서 잘못된건지 모르겠습니다.
.2 loopReceive 메서드에서 beginReceive로 기다리는 동작을 실행하는데. 클라이언트에서 소켓을 종료하게 되면 해당 메서드는 사라지지 않는건지 궁금합니다.

      public async Task<bool> Start()
        {
            if (!CheckSocketStart())
            {
                return false;
            }
            LocalSocket = GetTcpSocket();
            LocalSocket.Bind(LocalSocketIpEndPoint);
            LocalSocket.Listen(5);

            ActSysMessage?.Invoke("서버시작");
            LoopAccept();
            return true;
        }

        /// <summary>
        /// 클라이언트의 접속을 기다리고 접속이 처리되면 반복적으로 처리
        /// </summary>
        /// <returns></returns>
        private void LoopAccept()
        {
            var awaiter = AcceptTaskCompletionSource(LocalSocket).GetAwaiter();
            awaiter.OnCompleted(() => {
                var client = awaiter.GetResult();
                _sockets.Add(client);
                LoopReceive(client);
                LoopAccept();
            });
        }


        /// <summary>
        /// Socket의 beginAccept가 완료되면 endAccept로 완료처리를 하고 client를
        /// TaskCompletionSource를 통해서 전달함 block을 사용하지 않는게 목표.
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public Task<Socket> AcceptTaskCompletionSource (Socket socket)
        {
            var tcs = new TaskCompletionSource<Socket>(socket);
            socket.BeginAccept(a => {
                try
                {
                    var retsocket= a.AsyncState as Socket;
                    var client = retsocket.EndAccept(a);
                    tcs.SetResult(client);
                }
                catch (Exception e)
                {
                    tcs.SetException(e);
                }

            }, socket);
            return tcs.Task;
        }
        ///반복적으로 데이터를 받는 부분.
        private void LoopReceive( Socket socket)
        {
            if (socket == null || !socket.Connected)
            {
                return;
            }
            var awaiter = ReceiveTaskCompletionSource(socket).GetAwaiter();
            awaiter.OnCompleted(() => {
                var ret = awaiter.GetResult();
                if (ret ==null || ret.Length == 0)
                {
                    return;
                }
                string recv = Encoding.UTF8.GetString(ret);
                ActReceiveMessage?.Invoke(recv);
                LoopReceive(socket);
            });
        }


        /// <summary>
        /// beginReceive가 완료되면 endReceive로 완료처리를 한후에 TaskCompletionSource를 통해서
        /// 받은 데이터를 전송함.
        /// </summary>
        /// <param name="socket"></param>
        /// <returns></returns>
        public Task<Byte[]> ReceiveTaskCompletionSource (Socket socket)
        {

            if (socket == null || !socket.Connected)
            {
                return null;
            }
            var tcs = new TaskCompletionSource<Byte[]>(socket);
            var buffer = new byte[1024];
   
            socket.BeginReceive(buffer, 0, 1024, SocketFlags.None, ar => {
                try
                {
                    var ret = ar.AsyncState as Socket;
                    int retcnt = ret.EndReceive(ar);
                    var retbuffer = new byte[retcnt];
                    Buffer.BlockCopy(buffer, 0, retbuffer, 0, retcnt);
                    tcs.SetResult(retbuffer);
                }
                catch (Exception er)
                {
                    tcs.SetException(er);
                }
            }, socket);
   
            return tcs.Task;
        }








[최초 등록일: ]
[최종 수정일: 8/14/2018]


비밀번호

댓글 작성자
 



2018-08-14 06시36분
1. 작업자 스레드라는 것이 스레드 풀의 스레드를 의미하는 건가요? 풀 관리는 알아서 되는 것이므로 클라이이언트가 종료되는 것과는 아무런 상관이 없습니다.

2. 메서드가 사라진다는 의미가 스레드 풀의 스레드가 사라져야 한다는 것을 의미하는 건가요? 풀의 스레드는 풀 관리자가 알아서 하는 것입니다. 클라이언트 측 소켓의 종료와는 아무런 상관이 없습니다.
정성태
2018-08-14 06시50분
[유영태] 작업자 스레드라는 것은 visual studio에서 디버깅모드에 들어갔을때 표시되는 스레드를 지칭한겁니다.
그렇다면 해당 visual studio에서 보여주는 스레드가 풀에서 관리되는 것인가요?
beginreceive 메서드가 비동기로 실행되고 있는 상태에서 client측에서 데이터를 전송하지 않으면 전송될때까지 기다리는 메서드가 생성되는게 아닌가요? 단지 블럭상태만 아니고요. 그런데 클라이언트가 종료됬음으로 데이터를 전송하지 않아서 무제한 적으로 기다리게 되는 상황이 발생하지 않나요?
[guest]
2018-08-14 06시59분
Visual Studio에서 보여주는 스레드는 스레드 풀의 것이 아니고, 프로세스에 있는 모든 스레드를 보여줍니다. 따라서 작업자 스레드라고 해도 이런 경우에는 풀의 스레드입니다. (유영태 님이 생성하고 관리하는 스레드가 있나요?)

기다리는 메서드가 생성된다는 것은 곧, 비동기가 아닌 동기로 스레드를 점유하고 있는 것이 있느냐는 것과 같습니다. 코딩하신 거에 그런 동작이 있었나요? (혹시, 제 책을 가지고 있다면 비동기 부분에 대해 다시 한번 정독하실 것을 권합니다.)

일반적으로 클라이언트가 소켓을 끊으면 Read했던 동작이 반환됩니다. 비동기로 Read를 했으니 ReadCompleted 비스무리한 콜백이 호출되는데 위의 코드에서는 ReceiveTaskCompletionSource의 socket.BeginReceive에 등록한 콜백이 호출됩니다. 그리고 그때의 retcnt는 0으로 나오고 그걸로 소켓 종료를 판단할 수 있으므로 이후 대응 소켓의 Close를 호출해 소켓 자원만 잘 해제해주시면 됩니다.
정성태
2018-08-14 07시02분
[5256] 아하 감사합니다. 잘배우고 갑니다.
[guest]

1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
5753흰털너부리11/24/20223386List와 ObservableCollection을 비교 해서 다른 값 추출 FirstOrDefault 객체 비교 [4]파일 다운로드1
5752푸헐11/15/20223527app.config 에 connectionStrings를 aspnet_regiis로 enctyption [4]
5751차가워11/8/20224336vs2022 preview net7 AOT 콘솔 실행 성능 [7]
5749차가워11/4/20223746전처리 지시문 #if DEBUG 배포시 실행 여부 [1]
5748김기헌11/3/20224076안녕하세요 선생님 싱글톤 패턴을 꼭 이렇게 사용해야 하나요? [6]
5747김기헌11/2/20223474안녕하세요 선생님 네트워크 관련 용어 중 IP 주소가 왜 논리적 주소라고 표현되는 건가요? [2]
5746물냉면이...11/2/20223649서로 다른 클래스에 있는 동일 함수의 일괄 호출 방법에 대해 궁금합니다. [3]
5745흰털너부리11/1/20224010.net core web api 사용 제한에 관한 질문 입니다. [2]
5744차가워10/31/20224391윈폼 Console.WriteLine(); 연산 문의 [1]
5743흰털너부리10/27/20223714reflection, static, override 질문입니다. [1]
5742차가워10/27/20223616하나의 socket에 여러 스레드가 접근 하는 경우 [1]
5741조호상10/27/20223867OpenCVSharp4 구현 가능 문의 [1]
5740혜성10/26/20224707Visual Studio 2022 C# 콘솔 프로그램 기본 코드 변경된 이유는 무엇인가요? [2]
5739슬픈단잠10/25/20223668조언 주신 방법으로 해봤으나, 여전히 어디가 문제인지 파악을 못했습니다. [2]
5738슬픈단잠10/24/20224050C# 마샬링 관련 질문입니다. [2]
5737감사합니...10/20/20224680찾은 어셈블리의 매니페스트 정의와 어셈블리 참조가 일치하지 않습니다. [8]파일 다운로드1
5736초보 개...10/19/20223701c# winforms 프로그램을 setup 파일로 배포 시, 설정 문의 [2]
5735농상10/17/20224357싱글톤 공부중 질문이 있습니다. [8]
5734mins10/14/20224118델파이 dll을 c#에서 사용하기 관련해서 포인트 관련이라 질문을 올립니다. [2]
5733김경환10/12/20224112선생님 질문하나만드리겠습니다. [1]
5732kss10/8/20224421c# socket.poll 버그인가요? [2]파일 다운로드1
5731kss10/8/20223979c# socket.poll 버그인가요? [3]
5730김재환10/7/20224491WPF에서 디스플레이 배율이 100%가 아닌 경우, Window의 포지션 정보가 부정확해지는 문제 [2]
5729김기헌10/2/20224157안녕하세요 선생님 뮤텍스 관련 질문 드립니다 [2]
5728김경환9/29/20224165그리드뷰관련마지막질문하나드리겠습니다선생님 [5]파일 다운로드1
5727김경환9/26/20224057c# 윈폼 tcp/ip 기반 데이터그리드뷰질문하나드리겟습니다 [3]
1  2  3  4  5  6  7  [8]  9  10  11  12  13  14  15  ...