mojo's Blog

Socket Programming 본문

Java

Socket Programming

_mojo_ 2021. 8. 23. 13:43

소켓에 대해 알아보도록 한다.

 

소켓(socket) 통신은 개발자가 TCP/IP 네트워크를 이용하여 쉽게 통신 프로그램을 작성하도록 지원하는 기반 기술이다.

여기서 소켓은 통신하는 두 응용프로그램 간의 통신 링크의 각 끝단(endpoint)으로서, TCP/IP 의 네트워크 기능을 활용하여 다른 컴퓨터의 소켓과 데이터를 주고받는다.

소켓은 특정 포트에 연결되어 데이터를 보내거나 받을 때 해당 응용프로그램을 식별한다.

응용프로그램은 소켓과 연결한 후 소켓에 데이터를 주기만 하면, 소켓이 상대방 응용프로그램에 연결된 소켓에 데이터를 보낸다.

또는 응용프로그램은 연결된 소켓으로부터 도착한 데이터를 단순히 받기만 하면 된다.

인터넷을 경유하여 데이터를 주고받는 기능은 순전히 소켓 몫이다. 

데이터를 주고받는 동안 전송받은 데이터에 오류가 있는지 없는지를 검사하며 만일 손상된 데이터가 오면 다시 받기를 요청하는 등의 온전한 데이터를 받는 과정은 모두 소켓의 몫이다.

 

소켓과 서버 클라이언트의 통신에 대해 알아보도록 한다.

 

소켓을 이용하는 통신 => 서버 응용프로그램, 클라이언트 응용프로그램으로 구분한다.

정보를 제공하는 쪽은 서버(Server), 정보를 이용하는 쪽은 클라이언트(Client) 라고 부른다.통신은 서버가 먼저 클라이언트의 접속을 기다리고, 클라이언트에서 서버에 접속하면, 그 때부터 서버나 클라이언트가 데이터를 서로 주고받을 수 있다.서버나 클라이언트가 보내는 순서를 정하거나 순서에 상관없이 데이터를 전송하는 것은 개발자가 프로그램을 작성하기에 달려 있다.

 

서버 소켓과 클라이언트 소켓

 

소켓에는 서버 소켓, 클라이언트 소켓 2가지 종류가 있다. 이 둘은 다음과 같이 용도가 서로 다르다.

 

  • 서버 소켓은 서버 응용프로그램이 사용자의 접속을 기다리는(listen) 목적으로만 사용된다.
  • 클라이언트 응용프로그램에서는 클라이언트 소켓을 이용하여 서버에 접속한다.
  • 서버 소켓은 클라이언트가 접속해오면, 클라이언트 소켓을 추가로 만들어 상대 클라이언트와 통신하게 한다.

정리하자면 서버 소켓은 클라이언트의 접속을 기다리는 소켓이며, 클라이언트 소켓은 데이터 통신을 실시하는 소켓이다.

 

서버에서 클라이언트 소켓들의 포트 공유

 

서버 쪽의 통신 프로그램은 각각 독립된 소켓을 이용하여 클라이언트와 통신을 수행한다.

한편, 동일한 포트(80)를 여러 클라이언트 소켓들이 공유하고 있으면, 여러 클라이언트들로부터 전송받은 데이터를 서버 내 어떤 소켓으로 전달해야 하는지 어떻게 판단하는지는 운영체제에 의해 처리가 된다.

클라이언트가 서버 소켓에 처음으로 연결될 때, 운영체제는 연결된 클라이언트 IP 주소와 포트 번호를 저장하고 기억해둔다.

그 후 서버 컴퓨터의 운영체제는 클라이언트로부터 데이터 패킷을 받는다면 패킷 속에 들어있는 클라이언트의 IP 주소와 포트 번호를 참고하여, 서버에 있는 클라이언트 소켓을 찾아 그 곳으로 데이터를 보낸다.

 

소켓을 이용한 서버 클라이언트 통신 프로그램 구성

 

자바로 작성하는 서버 응용프로그램과 클라이언트 응용프로그램의 구조를 전체적으로 알아보도록 한다.

