[Java] 자바 소켓(socket) 프로그래밍 - 서버(server)와 클라이언트(client) 소켓 만드는 법
소켓(socket)
- 통신에 필요한 정보를 담고 있는 객체이다. 이 객체를 이용하여 통신을 수행한다.
- 소켓 생성에 필요한 값
1) 인터넷 주소(IP Address, 도메인 주소)
예) Socket socket = new Socket("localhost", 8888); localhost = 도메인 주소
2) 포트번호(통신하는 프로그램을 구분하기 위한 식별번호)
예) ServerSocket serverSocket = new ServerSocket(8888); 8888 = 포트번호
- 용어
1) 클라이언트(client) : 접속을 요청하는 쪽
2) 서버(server) : 접속을 받아들이는 쪽
3) 포트번호 :
- OS가 구분할 수 있도록 통신프로그램에 부여되는 식별 번호
- 클라이언트 쪽 프로그램은 OS가 포트번호를 자동으로 부여한다.
- 서버 쪽은 프로그램 스스로가 지정한다.
단 사용 중인 포트 번호를 중복 사용할 수 없다.
기존에 특정 목적으로 널리 이용되는 포트 번호도 피해야 한다.
예) FTP(20,21), Telnet(23), SSH(22), SMTP(25), HTTP(80), POP3(110),
IMAP4(143), HTTPS(443), 프록시서버(8080), MySQL(3306)
4) 대기열 :
- 클라이언트가 서버에 접속을 요청했을 때, 그 클라이언트의 연결 정보를
저장한 목록이다.
- 대기열이 꽉차있을 경우, 클라이언트의 연결 요청을 거절한다.
- 종류
1) 서버 소켓
- 서버측 프로그램을 작성할 때 사용한다.
- ServerSocket 클래스를 이용하여 만든다.
- 다른 프로그램과 중복되지 않도록 포트 번호를 지정한다.
2) 클라이언트 소켓
- 클라이언트측 프로그램을 작성할 때 사용한다.
- Socket 클래스를 이용하여 만든다.
- 접속하려는 상대편 인터넷주소(IP 주소, 도메인 주소)를 지정한다.
- 접속하려는 상대편의 포트번호를 지정한다.
(1) 단순하게 서버를 만들어보기만 해보자!
서버 소켓을 만드는 예제 I
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Test01_1 {
public static void main(String[] args) throws Exception {
System.out.println("서버 실행 중...");
// 1) 서버 소켓 만들기
ServerSocket serverSocket = new ServerSocket(8888);
// 2) 대기열의 첫 번째 클라이언트의 접속을 승인한다.
// => 만약 대기 중인 클라이언트가 없다면, 리턴하지 않고 접속할 때까지 기다린다.
// => 여러 클라이언트가 대기 중이라면 그 중 첫 번째 클라이언트와 연결한다.
// => 리턴 값: 대기열에 등록된 첫 번째 클라이언트의 연결 정보를 가지고 만든 소켓 객체이다.
Socket socket = serverSocket.accept();
// 3) 소켓을 통해 입출력을 할 수 있도록 스트림 객체를 만든다.
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// 4) 소켓으로 클라이언트와 연결되면 데이터를 주고 받는 순서는 상관없다.
// 그러나 일반적으로 클라이언트에서 먼저 데이터를 전송한다.
// 그래서 서버는 클라이언트의 데이터를 먼저 읽고 그에 대한 응답을 한다.
int b = in.read(); // 클라이언트에서 1바이트를 보낼 때까지 계속 기다린다(blocking).
System.out.printf("%x\n", b);
out.write(0x01); // 서버가 보낸 1바이트 데이터를 클라이언트에서 읽을 때까지 리턴하지 않는다.
// 5) 클라이언트와 데이터 입출력이 끝났으면 연결을 끊는다.
in.close();
out.close();
socket.close();
serverSocket.close();
}
}
- 실행 결과 :
서버 실행 중...
클라이언트 소켓을 만드는 예제 I
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
public class Test01_2 {
public static void main(String[] args) throws Exception {
// 1) 클라이언트 소켓 만들기
// => 첫 번째 파라미터: 접속하려는 상대편 프로그램의 컴퓨터 주소
// => 두 번째 파라미터: 접속하려는 상대편 프로그램의 포트 번호
// => 자신의 포트번호?
// OS가 자동으로 부여한다.
// 개발자가 신경쓸 필요가 없다.
// 즉 쌍방 모두 포트 번호가 반드시 있어야 한다는 의미!
//
Socket socket = new Socket("localhost", 8888);
// 2) 소켓을 통해 입출력을 할 수 있도록 스트림 객체를 만든다.
InputStream in = socket.getInputStream();
OutputStream out = socket.getOutputStream();
// 3) 서버에서 먼저 데이터를 읽는다면, 클라이언트는 먼저 데이터를 보내야 한다.
// 꺼꾸로 서버에서 데이터를 먼저 보낸다면, 클라이언트는 먼저 데이터를 읽어야 한다.
out.write(0x77); // 클라이언트가 보낸 데이터를 서버가 모두 읽을 때까지 리턴하지 않는다(blocking).
int b = in.read(); // 서버가 보낸 데이터를 1바이트 읽을 때까지 리턴하지 않는다.
System.out.printf("%x\n", b);
// 4) 서버와의 데이터 입출력이 끝났으면 연결을 끊는다.
in.close();
out.close();
socket.close();
}
}
- 실행 결과 :
서버 실행 중...
77
(2) 이번엔 직접 클라이언트로부터 값을 입력받아 서버에서 뿌려보자!
서버 소켓을 만드는 예제 II
public class Test02_1 {
public static void main(String[] args) throws Exception {
System.out.println("서버 실행 중...");
// ServerSocket(port, backlog)
// => port: 포트번호
// => backlog: 대기열의 크기
ServerSocket serverSocket = new ServerSocket(8888, 3);
System.out.println("=> 서버 소켓 생성 완료!");
// 대기열에 기다리고 있는 클라이언트가 없다면, 리턴하지 않는다.
Socket socket = serverSocket.accept();
System.out.println("=> 클라이언트 연결 승인!");
InputStream in0 = socket.getInputStream();
OutputStream out0 = socket.getOutputStream();
Scanner in = new Scanner(in0);
PrintStream out = new PrintStream(out0);
String str = in.nextLine(); // 클라이언트로부터 문자열을 한 줄 읽는다.
out.println(str); // 클라이언트가 보낸 문자열을 그대로 돌려준다.
in.close();
in0.close();
out.close();
out0.close();
socket.close();
serverSocket.close();
}
}
- 실행 결과 :
서버 실행 중...
=> 서버 소켓 생성 완료!
클라이언트 소켓을 만드는 예제 II
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;
public class Test02_2 {
public static void main(String[] args) throws Exception {
// 키보드에서 데이터를 읽는 스캐너 객체 준비
Scanner keyScanner = new Scanner(System.in);
// 1) 접속할 서버 주소 입력 받기
System.out.print("서버 주소: ");
String serverAddress = keyScanner.nextLine();
// 2) 접속할 서버의 포트 번호 입력 받기
System.out.print("포트 번호: ");
int port = Integer.parseInt(keyScanner.nextLine());
// 3) 사용자로부터 입력 받은 값을 가지고 서버에 접속한다.
Socket socket = new Socket(serverAddress, port);
System.out.println("=> 소켓 객체 생성 완료!");
// 4) 입출력할 스트림 객체 준비
InputStream in0 = socket.getInputStream();
OutputStream out0 = socket.getOutputStream();
// 5) 스트림 객체에 입출력 보조 객체를 연결한다.
Scanner in = new Scanner(in0);
PrintStream out = new PrintStream(out0);
System.out.println("=> 입출력 스트림 준비 완료!");
// 6) 키보드에서 서버에 보낼 메시지를 입력 받는다.
System.out.print("서버에 보낼 메시지: ");
String message = keyScanner.nextLine();
// 7) 키보드에서 입력 받은 메시지를 서버에 보낸다.
out.println(message);
System.out.println("=> 서버에 메시지 전송 완료!");
// 8) 서버가 보낸 메시지를 읽는다.
String response = in.nextLine();
System.out.println("=> 서버로부터 메시지 수신 완료!");
// 9) 서버가 보낸 메시지를 화면에 출력한다.
System.out.println(response);
in.close();
in0.close();
out.close();
out0.close();
socket.close();
keyScanner.close();
}
}
- 다음과 같이 명령 프롬프트 창에서 실행시켜보자! (이클립스도 상관 x)
결과는 다음과 같다.