메뉴 바로가기 검색 및 카테고리 바로가기 본문 바로가기

한빛출판네트워크

IT/모바일

.NET 소켓 프로그래밍

한빛미디어

|

2003-09-05

|

by HANBIT

13,304

저자: 부디 쿼니아완(Budi Kurniawan), 역 한동훈

네트워크 프로그램 작성은 어렵다. 그렇지만 닷넷에서라면 이러한 작업이 더 이상 어렵지 않을 수도 있다. 닷넷 프레임워크의 System.NetSystem.Net.Sockets 네임 스페이스에 네트워킹 작업을 쉽게 해주는 다양한 클래스들이 있기 때문이다. 본 기사에서는 System.Net.Sockets 네임 스페이스에서 가장 중요한 Socket 클래스에 대해 설명할 것이다. System.Net.Sockets.Socket 클래스는 클라이언트 응용 프로그램 뿐만 아니라 서버 응용 프로그램에서도 소켓으로 사용할 수 있으며 동기 동작과 비동기 동작 모두에 사용할 수 있다. 여기서는 Socket 클래스를 사용하여 클라이언트 응용 프로그램을 작성할 것이다.

네트워크 케이블의 양 끝을 종단점(endpoint)이라 하기 때문에 소프트웨어에서도 소켓 연결의 양 끝을 종단점(endpoint)이라 한다. 응용 프로그램에서는 종단점을 사용해서 네트워크에 대한 읽기와 쓰기를 수행한다. 클라이언트와 서버 응용 프로그램은 소켓 연결을 통해서 통신하며 바이트 스트림을 주고 받을 수 있다. 응용 프로그램에서 사용되는 다른 소켓에 메시지를 보내려면 응용 프로그램을 제공하는 PC의 IP 주소와 소프트웨어의 프로세스 식별자를 알아야 한다. PC에서 소프트웨어 프로세스 식별자는 포트(port)라고 하는 고유한 숫자를 사용한다. 따라서 한 응용 프로그램에서 연결의 반대편 끝에 있는 다른 소켓에 메시지를 전달하려면 상대편의 IP 주소와 응용 프로그램의 포트 번호를 알아야 한다. 닷넷 프레임워크에서는 이러한 소켓의 기능을 System.Net.Sockets.Socket 클래스로 제공한다. Socket 클래스는 소켓 API를 구현하고 있으며, 이 소켓 API는 버클리 소켓 인터페이스로 알려져 있다. 소켓 API는 80년대 초 버클리(Berkeley) 대학의 BSD UNIX 4.1c를 위해 개발된 것이며, 이 배포판에는 인터넷 프로토콜의 초기 버전이 포함되어 있다.

소켓 객체 구성

소켓 객체를 생성하고 사용하기 위한 Socket 클래스의 생성자는 다음과 같다.
public Socket (AddressFamily addressFamily,
               SocketType socketType,
               ProtocolType protocolType );
Socket 객체의 생성자는 세가지 열거형 AddressFamily, SocketType, ProtocolType을 인자로 받으며 각 열거형은 모두 System.Net.Sockets 네임 스페이스에 정의되어 있다. AddressFamily 열거형은 Socket 객체가 주소를 풀이하기 위해 사용하는 주소 지정 스키마를 정의하며, 여기서는 인터넷을 사용하는 소켓 응용 프로그램을 작성하므로 AddressFamily.InterNetwork를 사용한다.

SocketType 열거형은 소켓 종류를 지정하며 가장 많이 쓰이는 소켓 종류는 SocketType.Stream이다. Stream은 양방향 연결 기반 바이트 스트림(two-way connection-based byte stream)을 제공한다. ProtocolType 열거형은 소켓이 통신하기 위해 사용하는 저수준 프로토콜의 종류를 지정한다. 스트림 소켓은 TCP(Transmission Control Protocol)과 AddressFamily.InterNetwork를 사용해야 한다. 예를 들어, 다음 코드는 Socket 객체의 인스턴스를 생성한다.
Socket mySocket = new Socket(AddressFamily.InterNetwork,
                             SocketType.Stream,
                             ProtocolType.Tcp);
생성자에 전달할 수 있는 인자로는 세 가지 읽기 전용 속성은 AddressFamily, SocketType, ProtocolType이 있다.

원격 서버에 연결하기