서버 클라이언트의 전형적인 구조와 동작하는 과정은 다음과 같다.

 

1. 서버 응용프로그램은 ServerSocket 클래스를 이용하여 서버 소켓 객체를 생성하고 클라이언트의 접속을 받기 위해 기다린다. 서버 소켓을 생성할 때 포트 번호를 주어 해당 포트로 접속해 오는 클라이언트를 기다리게 한다.

 

// ServerSocket
listener = new ServerSocket(server port);
Socket socket = listener.accept();

 

2. 클라이언트 응용프로그램은 Socket 클래스를 이용하여 클라이언트 소켓 객체를 생성하고 서버에 접속을 시도한다. 소켓 객체를 생성할 때, 접속할 서버 소켓의 IP 주소와 포트 번호를 지정한다.

 

// Client Socket
clientSocket = new Socket(Server IP, Server port);

 

3. 서버는 클라이언트로부터 접속 요청을 받으면, accept() 메소드에서 접속된 클라이언트와 통신하도록 전용 클라이언트 소켓을 따로 생성한다.

 

4. 서버와 클라이언트 모두 소켓으로부터 입출력 스트림을 얻어내고 데이터를 주고받을 준비를 한다.

 

// Client
clientSocket.getOutputStream();

// Server
socket.getInputStream();

 

5. 서버에 생성된 클라이언트 전용 소켓과 클라이언트의 소켓이 상호 연결된 채 스트림을 이용하여 양방향으로 데이터를 주고받는다.

 

// Client
소켓 스트림을 이용하여 데이터 입출력

// Server
소켓 스트림을 이용하여 데이터 입출력

 

6. 서버는 클라이언트가 접속해 올 때마다 accpet() 메소드에서 따로 전용 클라이언트 소켓을 생성하여 클라이언트와 통신하도록 한다. 통신이 끝나면 소켓을 닫는다.

 

// Client
clientSocket.close();

// Server
socket.close();

 

 

Socket 클래스, 클라이언트 소켓에 대해 알아보도록 한다.

 

Socket은 java.net 패키지에 포함되어 있는 클래스로서 클라이언트 소켓을 구현한다.

즉, 서버와 통신하기 위해서 클라이언트 응용프로그램에서 사용하는 소켓이다. 

Socket 의 생성자는 연결할 서버의 IP 주소(or Domain 주소)와 포트 번호를 parameter로 받아서 Socket 객체를 생성한다. 

클라이언트 자신의 주소와 포트 번호가 아님에 주의해야 한다.

 

클라이언트 소켓 생성 및 서버 접속

 

IP 주소가 128.12.1.1 이고 포트 번호가 5550 인 서버에 연결하기 위해 다음과 같이 클라이언트 소켓 객체를 생성한다. (이때 클라이언트의 포트(local port)는 사용되지 않는 포트 중에서 자동으로 선택된다.)

 

Socket clientSocket = new Socket("128.12.1.1", 5550);

 

Socket 객체가 생성되면 바로 128.12.1.1 주소의 5550번 포트로 자동 접속이 이루어진다.

다음과 같이 빈 소켓 객체를 생성하고 서버에 접속이 가능하다. (자신의 IP 주소 bind => 서버의 IP 주소 connect)

 

Socket clientSocket = new Socket();
clientSocket.bind(new InetSocketAddress("192.168.1.21", 1234));
					// 소켓에 자신의 IP 주소(192.168.1.21)와 local 포트(1234)를 결합한다.
clientSocket.connect(new InetSocketAddress("128.12.1.1", 5550));
					// IP 주소가 128.12.1.1이고 포트가 5550인 서버 응용프로그램에 접속

 

네트워크 입출력 스트림 생성

 

소켓이 만들어지고 서버와 연결이 된 후에는 Socket 클래스의 getInputStream() 과 getOutputStream() 메소드를 이용하여 서버와 데이터를 주고받을 소켓 스트림을 얻어내고 이를 버퍼 스트림에 연결한다.

 

BufferedReader in = new BufferReader(new InputStreamReader(clientSocket.getInputStream()));
BufferedWriter out = new BufferWriter(new OutputStreamWriter(clientSocket.getOutputStream()));

 

