Contents
자바 통신 관련 예제 :1. 단방향 통신 (Simplex Communication)
단방향 통신은 데이터가 한쪽 방향으로만 전송되는 통신 방식입니다. 송신자는 데이터를 보내기만 하고, 수신자는 데이터를 받기만 합니다. 이런 방식은 주로 정보 전달이 일방향으로만 필요한 경우에 사용됩니다. 예를 들어, 라디오 방송이나 텔레비전 방송이 단방향 통신의 대표적인 예입니다. 방송국에서 프로그램을 송출하고, 청취자나 시청자는 이를 수신하기만 합니다.
단방향 통신의 주요 특징은 다음과 같습니다.
- 송신과 수신의 역할이 고정적입니다. 송신자는 항상 데이터를 보내기만 하고, 수신자는 받기만 합니다.
- 데이터 전송 속도가 빠르며 비용이 저렴합니다. 하지만 쌍방향 소통이 불가능하므로 피드백이나 응답이 필요한 경우에는 적합하지 않습니다.
2. 반이중 통신 (Half-Duplex Communication)
반이중 통신은 두 노드가 번갈아 가며 데이터를 주고받을 수 있는 통신 방식입니다. 한 번에 한쪽 방향으로만 데이터를 전송할 수 있으며, 송신과 수신이 동시에 이루어지지 않습니다. 예를 들어, 무전기 통신이 반이중 통신의 예입니다. 한쪽에서 말하고 있을 때 다른 쪽은 듣기만 하고, 말이 끝나면 반대로 작동합니다.
반이중 통신의 주요 특징은 다음과 같습니다.
- 양방향 통신이 가능하지만, 동시에 데이터를 전송할 수는 없습니다.
- 충돌 회피가 용이하며, 비교적 저렴한 비용으로 구현할 수 있습니다.
- 무전기, CB 라디오, 핸드헬드 무선 기기 등에서 흔히 사용됩니다.
3. 전이중통신(양방향) (Full-Duplex Communication)
전이중 통신은 데이터가 양쪽 방향으로 동시에 전송될 수 있는 통신 방식입니다. 송신자와 수신자가 동시에 데이터를 주고받을 수 있습니다. 전화 통화가 전이중 통신의 대표적인 예입니다. 통화 중에 두 사람이 동시에 말하고 들을 수 있습니다.
전이중 통신의 주요 특징은 다음과 같습니다.
- 동시 송수신이 가능하여 효율적인 소통이 가능합니다.
- 고속 데이터 전송이 가능하므로, 대용량 데이터 전송에 적합합니다.
- 네트워크 비용이 상대적으로 높고, 복잡한 회로 설계가 필요할 수 있습니다.
- 인터넷, VoIP(Voice over IP) 전화, 최신 모바일 통신 등이 양방향 통신을 활용합니다.
각 통신 방식은 특정한 상황과 용도에 따라 적합하게 사용됩니다. 예를 들어, 방송국과 시청자 간의 일방적인 정보 전달에는 단방향 통신이, 무전기와 같은 간헐적인 통신에는 반이중 통신이, 전화 통화나 인터넷 통신과 같은 상호 소통이 필요한 경우에는 전이중 통신이 사용됩니다.
자바 통신 관련 예제 :
단방향 통신:
단방향 통신에서는 클라이언트가 서버로 메시지를 보낸 후, 서버는 이를 읽기만 합니다. 클라이언트의 메시지에 대한 서버의 응답은 없습니다.
MyServer (서버 측):
클릭하면 전체 코드 내용을 볼 수 있습니다.
public class MyServer {
public static void main(String[] args) {
try {
// 1. 서버 소켓 생성
ServerSocket serverSocket = new ServerSocket(10000);
// 2. 클라이언트 요청을 기다림
Socket socket = serverSocket.accept();
// 연결되면 아래로 진행
System.out.println("oh! connect?");
// 3. 클라이언트의 메시지를 읽기 위해 버퍼 리더 생성
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 4. 메시지 읽기
String line = br.readLine(); // 버퍼에 있는 메시지를 \n까지 읽는다.
System.out.println("read : " + line);
} catch (Exception e) {
e.printStackTrace();
}
}
}
MyClient (클라이언트 측):
클릭하면 전체 코드 내용을 볼 수 있습니다.
public class MyClient {
public static void main(String[] args) throws IOException {
// 1. 서버에 연결
Socket socket = new Socket("localhost", 10000);
// 2. 서버로 메시지를 보내기 위해 버퍼 라이터 생성
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
// 3. 메시지 보내기
bw.write("Hello World\n");
bw.flush(); // 메시지를 버퍼에서 실제로 전송
}
}
반이중 통신:
반이중 통신에서는 클라이언트가 서버로 메시지를 보내고, 서버가 이를 처리한 후 클라이언트에게 응답을 보냅니다. 클라이언트는 서버의 응답을 받습니다.
MyServer (서버 측):
클릭하면 전체 코드 내용을 볼 수 있습니다.
package ex02;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
public class MyServer {
public static void main(String[] args) {
try {
// 1. 서버 소켓 생성
ServerSocket serverSocket = new ServerSocket(20000);
// 2. 클라이언트 요청을 기다림
Socket socket = serverSocket.accept();
System.out.println("1. connect");
// 3. 클라이언트의 메시지를 읽기 위해 버퍼 리더 생성
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 4. 클라이언트 메시지 읽기
String line = br.readLine(); // 버퍼에 있는 메시지를 \n까지 읽는다.
System.out.println("2. read : " + line);
// 5. 클라이언트에게 응답하기 위해 프린트 라이터 생성
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
pw.println("3. server send : 요청에 대한 서버응답 내용");
} catch (Exception e) {
e.printStackTrace();
}
}
}
MyClient (클라이언트 측):
클릭하면 전체 코드 내용을 볼 수 있습니다.
package ex02;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
public class MyClient {
public static void main(String[] args) throws IOException {
// 1. 서버에 연결
Socket socket = new Socket("localhost", 20000);
// 2. 서버로 메시지를 보내기 위해 프린트 라이터 생성
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
pw.println("client send : 클라이언트가 서버에게 요청하는 내용");
// 3. 서버의 응답을 읽기 위해 버퍼 리더 생성
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 4. 서버의 응답 읽기
String line = br.readLine(); // 버퍼에 \n까지 비우기
System.out.println(line); // 서버의 응답 출력
}
}
상태 유지 통신 (Stateful):
상태 유지 통신에서는 클라이언트와 서버가 지속적으로 통신을 주고받을 수 있으며, 여러 메시지를 주고받는 상태를 유지합니다.
아래 예제에서는 반이중 통신 예제의 내용(요청과 응답 후 연결해제) 에서 연결을 끊지 않고 지속적으로 반이중 통신을 할 수 있도록 구성되어있습니다.
MyServer (서버 측):
클릭하면 전체 코드 내용을 볼 수 있습니다.
package ex02;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/*
buyer:1 -> 사과
seller:1 -> 당근
buyer:2 -> 라면
seller:2 -> 우유
위 유형이 아니면 404 응답
*/
public class MyServer {
public static void main(String[] args){
try {
// 1. 리스너 생성 및 대기
ServerSocket serverSocket = new ServerSocket(20000);
Socket socket = serverSocket.accept();
// 2. 연결되었을 때 버퍼 달기
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
// 3. 요청받고, 응답하기
// 반복문을 사용하여 연결을 끊지 않고 지속적으로 받은 요청에 대한 응답을 한다
while(true) {
String line = br.readLine();
if (line.equals("exit")) break;
String response = parser(line);
pw.println(response);
// 요청이 너무 많이 들어왔을 때 쓰레드는 너무 빨리 돌아 부하가 커질 수 있기 때문에 쉴 시간을 추가로 부여한다.
Thread.sleep(10);
}
} catch (Exception e) {
e.printStackTrace();
}
}
private static String parser(String line) {
String[] split = line.split(":");
String response = "404 응답";
if (split[0].equals("buyer")) {
if(split[1].equals("1")) {
response = "사과";
} else if (split[1].equals("2")) {
response = "라면";
}
}
if (split[0].equals("seller")) {
if(split[1].equals("1")) {
response = "당근";
} else if (split[1].equals("2")) {
response = "우유";
}
}
return response;
}
}
MyClient (클라이언트 측):
클릭하면 전체 코드 내용을 볼 수 있습니다.
package ex02;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class MyClient {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
Socket socket = new Socket("localhost", 20000);
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
// 반복문을 통해 연결을 끊지 않고 계속 값을 쓰고 읽는다.
while (true) {
pw.println(scanner.nextLine());
String line = br.readLine();
System.out.println(line);
}
}
}
멀티스레드 서버 (ex03 문제):
멀티스레드 서버에서는 클라이언트와 연결되면 새로운 스레드를 만들어 요청을 처리하고, 메인 스레드는 다른 요청을 리스닝합니다.
MyServer (서버 측):
클릭하면 전체 코드 내용을 볼 수 있습니다.
package ex03;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
/*
buyer:1 -> 사과
seller:1 -> 당근
buyer:2 -> 라면
seller:2 -> 우유
위 유형이 아니면 404 응답
*/
public class MyServer {
public static void main(String[] args) {
try {
// 1. 리스너 생성
ServerSocket serverSocket = new ServerSocket(20000);
System.out.println("서버가 시작되었습니다.");
while (true) {
// 2. 요청에 대한 리스너
Socket socket = serverSocket.accept();
System.out.println("클라이언트가 연결되었습니다.");
// 3. 요청이 들어왔을 때 새로운 쓰레드를 만들어 요청을 처리, 메인 쓰레드는 2번으로 돌아감
new Thread(() -> {
try (BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true)) {
// 요청받고, 응답하기
String line = br.readLine();
String response = parser(line);
pw.println(response);
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
// 소켓을 제대로 닫지 않으면 리소스 누수 문제가 발생할 수 있음
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}).start(); // 스레드를 시작
}
} catch (Exception e) {
System.out.println("예외 발생");
e.printStackTrace();
}
}
private static String parser(String line) {
String[] split = line.split(":");
String response = "404 응답";
if (split[0].equals("buyer")) {
if (split[1].equals("1")) {
response = "사과";
} else if (split[1].equals("2")) {
response = "라면";
}
} else if (split[0].equals("seller")) {
if (split[1].equals("1")) {
response = "당근";
} else if (split[1].equals("2")) {
response = "우유";
}
}
return response;
}
}
MyClient (클라이언트 측):
클릭하면 전체 코드 내용을 볼 수 있습니다.
package ex03;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;
public class MyClient {
public static void main(String[] args) throws IOException {
Scanner scanner = new Scanner(System.in);
Socket socket = new Socket("localhost", 20000);
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
pw.println(scanner.nextLine());
String line = br.readLine(); // 버퍼에 \n까지 비우기
System.out.println(line);
// 해당 클라이언트는 단순히 요청과 응답을 받고 종료됨
}
}
※참고사항 :
네트워크 소켓을 제대로 닫지 않으면 리소스 누수 문제가 발생할 수 있습니다. 소켓은 네트워크 자원을 사용하는데, 이를 닫지 않으면 운영 체제에 의한 리소스 해제가 제대로 이루어지지 않아 시스템 자원이 낭비될 수 있습니다. 특히, 장기간 실행되는 서버 프로그램에서는 이러한 문제가 누적될 수 있습니다.
이유 :
- 시스템 자원 사용: 소켓은 운영 체제에서 파일 디스크립터를 사용하며, 이를 닫지 않으면 해당 자원이 해제되지 않습니다.
- 네트워크 포트 잠금: 소켓이 제대로 닫히지 않으면 포트가 해제되지 않아 다른 애플리케이션이 해당 포트를 사용할 수 없습니다.
- 메모리 누수: 소켓이 닫히지 않으면 메모리가 해제되지 않으며, 이는 메모리 누수로 이어질 수 있습니다.
Share article