CAFE

수학쟁이

[Socket][Socket] Visual Basic으로 윈속 API 주무르기 - (1) Winsock 입문

작성자수학쟁이|작성시간08.11.01|조회수9,471 목록 댓글 23

 

 [Socket] Visual Basic으로 윈속 API 주무르기 - (1) Winsock 입문

 

 윈속(Winsock) 이란?

 Windows Socket의 줄임말로, 윈도우에서 네트워크 통신을 하기 위해서 반드시 사용해야 하는 라이브러리 함수군이다. 소켓은 본래 영어 단어 socket에서 나온 말로, 본래 의미는 플러그를 의미하지만, 네트워크 통신 역시 연결의 개념과 가깝기 때문에 Socket 이라고 불리게 된 것이다.

 

 * Winsock 사용하기

 윈속(Winsock)을 비주얼 베이직에서 사용하는 가장 쉬운 방법은, [프로젝트 → 구성 요소]의 'Microsoft Winsock Control 6.0' 구성 요소를 사용하면 됩니다. 이 윈속 컨트롤(Winsock Control)은 기존 윈속 API를 비주얼 베이직 프로그래머들이 사용하기 편리하도록 만든 COM(ActiveX) 컨트롤입니다.

 

 윈속 API 강좌를 시작하기 전에 윈속 컨트롤을 사용하는 방법에 대해 먼저 알아보도록 하겠습니다.

 우선, Microsoft Winsock Control을 도구 모음에 삽입하기 위해 [프로젝트 → 구성 요소]에서 다음과 같이 하시면 됩니다.

 

 

그 후 도구 모음에 추가된 Microsoft Winsock Control 6.0을 폼 위에 올려놓으신 후,

 

  

코딩 하시면 됩니다. 폼에 추가된 윈속 컨트롤은 프로그램 실행 시 폼에 나타나지 않습니다.

 

윈속 컨트롤에서 제공하는 주요 속성과 메서드는 다음과 같습니다.

 

************************* 주요 속성과 메서드 *************************

 

Socket.Accept (메서드)

- 연결을 승낙합니다. 인수로는 ConnectionRequest 이벤트에 전달된 RequestID를 대입하면 됩니다.

 

Socket.Bind (메서드)

- 소켓을 바인딩합니다. 보통 Listen 메서드를 하기 전에 합니다.

 

Socket.BytesReceived (속성)

- 수신된 데이터를 바이트 크기로 반환합니다.

 

Socket.Close (메서드)

- 소켓의 연결을 끊습니다.

 

Socket.Connect (메서드)

- 상대 컴퓨터에 연결합니다.

 

Socket.GetData (메서드)

- 데이터를 수신합니다.

 

Socket.LocalIP (속성)

- 로컬 컴퓨터(프로그램이 실행된 컴퓨터)의 아이피를 얻어옵니다.

 

Socket.LocalHostName (속성)

- 로컬 컴퓨터(프로그램이 실행된 컴퓨터)의 호스트 네임(일반적으로 컴퓨터 이름)를 얻어옵니다.

 

Socket.LocalPort (속성)

- 연결을 대기할 때 사용할 포트를 지정합니다. 포트의 범위는 0부터 65535까지 지정할 수 있고,

일반적으로 1024 이상의 포트를 사용합니다. (1023 이하의 포트는 시스템에서 예약되어있습니다.)

 

Socket.PeekData (메서드)

- 데이터를 엿봅니다(peek). 즉, GetData처럼 데이터를 수신하지만, 또 호출하면 다음번에 온 데이터를 수신하게 됩니다. (즉 GetData는 버퍼에 저장된 패킷 데이터를 지웁니다.) 하지만 PeekData는 버퍼에 저장된 패킷 데이터를 유지한 채 데이터를 수신합니다.

 

Socket.RemoteHost (속성)

- 연결된 상대의 호스트(일반적으로 컴퓨터 이름이나, 보통 빈 문자열이다.)입니다.

 

Socket.RemoteHostIP (속성)

- 연결된 상대의 IP입니다.

 

Socket.SendData (메서드)

- 데이터를 상대에게 전송합니다.

 

Socket.State (속성)

- 소켓의 상태를 서술합니다.

State 속성에 올 수 있는 것들로는 다음과 같은 상수가 정의되어있습니다.

* sckClosed: 소켓이 현재 닫혀있습니다.

* sckClosing: 연결을 끊고 있습니다.

* sckConnected: 상대 컴퓨터와 연결 되었습니다.

* sckConnecting: 상대 컴퓨터에 연결을 시도하고 있습니다.

