2022년 3월 13일 일요일

[c# TCP/Server Session]

 



멀티스레드 비동기 Send 기본완성

using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ServerCore
{
    class Session
    {
        Socket _socket;
        int _disconnected = 0;
        SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs(); /// 재사용을 위해 전역변수로 선언
        Queue<byte[]> _sendQueue = new Queue<byte[]>();
        bool _pending;
        object _lock = new object();

        public void Start(Socket socket)
        {
            _socket = socket;
            SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
            recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
            ///recvArgs.UserToken = 1;///아무거나 넘겨줘도 됨 this,숫자,아무거나 가능!! 식별자로 구분하고싶을때 사용
            recvArgs.SetBuffer(new byte[1024],0,1024);


            _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);

            RegisterRecv(recvArgs);
        }

        /// <summary>
        /// send는 recv 만큼 단순하지 않다
        /// 뭘 보낼지 모르니까 필요할때 써야한다.
        /// 많이 까다롭고 방법도 여러가지가 존재한다!
        /// </summary>
        /// <param name="sendBuff"></param>
        public void Send(byte[] sendBuff)
        {
            ///_socket.Send(sendBuff);
            ///
            ///문제 여기서 게속 생성해서 샌드하면 너무 손해다 (new)
           

            lock (_lock)
            {
                ///멀티스레드 이니까 락이 무조건 필요!!(여러명이 Queue에 접근못하게!)
                _sendQueue.Enqueue(sendBuff);
                if (_pending == false)
                {
                    ///처음 send를 호출한 상태(Register가능)
                    RegisterSend();
                }
            }



            ///매번 이렇게 호출하면 너무 비효율적! Queue에 담아서 될때만 호출해주자!
            //_sendArgs.SetBuffer(sendBuff,0,sendBuff.Length);
            //RegisterSend();

        }

        public void Disconnect()
        {
           
            if (Interlocked.Exchange(ref _disconnected, 1) == 1)
                return;
           
            _socket.Shutdown(SocketShutdown.Both);
            _socket.Close();

            ///안좋은 예
            ///

            ///if (_socket == null)
            ///    return;
            ///_socket = null;
           
        }


        #region 네트워크 통신 (내부사용)



        ///Send부분
        void RegisterSend()
        {

            ///멀티스레드여도 RegisterSend자체가 Lock에서 호출되고 있으니까 lock을 걸필요는 없음

            /// 심지어 이부분에서 sendAsync를 여러번 반복하면 부하가 너~~무 심하다!
            /// _socket.SendAsync(args) 이것을 아무대나 막쓰는것은 문제가 심하다.
            /// 뭉쳐서 보내야 한다!
            _pending = true;
            byte[] buff = _sendQueue.Dequeue();
            _sendArgs.SetBuffer(buff, 0, buff.Length);

            bool pending = _socket.SendAsync(_sendArgs); ///전역변수 사용으로 함수 인자 하나 없애기!(꼬이기 방지)
            if (pending == false)
                OnSendCompleted(null, _sendArgs);
        }
        void OnSendCompleted(object sender, SocketAsyncEventArgs args)
        {

            ///여기는 callback 방식으로 다른스레드에서 실행될수도 있으니 lock을 걸어줘야한다.
            ///_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted); <-- 이부분
            /// 사실 잘 와닿지 않을수 있음
            /// 멀티스레드 프로그램은 계속 크래시를 내보면서 해볼수 밖에 없음 그래야지 감각이 좋아짐

            lock (_lock)
            {
                if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
                {
                    try
                    {
                        ///recv와 다르게 보낸개 성공했을때 굳이 뭐 할게 없긴하다
                        ///
                        ///Send는 Receive처럼 시점이 정해져있지 않음
                        ///Send 하기!
                        /// RegisterSend는 재사용 당연히 불가능!


                        ///_pending 을 통과못한 큐에 쌓인 데이터 전부 처리해주기
                        if (_sendQueue.Count > 0)
                            RegisterSend();
                        else
                        {
                            ///아무도 Queue에 데이터를 넣지 않았으니까 진짜 끝!
                            _pending = false;
                        }




                    }
                    catch (Exception e)
                    {
                        Console.WriteLine($"On RecvCompleted Failed{e}");
                    }
                }
                else
                {
                    Disconnect();
                }
               
            }
        }



        ///비동기 방식으로 처리하려면
        ///두단계 필요!
        ///1.등록
        ///2.완료

        void RegisterRecv(SocketAsyncEventArgs args)
        {
            bool pending= _socket.ReceiveAsync(args);
            if (pending == false) ///완료됬으면 complete 호출!
                OnRecvCompleted(null,args);
        }
        void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
        {
            if (args.BytesTransferred >0 && args.SocketError == SocketError.Success)
            {

                try
                {
                    /// 성공적으로 데이터 가져오기 완료
                    string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                    Console.WriteLine($"From Client[{recvData}]");
                    RegisterRecv(args);
                }
                catch (Exception e)
                {
                    Console.WriteLine($"On RecvCompleted Failed{e}");
                }
               
            }
            else
            {
                /// TODO Disconnect!!
                Disconnect();
            }
        }



        #endregion
    }
}








