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

한빛출판네트워크

IT/모바일

네트워크 스트림(NetworkStream)

한빛미디어

|

2003-07-22

|

by HANBIT

16,091

저자: 워이밍 리(Wei-Meng Lee), 역 한동훈

닷넷 스트림을 설명한 이전 기사(.NET Streams Explained)에서 Stream 클래스를 구현한 BufferedStream, FileStream, MemoryStream, CryptoStream 클래스에 대해서 이야기 한 적이 있다. 하지만 그때 NetworkStream 클래스에 대한 논의는 빠져있었다.

이번 기사에서 필자는 네트워크 통신에 사용되는 NetworkStream 클래스의 사용법을 알아보고, 이를 소켓 프로그래밍에 얼마나 쉽게 사용할 수 있는지에 대해 살펴볼 것이다.

NetworkStream 클래스를 사용하여 텍스트 보내기

NetworkStream 클래스를 사용하여 보여줄 첫번째 예제는 두 PC간에 텍스트를 교환하는 클라이언트와 서버이다. 예제를 간단하기 하기 위해 동기(synchronous) 서버로 작성했으며 서버는 단순히 클라이언트가 보낸 것을 그대로 되돌려 준다. 이와 같은 종류의 서버를 에코(Echo) 서버라 하는데 이는 RFC 862에 정의되어있다. 마찬가지로 예제를 간단하게 하기 위해 VB.NET 콘솔 응용 프로그램을 사용하였다.

에코 서버 만들기

본 기사에 나오는 모든 예제는 다음과 같은 네임스페이스를 사용한다.
Imports System.Net.Sockets
Imports System.Net
Imports System.Text
Imports System.IO
우선 에코 서버부터 만들어보자. (필자는 먼저 완전한 소스 코드를 보여준 다음, 코드를 자세히 설명할 생각이다. 이와 같은 방식은 독자들이 자신의 프로젝트에 코드를 더 쉽게 추가하고 테스트할 수 있게 해준다.)
Const portNo As Integer = 500
Dim localAdd As System.Net.IPAddress = _
    IPAddress.Parse("127.0.0.1")
Dim listener As New TcpListener(localAdd, portNo)
listener.Start()
Console.WriteLine("Listening...")

Dim tcpClient As TcpClient = listener.AcceptTcpClient()
Dim NWStream As NetworkStream = tcpClient.GetStream
Dim bytesToRead(tcpClient.ReceiveBufferSize) As Byte

"---read incoming stream
Dim numBytesRead As Integer = NWStream.Read(bytesToRead, 0, _
    CInt(tcpClient.ReceiveBufferSize))
Console.WriteLine("Received :" & _
    Encoding.ASCII.GetString(bytesToRead, 0, numBytesRead))

"---write back the text
Console.WriteLine("Sending back : " & _
    Encoding.ASCII.GetString(bytesToRead, 0, numBytesRead))
NWStream.Write(bytesToRead, 0, numBytesRead)

tcpClient.Close()
listener.Stop()
Console.ReadLine()
소스 코드의 첫번째 부분은 TcpListener 객체를 설정하고 500번 포트를 사용하여 로컬호스트에서 요청을 대기(listen)한다. 닷넷 1.1에서는 TcpListener 생성자에 매개변수로 로컬 주소를 사용해야 한다(포트 번호만 사용하던 생성자는 이제 폐기되었다). IPAddress 클래스를 사용하기 위해 System.Net 네임 스페이스의 IPAddress.Parse() 메소드를 사용하여 문자열 형식으로 된 주소를 IP 형식으로 변환한다.
Const portNo As Integer = 500
Dim localAdd As System.Net.IPAddress = _
    IPAddress.Parse("127.0.0.1")
Dim listener As New TcpListener(localAdd, portNo)
listener.Start()
Console.WriteLine("Listening...")
Dim tcpClient As TcpClient = listener.AcceptTcpClient()
접속이 이루어지면 TcpClient 클래스의 GetStream() 메소드를 사용하여 NetworkStream 클래스의 인스턴스를 생성하고 클라이언트에서 받아온 텍스트를 저장하기 위해 바이트 배열을 선언한다.
Dim NWStream As NetworkStream = tcpClient.GetStream
Dim bytesToRead(tcpClient.ReceiveBufferSize) As Byte
NetworkStream 클래스의 Read() 메소드는 클라이언트에서 보낸 데이터를 받아오며 이는 모두 바이트 배열로 저장된다. Read() 메소드의 반환값은 읽어온 문자 길이가 된다. 데이터를 받아온 다음에는 System.Text 네임스페이스의 Encoding.ASCII.GetString() 메소드로 바이트 배열을 문자열로 변환한 후 화면에 표시한다.
"---read incoming stream
Dim numBytesRead As Integer = NWStream.Read(bytesToRead, 0, _
    CInt(tcpClient.ReceiveBufferSize))
