Microsoft MVP성태의 닷넷 이야기
.NET Framework: 488. TCP 소켓 연결의 해제를 알 수 있는 방법 [링크 복사], [링크+제목 복사]
조회: 65228
글쓴 사람
정성태 (techsharer at outlook.com)
홈페이지
첨부 파일
(연관된 글이 3개 있습니다.)
(시리즈 글이 6개 있습니다.)
.NET Framework: 487. Socket.Receive 메서드의 SocketFlags.Peek 동작을 이용해 소켓 연결 유무를 확인?
; https://www.sysnet.pe.kr/2/0/1824

.NET Framework: 488. TCP 소켓 연결의 해제를 알 수 있는 방법
; https://www.sysnet.pe.kr/2/0/1825

닷넷: 2204. C# - TCP KeepAlive에 새로 추가된 Retry 옵션
; https://www.sysnet.pe.kr/2/0/13531

닷넷: 2206. C# - TCP KeepAlive의 서버 측 구현
; https://www.sysnet.pe.kr/2/0/13533

Windows: 255. (디버거의 영향 등으로) 대상 프로세스가 멈추면 Socket KeepAlive로 연결이 끊길까요?
; https://www.sysnet.pe.kr/2/0/13546

Windows: 256. C# - Server socket이 닫히면 Accept 시켰던 자식 소켓이 닫힐까요?
; https://www.sysnet.pe.kr/2/0/13550




TCP 소켓 연결의 해제를 알 수 있는 방법

마치 소켓 연결 여부를 나타내는 듯한 Socket.Connected 속성은,

Socket.Connected Property
; https://learn.microsoft.com/en-us/dotnet/api/system.net.sockets.socket.connected

이름에서 의미하는 바와 같이 일단 다음의 동작은 충실히 수행하고 있습니다.

Socket Socket.Connected 속성 값
new Socket(...) Connected == false
Socket.Connect(...) 성공: Connected == true
Socket.Close() Connected == false

그런데 문서를 자세히 보면,

Gets a value that indicates whether a Socket is connected to a remote host as of the last Send or Receive operation.

Connected 속성 값이 "마지막으로 수행했던 Send/Receive 호출 당시의 연결 상태"임을 알 수 있습니다. 즉, Send/Receive 호출 이후에 시간이 지나 연결이 끊겼다면 이를 알 수 없다는 것입니다. 따라서 서버 측에서 소켓 연결을 끊어도 클라이언트 측 소켓의 Connected 속성값은 바뀌지 않습니다. 가령 이 상황을 다음과 같이 재현해 볼 수 있습니다.

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(serverAddress, 5000); // 연결이 된 후,

while (true)
{
    Console.WriteLine(socket.Connected); // 서버에서 연결을 끊어도 언제나 true 반환
    Thread.Sleep(1000);
}

이 뿐만이 아닙니다. 실제로 테스트해 보면 Send/Receive에 국한하지 않고, Socket 관련한 전반적인 메서드 호출에 대해 오류가 발생하면 Connected 속성은 이후 false를 반환하게 됩니다. 예를 들어, 다음과 같이 일부러 잘못된 소켓 옵션 설정으로 오류를 발생시켜 보면,

Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
socket.Connect(serverAddress, 5000);

try
{
    socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.UpdateConnectContext); // 예외 발생
}
catch
{
}

// ... socket.Connected == false

Connected 속성 값이 GetSocketOption 호출의 예외로 인해 false로 바뀝니다. 하지만, 소켓 연결이 해제된 것은 아닙니다. Connected == false로 떨어졌지만, Send/Receive에 대한 모든 호출이 가능합니다.

더욱 재미있는 것은, 일단 이렇게 한번 바뀐 Connected 값이 이후의 Send/Receive 성공 여부에 상관없이 무조건 false로 고정되어 유지된다는 점입니다. 도대체가 믿을 수 없는 Connected 속성 값입니다.




