Microsoft MVP성태의 닷넷 이야기
비동기 소켓사용후 해당 스레드가 남아있을때 처리 방법 [링크 복사], [링크+제목 복사]
조회: 10252
글쓴 사람
유영태 (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)
5781List맨1/5/20233781List.Add("newobj") 속도는 빠른 편인지요? [11]
5780임세1/3/20234401C# 프로그래밍 10 책 구매한 사람입니다. 3부 자료는 어디서 다운 받을 수 있나요? [4]
5779이건우1/2/20233269안녕하십니까 루프안에서 메세지처리에 관하여 질문드립니다! [2]
5778이건우12/30/20224533안녕하세요 c#에서 dll참조 관련 질문드립니다. [4]
5777감사합니...12/29/20223552UI Thread에 Invoke 처리관련 궁금합니다. [3]
5776pdf맨12/28/20223345C# MouseUp Event + pdf [10]
5775민성12/28/20223131안녕하세요 Class 관련해서 예외처리를 하나로 받아낼수 있는 방법 [1]
5774중급12/27/20223921중급개발자란 어느 수준인지요? [4]
5773김영식12/26/20223453c# 압축파일 읽어 올 때 BinaryRead 한글 처리 문제 [1]
5772눈송이12/26/20223584Excel VSTO 는 왜 Net Core, Net 5, 6 버전을 사용하지 않나요? [2]
5771김훈12/26/20223689c# .net client application 망분리(내부망,외부망) 환경에서 의문의 외부사이트 호출 대기 [2]
5770lsh12/26/20223295클라우디움안에 있는 파일을 File.Copy 하고싶은데 코드로는 접근을 못하나요? [1]
5769울타리12/20/20223333Active Directory 2012R2 2016 또는 2019 마이그렝션 문의 드립니다. [1]
5768c++12/14/20224082Thread를 사용한 C++ DLL에 관련된 질문입니다. [6]파일 다운로드2
5767민성12/9/20223432안녕하세요 ashx로 화일을 저장하고 화일명을 리턴하는데요 [1]
5766김명훈12/9/20223579웹브라우저에서 묻지 않고 바로 다운로드 [2]
5765hong12/1/20223819Winform(.Net6) 클라이언트에서 SignalR Core 웹서버에 접속시 인증서 문제 [3]파일 다운로드1
5764요한11/30/20224028c++ 동일한 객체인지 비교 방법문의 [2]
5763고필석11/30/20223541시작하자마자 비정상 종료하는 프로세스에 대한 문제 해결 조언 요청 드립니다. [3]
5762흰털너부리11/30/20223529wpf mvvm ui update 로딩중 표시 [1]
5761민성11/29/20223429죄송하지만 한가지만 더 여쭈어 보겠습니다 [1]
5760민성11/29/20223353안녕하세요 [2]
5759문정환11/28/20223702c# socket 통신할때 빅엔디언으로 바꿔줘야 하나요? [1]
5758라떼11/28/20224893Linux 에서 winform UI 어플리케이션 실행하기 [3]
5757흰털너부리11/25/20223844asp.net core EF AddDbContext,AddDbContextFactory 차이점 알려주세요 [1]
5756흰털너부리11/25/20223413asp.net core web api에서 json 특정 property 무시하는 방법 문의 드립니다. System.Text.Json 사용중입니다. [1]
1  2  3  4  5  6  [7]  8  9  10  11  12  13  14  15  ...