소켓 인스턴스를 생성한 다음에는 Socket 클래스의 Connect() 메소드를 사용해서 원격 서버에 연결할 수 있다. Connect() 메소드는 원격 서버에 연결하기 위해 동기적으로 시도한다. 즉, Connect() 메소드를 호출한 다음에 연결 시도가 성공할 때 까지 기다리거나, 연결이 실패한 다음에 코드에서 연결을 제어하는 코드가 나올 때 까지 기다린다. Connect() 메소드를 사용하는 것은 쉽지만 원격 서버에 연결하려면 몇 가지 작업을 해야 한다. Connect() 메소드의 서명은 다음과 같다.
public void Connect( EndPoint remoteEP);
Connect() 메소드 인자는 System.Net.EndPoint 클래스의 인스턴스이며, EndPoint 클래스는 추상 클래스로 네트워크 주소를 의미한다. EndPoint 클래스의 하위 클래스는 System.Net.IPEndPoint이다. Connect() 메소드를 사용할 때는 연결하려는 원격 서버의 IP 주소와 포트 번호를 포함한 IPEndPoint 객체를 인자로 사용해야 한다. 소켓에서 원격 서버에 연결하기 위해 IPEndPoint 객체를 어떻게 구성할 수 있을까? IPEndPoint 클래스 정의를 살펴보면 두 가지 생성자가 있다.
public IPEndPoint(long address, int port);
public IPEndPoint(IPAddress address, int port);
일반적으로 129.36.129.44와 같이 IP 주소를 인자로 표현하는 두 번째 생성자를 자주 사용한다. 다음에 보게 될 것처럼 닷넷 소켓 프로그래밍에서는 long형의 주소를 사용하는 표기법 보다 IP 주소를 사용하는 것이 더 쉽다. 첫번째 생성자가 원격지 IP 주소를 long 형으로 사용하고, 두 번째 생성자가 System.Net.IPAddress 객체를 사용하는 것을 제외하면 두 생성자 모두 큰 차이점은 없다. 어떤 생성자를 선택하든 간에 원격 서버의 IP 주소와 포트 번호를 알고 있어야 하며, 대부분의 잘 알려진 서비스들은 할당된 기본 포트 번호가 있기 때문에 포트 번호는 별 문제가 되지 않는다. 예를 들어 HTTP는 80번, 텔넷은 25번, FTP는 21번 포트를 사용한다. IP 주소는 일반적으로 직접 사용하지 않는다. 왜냐하면 Microsoft.com 또는 oreillynet.com과 같이 IP 주소를 대신하는 도메인 이름을 기억하는 것이 IP 주소를 기억하는 것 보다 훨씬 쉽기 때문이다. 따라서 연결할 원격 서버의 IP 주소를 알아내기 위해 먼저 도메인 이름을 IP 주소로 변환해야 한다. 원격 서버 연결에 사용할 IPAddress 클래스의 인스턴스를 얻기 위해서는 System.Net.DnsSystem.Net.IPHostEntry 클래스를 사용한다. 인터넷 도메인 네임 시스템(DNS, Domain Name System)에서 특정 호스트에 관한 정보를 가져오기 위해 사용하는 클래스이므로 클래스 이름은 Dns 클래스이며, 도메인 네임에 대한 IP 주소들을 가져오기 위해 Dns 클래스의 Resolve() 메소드를 사용한다. Resolve() 메소드는 IP 주소들의 배열을 갖는 IPHostEntry 객체를 반환하며 IP 주소들의 배열을 가져오는데에는 IPHostEntry 클래스의 AddressList 속성을 사용한다. 예를 들어, 다음 코드는 DNS 이름에 해당하는 모든 IP 주소를 표시한다.
using System.Net.Sockets;
using System.Net;