분명한 점은, 소켓은 실제 데이터를 Send/Receive 해보지 않고서는 연결이 끊겼는지에 대한 여부를 확신할 수 없다는 점입니다. 따라서, IsConnected와 같은 기능을 정의하고 싶다면 소켓의 Send/Receive 메서드에 대한 메서드를 한번 감싸서 그 반환값을 살피면서 IsConnected 상황을 판단하는 것이 좋습니다.

문제는 연결이 끊긴 상황에서 Send/Receive를 했을 때 어떤 경우는 예외가 발생하고 어떤 경우는 예외가 발생하지 않는다는 점입니다.

동작 Send Receive
연결 전 예외 발생: An unhandled exception of type 'System.Net.Sockets.SocketException' occurred in System.dll Additional information: A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied 예외 발생: An unhandled exception of type 'System.Net.Sockets.SocketException' occurred in System.dll Additional information: A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied
연결 후 서버가 곧바로 연결을 해제한 후 클라이언트의 Send/Receive 수행 최초 한번의 send는 성공, 두 번째 send는 예외 발생: An unhandled exception of type 'System.Net.Sockets.SocketException' occurred in System.dll Additional information: An established connection was aborted by the software in your host machine 0 반환
연결 후 서버가 Close하기 전 클라이언트의 Send/Receive 반복 수행 Send(1MB-buffer); 첫 번째 실행은 1048576 반환하면서 성공, 서버가 Close하지 않은 상태에서 두 번째 Send(1MB-buffer)를 호출하면 blocking, 그 상태에서 서버가 연결을 끊으면 예외 발생: An unhandled exception of type 'System.Net.Sockets.SocketException' occurred in System.dll Additional information: An existing connection was forcibly closed by the remote host 0 반환(반복 호출해도 계속 0반환)
연결 후 클라이언트가 Send 이후 Receive 진입, 서버는 Send/Receive없이 연결을 해제했을 때 Send의 반환값은 1048576으로 성공, 이후 호출된 Receive 호출은 blocking되지만 서버가 연결을 끊으면 예외 발생 "An unhandled exception of type 'System.Net.Sockets.SocketException' occurred in System.dll Additional information: An existing connection was forcibly closed by the remote host"
Receive 호출 후 blocking 상태에서 다른 스레드에서 해당 소켓을 Close 한 경우 예외 발생: Unhandled Exception: System.Net.Sockets.SocketException: A blocking operation wa s interrupted by a call to WSACancelBlockingCall

보시는 바와 같이 Send/Receive를 수행해서 연결이 끊겼는지를 판단하는 것도 애매합니다. Send의 경우 서버가 Close를 했지만 첫 번째 호출이 성공하는 경우도 있고, Receive의 경우 서버 측에서 연결을 끊었을 때 0을 반환하기도 하고, 때로는 예외를 발생시키기도 합니다. (따라서, Send에 대한 성공 여부가 중요한 경우라면 반드시 서버로부터 응답 패킷을 하나 정의해서 처리해야 한다는 것을 알 수 있습니다.)

그래도, 어쨌든 Send/Receive를 통해 연결 끊김을 감지하는 것이 좋습니다. 따라서, 이를 반영해서 새롭게 IsConnected 속성을 정의한 TcpSocket 클래스를 만들어 볼 수 있습니다.

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Net.Sockets;
using System.Text;

public class MyTcpSocket : IDisposable
{
    Socket _socket;
    string _host;
    int _port;
    bool _connected;
    public bool IsConnected
    {
        get { return _connected; }
    }

    public MyTcpSocket(string host, int port)
    {
        _host = host;
        _port = port;
    }

    public bool Connect()
    {
        try
        {
            _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            _socket.Connect(_host, _port);

            _connected = true;

            OnConnected();
            return true;
        }
        catch
        {
            Close();
        }

        return false;
    }

    public void Close()
    {
        bool fireLostEvent = _connected == true;

        _connected = false;
        if (_socket != null)
        {
            try
            {
                _socket.Close();
            }
            catch { }

            if (fireLostEvent == true)
            {
                OnConnectionLost();
            }

            _socket = null;
        }
    }

    public event Action Connected;
    protected virtual void OnConnected()
    {
        Debug.WriteLine("Connected.");
        if (Connected == null)
        {
            return;
        }

        Connected();
    }

