자바 네트워킹 마스터하기: java.net으로 견고한 네트워크 애플리케이션 구축
네트워크 프로그래밍(network programming)은 여러 장치(컴퓨터들)에서 실행되며, 이 장치들이 네트워크를 사용하여 모두 서로 연결된 프로그램들을 작성하는 것을 의미합니다.
J2SE API의 java.net 패키지에는 저수준 통신 세부사항을 제공하는 클래스들과 인터페이스들의 모음이 들어 있어, 당면한 문제 해결에 집중하는 프로그램들을 작성할 수 있게 합니다.
java.net 패키지는 두 가지 일반적인 네트워크 프로토콜을 지원합니다.
- TCP - Transmission Control Protocol의 약자로, 두 애플리케이션 간 신뢰할 수 있는 통신을 허용. TCP는 일반적으로 인터넷 프로토콜 위에서 사용되며 TCP/IP라고 불림
- UDP - User Datagram Protocol의 약자로, 애플리케이션 간 데이터 패킷 전송을 허용하는 비연결형 프로토콜
이 장에서는 다음 두 주제에 대한 좋은 이해를 제공합니다.
- 소켓 프로그래밍(Socket Programming) - 네트워킹에서 가장 널리 사용되는 개념으로 매우 상세히 설명
- URL 처리(URL Processing) - 별도로 다룸. 자바 언어에서 URL Processing 학습하려면 여기를 클릭
소켓 프로그래밍(Socket Programming)
소켓은 TCP를 사용하여 두 컴퓨터 간 통신 메커니즘을 제공합니다. 클라이언트 프로그램은 통신 끝단에 소켓을 생성하고 그 소켓을 서버에 연결하려 시도합니다.
연결이 성립되면 서버는 통신 끝단에 소켓 객체를 생성합니다. 이제 클라이언트와 서버는 소켓에 쓰고 읽음으로써 통신할 수 있습니다.
java.net.Socket 클래스는 소켓을 나타내며, java.net.ServerSocket 클래스는 서버 프로그램이 클라이언트를 기다리고 연결을 설정하는 메커니즘을 제공합니다.
소켓을 사용하여 두 컴퓨터 간 TCP 연결을 설정할 때 다음 단계들이 발생합니다.
- 서버가
ServerSocket 객체를 인스턴스화하여 통신이 발생할 포트 번호 지정
- 서버가
ServerSocket 클래스의 accept() 메서드 호출. 이 메서드는 주어진 포트에서 클라이언트가 서버에 연결될 때까지 대기
- 서버가 대기 중이면 클라이언트가
Socket 객체를 인스턴스화하며 서버 이름과 연결할 포트 번호 지정
Socket 클래스 생성자가 지정된 서버와 포트 번호에 클라이언트 연결 시도. 통신이 성립되면 클라이언트는 서버와 통신 가능한 Socket 객체를 가짐
- 서버 측에서
accept() 메서드가 클라이언트 소켓과 연결된 서버 측 새 소켓에 대한 참조 반환
연결이 설정되면 I/O 스트림을 사용하여 통신할 수 있습니다. 각 소켓은 OutputStream과 InputStream을 모두 가집니다. 클라이언트의 OutputStream은 서버의 InputStream에 연결되고, 클라이언트의 InputStream은 서버의 OutputStream에 연결됩니다.
TCP는 양방향 통신 프로토콜이므로 데이터가 두 스트림을 통해 동시에 전송될 수 있습니다. 소켓 구현을 위한 완전한 메서드 세트를 제공하는 유용한 클래스들:
ServerSocket 클래스 메서드
java.net.ServerSocket 클래스는 서버 애플리케이션이 포트를 얻고 클라이언트 요청을 대기하는 데 사용됩니다.
ServerSocket 클래스에는 네 가지 생성자가 있습니다.
| 번호 |
메서드 및 설명 |
| 1 | public ServerSocket(int port) throws IOException - 지정된 포트에 바인딩된 서버 소켓 생성 시도. 다른 애플리케이션이 이미 포트를 바인딩한 경우 예외 발생 |
| 2 | public ServerSocket(int port, int backlog) throws IOException - 이전 생성자와 유사, backlog 매개변수는 대기 큐에 저장할 수신 클라이언트 수 지정 |
| 3 | public ServerSocket(int port, int backlog, InetAddress address) throws IOException - 이전 생성자와 유사, InetAddress 매개변수는 바인딩할 로컬 IP 주소 지정. 여러 IP 주소를 가진 서버에서 어떤 IP로 클라이언트 요청을 받을지 지정 가능 |
| 4 | public ServerSocket() throws IOException - 언바운드 서버 소켓 생성. 이 생성자 사용 시 서버 소켓을 바인딩할 준비가 되면 bind() 메서드 사용 |
ServerSocket 생성자가 예외를 던지지 않으면 애플리케이션이 지정된 포트에 성공적으로 바인딩되어 클라이언트 요청을 받을 준비가 된 것입니다.
ServerSocket 클래스의 일반적인 메서드들:
| 번호 |
메서드 및 설명 |
| 1 | public int getLocalPort() - 서버 소켓이 대기 중인 포트 반환. 생성자에서 포트 번호로 0을 전달하여 서버가 포트를 찾게 한 경우 유용 |
| 2 | public Socket accept() throws IOException - 수신 클라이언트 대기. 지정된 포트에서 클라이언트가 서버에 연결되거나 소켓 타임아웃이 발생할 때까지 블록(setSoTimeout()으로 타임아웃 값 설정된 경우). 그렇지 않으면 무한정 블록 |
| 3 | public void setSoTimeout(int timeout) - 서버 소켓이 accept() 동안 클라이언트를 기다릴 타임아웃 값 설정 |
| 4 | public void bind(SocketAddress host, int backlog) - SocketAddress 객체에 지정된 서버와 포트에 소켓 바인딩. 인수 없는 생성자로 ServerSocket 인스턴스화한 경우 이 메서드 사용 |
ServerSocket이 accept()를 호출하면 클라이언트가 연결될 때까지 메서드가 반환되지 않습니다. 클라이언트가 연결되면 ServerSocket은 미지정 포트에 새 Socket을 생성하고 이 새 Socket에 대한 참조를 반환합니다. 이제 클라이언트와 서버 간 TCP 연결이 존재하며 통신을 시작할 수 있습니다.
Socket 클래스 메서드
java.net.Socket 클래스는 클라이언트와 서버가 서로 통신하는 데 사용하는 소켓을 나타냅니다. 클라이언트는 Socket 객체를 인스턴스화하여 얻고, 서버는 accept() 메서드의 반환 값으로 Socket 객체를 얻습니다.
Socket 클래스에는 클라이언트가 서버에 연결하는 데 사용하는 다섯 가지 생성자가 있습니다.
| 번호 |
메서드 및 설명 |
| 1 | public Socket(String host, int port) throws UnknownHostException, IOException - 지정된 서버의 지정된 포트에 연결 시도. 이 생성자가 예외를 던지지 않으면 연결 성공, 클라이언트가 서버에 연결됨 |
| 2 | public Socket(InetAddress host, int port) throws IOException - 이전 생성자와 동일하나 호스트가 InetAddress 객체로 표시됨 |
| 3 | public Socket(String host, int port, InetAddress localAddress, int localPort) throws IOException - 지정된 호스트와 포트에 연결하며 로컬 호스트에 지정된 주소와 포트에 소켓 생성 |
| 4 | public Socket(InetAddress host, int port, InetAddress localAddress, int localPort) throws IOException - 이전 생성자와 동일하나 호스트가 String 대신 InetAddress 객체로 표시됨 |
| 5 | public Socket() - 비연결 소켓 생성. connect() 메서드로 이 소켓을 서버에 연결 |
Socket 생성자가 반환되면 단순히 Socket 객체를 인스턴스화하는 것이 아니라 지정된 서버와 포트에 실제로 연결을 시도합니다.
Socket 클래스의 관심 메서드들입니다. 클라이언트와 서버 모두 Socket 객체를 가지므로 이 메서드들은 양쪽에서 호출 가능합니다.
| 번호 |
메서드 및 설명 |
| 1 | public void connect(SocketAddress host, int timeout) throws IOException - 소켓을 지정된 호스트에 연결. 인수 없는 생성자로 Socket 인스턴스화한 경우에만 필요 |
| 2 | public InetAddress getInetAddress() - 이 소켓이 연결된 다른 컴퓨터의 주소 반환 |
| 3 | public int getPort() - 소켓이 원격 머신에 바인딩된 포트 반환 |
| 4 | public int getLocalPort() - 소켓이 로컬 머신에 바인딩된 포트 반환 |
| 5 | public SocketAddress getRemoteSocketAddress() - 원격 소켓 주소 반환 |
| 6 | public InputStream getInputStream() throws IOException - 소켓의 입력 스트림 반환. 입력 스트림은 원격 소켓의 출력 스트림에 연결됨 |
| 7 | public OutputStream getOutputStream() throws IOException - 소켓의 출력 스트림 반환. 출력 스트림은 원격 소켓의 입력 스트림에 연결됨 |
| 8 | public void close() throws IOException - 소켓 닫음. 이 Socket 객체가 더 이상 어떤 서버에도 연결할 수 없게 됨 |
InetAddress 클래스 메서드
이 클래스는 인터넷 프로토콜(IP) 주소를 나타냅니다. 소켓 프로그래밍 시 필요할 유용한 메서드들:
| 번호 |
메서드 및 설명 |
| 1 | static InetAddress getByAddress(byte[] addr) - 원시 IP 주소로 InetAddress 객체 반환 |
| 2 | static InetAddress getByAddress(String host, byte[] addr) - 제공된 호스트 이름과 IP 주소로 InetAddress 생성 |
| 3 | static InetAddress getByName(String host) - 호스트 이름으로 호스트의 IP 주소 결정 |
| 4 | String getHostAddress() - 텍스트 표현으로 IP 주소 문자열 반환 |
| 5 | String getHostName() - 이 IP 주소의 호스트 이름 얻기 |
| 6 | static InetAddress getLocalHost() - 로컬 호스트 반환 |
| 7 | String toString() - 이 IP 주소를 String으로 변환 |
소켓 클라이언트 예제
다음 GreetingClient는 소켓을 사용하여 서버에 연결하고 인사말을 보낸 뒤 응답을 기다리는 클라이언트 프로그램입니다.
예제
// File Name: GreetingClient.java
import java.net.*;
import java.io.*;
public class GreetingClient {
public static void main(String[] args) {
String serverName = args[0];
int port = Integer.parseInt(args[1]);
try {
System.out.println("Connecting to " + serverName + " on port " + port);
Socket client = new Socket(serverName, port);
System.out.println("Just connected to " + client.getRemoteSocketAddress());
OutputStream outToServer = client.getOutputStream();
DataOutputStream out = new DataOutputStream(outToServer);
out.writeUTF("Hello from " + client.getLocalSocketAddress());
InputStream inFromServer = client.getInputStream();
DataInputStream in = new DataInputStream(inFromServer);
System.out.println("Server says " + in.readUTF());
client.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
소켓 서버 예제
다음 GreetingServer 프로그램은 명령줄 인수로 지정된 포트 번호에서 클라이언트들을 대기하는 Socket 클래스를 사용하는 서버 애플리케이션 예제입니다.
예제
// File Name: GreetingServer.java
import java.net.*;
import java.io.*;
public class GreetingServer extends Thread {
private ServerSocket serverSocket;
public GreetingServer(int port) throws IOException {
serverSocket = new ServerSocket(port);
serverSocket.setSoTimeout(10000);
}
public void run() {
while (true) {
try {
System.out.println("Waiting for client on port " + serverSocket.getLocalPort() + "...");
Socket server = serverSocket.accept();
System.out.println("Just connected to " + server.getRemoteSocketAddress());
DataInputStream in = new DataInputStream(server.getInputStream());
System.out.println(in.readUTF());
DataOutputStream out = new DataOutputStream(server.getOutputStream());
out.writeUTF("Thank you for connecting to " + server.getLocalSocketAddress() + "\nGoodbye!");
server.close();
} catch (SocketTimeoutException s) {
System.out.println("Socket timed out!");
break;
} catch (IOException e) {
e.printStackTrace();
break;
}
}
}
public static void main(String[] args) {
int port = Integer.parseInt(args[0]);
try {
Thread t = new GreetingServer(port);
t.start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
클라이언트와 서버를 컴파일한 뒤 서버를 다음과 같이 시작하세요.
$ java GreetingServer 6066
Waiting for client on port 6066...
클라이언트 프로그램을 다음과 같이 확인하세요.
출력
$ java GreetingClient localhost 6066
Connecting to localhost on port 6066
Just connected to localhost/127.0.0.1:6066
Server says Thank you for connecting to /127.0.0.1:6066
Goodbye!