본문

TCP 소켓 프로그래밍

반응형

# TCP 소켓 프로그래밍


소켓 프로그래밍은 소캣을 이용한 통신 프로그래밍을 뜻하는데, 소켓(socket)이란 프로세스간의 통신에 사용되는 양쪽 끝단(endpoint)을 의미한다. 서로 멀리 떨어진 두 사람이 통신하기 위해서 전화기가 필요한 것처럼, 프로세스간의 통신을 위해서는 그 무언가가 필요하고 그것이 바로 소켓이다.


# TCP와 UDP

TCP/IP 프로토콜은 이기종 시스템간의 통신을 위한 프로토콜의 집합이며, TCP와 UDP 모두 TCP/IP 프로토콜에 포함되어 있으며,
OSI 7계층의 전송계층(transport layer)에 해당하는 프로토콜이다.


항목

TCP 

UDP 

연결방식 

.연결기반(connection-oriented) 

- 연결 후 통신(전화기)

- 1:1 통신방식

.비연결기반(connectionless-oriendted) 

- 연결없이 통신(소포)

- 1:1, 1:n, n:n 통신방식

특징 

.데이터의 경계를 구분안함(byte-stream)

.신뢰성있는 데이터 전송

- 데이터의 전송순서가 보장

- 데이터의 수신여부를 확인

  (데이터가 손실되면 재전송됨)

- 패킷을 관리할 필요가 없음

.UDP보다 전송속도 느림

.데이터의 경계를 구분함.(datagram)

.신뢰성 없는 데이터 전송

- 데이터의 전송순서가 바뀔 수 있음

- 데이터의 수신여부를 확인안함

  (데이터가 손실되어도 알 수 없음)

- 패킷을 관리해주어야 함

.TCP보다 전송속도가 빠름

관련 클래스 

.Socket

.ServerSocket 

.DatagramSocket

.DatagramPacket

.MulticastSocket 


TCP를 이용한 통신은 전화에, UDP를 이용한 통신은 소포에 비유된다. 

TCP는 데이터를 전송하기 전에 먼저 상대편과 연결을 한 후에 데이터를 전송하며 잘 전송되었는지 확인하고 전송에 실패했다면 해당 데이터를 재전송하기 때문에 신뢰있는 데이터의 전송이 요구되는 통신에 적합하다. 예를들면 파일을 주고받는데 적합하다.

UDP는 상대편과 연결하지 않고 데이터를 전송하며, 데이터를 전송하지만 데이터가 바르게 수신되었는지 확인하지 않기 때문에 데이터가 전송되었는지 확인할 길이없다. 또한 데이터를 보낸 순서대로 수신한다는 보장이 없다. 대신 이러한 확인과정이 필요하지 않기 때문에 TCP에 비해 빠른 전송이 가능하다. 게임이나 동영상의 데이터를 전송하는 경우와 같이 데이터가 중간에 손실되어 좀 끊기더라도 빠른 전송이 필요할 때 적합하다. 이때 전송 순서가 바뀌어 늦게 도착한 데이터는 무시하면 된다.



* 자바에서는 TCP를 이용한 소켓프로그래밍을 위해 Socket과 ServerSocket클래스를 제공하며 다음과 같은 특징을 갖는다.

- ServerSocket

포트와 연결(bind)되어 외부의 연결요청을 기다리다 연결요청이 들어오면, Socket을 생성해서 소켓과 소켓간의 통신이 이루어지도록 한다.

한 포트에 하나의 ServerSocket만 연결할 수 있다.(프로토콜이 다르면 같은 포트를 공유할 수 있다.)


- Socket

프로세스간의 통신을 담당하며, InputStream과 OutputStream을 가지고 있다. 이 두 스트림을 통해 프로세스간의 통신(입출력)이 이루어진다.



- 간단한 TCP/IP서버 구현 예제

서버소켓이 7777번 포트에서 클라이언트 프로그램의 연결요청을 기다린다. 클라이언트의 요청이 올 때까지 진행을 멈추고 계속 기다린다.

그러다가 클라이언트 프로그램이 서버에 연결을 요청하면, 서버소켓은 새로운 소켓을 생성하여 클라이언트 프로그램의 소켓(원격소켓)과 연결한다.

새로 생성된 소켓은 "[Notice] Test Message1 from Server."라는 데이터를 원격소켓에 전송하고 다시 연결을 종료한다.
그리고 서버소켓은 다시 클라이언트 프로그램의 요청을 기다린다.

클라이언트 프로그램의 요청을 지속적으로 처리하기 위해 무한반복문을 사용했기 때문에 서버 프로그램을 종료시키려면 Ctrl+C를 눌러서 강제종료 시켜야한다.


Source01) TcpIpServerEx01.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package networkingEx;
 
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
 
import org.omg.CORBA.portable.OutputStream;
 
public class TcpIpServerEx01 {
    final static int SERVER_PORT = 7777;
 
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
 
        try {
            // 서버소켓을 생성하여 777번 포트와 결합(bind)시킨다.
            serverSocket = new ServerSocket(SERVER_PORT);
            System.out.println(getTime() + "서버가 준비되었습니다.");
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        while (true) {
            try {
                System.out.println(getTime() + "연결요청을 기다립니다.");
                // 서버소켓은 클라이언트의 연결요청이 올 때까지 실행을 멈추고 계속 기다린다.
                // 클라이언트의 연결요청이 오면 클라이언트 소켓과 통신할 새로운 소켓을 생성한다.
                Socket socket = serverSocket.accept();
                System.out.println(getTime() + socket.getInetAddress() + "로부터 연결요청이 들어왔습니다.");
 
                // 소켓의 출력스트림은 얻는다.
                java.io.OutputStream out = socket.getOutputStream();
                DataOutputStream dos = new DataOutputStream(out);
 
                // 원격 소켓(remote socket)에 데이터를 보낸다.
                dos.writeUTF("[Notice] Test Message1 from Server.");
                System.out.println(getTime() + "데이터를 전송했습니다.");
 
                // 스트림과 소켓을 달아준다.
                dos.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
 
    }
 
    private static String getTime() {
        SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss}");
        return f.format(new Date());
    }
 
}
cs


Source02) TcpIpClientEx01.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
package networkingEx;
 