    public event Action ConnectionLost;
    protected virtual void OnConnectionLost()
    {
        Debug.WriteLine("Connection lost");
        if (ConnectionLost == null)
        {
            return;
        }

        ConnectionLost();
    }

    public int Write(byte[] buffer)
    {
        if (_connected == false)
        {
            return 0;
        }

        int offset = 0;
        int size = buffer.Length;

        int totalSent = 0;

        while (true)
        {
            int sent = 0;

            try
            {
                sent = _socket.Send(buffer, 0, size, SocketFlags.None);
            }
            catch
            {
                Close();
                break;
            }

            totalSent += sent;

            if (totalSent == buffer.Length)
            {
                break;
            }

            offset += sent;
            size -= sent;
        }

        return totalSent;
    }

    public int Read(byte[] readBuf)
    {
        return Read(readBuf, 0, readBuf.Length);
    }

    public int Read(byte[] buffer, int offset, int size)
    {
        if (_connected == false)
        {
            return 0;
        }

        if (size == 0)
        {
            return 0;
        }

        int totalRead = 0;
        int readRemains = size;

        while (true)
        {
            int readLen = 0;

            try
            {
                readLen = _socket.Receive(buffer, offset, readRemains, SocketFlags.None);
            }
            catch
            {
            }

            if (readLen == 0)
            {
                Close();
                break;
            }

            totalRead += readLen;

            if (totalRead == size)
            {
                break;
            }

            offset += readLen;
            readRemains -= readLen;
        }

        return totalRead;
    }

    void IDisposable.Dispose()
    {
        Close();
    }
}




그런데, 여기서 한 가지 더 고려할 것이 있습니다. 연결 끊김을 인지하는 데 중요한 또 다른 변수가 바로 네트워크 선이 끊겼다거나... 하는 예상치 못한 상황인데, 이런 경우에는 Send/Receive 후 오류 체크 방식이 그다지 도움이 안 됩니다.

예를 들어, Socket.Receive 상태에서 대상 서버의 네트워크 선을 뽑아버리면(간단한 테스트를 위해 가상 머신의 경우 "Paused" 상태로 바꾸면) 클라이언트 측의 스레드는 Receive 호출로 인한 Blocking 상태에서 무한대기(hang)로 빠져버립니다. (운이 좋으면, 중간의 라우터가 일정 시간 동안 idle 상태인 TCP 연결을 끊어버려 감지되는 경우가 있습니다.)

이에 대한 대책으로 System.Net.Sockets.TcpClient 타입이 그랬던 것처럼 Send/Receive에 대한 Timeout을 지정하는 것입니다. 따라서 이에 대한 코드를 다음과 같이 추가할 수 있습니다.

public class MyTcpSocket : IDisposable
{
    // ...[생략]...


    public int SendTimeout
    {
        get { return (int)_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout); }
        set { _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.SendTimeout, value); }
    }

    public int ReceiveTimeout
    {
        get { return (int)_socket.GetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout); }
        set { _socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.ReceiveTimeout, value); }
    }


    public int Write(byte[] buffer)
    {
        // ...[생략]...

        while (true)
        {
            int sent = 0;

            try
            {
                sent = _socket.Send(buffer, 0, size, SocketFlags.None);
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode == SocketError.TimedOut)
                {
                    break;
                }

                Close();
                break;
            }

            // ...[생략]...
        }

        return totalSent;
    }

    public int Read(byte[] buffer, int offset, int size)
    {
        // ...[생략]...

        while (true)
        {
            int readLen = 0;

            try
            {
                readLen = _socket.Receive(buffer, offset, readRemains, SocketFlags.None);
            }
            catch (SocketException ex)
            {
                if (ex.SocketErrorCode == SocketError.TimedOut)
                {
                    break;
                }
            }

            // ...[생략]...
        }

        return totalRead;
    }
}

그럼, Send/Receive에서 다음과 같이 timeout을 지정해 주면,

MyTcpSocket socket = new MyTcpSocket(serverAddress, 5000);