* sckConnectionPending: 연결이 지연되고 있습니다.

* sckError: 오류가 발생했습니다.

* sckHostResolved: 도메인을 아이피로 바꾸었습니다.

* sckListening: 연결을 대기하고 있습니다.

* sckOpen: 소켓이 열려있습니다.

* sckResolvingHost: 도메인을 아이피로 바꾸고 있습니다.

 

Socket.SocketHandle (속성)

- 윈속 API에서 직접 이용할 수 있는 소켓 핸들을 제공합니다. (p.s: 중요한건 아닙니다만, 알아두면 좋습니다.)

 

Socket : 윈속 컨트롤 개체

 

**********************************************************************

 

 이제 자주 쓰이는 주요 속성과 메서드를 알아보았으니… 실전에 응용해봐야 겠죠?

 

 간단한 1:1 채팅방 프로그램을 만들어봅시다.

 (1:n 채팅방도 만드는건 어렵지 않지만, 약간 복잡하고 이해하기가 좀 어려울 수 있으므로 1:1 로 하였습니다.)

 

 프로젝트를 생성하시고, 폼을 만듭니다. 폼 위에 윈속 컨트롤을 올립니다.

 

 폼과 컨트롤 배치는 다음과 같이 하시면 됩니다.

 

 

 

 

 그럼 본격적으로 코딩을 시작해보겠습니다.

 

 우선 테스트할 때 귀찮을 수 있으므로 아이피에는 초기값으로 자신의 아이피가 뜨게 해봐야겠습니다.

 

 윈속 속성 중에서 LocalIP 가 자신의 아이피를 반환하는 속성이므로…

 

 다음과 같이 코딩할 수 있겠습니다.

 

Private Sub Form_Load()
    ' ### 아이피를 기본으로 자신의 아이피를 입력시킨다.
    txtRemoteIP.Text = socket.LocalIP

    ' ### 모든 글자를 선택시킨다.
    txtRemoteIP.SelStart = 0
    txtRemoteIP.SelLength = Len(txtRemoteIP.Text)
End Sub

 

 아… 빼먹은 코드가 있는데 소켓을 가장 처음에 사용할 때에는 Close 를 해주어야합니다.

 안그러면 연결을 대기하기 위해 .Listen 등을 할 때, 오류가 발생할 수 있기 때문입니다.

 수정된 코드는 아래 코드입니다.

 

Private Sub Form_Load()
    ' ### 소켓을 초기화한다.
    socket.Close

    ' ### 아이피를 기본으로 자신의 아이피를 입력시킨다.
    txtRemoteIP.Text = socket.LocalIP

    ' ### 모든 글자를 선택시킨다.
    txtRemoteIP.SelStart = 0
    txtRemoteIP.SelLength = Len(txtRemoteIP.Text)
End Sub

 

 대충 된 것 같습니다… 그럼 이제 [접속]과 [서버] 버튼 처리 루틴을 추가시켜봅시다.

 [접속]은 상대 컴퓨터에 연결하는 것을 의미하고, [서버]는 자기 자신이 연결을 대기하는

 서버 역할을 함을 의미합니다. 우선, 다음 코드처럼 [접속] 버튼의 연결 코드는 매우 간단합니다.

 

Private Sub cmdConnect_Click()
    socket.Close
    socket.Connect txtRemoteIP.Text, txtPort.Text
End Sub

 

 하지만 위 코드는 진짜 알맹이만 적은 것으로, 위 코드가 실제 접속을 담당합니다.

 하지만 UI 를 계속 활성화시킨 채 연결을 시도한다면, 접속 버튼을 계속 누르는 등 예상하지 못한

 일을 하면 오류가 발생할 여지가 있으므로, 버튼을 비활성화하고 활성화하는 코드를 추가하겠습니다.

 

 우선 다음은 버튼을 비활성화/활성화하는 코드입니다.

 

' ### 버튼을 비활성화한다.
Public Sub DisableAction()

    cmdConnect.Enabled = False
    cmdServer.Enabled = False

End Sub

' ### 버튼을 활성화한다.
Public Sub EnableAction()

    cmdConnect.Enabled = True
    cmdServer.Enabled = True

End Sub

 

 이제 이 함수를 호출하는 루틴을 추가시켜 보았습니다.

 

Private Sub cmdConnect_Click()
    On Error Resume Next

    ' ### 버튼을 비활성화시킨다.
    DisableAction

    socket.Close
    socket.Connect txtRemoteIP.Text, txtPort.Text

    ' ### 버튼을 활성화시킨다.
    EnableAction