import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.ConnectException;
import java.net.Socket;
 
public class TcpIpClientEx01 {
    final static String SERVER_IP = "127.0.0.1";
    final static int SERVER_PORT = 7777;
 
    public static void main(String[] args) {
        try {
            String serverIp = SERVER_IP;
 
            System.out.println("서버에 연결되었습니다. 서버 IP : " + serverIp);
            // 소켓을 생성하여 연결을 요청한다.
            Socket socket = new Socket(serverIp, SERVER_PORT);
 
            // 소켓의 입력스트림을 얻는다.
            InputStream in = socket.getInputStream();
            DataInputStream dis = new DataInputStream(in);
 
            // 소켓으로부터 받은 데이터를 출력한다.
            System.out.println("서버로부터 받은 메세지 : " + dis.readUTF());
            System.out.println("연결을 종료합니다.");
 
            // 스트림과 소켓을 닫는다.
            dis.close();
            socket.close();
            System.out.println("연결이 종료되었습니다.");
        } catch (ConnectException ce) {
            ce.printStackTrace();
        } catch (IOException ie) {
            ie.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
cs

Result-server)
1
2
3
4
5
6
7
8
9
10
11
[05:08:14}서버가 준비되었습니다.
[05:08:14}연결요청을 기다립니다.
[05:08:36}/127.0.0.1로부터 연결요청이 들어왔습니다.
[05:08:36}데이터를 전송했습니다.
[05:08:36}연결요청을 기다립니다.
[05:09:50}/127.0.0.1로부터 연결요청이 들어왔습니다.
[05:09:50}데이터를 전송했습니다.
[05:09:50}연결요청을 기다립니다.
[05:09:57}/127.0.0.1로부터 연결요청이 들어왔습니다.
[05:09:57}데이터를 전송했습니다.
[05:09:57}연결요청을 기다립니다.
cs

Result-client)

1
2
3
4
서버에 연결되었습니다. 서버 IP : 127.0.0.1
서버로부터 받은 메세지 : [Notice] Test Message1 from Server.
연결을 종료합니다.
연결이 종료되었습니다.
cs


위, 아래의 예제에서는 한 대의 호스트 서버에서 서버 프로그램과 클라이언트 프로그램을 테스트할 수 있도록 서버의 IP를 127.0.0.1로 설정하였지만, 원래는 서버가 실제로사용하고 있는 IP를 지정해주어야한다.


- TCP/IP 쓰레드 응용 예제

소켓으로 데이터를 송신하는 작업과 수신하는 작업을 별도의 쓰레드 Sender와 Receiver가 처리하도록 하여 송신과 수신이 동시에 이루어지도록 했다.

Source01) TcpIpServer.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
package networkingEx;
 
import java.io.DataInput;
import java.io.DataInputStream;
import java.io.DataOutput;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
 
public class TcpIpServer {
 
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        Socket socket = null;
 
        try {
            // 서버소켓을 생성하여 7777번 포트와 결합(bind) 시킨다.
            serverSocket = new ServerSocket(7777);
            System.out.println("서버가 준비되었습니다.");
 
            socket = serverSocket.accept();
 
            Sender sender = new Sender(socket);
            Receiver receiver = new Receiver(socket);
 
            sender.start();
            receiver.start();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
 
class Sender extends Thread {
    Socket socket;
    DataOutputStream out;
    String name;
 
    Sender(Socket socket) {
        this.socket = socket;
        try {
            out = new DataOutputStream(socket.getOutputStream());
            name = "[" + socket.getInetAddress() + ":" + socket.getPort() + "]";
        } catch (Exception e) {
        }
    }
 
    public void run() {
        Scanner scanner = new Scanner(System.in);
        while (out != null) {
            try {
                out.writeUTF(name + scanner.nextLine());
            } catch (Exception e) {
            }
        }
    }
}
 
class Receiver extends Thread {
    Socket socket;
    DataInputStream in;
 
    Receiver(Socket socket) {
        this.socket = socket;
        try {
            in = new DataInputStream(socket.getInputStream());
        } catch (Exception e) {
        }
    }
 
    public void run() {
        while (in != null) {
            try {
                System.out.println(in.readUTF());
            } catch (IOException ie) {
            }
        }
    }
 
}
cs

Source02) TcpIpClient.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
package networkingEx;
 
import java.io.IOException;
import java.net.ConnectException;
import java.net.Socket;
 
public class TcpIpClient {
 
    public static void main(String[] args) {
        try {
            String serverIp = "127.0.0.1";
            
            // 소켓을 생성하여 연결을 요청한다.
            Socket socket = new Socket(serverIp, 7777);
            
            System.out.println("서버에 연결되었습니다.");
            Sender sender = new Sender(socket);
            Receiver receiver = new Receiver(socket);
            
            sender.start();
            receiver.start();
        } catch (ConnectException ce) {
            ce.printStackTrace();
        } catch (IOException ie) {
                ie.printStackTrace();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
cs

Result-server)



Result-client)





출처 및 참고자료 : JAVA의정석(남궁성 저)

반응형

공유

댓글