Console.WriteLine("Received :" & _
    Encoding.ASCII.GetString(bytesToRead, 0, numBytesRead))
일단 데이터가 읽히면 NetworkStream 클래스의 Write() 메소드를 사용해서 받아온 데이터를 클라이언트에 돌려준다.
"---write back the text
Console.WriteLine("Sending back : " & _
    Encoding.ASCII.GetString(bytesToRead, 0, numBytesRead))
NWStream.Write(bytesToRead, 0, numBytesRead)
마지막으로 TcpClient 객체를 닫고, TcpListener 객체를 중지한다.
     tcpClient.Close()
     listener.Stop()
     Console.ReadLine()
에코 클라이언트 만들기

서버를 만들었던 것처럼 클라이언트도 만들어 보자. 클라이언트에 해당하는 코드는 아래와 같다.
Const portNo = 500
Const textToSend = "1234567890098765432111"
Dim tcpclient As New System.Net.Sockets.TcpClient
tcpclient.Connect("127.0.0.1", portNo)

Dim NWStream As NetworkStream = tcpclient.GetStream
Dim bytesToSend As Byte() = Encoding.ASCII.GetBytes(textToSend)
"---send the text
Console.WriteLine("Sending : " & textToSend)
NWStream.Write(bytesToSend, 0, bytesToSend.Length)

"---read back the text
Dim bytesToRead(tcpclient.ReceiveBufferSize) As Byte
Dim numBytesRead = NWStream.Read(bytesToRead, 0, _
    tcpclient.ReceiveBufferSize)
Console.WriteLine("Received : " & _
    Encoding.ASCII.GetString(bytesToRead, 0, _
                                numBytesRead))
Console.ReadLine()
tcpclient.Close()
에코 클라이언트에서 사용된 코드와 서버에서 사용된 코드를 비교해보면 거의 똑같다는 것을 알 수 있다. 따라서 여기서는 자세한 설명은 생략하기로 하겠다. 예제 응용 프로그램을 테스트 하기 위해 일단 서버 응용 프로그램을 실행한 후 클라이언트를 실행해야 한다. 실행한 결과화면은 다음과 같다.


[그림 1] 클라이언트와 데이터에 의한 데이터 송수신

Figure 1. Sending and receiving of data by the client and the server 용량이 큰 데이터 받아오기

앞의 예제에서는 ReceiveBufferSize 상수를 사용하여 수신되는 텍스트에서 읽을 수 있는 최대 바이트를 8192 바이트로 제한했었다.
Dim bytesToRead(tcpClient.ReceiveBufferSize) As Byte
"---read incoming stream
Dim numBytesRead As Integer = NWStream.Read(bytesToRead, 0, _
    CInt(tcpClient.ReceiveBufferSize))
수신된 데이터가 설정한 버퍼 보다 크면 어떻게 될까? 다음 예제에서는 한 번에 10 바이트씩 읽어오면서 매우 긴 텍스트를 읽어오는 경우를 실험해보자.
Const BUFFER_SIZE As Integer = 10

Dim bytesToRead(BUFFER_SIZE) As Byte
"---read incoming stream
Dim textReceived As String
Do
    Dim numBytesRead As Integer = _
        NWStream.Read(bytesToRead, 0, BUFFER_SIZE)
    textReceived += Encoding.ASCII.GetString(bytesToRead, _
                    0, numBytesRead)
    Console.WriteLine("Received :" & _
                        Encoding.ASCII.GetString(bytesToRead, _
                        0, numBytesRead))
Loop Until Not NWStream.DataAvailable

"---write back the text
Console.WriteLine("Sending back : " & textReceived)
NWStream.Write(Encoding.ASCII.GetBytes(textReceived), 0, _
                textReceived.Length)
위 예제는 수신된 데이터를 한 번에 모두 읽어오는 대신, 루프를 사용하여 한 번에 10 바이트씩 읽어온다. NetworkStream 클래스의 DataAvailable 속성은 이미 받아놓은 데이터가 소켓 내부에 있는지 없는지를 알려준다. 만약 소켓 내부에 이미 받아 놓은 데이터가 있으면 데이터가 없을 때까지 읽기를 계속 진행한다. 서버와 클라이언트를 실행하면 다음 결과를 볼 수 있다.


