개인참고자료/자바(네트워크)

TCP 프로그래밍

경진 2008. 7. 17. 11:20
TCP(Transfer Control Protocol) 프로그래밍 기본

TCP는 다른 말로 스트림 통신 프로토콜이라고 부르며, TCP 통신을 하려면 양쪽의 소켓이 연결된 상태여야만 가능하다. 그렇기 때문에 연결지향 프로토콜이라고도 한다.

TCP 프로그래밍에서 가장 중요한 클래스는 java.net.ServerSocket과 java.net.Socket이다. ServerSocket은 서버 쪽에서 클라이언트의 접속을 대기하기 위해서 반드시 필요한 클래스며, Socket은 서버와 클라이언트가 통신하기 위해서 반드시 필요한 클래스다.

TCP 방법을 이용한 클라이언트/서버의 통신 순서

TCP 방법을 이용한 클라이언트/서버의 통신 순서

TCP방법을 이용해서 클라이언트와 서버가 통ㅇ신하려면 양쪽에 Socket 객체가 있어야 한다. 이때 소켓을 생성하는 방법은 클라이언트 쪽과 서버쪽이 다르다.

서버와 클라이언트의 가장 큰 차이점은 서버는 클라이언트가 접속하기를 기다리고 있어야 한다는 것이고, 클라이언트는 기다리고 있는 서버에게 접속을 시도한다는 점이다.

클라이언트의 접속 대기

서버 쪽에서는 클라이언트의 접속을 기다리기 위해서 ServerSocket 객체를 생성한 후 ServerSoket에 있는 accept() 메소드를 실행해서 대기하게 된다.

이렇게 무엇인가 대기하기 위해서 멈춰있는 메소드를 블로킹 메소드라고 한다. 여기까지의 과정을 소스로 표현하면 다음과 같은데, 굵은 글자로 표시된 부분만 해당된다.

ServerSocket server = new ServerSocket(10001);
System.out.println("접속을 기다립니다.");
Socket sock = server.accept();

클라이언트 접속

서버에서 ServerSocket을 생성한 후 accept()로 대기하고 있다면, 클라이언트는 서버로 접속할 수 있다. 클라이언트가 서버에 접속하는 방법은 간단한데, 클라이언트에서 Socket만 생성하면 내부적으로 알아서 접속이 일어나게 된다.

클라이언트에서 Socket 객체가 성공적으로 생성되었다면, 서버의 accept() 메소드는 클라이언트에 대한 Socket 객체를 반환하게 된다.

Socket sock = enw Socket("127.0.0.1", 10001);

Socket 생성자는 접속할 서버의 IP나 도메인을 첫번째 인자로 지정하고, 두번째 인자는 ServerSocket과 같은 포트 번호를 지정한다.

클라이언트에서 위 문장이 실행되면 accept() 메소드는 대기하고 있다가, 다음 방법으로 클라이언트의 Socket 객체를 반환한다.

Socket sock = server.accept();

Socket으로부터 InputStream과 OutputStream 구하기

클라이언트에서 서버로 접속에 성공했다면(클라이언트에서 Socket을 성공적으로 생성했다면), 서버와 클라이언트는 각각 Socket 객체를 가지게 된다. 클라이언트에 있는 소켓은 서버로부터 읽어 들이고 서버에 쓰기 위한 객체며, 반대로 서버에 있는 소켓은 클라이언트로 부터 읽어 들이고 클라이언트에 쓰기 위한 객체다.

소켓이 있다는 것은 소켓이 연결되어 있는 곳에 읽고 쓸수 있는 InputStream과 OutputStream을 구할 수 있다. 소켓을 통해서 InputStream과 OutputStream을 구하는 방법은 다음과 같다.

OutputStream out = sock.getOutputStream();
InputStream in = sock.getInputStream();

소켓을 통해서 OutputStream과 InputStream을 구했다면 OutputStream을 통해서 연결된 상대방에게 스트림을 전송할 수 있음을, InputStream을 통해서는 상대방이 전송한 스트림을 읽어 들일 수 있다.

서버의 OutputStream에서 쓰게 되면 클라이언트의 InputStream에서 읽어 들일 수 있게 되는 것이고, 반대로 클라이언트의 OuputStream에서 쓰게 되면 서버의 InputStream을 통해서 읽어 들일 수 있다.

클라이언트에서 OutputStream을 이용해서 썻다면, 서버에서는 InputStream을 이용해서 읽어 들여야 한다. 양쪽에서 쓰기만 한다거나, 읽기만 할 경우에는 서버와 클라이언트가 멈출 수도 있다. 그렇기 때문에 TCP 방식의 클라이언트/서버 프로그래밍은 프로토콜에 맞춰 조심스럽게 작성한다.

Socket으로부터 직접 구한 InputStream과 OutputStream을 이용할 수도 있지만, InputStream과 OutputStream을 다른 IO에 인자로 넣어서 사용한다. 다음은 소켓에서 구한 InputStream과 OutputStream을 BufferedReader와 PrintWriter 형태로 변환시키는 예제다.

PrintWriter pw = new PrintWriter(new OutputStreamWriter(out));
BufferedReader br = new BufferedReader(new InputStreamReader(in));

BufferedReader에는 문자열 한 줄을 읽어 들일 수 있는 readLine() 메소드가 있고, PrintWriter는 문자열 한 줄을 출력할 수 잇는 print() 메소드가 있다. 서버나 클라이언트에서 문자열 한 줄을 PrintWriter의 println() 메소드를 이용해서 출력하면, 반대쪽에서는 BufferedReader에 있는 readLine() 메소드를 이용해서 읽어 들이게 된다.

접속 끊기

소켓으로부터 구한 InputStream과 OutputStream을 이용해서, 클라이언트와 서버는 서로간에 읽고 쓸 수 있으며, 읽고 쓰는 것은 양쪽의 소켓 중에서 어느 한쪽의 소켓의 연결이 끊어질 때까지 가능하다.

소켓의 연결을 끊는 방법

sock.close();

소켓의 연결을 끊기 전에, 소켓을 통해서 얻은 IO 객체가 있을 경우에는 반드시 close() 메소드를 이용해서 종료해야 한다.