Programming/.NET

TCP는 UDP에서 보장받지 못하는 신뢰성을 확보할 수 있습니다. 즉, 데이터를 송신하면 수신받은 측에서는 데이터를 받았다는 응답을 반드시 해야 하고 만약 응답이 없으면 데이터를 자동으로 다시 보내는등의 동작을 수행합니다. 뿐만 아니라 데이터의 송신순서와 수신순서가 일치하는등 높은 신뢰성을 유지합니다.

 

TCP도 UDP때와 마찬가지로 클라이언트에서 특정 내용을 보내면 서버에서 'server : '문자열을 붙여 회신하는 방식으로 구현해 보고자 합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

    t.Start();

Console.Read();

}

 

static void myMethod()
{
    using (Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp)) {
        IPEndPoint iEp = new IPEndPoint(IPAddress.Any, 2020);
        socket.Bind(iEp);
        socket.Listen(10);

        while (true) {
            Socket client = socket.Accept();

            myData md = new myData();
            md.client = client;
            md.rec = new byte[1024];

            client.BeginReceive(md.rec, 0, md.rec.Length, SocketFlags.None, myRecCallBack, md);
        }
    }
}

 

서버에서는 우선 TCP통신을 위한 소켓을 생성합니다. UDP와는 다르게 SocketType을 Stream으로 하며 ProtocolType을 TCP로 지정합니다.

 

그런다음 모든 IP에 대해 2020포트로 데이터 수신을 대기하도록 합니다. 여기까지는 UDP와 비슷합니다. 그런데 TCP에서는 Bind를 하고 난뒤 Listen메서드를 호출해서 클라이언트의 접속을 대기해야 합니다. Listen메서드 호출시 전달된 정수값은 클라이언트를 최대보관할 수 있는 큐의 값입니다. 10이면 10개의 클라이언트접속을 보관하겠다는 의미입니다.

 

이제 while문을 통해 무한정 클라이언트의 접속을 처리하게 되는데 UDP에서는 클라이언트와 1:1로 연결되는 형태가 아니므로 데이터의 송수신을 위해 일일이 클라이언트의 접점을 필요로 했으나 TCP는 클라이언트와 직접적으로 1:1연결되는 형태이므로 클라이언트의 접점을 필요로 하지 않습니다. 대신 Socket의 Accept메서드를 통해 접속중인 클라이언트의 개체를 꺼냄으로서 각 클라이언트를 구별할 수 있고 통신도 이 Accept에서 나온 개체와 통신하게 됩니다. 소켓은 그저 클라이언트의 접속을 생성해줄 뿐입니다.

 

이제 BeginReceive메서드로 가져온 접속중인 클라언트에게서 데이터를 수신받기 위해 BeginReceive메서드를 호출합니다. 이는 비동기 호출입니다. 물론 동기호출로 Receive메서드를 사용할 수 있지만 해당 클라이언트와의 통신이 모두 마무리될때까지 다른 클라이언트는 무작정 대기상태에 들어가게 되므로 성능상 불리하게 됩니다.

 

static void myRecCallBack(IAsyncResult ar)
{
    myData md = (myData)ar.AsyncState;

    int i = md.client.EndReceive(ar);

    string s = Encoding.UTF8.GetString(md.rec, 0, i);

    byte[] sndB = Encoding.UTF8.GetBytes("server : " + s);
    md.client.BeginSend(sndB, 0, sndB.Length, SocketFlags.None, mySadCallBack, md.client);
}

 

비동기 호출에서 데이터가 수신완료될때 호출될 콜백메서드입니다. 이 콜백메서드에서는 접속중인 클라이언트와 데이터를 수신한 바이트배열 모두를 다루어야 하기에 비동기메서드호출시

 

public class myData {

public Socket client;
    public byte[] rec;
}

 

위와 같이 별도의 클래스를 만들어 client와 배열 모두를 담아 마지막 매개변수로 전달해야 합니다.

 

그러면 콜백에서는 해당 데이터타입으로 변환하여 수신을 마무리 짓고 받은 데이터를 문자열로 변환해 'server : '를 붙여 다시 BeginSend메서드로 데이터를 보냅니다.

 

static void mySadCallBack(IAsyncResult ar)
{
    Socket client = (Socket)ar.AsyncState;
    client.EndSend(ar);
    client.Close();
}

 

데이터를 보낼 메서드도 비동기로 호출하였기 때문에 이를 처리할 콜백메서드가 필요합니다. 이 콜백메서드는 데이터 송신을 모두 완료한뒤 호출되므로 송신을 마무리짓고 클라이언트와의 접속을 해제합니다.

 

static void Main(string[] args)
{
    Thread t = new Thread(myMethod);
    t.IsBackground = true;

 

        t.Start();

Console.Read();

}


static void myMethod(object o)
{
    Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

    IPAddress ip = IPAddress.Parse("192.168.20.126");
    EndPoint sEP = new IPEndPoint(ip, 2020);

    socket.Connect(sEP);

    byte[] send = Encoding.UTF8.GetBytes("hi server!!");
    socket.Send(send);

    byte[] rec = new byte[1024];
    int i = socket.Receive(rec);
    string s = Encoding.UTF8.GetString(rec, 0, i);

    Console.WriteLine(s);

    socket.Close();
}

 

클라이언트는 비교적 간소합니다. Stream와 Tcp로 소켓을 생성해 접속할 서버의 접점을 생성하고 Connect메서드를 통해 서버에 접속합니다.

 

그리고 Send메서드로 서버에 데이터를 보내고 다시 Receive메서드로 서버에서 보낸 데이터를 수신해 사용자에게 표시합니다. 이와 같은 방법으로 클라이언트와 통신하는데 대한 대부분의 서버를 구성할 수 있습니다.

 

static void myRecCallBack(IAsyncResult ar)
{
    myData md = (myData)ar.AsyncState;

    int i = md.client.EndReceive(ar);

    string s = Encoding.UTF8.GetString(md.rec, 0, i); //일단 웹서버 테스트이므로 수신내용은 무시

    string sendMsg = "HTTP/1.0 200 OK/nContent-Type: text/html; charset=UTF-8\r\n\r\n" +
                    "<html><body>Web Server Response</body></html>";

    byte[] sndB = Encoding.UTF8.GetBytes(sendMsg);
    md.client.BeginSend(sndB, 0, sndB.Length, SocketFlags.None, mySadCallBack, md.client);
}

 

위 예제는 myRecCallBack메서드를 수정해 웹서버를 구성한 것입니다. 테스트용이므로 클라이언트에게 어떤 요청을 받든지 그것은 무시하기로 하고 응답에 대한 적절한 메세지를 작성해 클라언트에 보내면 됩니다. 클라이언트는 웹브라우저를 통해 설정된 포트로 접근하면 수신된 내용을 화면에 표시할 것입니다.

0 0