[그림 2] "블록" 내에서 데이터 수신

바이너리 데이터의 송신과 수신

마지막 예제는 NetworkStream 클래스를 사용하여 바이너리 데이터를 보내는 것이다. 전체 소스는 다음과 같다.
Const portNo As Integer = 500
Dim localAdd As System.Net.IPAddress = _
    IPAddress.Parse("127.0.0.1")
Dim listener As New TcpListener(localAdd, portNo)
listener.Start()
Console.WriteLine("Listening...")

Dim tcpClient As TcpClient = listener.AcceptTcpClient()
Dim NWStream As NetworkStream = tcpClient.GetStream
Dim bytesToRead(tcpClient.ReceiveBufferSize) As Byte

"---read incoming stream
Dim numBytesRead As Integer = NWStream.Read(bytesToRead, _
                        0, CInt(tcpClient.ReceiveBufferSize))

"---write the bytes to file
Const FILE_NAME = "c:\image.gif"
Dim fs As System.IO.FileStream
fs = New FileStream(FILE_NAME, FileMode.CreateNew, _
                    FileAccess.Write)
fs.Write(bytesToRead, 0, numBytesRead)
fs.Close()

tcpClient.Close()
listener.Stop()
Console.ReadLine()
마지막 예제와 마찬가지로 받아놓은 데이터를 바이트 배열로 읽어들인다.
"---read incoming stream
Dim numBytesRead As Integer = NWStream.Read(bytesToRead, _
                        0, CInt(tcpClient.ReceiveBufferSize))
FileStream 객체를 사용하여 읽어온 데이터를 파일로 저장한다.(이에 대한 것은 이전 기사(.NET Streams Explained)에서 다루었다)
"---write the bytes to file
Const FILE_NAME = "c:\image.gif"
Dim fs As System.IO.FileStream
fs = New FileStream(FILE_NAME, FileMode.CreateNew, _
                    FileAccess.Write)
fs.Write(bytesToRead, 0, numBytesRead)
fs.Close()
클라이언트 코드는 다음과 같다.
Const portNo = 500
Const FILE_NAME = "c:\ondotnet_logo.gif"
Dim tcpClient As New System.Net.Sockets.TcpClient
tcpClient.Connect("127.0.0.1", portNo)

Dim NWStream As NetworkStream = tcpclient.GetStream
Dim bytesToSend(tcpClient.ReceiveBufferSize) As Byte

Dim fs As FileStream
fs = New FileStream(FILE_NAME, FileMode.Open, _
                    FileAccess.Read)
Dim numBytesRead As Integer = fs.Read(bytesToSend, _
                    0, bytesToSend.Length)
fs.Close()

"---send the text
Console.WriteLine("Sending ...")
NWStream.Write(bytesToSend, 0, numBytesRead)

Console.ReadLine()
tcpclient.Close()
서버와 마찬가지로 FileStream 클래스를 사용하여 파일을 열고 수신된 데이터를 받아온다.

NetworkStream 클래스 참고사항

Stream 클래스의 다른 구현들과 달리 NetworkStream 클래스 사용에는 제한사항이 따른다. 예를 들어 NetworkStream 클래스의 CanSeek 속성은 지원되지 않으며 항상 false를 반환한다. 마찬가지로 LengthPosition 속성을 사용하면 NotSupportedException 예외가 발생한다.

마찬가지로 Seek() 연산, SetLength() 메소드도 사용할 수 없으며 사용할 경우, NotSupportedException 예외가 발생한다. ( 역자 주: 모든 스트림 클래스들은 CanRead, CanWrite, CanSeek 속성이 있으며, 스트림의 특성상 특정 위치로 이동하거나 특정 위치의 데이터를 가져오는 Seek() 메소드를 사용할 수 없고 CanSeek 속성도 항상 false가 된다.

반면에 NetworkStream 클래스를 사용하면 네트워크 프로그래밍을 매우 쉽게 할 수 있으며 복잡한 소켓 프로그래밍의 많은 부분을 숨길 수 있다. 스트림 프로그래밍에 익숙한 개발자라면 NetworkStream 클래스를 쉽게 사용할 수 있다.
Wei-Meng Lee는 닷넷 전문가이며 저자이며 「SQL Server Magazin」과 「Visual Studio Magazine」, 「.NET Magazine」에 기고하고 있다.
TAG :
댓글 입력
자료실