멀티스레드 비동기 Send 개선버전1
(Queue,_sendArgs.BufferList 사용)











using System;
using System.Collections.Generic;
using System.Net.Sockets;
using System.Text;
using System.Threading;

namespace ServerCore
{
    class Session
    {
        Socket _socket;
        int _disconnected = 0;
        List<ArraySegment<byte>> _pendinglist = new List<ArraySegment<byte>>();
        SocketAsyncEventArgs _sendArgs = new SocketAsyncEventArgs(); /// 재사용을 위해 전역변수로 선언
        Queue<byte[]> _sendQueue = new Queue<byte[]>();


        //bool _pending;
        object _lock = new object();

        public void Start(Socket socket)
        {
            _socket = socket;
            SocketAsyncEventArgs recvArgs = new SocketAsyncEventArgs();
            recvArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnRecvCompleted);
            ///recvArgs.UserToken = 1;///아무거나 넘겨줘도 됨 this,숫자,아무거나 가능!! 식별자로 구분하고싶을때 사용
            recvArgs.SetBuffer(new byte[1024],0,1024);


            _sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted);

            RegisterRecv(recvArgs);
        }

        /// <summary>
        /// send는 recv 만큼 단순하지 않다
        /// 뭘 보낼지 모르니까 필요할때 써야한다.
        /// 많이 까다롭고 방법도 여러가지가 존재한다!
        /// </summary>
        /// <param name="sendBuff"></param>
        public void Send(byte[] sendBuff)
        {
            ///_socket.Send(sendBuff);
            ///
            ///문제 여기서 게속 생성해서 샌드하면 너무 손해다 (new)
           

            lock (_lock)
            {
                ///멀티스레드 이니까 락이 무조건 필요!!(여러명이 Queue에 접근못하게!)
                _sendQueue.Enqueue(sendBuff);
                if (_pendinglist.Count==0)///_pending == false
                {
                    ///처음 send를 호출한 상태(Register가능)
                    RegisterSend();
                }
            }



            ///매번 이렇게 호출하면 너무 비효율적! Queue에 담아서 될때만 호출해주자!
            //_sendArgs.SetBuffer(sendBuff,0,sendBuff.Length);
            //RegisterSend();

        }

        public void Disconnect()
        {
           
            if (Interlocked.Exchange(ref _disconnected, 1) == 1)
                return;
           
            _socket.Shutdown(SocketShutdown.Both);
            _socket.Close();

            ///안좋은 예
            ///

            ///if (_socket == null)
            ///    return;
            ///_socket = null;
           
        }


        #region 네트워크 통신 (내부사용)



        ///Send부분
        void RegisterSend()
        {

            ///멀티스레드여도 RegisterSend자체가 Lock에서 호출되고 있으니까 lock을 걸필요는 없음

            /// 심지어 이부분에서 sendAsync를 여러번 반복하면 부하가 너~~무 심하다!
            /// _socket.SendAsync(args) 이것을 아무대나 막쓰는것은 문제가 심하다.
            /// 뭉쳐서 보내야 한다!
            //_pending = true;
            //byte[] buff = _sendQueue.Dequeue();
            //_sendArgs.SetBuffer(buff, 0, buff.Length);

            /// _sendArgs.BufferList 한번에 리스트로 Async 콜을 줄일수있다.
            /// SetBuffer와 같이 사용할순 없다(둘중 하나 선택해야함)

            ///_pendinglist.Clear();

            while (_sendQueue.Count>0)
            {
                byte[] buff = _sendQueue.Dequeue();
                ///Add 하는 방식이 특이! ArraySegment 어떤 배열의 일부를 나타내는 구조체!
                /// a[ ][ ][ ][ ][ ][ ][ ][ ][ ][ ] c++은 포인터 개념이 있어서 포인터로 시작주소를 옮겨서 건내주면됨
                /// 하지만 c#이라면 포인터가 없어서 무조건 첫 주소만 알수있음
                /// 그래서 시작주소를 알수있게 보통 (buff, 0, buff.Length) 이런식으로 생겨있다.
                //_sendArgs.BufferList.Add(new ArraySegment<byte>(buff, 0, buff.Length));

                _pendinglist.Add(new ArraySegment<byte>(buff, 0, buff.Length));
            }

            _sendArgs.BufferList = _pendinglist;




            bool pending = _socket.SendAsync(_sendArgs); ///전역변수 사용으로 함수 인자 하나 없애기!(꼬이기 방지)
            if (pending == false)
                OnSendCompleted(null, _sendArgs);
        }
        void OnSendCompleted(object sender, SocketAsyncEventArgs args)
        {

            ///여기는 callback 방식으로 다른스레드에서 실행될수도 있으니 lock을 걸어줘야한다.
            ///_sendArgs.Completed += new EventHandler<SocketAsyncEventArgs>(OnSendCompleted); <-- 이부분
            /// 사실 잘 와닿지 않을수 있음
            /// 멀티스레드 프로그램은 계속 크래시를 내보면서 해볼수 밖에 없음 그래야지 감각이 좋아짐

            lock (_lock)
            {
                if (args.BytesTransferred > 0 && args.SocketError == SocketError.Success)
                {
                    try
                    {
                        ///recv와 다르게 보낸개 성공했을때 굳이 뭐 할게 없긴하다
                        ///
                        ///Send는 Receive처럼 시점이 정해져있지 않음
                        ///Send 하기!
                        /// RegisterSend는 재사용 당연히 불가능!

                        _sendArgs.BufferList = null; ///필요는 없음 그냥 구분하기 위해서 넣어준거라고 보면됨
                        _pendinglist.Clear();

                        Console.WriteLine($"Transferred bytes:{_sendArgs.BytesTransferred}");


                        ///_pending 을 통과못한 큐에 쌓인 데이터 전부 처리해주기
                        if (_sendQueue.Count > 0)
                            RegisterSend();
                       




                    }
                    catch (Exception e)
                    {
                        Console.WriteLine($"On RecvCompleted Failed{e}");
                    }
                }
                else
                {
                    Disconnect();
                }
               
            }
        }



        ///비동기 방식으로 처리하려면
        ///두단계 필요!
        ///1.등록
        ///2.완료

        void RegisterRecv(SocketAsyncEventArgs args)
        {
            bool pending= _socket.ReceiveAsync(args);
            if (pending == false) ///완료됬으면 complete 호출!
                OnRecvCompleted(null,args);
        }
        void OnRecvCompleted(object sender, SocketAsyncEventArgs args)
        {
            if (args.BytesTransferred >0 && args.SocketError == SocketError.Success)
            {

                try
                {
                    /// 성공적으로 데이터 가져오기 완료
                    string recvData = Encoding.UTF8.GetString(args.Buffer, args.Offset, args.BytesTransferred);
                    Console.WriteLine($"From Client[{recvData}]");
                    RegisterRecv(args);
                }
                catch (Exception e)
                {
                    Console.WriteLine($"On RecvCompleted Failed{e}");
                }
               
            }
            else
            {
                /// TODO Disconnect!!
                Disconnect();
            }
        }



        #endregion
    }
}



댓글 없음:

댓글 쓰기

git rejected error(feat. cherry-pick)

 문제 아무 생각 없이 pull을 받지않고 로컬에서 작업! 커밋, 푸시 진행을 해버렷다. push에선 remote와 다르니 당연히 pull을 진행해라고 하지만 로컬에서 작업한 내용을 백업하지 않고 진행하기에는 부담스럽다(로컬작업 유실 가능성) 해결하려...