End Sub

 

 하지만, Connect 메서드는 연결이 끝날 때까지 대기하는 것이 아닌, 연결을 시도한 채로 바로 다음 코드를

 실행시켜 버립니다. 그러므로 연결이 끝날때까지 대기하는 코드가 있어야할 것입니다.

 

 이 코드는 State 속성을 이용함으로써 구현이 가능합니다.

 저는 다음과 같이 구현하였습니다.

 

Private Sub cmdConnect_Click()
    On Error Resume Next

    ' ### 버튼을 비활성화시킨다.
    DisableAction

    socket.Close
    socket.Connect txtRemoteIP.Text, txtPort.Text

    ' ### 소켓의 상태가 '연결됨'이 될때까지 루프를 돈다.
    Do Until socket.State = sckConnected

        ' ### 연결이 끊어지거나 오류가 발생하였다면…
        If socket.State = sckClosed Or _
           socket.State = sckError Then

            ' ### 연결을 제대로 끊고 오류 메시지를 출력한다.
            socket.Close
            MsgBox "연결에 실패하였습니다.", vbCritical, "오류"

            ' ### 루프 탈출
            Exit Do

        End If

        ' ### 창 메시지가 처리될 시간을 준다.
        DoEvents
    ' ### 반복
    Loop

    ' ### 버튼을 활성화시킨다.
    EnableAction
End Sub

 

 이 상태에서 F5 로 실행하신 후, [접속]을 눌러보시면 [연결 실패]가 잘 뜨는 것을 볼 수 있습니다.

 (서버 구현을 안했으므로…)

 

 그런데 뭔가 허전합니다… 생각해보면 대부분의 프로그램이 뭔가를 할 때는 마우스 커서를 모래시계로

 바꾸어 '대기중'임을 알리기 때문입니다. 이는 Screen.MousePointer 속성으로 구현할 수 있습니다. 따라서 저도 추가해보겠습니다. 코드는 DisableAction/EnableAction 고치면 됩니다.

 

' ### 버튼을 비활성화한다.
Public Sub DisableAction()

    ' ### 마우스 커서를 모래시계로 바꾼다.
    Screen.MousePointer = vbHourglass

    cmdConnect.Enabled = False
    cmdServer.Enabled = False

End Sub

' ### 버튼을 활성화한다.
Public Sub EnableAction()

    cmdConnect.Enabled = True
    cmdServer.Enabled = True

    ' ### 마우스 커서를 표준으로 되돌린다.
    Screen.MousePointer = vbDefault

End Sub

 

 연결이 되었다면, 끊는 버튼도 만들어야 되는데… 기존의 디자인에서는 끊기 버튼을 만들지 않았습니다.

 미봉책으로 [접속] 버튼을 연결되었을 땐 [끊기]로 되도록 바꾸어 보겠습니다.

 이를 구현하기 위해서 별도의 서브 루틴을 하나 더 만들었습니다.

 

' ### 버튼의 상태를 조작한다.
Public Sub CommandAppear()
    If socket.State = sckConnected Then
        ' ### 연결이 되었으면…
        cmdConnect.Caption = "끊기"
        cmdServer.Caption = "서버"
        cmdServer.Enabled = False
    Else
        ' ### 연결이 안되었으면
        cmdConnect.Caption = "접속"
        cmdServer.Caption = "서버"
    End If
End Sub

 

 이제 이 루틴은 윈속의 Socket_Connect (연결되었을 때 호출되는 이벤트) 이벤트에서 호출될 것입니다.

 

 또한 Socket_Close (상대에 의해 끊김) 이벤트와 Socket_Error (오류가 발생했음) 이벤트에서도 호출해보겠습니다.

 

' ### socket_Close는 상대 쪽에서 끊었거나 연결이 끊겼음을 의미한다.
Private Sub socket_Close()
    CommandAppear

    EnableAction

    ' ### 아직 제대로 닫아지지 않았으므로, 닫아준다.
    socket.Close
End Sub


Private Sub socket_Connect()
    EnableAction
    CommandAppear
End Sub

Private Sub socket_Error(ByVal Number As Integer, Description As String, ByVal Scode As Long, ByVal Source As String, ByVal HelpFile As String, ByVal HelpContext As Long, CancelDisplay As Boolean)
    CommandAppear
    EnableAction
End Sub

 

끊기 버튼을 추가했으므로 끊기에 해당하는 동작도 구현해보겠습니다. 또한, EnableAction 이 중복되어 버그가 발생하는 것을 막기 위해 cmdConnect_Click 프로시저 내의 EnableAction 호출 부분을 지웠습니다. cmdConnect_Click 메서드를 수정하면 됩니다.

 

