Microsoft MVP성태의 닷넷 이야기
.NET Framework: 488. TCP 소켓 연결의 해제를 알 수 있는 방법 [링크 복사], [링크+제목 복사]
조회: 65226
글쓴 사람
정성태 (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)
13248정성태2/7/20234052오류 유형: 841. 리눅스 - [사용자 계정] is not in the sudoers file. This incident will be reported.
13247정성태2/7/20234964VS.NET IDE: 180. Visual Studio - 닷넷 소스 코드 디버깅 중 "Decompile source code"가 동작하는 않는 문제
13246정성태2/6/20234090개발 환경 구성: 664. Hyper-V에 설치한 리눅스 VM의 VHD 크기 늘리는 방법 - 두 번째 이야기
13245정성태2/6/20234641.NET Framework: 2093. C# - PEM 파일을 이용한 RSA 개인키/공개키 설정 방법파일 다운로드1
13244정성태2/5/20233990VS.NET IDE: 179. Visual Studio - External Tools에 Shell 내장 명령어 등록
13243정성태2/5/20234856디버깅 기술: 190. windbg - Win32 API 호출 시점에 BP 거는 방법 [1]
13242정성태2/4/20234304디버깅 기술: 189. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.UnauthorizedAccessException
13241정성태2/3/20233827디버깅 기술: 188. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.IO.FileNotFoundException
13240정성태2/1/20233987디버깅 기술: 187. ASP.NET Web Application (.NET Framework) 프로젝트의 숨겨진 예외 - System.Web.HttpException
13239정성태2/1/20233629디버깅 기술: 186. C# - CacheDependency의 숨겨진 예외 - System.Web.HttpException
13238정성태1/31/20235641.NET Framework: 2092. IIS 웹 사이트를 TLS 1.2 또는 TLS 1.3 프로토콜로만 운영하는 방법
13237정성태1/30/20235330.NET Framework: 2091. C# - 웹 사이트가 어떤 버전의 TLS/SSL을 지원하는지 확인하는 방법
13236정성태1/29/20234972개발 환경 구성: 663. openssl을 이용해 인트라넷 IIS 사이트의 SSL 인증서 생성
13235정성태1/29/20234515개발 환경 구성: 662. openssl - 윈도우 환경의 명령행에서 SAN 적용하는 방법
13234정성태1/28/20235562개발 환경 구성: 661. dnSpy를 이용해 소스 코드가 없는 .NET 어셈블리의 코드를 변경하는 방법 [1]
13233정성태1/28/20236904오류 유형: 840. C# - WebClient로 https 호출 시 "The request was aborted: Could not create SSL/TLS secure channel" 예외 발생
13232정성태1/27/20234704스크립트: 43. uwsgi의 --processes와 --threads 옵션
13231정성태1/27/20233618오류 유형: 839. python - TypeError: '...' object is not callable
13230정성태1/26/20234031개발 환경 구성: 660. WSL 2 내부로부터 호스트 측의 네트워크로 UDP 데이터가 1개의 패킷으로만 제한되는 문제
13229정성태1/25/20234975.NET Framework: 2090. C# - UDP Datagram의 최대 크기
13228정성태1/24/20235120.NET Framework: 2089. C# - WMI 논리 디스크가 속한 물리 디스크의 정보를 얻는 방법 [2]파일 다운로드1
13227정성태1/23/20234824개발 환경 구성: 659. Windows - IP MTU 값을 바꿀 수 있을까요? [1]
13226정성태1/23/20234497.NET Framework: 2088. .NET 5부터 지원하는 GetRawSocketOption 사용 시 주의할 점
13225정성태1/21/20233749개발 환경 구성: 658. Windows에서 실행 중인 소켓 서버를 다른 PC 또는 WSL에서 접속할 수 없는 경우
13224정성태1/21/20234098Windows: 221. Windows - Private/Public/Domain이 아닌 네트워크 어댑터 단위로 방화벽을 on/off하는 방법
13223정성태1/20/20234288오류 유형: 838. RDP 연결 오류 - The two computers couldn't connect in the amount of time allotted
1  2  3  4  5  6  7  8  9  10  11  12  13  14  [15]  ...