in, out 스트림 객체를 이용하여 네트워크 데이터를 보내고 받으면 된다.

위 코드로 in, out은 문자만 보내고 받을 수 있는 문자 입출력 스트림이다.

 

서버로 데이터 전송

 

버퍼 출력 스트림 out 을 통해 데이터를 전송해 보도록 한다. 

 

out.write("hello"+"\n");
out.flush();

 

"\n" 개행문자를 덧붙여 보내는 이유는 서버 쪽에서 라인 단위('\n' 문자가 올 때까지 한번에 읽는)로 수신한다고 가정하였기 때문이다.

스트림 out은 버퍼 입출력 스트림이기 때문에 버퍼가 차기 전까지 데이터를 보내지 않기 때문에 강제로 out.flush() 를 호출하여 스트림 속의 데이터를 모두 즉각적으로 전송하도록 한다.

 

서버로부터 데이터 수신

 

버퍼 입력 스트림을 in 을 이용한다면 서버로부터 문자 데이터를 수신할 수 있다.

 

int x = in.read(); // Client로부터 한 개의 문자를 수신

 

한 행의 문자열을 입력받는 코드는 다음과 같다.

 

String line = in.readLine(); // Client로부터 한 행의 문자열 수신

 

in.readLine() 메소드는 '\n' 문자가 올 때까지 계속 읽어들이고 '\n' 이 도착하면 그때까지 읽은 문자열을 return 하도록 한다. 이 문자열 속에는 '\n' 이 삽입되지 않는다.

 

데이터 송수신 종료

 

데이터 송수신을 모두 수행하고 소켓의 연결을 끊도록 하는 코드는 다음과 같다.

 

socket.close();

 

ServerSocket 클래스, 서버 소켓에 대해 알아보도록 한다.

 

ServerSocket 클래스는 서버 소켓을 구현한다.

ServerSocket 클래스는 java.net 패키지에 포함되어 있으며 ServerSocket은 클라이언트로부터 연결 요청을 기다리는 목적으로만 사용되며, 서버가 클라이언트의 연결 요청을 수락하면 Socket 객체를 별도로 생성하고, 이 Socket 객체가 클라이언트와 데이터를 주고받는다.

ServerSocket은 데이터의 송수신에 사용되지 않는다.

 

서버 소켓 생성

 

ServerSocket 생성자는 포트 번호를 parameter로 받아서 ServerSocket 객체를 생성한다.

이 포트 번호는 클라이언트의 접속을 기다릴 자신의 포트 번호이다.

이미 사용 중인 포트 번호를 지정하면 오류가 발생한다. 

9999번 포트를 사용하는 서버 소켓을 생성하는 예를 들면 다음과 같다.

 

ServerSocket listener = new ServerSocket(9999);

 

클라이언트로부터 접속 대기

 

ServerSocket 클래스의 accept() 메소드를 이용하여 클라이언트로부터의 연결 요청을 기다린다.

accept() 메소드가 연결을 수락하면 다음과 같이 Socket 객체를 하나 별도로 생성하여 리턴한다.

 

Socket socket = listener.accept();

 

서버에서 지금 접속된 클라이언트와의 데이터 통신은 새로 만들어진 socket을 이용하여 이루어진다.

새로 만들어진 socket은 서버 소켓과 동일하게 9999번 포트를 통해 데이터를 주고받는다.

 

네트워크 입출력 스트림 생성

 

클라이언트로 데이터를 주고 받기 위한 스트림 객체는, ServerSocket의 accpet() 메소드로부터 얻은 socket 객체의 getInputStream() 과 getOutputStream() 메소드를 이용하여 얻어낸다.

다음과 같이 소켓 스트림을 버퍼 입출력 스트림에 연결하여 사용한다.

 

 BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));

 

버퍼 입출력 스트림 in, out 을 이용하여 클라이언트와 데이터를 주고받으면 된다.

 

데이터 수신, 전송, 송수신 종료는 Client 와 동일하므로 생략한다.

 

 

Comments