// ...[생략]...

socket.ReceiveTimeout = 5000; // Send/Receive에 대해 5초 타임아웃 지정
socket.SendTimeout = 5000;

Console.WriteLine("ReceiveTimeout: " + socket.ReceiveTimeout);
Console.WriteLine("SendTimeout: " + socket.SendTimeout);

bool loop = true;

while (loop)
{
    byte [] buf = new byte[4];
    Console.WriteLine("Reading...");
    socket.Read(buf); // Receive 호출 후 5초 이내에 데이터를 받지 못하면 Timeout SocketException 발생
    Console.WriteLine(socket.IsConnected);
}

Timeout 에러 코드를 동반한 SocketException 예외가 발생합니다. 물론, 이것은 타임 아웃일뿐 연결 끊김을 감지하는 것은 아닙니다. 단지, 적절한 기준을 세워서 지정한 시간 동안 Send/Receive 동작을 실패하면 클라이언트 측에서 연결을 끊겼다고 판단하고 Socket.Close를 하는 정도가 최선입니다.




하지만, Timeout으로 인해 클라이언트 측에서 연결을 끊는 것이 좋은 선택은 아닙니다. 왜냐하면 상대방 소켓을 관리하는 스레드가 바빠서 데이터 전송을 못하는 걸 수도 있고, 때로는 패킷 정의를 하다 보면 각각의 요청/응답에 요구되는 timeout이 다르기 때문에 그것에 따라 연결 관리까지 함께 하는 것은 복잡도만 증가시킬 뿐입니다.

이에 대한 대안으로 선택될 수 있는 옵션이 바로 Keep-Alive입니다. 다음과 같이 KeepAlive 설정을 IOControl 메서드로 지정해 두면,

_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
_socket.Connect(_host, _port);

int size = sizeof(UInt32);
UInt32 on = 1;
UInt32 keepAliveInterval = 10000;   // Send a packet once every 10 seconds.
UInt32 retryInterval = 1000;        // If no response, resend every second.
byte[] inArray = new byte[size * 3];
Array.Copy(BitConverter.GetBytes(on), 0, inArray, 0, size);
Array.Copy(BitConverter.GetBytes(keepAliveInterval), 0, inArray, size, size);
Array.Copy(BitConverter.GetBytes(retryInterval), 0, inArray, size * 2, size);

_socket.IOControl(IOControlCode.KeepAliveValues, inArray, null);

TCP 하부단에서 자동으로 상대방 소켓에 ACK를 요구하는 패킷을 전송하게 됩니다. (ACK 응답을 보내는 것은 상대방 소켓을 관리하는 스레드가 아니라는 점!) 위의 설정에서는 10초마다 한번씩 KeepAlive 신호를 보내다가, 상대방으로부터 ACK 응답이 없으면 이후 1초마다 KeepAlive 신호를 보내게 됩니다. (문서에 따르면 윈도우의 경우 기본적으로 keepAliveInterval 설정이 2시간으로, retryInterval은 1초로 설정되어 있습니다.)

예상할 수 있듯이 retryInterval 간격으로 일정 재시도 횟수(TcpMaxDataRetransmissions)를 넘으면 TCP 하부단에서 서버와의 연결을 끊어버립니다.

예를 들어 TcpMaxDataRetransmissions 값이 5라면, 10초마다 KeepAlive 패킷을 전송하고 ACK 응답이 없으면 그 순간부터는 5회 동안 1초마다 KeepAlive 패킷을 전송한 후 그래도 ACK 응답이 없으면 TCP 연결을 해제하게 됩니다.




데이터 전송이 빈번한 종류의 통신 프로그램을 만들고 있다면 여기까지의 구현만으로 연결 끊김 여부를 판단하는 데 딱히 무리는 없습니다. 하지만, 그래도 자꾸 ^^ 좀 더 정확한 소켓 연결 해제에 대한 이벤트를 원할 수 있을 텐데요.

