코드로 두 컴퓨터가 대화할 수 있을까? 소켓(Socket)이 그 창구다. 자바에서는 java.net 패키지로 네트워크 통신을 구현할 수 있다.
소켓은 네트워크 상에서 두 프로그램이 데이터를 주고받기 위한 연결 끝점이다. 전화기로 비유하면, 소켓은 전화기고 IP 주소와 포트 번호는 전화번호다.
자바에서는 두 가지 소켓을 사용한다.
ServerSocket: 서버 측에서 클라이언트의 연결을 기다린다Socket: 클라이언트 측에서 서버에 연결하거나, 연결이 수락된 후 실제 통신에 사용한다
서버와 클라이언트로 역할이 나뉜다.
import java.net.*;
import java.io.*;
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080); // 8080 포트에서 대기
System.out.println("서버 시작. 연결 대기 중...");
Socket socket = serverSocket.accept(); // 클라이언트 연결 수락 (블로킹)
System.out.println("클라이언트 연결됨");
// 데이터 읽기
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
String message = in.readLine();
System.out.println("받은 메시지: " + message);
socket.close();
serverSocket.close();
}
}
import java.net.*;
import java.io.*;
public class Client {
public static void main(String[] args) throws IOException {
Socket socket = new Socket("localhost", 8080); // 서버에 연결
// 데이터 보내기
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
out.println("안녕하세요, 서버!");
socket.close();
}
}
accept()는 클라이언트가 연결될 때까지 그 자리에서 기다린다. 연결이 오면 Socket 객체를 반환하고, 이후 이 소켓을 통해 데이터를 주고받는다.
소켓 통신은 I/O 스트림을 통해 이루어진다. getInputStream()으로 읽고, getOutputStream()으로 쓴다.
Socket socket = new Socket("localhost", 8080);
// 쓰기 스트림
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
// 읽기 스트림
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
out.println("Hello"); // 서버로 전송
String response = in.readLine(); // 서버 응답 수신
System.out.println(response);
socket.close();
PrintWriter의 두 번째 인자 true는 자동 flush 옵션이다. println() 호출 시 즉시 전송된다.
서버가 클라이언트 메시지를 받아서 다시 돌려보내는 에코 서버다.
// 에코 서버
public class EchoServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String line;
while ((line = in.readLine()) != null) {
System.out.println("받음: " + line);
out.println("에코: " + line); // 그대로 돌려보냄
}
socket.close();
serverSocket.close();
}
}
위 코드는 클라이언트 하나만 처리할 수 있다. accept()는 한 번만 호출되기 때문이다. 여러 클라이언트를 동시에 처리하려면 연결마다 스레드를 만들어야 한다.
public class MultiServer {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket(8080);
while (true) {
Socket socket = serverSocket.accept(); // 연결마다 반복
new Thread(() -> {
try {
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
String line;
while ((line = in.readLine()) != null) {
out.println("에코: " + line);
}
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}).start(); // 각 클라이언트를 별도 스레드에서 처리
}
}
}

소켓을 다 쓰고 닫지 않으면 자원이 낭비된다. try-with-resources를 쓰면 자동으로 닫힌다.
try (Socket socket = new Socket("localhost", 8080);
PrintWriter out = new PrintWriter(socket.getOutputStream(), true);
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {
out.println("Hello");
System.out.println(in.readLine());
} // 블록을 벗어나면 자동으로 close()
소켓은 네트워크 통신의 가장 기본 단위다. 서버는 ServerSocket으로 기다리고, 클라이언트는 Socket으로 연결한다. 연결 후엔 스트림으로 데이터를 주고받는다. 클라이언트가 여럿이라면 Thread와 함께 써야 한다는 것도 기억해두면 좋다.