Microsoft MVP성태의 닷넷 이야기
.NET Framework: 488. TCP 소켓 연결의 해제를 알 수 있는 방법 [링크 복사], [링크+제목 복사]
조회: 35200
글쓴 사람
홈페이지
첨부 파일

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

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

Socket.Connected Property
; http://msdn.microsoft.com/en-us/library/vstudio/system.net.sockets.socket.connected(v=vs.100).aspx

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

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로 변경하지 않아도 될 듯 싶고.

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

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




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





[최초 등록일: ]
[최종 수정일: 12/13/2014 ]

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

비밀번호

댓글 쓴 사람
 



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

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

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

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

[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...
NoWriterDateCnt.TitleFile(s)
12254정성태7/2/202022오류 유형: 626. git - REMOTE HOST IDENTIFICATION HAS CHANGED!
12253정성태7/2/202055.NET Framework: 922. C# - .NET ThreadPool의 Local/Global Queue파일 다운로드1
12252정성태7/2/202049.NET Framework: 921. C# - I/O 스레드를 사용한 비동기 소켓 서버/클라이언트파일 다운로드2
12251정성태7/1/202077.NET Framework: 920. C# - 파일의 비동기 처리 유무에 따른 스레드 상황파일 다운로드2
12250정성태7/1/2020249.NET Framework: 919. C# - 닷넷에서의 진정한 비동기 호출을 가능케 하는 I/O 스레드 사용법 [1]파일 다운로드1
12249정성태6/29/202027오류 유형: 625. Microsoft SQL Server 2019 RC1 Setup - 설치 제거 시 Warning 26003 오류 발생
12248정성태6/29/202024오류 유형: 624. SQL 서버 오류 - service-specific error code 17051
12247정성태6/29/202097.NET Framework: 918. C# - 불린 형 상수를 반환값으로 포함하는 3항 연산자 사용 시 단축 표현 권장(IDE0075) [2]파일 다운로드1
12246정성태6/29/202054.NET Framework: 917. C# - USB 관련 ETW(Event Tracing for Windows)를 이용한 키보드 입력을 감지하는 방법
12245정성태6/25/2020174.NET Framework: 916. C# - Task.Yield 사용법 (2) [2]파일 다운로드1
12244정성태6/29/202072.NET Framework: 915. ETW(Event Tracing for Windows)를 이용한 닷넷 프로그램의 내부 이벤트 활용파일 다운로드1
12243정성태6/23/202054VS.NET IDE: 147. Visual C++ 프로젝트 - .NET Core EXE를 "Debugger Type"으로 지원하는 기능 추가
12242정성태6/24/202030오류 유형: 623. AADSTS90072 - User account '...' from identity provider 'live.com' does not exist in tenant 'Microsoft Services'
12241정성태6/26/2020104.NET Framework: 914. C# - Task.Yield 사용법파일 다운로드1
12240정성태6/23/202073오류 유형: 622. 소켓 바인딩 시 "System.Net.Sockets.SocketException: An attempt was made to access a socket in a way forbidden by its access permissions" 오류 발생
12239정성태6/21/202055Linux: 30. (윈도우라면 DLL에 속하는) .so 파일이 텍스트로 구성된 사례
12238정성태6/21/202083.NET Framework: 913. C# - SharpDX + DXGI를 이용한 윈도우 화면 캡처 라이브러리
12237정성태6/20/202096.NET Framework: 912. 리눅스 환경의 .NET Core에서 "test".IndexOf("\0")가 0을 반환
12236정성태6/19/202053오류 유형: 621. .NET Standard 대상으로 빌드 시 dynamic 예약어에서 컴파일 오류 - error CS0656: Missing compiler required member 'Microsoft.CSharp.RuntimeBinder.CSharpArgumentInfo.Create'
12235정성태6/19/202039오류 유형: 620. Windows 10 - Inaccessible boot device 블루 스크린
12234정성태6/19/202035개발 환경 구성: 494. NuGet - nuspec의 패키지 스키마 버전(네임스페이스) 업데이트 방법
12233정성태6/19/202033오류 유형: 619. SQL 서버 - The transaction log for database '...' is full due to 'LOG_BACKUP'. - 두 번째 이야기
12232정성태6/19/202025오류 유형: 618. SharePoint - StoreBusyRetryLater 오류
12231정성태6/15/2020104.NET Framework: 911. Console/Service Application을 위한 SynchronizationContext - AsyncContext
12230정성태6/15/202043오류 유형: 617. IMetaDataImport::GetMethodProps가 반환하는 IL 코드 주소(RVA) 문제
[1]  2  3  4  5  6  7  8  9  10  11  12  13  14  15  ...