지금까지 본 바와 같이, 일단 표준 socket 인터페이스에서는 Close에 대한 이벤트를 지원하지 않는 듯합니다. 일례로 (닷넷의 Socket.Poll 메서드가 내부적으로 호출하는) 표준 socket 함수의 select만 봐도 READ/WRITE에 대한 상태(FD_READ, FD_WRITE)는 확인할 수 있어도,

int select(
  _In_     int nfds,
  _Inout_  fd_set *readfds,
  _Inout_  fd_set *writefds,
  _Inout_  fd_set *exceptfds,
  _In_     const struct timeval *timeout
);

연결이 해제(FD_CLOSE)되었다는 것에 대한 상태 체크 기능은 없습니다. 자... 그럼 이제 윈도우 전용으로 한번 내려가 볼까요?

윈도우의 경우 WSAAsyncSelect Win32 API를 통해 FD_CLOSE에 대한 이벤트를 제공합니다. 하지만 아쉽게도, 함수 원형에 보는 것처럼 HWND를 전달해야 합니다.

int WSAAsyncSelect(
  _In_  SOCKET s,
  _In_  HWND hWnd,
  _In_  unsigned int wMsg,
  _In_  long lEvent
);

즉, WSAAsyncSelect는 FD_CLOSE 이벤트를 Win32 메시지 루프를 통해 전달하도록 되어 있는 것입니다. 따라서, 윈도우가 없는 서비스 응용 프로그램 환경에서는 사용할 수 없습니다. (굳이 사용하고 싶다면 이벤트 수신을 위한 윈도우를 하나 만들면 됩니다.)

윈도우 자원을 생성할 필요 없이 이벤트를 다루고 싶다면 WSAEventSelect Win32 API를 사용해도 됩니다. 단지 제약이라면 Vista/2003이후부터 제공되므로 Windows XP를 사용해야 한다면 이 함수를 사용해서는 안됩니다. (현실적으로, 사실 XP용 응용 프로그램이라면 대개의 경우 UI를 가지고 있을 것이고 그럼 WSAAsyncSelect를 사용해도 됩니다. 반면, 서버용에서 돌아가는 응용 프로그램이라면 2003 이후부터 WSAEventSelect API를 지원하기 때문에 그런 경우에는 WSAEventSelect를 사용할 수 있으므로 적절한 고려만 되어 있다면 FD_CLOSE를 받는 데에는 지장이 없습니다.)

대략 다음과 같이 사용할 수 있습니다.

[DllImport("ws2_32.dll")]
public static extern int WSAEventSelect(IntPtr socket, SafeWaitHandle hEventObject, NetworkEvent networkEvent);

[Flags]
public enum NetworkEvent
{
    FD_READ = 0x01,
    FD_WRITE = 0x02,
    FD_OOB = 0x04,
    FD_ACCEPT = 0x08,
    FD_CONNECT = 0x10,
    FD_CLOSE = 0x20,
}

Thread _closeDetectThread;
EventWaitHandle _closeEvent = new EventWaitHandle(false, EventResetMode.ManualReset);

public bool Connect()
{
    try
    {
        _socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        _socket.Connect(_host, _port);

        // ...[생략]...

        WSAEventSelect(_socket.Handle, _closeEvent.SafeWaitHandle, NetworkEvent.FD_CLOSE);

        _closeDetectThread = new Thread(closeDetectThreadFunc);
        _closeDetectThread.IsBackground = true;
        _closeDetectThread.Start();
        return true;
    }
    catch
    {
        Close();
    }

    return false;
}

private void closeDetectThreadFunc(object obj)
{
    _closeEvent.WaitOne();

    if (_connected == false)
    {
        return;
    }

    this.Close();
}

작은(?) 문제가 있다면, WSAEventSelect API의 사용으로 인해 소켓이 blocking 모드에서 non-blocking 모드로 자동 설정된다는 점입니다. 이 때문에 Send/Receive를 호출하면 [송신 버퍼가 찬 경우, 수신 데이터가 없는 경우] WOULDBLOCK 반환 처리가 됩니다. 따라서, blocking 모드로 프로그램을 만들어 놓은 경우 non-blocking 모드로 동작하기 위해 프로그램 변경을 좀 해야 합니다.