Private Sub cmdConnect_Click()
    
On Error Resume Next

    If socket.State = sckClosed Then

        ' ### 버튼을 비활성화시킨다.
        DisableAction

        socket.Close

        ' ### 연결에 사용되는 포트는 시스템이 정할 수 있도록 한다.
        socket.LocalPort = vbNullString

        ' ### 연결 시도
        socket.Connect txtRemoteIP.Text, txtPort.Text

        ' ### 소켓의 상태가 '연결됨'이 될때까지 루프를 돈다.
        Do Until socket.State = sckConnected

            ' ### 연결이 끊어지거나 오류가 발생하였다면…
            If socket.State = sckClosed Or _
               socket.State = sckError
Then

                ' ### 연결을 제대로 끊고 오류 메시지를 출력한다.
                socket.Close
                MsgBox "연결에 실패하였습니다.", vbCritical, "오류"

                
' ### 루프 탈출
                Exit Do

            End If

            ' ### 창 메시지가 처리될 시간을 준다.
            DoEvents
        ' ### 반복
        Loop
    Else

        DisableAction
        socket.Close
        EnableAction
        ' ### 버튼 상태 제어
        CommandAppear

    End If

End Sub

 

이제, [서버]의 코드를 구현해보겠습니다. 서버의 코드는 매우 간단합니다.

 

Private Sub cmdServer_Click()
    
' ### 소켓이 닫혀있으면
    If socket.State = sckClosed Then
        ' ### 접속 버튼의 캡션 바꾸고 닫기 버튼 만든다.
        cmdConnect.Caption = "접속"
        cmdConnect.Enabled =
False
        cmdServer.Caption = "닫기"
        
' ### 지정한 포트로 대기한다.
        socket.LocalPort = txtPort.Text
        ' ### 연결을 대기한다.
        socket.Listen
    ' ### 연결을 대기하고 있으면
    ElseIf socket.State = sckListening Then
        DisableAction
        ' ### 연결 끊고
        socket.Close
        ' ### 버튼 바꿈
        CommandAppear
        EnableAction
    End If
End Sub

 

 주석 때문에 약간 복잡해 보이지만, 주석을 빼면 매우 간단한 코드임을 알 수 있습니다.

 

 연결이 들어올 때의 처리도 같이 해보겠습니다. 가장 먼저 추가할 부분은,

 

 연결이 들어왔을 때 가장 먼저 처리되는 이벤트인 socket_ConnectRequest 부분입니다.

 

Private Sub socket_ConnectionRequest(ByVal requestID As Long)
    ' ### 연결이 들어왔으면…

    ' ### 다른 연결이 못 들어오도록 막고
    socket.Close
    ' ### 연결을 수락한다.
    socket.Accept requestID

    ' ### 버튼 상태를 고친다.
    EnableAction
    CommandAppear
End Sub

 

 ConnectionRequest 에서는 Accept 함수를 호출하여 연결을 최종적으로 수락할 수 있습니다.

 

 연결이 수락되었으므로 버튼 상태를 고치는 것도 잊지 않고 호출하였습니다.

 

 이제 본격적인 채팅 루틴을 구현해보겠습니다.

 

 아, 그 전에 채팅 닉네임을 자동으로 Client 와 Server 로 맞출까 합니다…

 

 따라서 접속 버튼과 서버 버튼 코드 살짝 바꾸어줘야 겠습니다.

 

' ################# 접속 버튼의 구현 코드 ##################

 

Private Sub cmdConnect_Click()
    On Error Resume Next

    If socket.State = sckClosed Then

        ' ### 버튼을 비활성화시킨다.
        DisableAction
       
        socket.Close
       
        ' ### 연결에 사용되는 포트는 시스템이 정할 수 있도록 한다.
        socket.LocalPort = vbNullString
       
        ' ### 닉네임
        socket.Tag = "Client"

       
        ' ### 연결 시도
        socket.Connect txtRemoteIP.Text, txtPort.Text
       
        ' ### 소켓의 상태가 '연결됨'이 될때까지 루프를 돈다.
        Do Until socket.State = sckConnected
       
            ' ### 연결이 끊어지거나 오류가 발생하였다면…
            If socket.State = sckClosed Or _
               socket.State = sckError Then
               
                ' ### 연결을 제대로 끊고 오류 메시지를 출력한다.
                socket.Close
                MsgBox "연결에 실패하였습니다.", vbCritical, "오류"
               
                ' ### 루프 탈출
                Exit Do
           
            End If
               
            ' ### 창 메시지가 처리될 시간을 준다.
            DoEvents
        ' ### 반복
        Loop
    Else
   
        DisableAction
        socket.Close
        EnableAction
        ' ### 버튼 상태 제어
        CommandAppear
       
    End If
   