try
{
  String server = "microsoft.com"; // or any other domain name

  IPHostEntry hostEntry = Dns.Resolve(server);
  IPAddress[] ipAddresses = hostEntry.AddressList;

  Console.WriteLine(server + " is mapped to:");

  foreach (IPAddress ipAddress in ipAddresses)
  {
    Console.WriteLine(ipAddress.ToString());
  }
}
catch (Exception e)
{
  Console.WriteLine(e.ToString());
}
위 코드를 실행하면 DNS 이름(여기서는 Microsoft.com)에 해당하는 모든 IP 주소들을 표시한다. DNS 이름에 하나 이상의 IP가 연결되어 있다면 이에 해당하는 모든 IP가 표시될 것이다. DNS 이름에 하나 이상의 IP가 있더라도 사람들은 첫번째 IP만을 사용한다. 이와 같이 첫번째 IP를 이용하는 이유는 보통 DNS 이름이 하나의 IP 주소에만 연결되어 있기 때문이다. DNS 이름에 연결된 첫번째 IP 주소를 가져오려면 다음 코드를 사용해야 한다.
HostEntry.AddressList[0]
IPAddress 객체를 얻어왔으면 이제 원격 서버에 연결하는 데 필요한 IPEndPoint 객체를 구성할 수 있다. 연결에 성공하면 Socket 인스턴스의 Connected 속성이 true로 설정된다. 서버 응용 프로그램은 일정 시간이 지나고 나면, 연결을 닫도록 되어 있기 때문에 프로그래머는 소켓 인스턴스를 사용하여 어떤 작업을 시작하기 전에 Connected 속성을 사용해 연결을 확인할 수 있다. 소켓을 이용한 작업이 끝난 후에는 연결을 명시적으로 닫기 위해 Close() 메소드를 사용한다. 보통은 수신된 모든 데이터를 비우고 Close() 메소드를 호출하는 Shutdown() 메소드를 사용하는 것이 좋다.


.NET Framework Essentials, 3rd Edition

참고 도서

.NET Framework Essentials, 3rd Edition
Thuan L. Thai, Hoang Lam




스트림 송수신하기

소켓을 원격 PC에 연결한 다음에는 데이터를 송수신할 수 있다. 동기모드로 데이터를 보내기 위해 Send 메소드를 사용하며 전송할 데이터는 반드시 바이트(Byte) 배열이어야 한다. Send 메소드는 4가지 오버로드된 버전을 가지며, 4가지 버전 모두 전송한 바이트 수를 반환하는 Integer 형을 반환값으로 한다. 첫번째 오버로드된 버전이 4가지 버전들 중에서 가장 간단하고 쉽다. 메소드 서명은 다음과 같다.
public int Send(byte[] buffer);
buffer에 전송하길 원하는 데이터를 포함한 바이트 배열을 저장한다. 이 오버로드된 버전을 사용하여 버퍼에 있는 모든 데이터를 전송할 수 있다. 두 번째 오버로드는 버퍼에 있는 모든 데이터를 전송할 수 있고, System.Net.Sockets.SocketFlags 열거형을 사용할 수 있으며 비트 연산을 사용하여 여러 값을 조합할 수 있다. 메소드 서명은 다음과 같다.
public int Send(byte[] buffer, SocketFlags socketFlags);
세 번째 오버로드된 버전은 버퍼에 있는 데이터를 모두 혹은 일부분만 전송할 수 있으며 SocketFlags 열거형을 사용한다.
public int Send( byte[] buffer, int size, SocketFlags socketFlags );
위 오버로드된 버전에서 size는 전송할 바이트 수를 의미한다. 마지막 오버로드 버전은 세 번째 오버로드된 버전과 비슷하지만 전송할 데이터의 시작위치를 지정하기 위해 옵셋 위치를 지정할 수 있다. 메소드 서명은 다음과 같다.
public int Send( byte[] buffer, int offset, int size, SocketFlags socketFlags );
이 오버로드에서 offset은 옵셋 위치이다. 데이터를 동기적으로 받아오기 위해서는 Receive() 메소드를 사용한다. 이 메소드는 4가지 오버로드된 버전이 있으며 Send() 메소드의 오버로드된 버전과 비슷하다. 오버로드된 버전의 서명은 다음과 같다.
public int Receive(byte[] buffer);
public int Receive(byte[] buffer, SocketFlags socketFlags);
public int Receive( byte[] buffer, int size, SocketFlags socketFlags );
public int Receive( byte[] buffer, int offset, int size, SocketFlags socketFlags );
Receive() 메소드를 사용할 때 수신된 데이터의 크기와 데이터를 읽어들일 수 있는지 확인하기 위해 Socket 클래스의 Available 속성을 사용할 수 있다.

요약

이번 기사에서는 System.Net.Sockets.Socket 클래스와 System.Net 네임스페이스에 속한 몇 가지 클래스를 소개했다. Socket 클래스를 사용하면 닷넷 응용 프로그램에서 네트워크에 쉽게 액세스할 수 있다. 이 외에도 Socket 객체의 인스턴스를 생성하는 방법과 스트림 송수신 방법에 대하여 살펴보았다.
Budi Kurniawan은 인터넷 및 객체 지향 프로그래밍 전문 IT 컨설턴트이며 마이크로소프트와 자바 두 가지 모두를 교육하고 있다.
TAG :
댓글 입력
자료실

최근 본 상품0