제 개인적인 판단으로는, non-blocking 모드가 절실하게 필요하지 않은 경우라면 상대적으로 blocking 모드의 프로그램이 더 쉽기 때문에 WSAEventSelect까지 쓸 필요는 없어 보입니다. 게다가 소켓 I/O가 빈번한 프로그램의 경우에는 더더욱 FD_CLOSE 감지를 위해 WSAEventSelect로 변경하지 않아도 될 듯 싶고.

결정은... 여러분의 몫입니다. ^^

(첨부한 파일은 이 글의 소스 코드를 포함합니다.)




[이 글에 대해서 여러분들과 의견을 공유하고 싶습니다. 틀리거나 미흡한 부분 또는 의문 사항이 있으시면 언제든 댓글 남겨주십시오.]

[연관 글]






[최초 등록일: ]
[최종 수정일: 1/3/2024]

Creative Commons License
이 저작물은 크리에이티브 커먼즈 코리아 저작자표시-비영리-변경금지 2.0 대한민국 라이센스에 따라 이용하실 수 있습니다.
by SeongTae Jeong, mailto:techsharer at outlook.com

비밀번호

댓글 작성자
 



2014-12-12 04시08분
[spowner] 심도있는 분석 감사합니다. 많은 도움이 되었습니다
[guest]
2014-12-16 01시45분
요즘 네트워크 공부를 하기 시작했더니...성태님께서 좋은 글들을...^^ 감사합니다~
Beren Ko
2015-10-02 08시32분
[cjyoun] 감사합니다. 많은 도움이 되었습니다.
[guest]
2016-07-25 11시21분
[cok2528] 정말 좋은 글이네요 ! !
[guest]
2016-11-04 06시50분
[요원009] 비동기로 구현 중인데 연결 끊김 확인을 위해서 각종 상태를 일일이 파악해 봤습니다.
ObjectDisposedException, SocketException, NullReferenceException 이 3개의 경우를 중심으로 해봤는데요. 어쩔 수 없이 3가지의 경우에 더해 Exception 부분까지 구현을 했습니다.

소켓 객체의 상태가 Send 시점, Receive 시점, 연결 시도 시점, 연결 끊기 시점, 프로그램 오류로 인한 끊김, 이더넷 끊김, 서버 비정상 종료, 클라이언트 비정상 종료, 기타 등등 상태에 따라 달라지더라고요. 이렇게 많은 시점과 많은 상세 오류를 따라가다 보니 예외 처리 소스가 지나치게 길어진다는 느낌이 듭니다.

혹시, 이 부분에 대한 참조할만한 글이 더 있을까요?
[guest]
2016-11-04 07시24분
이 참에, @요원009 님이 정리해서 공개해 주시는 것도 좋겠지요. ^^
정성태
2019-09-03 09시02분
[기펴] 감사합니다. 이래서 클라이언트 쪽에서는 접속이 된걸로 나오고 서버쪽에서는 클라이언트가 접속이 끊긴걸로 나오는것이었군요;; 어떠한 예외도 없이 쩝.. 삽집을 많이 했네요
[guest]
2020-06-01 09시10분
[gwise] 비동기로 소켓 개발 했더니 클라이언트에서 한번만 성공하고 두번째부터 실패..
결국 동기방식으로 개발 했습니다.

에러 메시지
SocketException : System.Net.Sockets.SocketException (0x80004005): The socket is not connected
[guest]
2020-06-01 09시44분
@gwise 어떻게 하셨는지는 모르겠지만, 그건 분명히 코드상의 오류일 것입니다. 그렇다고는 해도, 비동기를 제대로 이해하지 못하고 그 방식으로 만들려고 밀고 나가는 것도 좋은 선택은 아닙니다. 단지, 공부하는 것이라면 그런 시행착오에서 배우는 것도 크겠지만 현업 프로젝트라면 우선 비동기 방식에 대한 충분한 선행 학습이 필요하고, 상황이 여의치 않으면 동기 방식으로 프로젝트를 진행하시는 것을 권장합니다.
정성태
2023-01-20 12시03분
[배건우] 심도 있는 내용 아주 잘 읽어 보았습니다.
[guest]