End Sub

' ################# 서버 버튼의 구현 코드 ##################

 

Private Sub cmdServer_Click()
    ' ### 소켓이 닫혀있으면
    If socket.State = sckClosed Then
        ' ### 접속 버튼의 캡션 바꾸고 닫기 버튼 만든다.
        cmdConnect.Caption = "접속"
        cmdConnect.Enabled = False
        cmdServer.Caption = "닫기"
        ' ### 닉네임
        socket.Tag = "Server"

        ' ### 지정한 포트로 대기한다.
        socket.LocalPort = txtPort.Text
        ' ### 연결을 대기한다.
        socket.Listen
    ' ### 연결을 대기하고 있으면
    ElseIf socket.State = sckListening Then
        DisableAction
        ' ### 연결 끊고
        socket.Close
        ' ### 버튼 바꿈
        CommandAppear
        EnableAction
    End If
End Sub

 

자, 그럼 이제 본격적으로 입력 창에 엔터를 누르면 전송되게 할 것이므로 KeyPress 이벤트를 추가합니다.

 

 코드는 다음과 같이 입력하였습니다.

 

Private Sub txtInput_KeyPress(KeyAscii As Integer)
    ' ### 엔터 키가 입력되었다면…
    If KeyAscii = vbKeyReturn Then
        ' ### 키 입력을 무시하고…
        ' (땅땅거리는 소리를 안나게 한다.)
        KeyAscii = 0

        ' ### 문자열이 입력되었고 연결이 됐다면…
        If Len(txtInput.Text) > 0 Then
            If socket.State = sckConnected Then

                ' ### 메시지를 만든다. (닉네임 + "> " + 내용 + 줄바꿈 문자)
                Dim sMsg As String
                sMsg = socket.Tag & "> " & txtInput.Text & vbCrLf

                ' ### 텍스트 박스를 비운다.
                txtInput.Text = vbNullString

                ' ### 텍스트 박스에 보여준다.
                txtChat.Text = txtChat.Text & sMsg

                ' ### 자동 스크롤 구현
                txtChat.SelStart = Len(txtChat.Text)

                ' ### 패킷 전송
                socket.SendData sMsg
            End If
        End If
    End If
End Sub

 

 마지막으로 수신 코드입니다.

 

Private Sub socket_DataArrival(ByVal bytesTotal As Long)
    ' ### 메시지가 도착하면
    Dim sMsg As String
    socket.GetData sMsg, vbString

    ' ### 내용 잘림 방지
    sMsg = Replace$(sMsg, vbNullChar, vbNullString)

    ' ### 텍스트 박스에 보여준다.
    txtChat.Text = txtChat.Text & sMsg

    ' ### 자동 스크롤 구현
    txtChat.SelStart = Len(txtChat.Text)
End Sub

 

 중간의

 

 sMsg = Replace$(sMsg, vbNullChar, vbNullString)

 

 코드는, 문자열의 끝을 나타내는 문자인 NULL 문자를 걸러내어 채팅을 방해하는 행위를 할 수 없도록

 

 하기 위함으로 추가하였습니다.

 

 *** 이제 채팅 프로그램 개발이 성공되었습니다. 한번 프로그램을 컴파일한 후 두개를 띄워 테스트해보세요. ***

 

 

 다음 시간에는 Winsock Control 팁을 몇가지 써보겠습니다.

 

 읽어주셔서 감사합니다.

 

 * 완성된 예제 다운로드: 

 

첨부파일 ChattingExample.zip

 

다음검색
현재 게시글 추가 기능 열기

댓글

댓글 리스트
  • 작성자tongsool | 작성시간 09.04.12 좋은 자료 잘 보았습니다. 정말로 감사드립니다.
  • 작성자팡팡 | 작성시간 09.07.04 좋은 강좌 감사합니다^^ 예전 부터 윈속에대해 공부해보고 싶었는데 이번 기회로 공부해보내요 ㅎㅎ
  • 작성자초보예요 ㅠ | 작성시간 09.08.24 와우... 비베 입문했는데 저런거 보니.... 참 배우기 무서워지네요.....
  • 작성자zlGODlz | 작성시간 09.10.10 와...정말 감사합니다. 초보인데 정말 도움이 많이됬습니다. 정말 감사합니다.
  • 작성자고프다 | 작성시간 15.02.06 잘 읽었습니다. 감사합니다.
댓글 전체보기
맨위로

카페 검색

카페 검색어 입력폼