1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
13527정성태1/14/20241960오류 유형: 892. Visual Studio - Failed to launch debug adapter. Additional information may be available in the output window.
13526정성태1/14/20242051닷넷: 2201. C# - Facebook 연동 / 사용자 탈퇴 처리 방법
13525정성태1/13/20242021오류 유형: 891. Visual Studio - Web Application을 실행하지 못하는 IISExpress
13524정성태1/12/20242066오류 유형: 890. 한국투자증권 KIS Developers OpenAPI - GW라우팅 중 오류가 발생했습니다.
13523정성태1/12/20241889오류 유형: 889. Visual Studio - error : A project with that name is already opened in the solution.
13522정성태1/11/20242030닷넷: 2200. C# - HttpClient.PostAsJsonAsync 호출 시 "Transfer-Encoding: chunked" 대신 "Content-Length" 헤더 처리
13521정성태1/11/20242110닷넷: 2199. C# - 한국투자증권 KIS Developers OpenAPI의 WebSocket Ping, Pong 처리
13520정성태1/10/20241858오류 유형: 888. C# - Unable to resolve service for type 'Microsoft.Extensions.ObjectPool.ObjectPool`....'
13519정성태1/10/20241937닷넷: 2198. C# - Reflection을 이용한 ClientWebSocket의 Ping 호출파일 다운로드1
13518정성태1/9/20242174닷넷: 2197. C# - ClientWebSocket의 Ping, Pong 처리
13517정성태1/8/20242017스크립트: 63. Python - 공개 패키지를 이용한 위성 이미지 생성 (pystac_client, odc.stac)
13516정성태1/7/20242104닷넷: 2196. IIS - AppPool의 "Disable Overlapped Recycle" 옵션의 부작용
13515정성태1/6/20242376닷넷: 2195. async 메서드 내에서 C# 7의 discard 구문 활용 사례 [1]
13514정성태1/5/20242065개발 환경 구성: 702. IIS - AppPool의 "Disable Overlapped Recycle" 옵션
13513정성태1/5/20242005닷넷: 2194. C# - WebActivatorEx / System.Web의 PreApplicationStartMethod 특성
13512정성태1/4/20241979개발 환경 구성: 701. IIS - w3wp.exe 프로세스의 ASP.NET 런타임을 항상 Warmup 모드로 유지하는 preload Enabled 설정
13511정성태1/4/20241995닷넷: 2193. C# - ASP.NET Web Application + OpenAPI(Swashbuckle) 스펙 제공
13510정성태1/3/20241937닷넷: 2192. C# - 특정 실행 파일이 있는지 확인하는 방법 (Linux)
13509정성태1/3/20241966오류 유형: 887. .NET Core 2 이하의 프로젝트에서 System.Runtime.CompilerServices.Unsafe doesn't support netcoreapp2.0.
13508정성태1/3/20242016오류 유형: 886. ORA-28000: the account is locked
13507정성태1/2/20242700닷넷: 2191. C# - IPGlobalProperties를 이용해 netstat처럼 사용 중인 Socket 목록 구하는 방법파일 다운로드1
13506정성태12/29/20232191닷넷: 2190. C# - 닷넷 코어/5+에서 달라지는 System.Text.Encoding 지원
13505정성태12/27/20232704닷넷: 2189. C# - WebSocket 클라이언트를 닷넷으로 구현하는 예제 (System.Net.WebSockets)파일 다운로드1
13504정성태12/27/20232322닷넷: 2188. C# - ASP.NET Core SignalR로 구현하는 채팅 서비스 예제파일 다운로드1
13503정성태12/27/20232191Linux: 67. WSL 환경 + mlocate(locate) 도구의 /mnt 디렉터리 검색 문제
13502정성태12/26/20232298닷넷: 2187. C# - 다른 프로세스의 환경변수 읽는 예제파일 다운로드1
1  2  3  [4]  5  6  7  8  9  10  11  12  13  14  15  ...