수정필요
1. 통신의 기본

2. Socket 통신

3. 단방향 통신 (Simplex)
1. 단방향 통신
- 서버 측은 수신만 가능하고 클라이언트 측은 송신만 가능하다.
2. 서버 측 코드
package ex20.ch01;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
// 서버는 소켓 두 개 필요
public class MyServer01 {
public static void main(String[] args) {
try {
// 20000번이라는 포트번호로 대기하고 있는 서버 소켓 생성
ServerSocket ss = new ServerSocket(20000);
System.out.println("서버 소켓이 대기 중 입니다. 연결을 시도해주세요.");
// 소켓 연결되면 소켓 추가로 생성됨(포트번호는 랜덤)
// 기본의 서버 소켓(다른 사람의 요청을 받아야 되기 때문)과의 연결은 끊기고 새로 생성된 소켓과 연결됨
Socket socket = ss.accept(); // 프로세스 대기
System.out.println("소켓이 연결되었습니다.");
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
); // 소켓만 읽으면 됨 알아서 상대방 컴퓨터에 연결되어있으니까
String body = br.readLine(); // 버퍼 소비 (여기서 멈춰있음)
System.out.println("서버 측: " + body);
/*
String body = ""; // 데이터 받을 곳
while (true) { // 한 번에 처리 못하는 큰 데이터일 수 있으니까 while 돌리기
body = br.readLine(); // 버퍼 소비 (여기서 멈춰있음)
// 최초에는 대기
// 여기서 대기 상태인데 다 찰 때까지 기다림. \n까지 소비
// 안녕\n 안녕\n 안녕 -> 세 번째 안녕은 null 처리됨
if (body == null) break; // (읽어봤을 때 값 없으면) 버퍼에 값 없으면 while
}
*/
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3. 클라이언트 측 코드
package ex20.ch01;
import java.io.*;
import java.net.Socket;
// 단방향 통신 (Simplex)
public class MyClient01 {
public static void main(String[] args) {
// loop back 주소 = localhost = 내 주소를 의미
try {
Socket socket = new Socket("localhost", 20000); // 소켓 연결
// 키보드로부터 입력 받은 것을 쓸 거임 => 1. 키보드와 연결된 소켓/ 2. 서버와 연결된 소켓
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
System.out.println("키보드 입력 대기 중.....");
String msg = keyboard.readLine(); // 여기서 멈춰있음
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
);
//output - 앱 입장에서 쓰는거, input - 앱 입장에서 받는거
bw.write(msg);
bw.write("\n"); // 중요~~~ 외우세용
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
4. 코드 실행 과정
Current File로 두고 Server 파일과 Client 파일을 각각 실행하기

- MyServer01 파일을 실행한다.
- ServerSocket 이 만들어지고 연결 전 대기
Socket socket = ss.accept(); // 여기서 대기

- MyClient01 파일을 실행한다.
- Socket이 생성되고 서버 측 소켓과 연결
- BufferedReader를 통해 키보드로부터 입력받기 위해 대기
String msg = keyboard.readLine(); // 여기서 멈춰 있음

- 서버 - 클라이언트 소켓이 연결됨을 확인할 수 있다.
- 서버 소켓으로 클라이언트 측의 소켓이 생성되어 보낸 연결 요청을 받는다. (accept())
- 연결을 위한 소켓 생성
- 클라이언트 소켓과 연결
- BufferedReader를 통해 클라이언트 측에서 보낸 문자열 읽어들이기 위해 대기
Socket socket = ss.accept(); // 정확히는 서버 파일 실행 시 여기서 대기 중임
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
String body = br.readLine(); // 버퍼 소비 (여기서 멈춰있음)

- 클라이언트 측에서 문자열 입력
- 키보드로 입력 받은 문자열을 BufferedWriter를 통해 서버 측으로 보낸다.
- 문자열을 읽어들일 수 있도록 반드시 ‘\n’이 필요하다.
- 버퍼가 다 차지 않더라도 보낼 수 있도록 flush()해준다.
bw.write(msg);
bw.write("\n");
bw.flush();

- 서버 측에서 클라이언트가 보낸 문자열 확인

4. 반이중 통신 (Half Duplex)
1. 반이중 통신
- 클라이언트가 보낸 데이터를 서버에서 받아서 확인만 하고 끝나는 게 아니라 받은 데이터에 대하여 응답을 돌려준다. (정보의 전송이 양방향임)
- 양 쪽에서 동시에 송수신이 가능한 전이중 통신(예 - 채팅)과는 달리 한 쪽이 송신하는 동안은 다른 쪽이 송신하지 못한다.
- 예 - 웹
2. 서버 측 코드
package ex20.ch02;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
// 반이중이니까 서버가 받아서 응답 -> BW
public class MyServer02 {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(20000);
System.out.println("서버 소켓이 대기 중 입니다. 연결을 시도해주세요.");
Socket socket = ss.accept();
System.out.println("소켓이 연결되었습니다.");
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream())
);
// request
String reqBody = br.readLine();
// response 응답할 body 내용
// 받은 내용 parsing해서 프로토콜 만들어서 응답
String respBody = "";
// 프로토콜
// 내가 만들었으니까 client에게 알려주어야 됨.
if (reqBody.equals("name")) {
respBody = "metacoding";
} else if (reqBody.equals("age")) {
respBody = "39";
} else {
respBody = "error";
}
// 읽는 버퍼 & 쓰는 버퍼 -> 메모리에 두 개 떠 있음
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
);
bw.write(respBody);
bw.write("\n");
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
3. 클라이언트 측 코드
package ex20.ch02;
import java.io.*;
import java.net.Socket;
// 반이중 Half Duplex (예 - 반이중) (전이중 - 채팅)
public class MyClient02 {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 20000);
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
System.out.println("키보드 입력 대기 중.....");
// client 입장에서 요청하는 것 - request
String reqBody = keyboard.readLine(); // name, age
BufferedWriter bw = new BufferedWriter(
new OutputStreamWriter(socket.getOutputStream())
);
bw.write(reqBody);
bw.write("\n");
bw.flush();
// 응답 받음
BufferedReader br = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
String respBody = br.readLine();
System.out.println(respBody);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
4. 코드 실행 과정
- 단방향 통신과 1,2,3번 과정 동일



- 클라이언트 측에서 키보드로부터 데이터 입력받음
- 이때 키보드로부터 클라이언트가 요청하므로 reqBody라고 지정

- 서버 측에서 클라이언트로부터 받은 데이터를 프로토콜 기준을 확인
- name을 보냈으므로 metacoding을 응답을 보내기 위한 respBody에 담는다
- BufferedWriter를 통해 클라이언트 측으로 respBody를 보낸다
- 클라이언트 측에서 서버가 보낸 응답을 확인
- BufferedReader를 통해 서버가 보낸 응답 받아서 확인 가능하다.

- 연결 종료

5. 전이중
package ex20.ch03;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
// Full Duplex (전이중)
// 논리적인 선 2개 / 비동기적
public class MyServer03 {
public static void main(String[] args) {
try {
ServerSocket ss = new ServerSocket(20000);
System.out.println("서버 소켓이 대기 중 입니다. 연결을 시도해주세요.");
Socket socket = ss.accept();
System.out.println("소켓이 연결되었습니다.");
// 1. 쓰기 분신
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
new Thread(() -> {
while (true) {
try {
String reqBody = keyboard.readLine(); // 쓰기 스레드 대기 -> 굳이 sleep 안줘도 됨!
bw.write(reqBody);
bw.write("\n");
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
// 계속 true니까 BufferedReader 쪽에 에러남 -> 스레드에 이 부분을 맡기자!
}
}).start();
// 2. 읽기 분신
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 새 스레드에 안 맡기면 메인 스레드가 담당
new Thread(() -> {
while (true) {
try {
String respBody = br.readLine();
System.out.println("받은 메세지 : " + respBody);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
// Q. main 스레드가 종료되면 모든게 종료?
// A. X. 모든 스레드가 종료되어야
package ex20.ch03;
import java.io.*;
import java.net.Socket;
public class MyClient03 {
public static void main(String[] args) {
try {
Socket socket = new Socket("localhost", 20000);
// 키보드로부터 받아서 쓰는 것까지는 동기적임
// 1. 쓰기 분신
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
new Thread(() -> {
while (true) {
try {
String reqBody = keyboard.readLine();
bw.write(reqBody);
bw.write("\n");
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
// 2. 읽기 분신
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
new Thread(() -> {
while (true) {
try {
String respBody = br.readLine();
System.out.println("서버로부터 받은 메세지 : " + respBody);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
6. Multiflex
package ex20.ch04;
import java.io.*;
import java.util.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
// Multiflex (채팅)
public class MyServer04 {
private List<ManagerThread> threads = new ArrayList<>();
// 내부 클래스 - 외부에서 접근이 안됨 내부에서만 쓰기 위한 것. (상태에 접근 가능)
class ManagerThread extends Thread {
private String username; // s1, s2, s3
private Socket socket;
private BufferedReader br;
private BufferedWriter bw;
public ManagerThread(Socket socket) {
this.socket = socket;
try {
this.br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
this.bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (Exception e) {
throw new RuntimeException(e);
}
}
public String getUsername() {
return username;
}
public void setUsername() {
try {
// 최초로 들어오는 메세지를 id로 설정
username = br.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
send("당신의 아이디를 전달해주세요."); // send로 한번 호출하고 시작
setUsername();
send("당신의 아이디는 " + getUsername() + "입니다.");
while (true) {
try {
String msg = br.readLine(); // 분신 대기
// 1. + 2. = 서비스?
// 1. 파싱
String[] sps = msg.split(":"); // ALL:msg, ssar:msg
String protocol = sps[0];
String body = sps[1];
// 2. 프로토콜 분석
if (protocol.equals("ALL")) {
for (ManagerThread mt : threads) {
mt.send(body); // 전체 메세지
}
} else {
for (ManagerThread mt : threads) {
if (protocol.equals(mt.getUsername())) {
mt.send(body); // 귓속말
}
}
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public void send(String msg) {
try {
bw.write(msg);
bw.write("\n");
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public MyServer04() {
try {
ServerSocket serverSocket = new ServerSocket(20000);
while (true) {
Socket socket = serverSocket.accept();
// 박스 만들기
ManagerThread mt = new ManagerThread(socket);
// 박스에 담기
threads.add(mt);
// 분신 만들기
mt.start();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
new MyServer04();
}
}
// Q. main 스레드가 종료되면 모든게 종료?
// A. X. 모든 스레드가 종료되어야
package ex20.ch04;
import java.io.*;
import java.net.Socket;
public class MyClient04 {
public static void main(String[] args) {
try {
Socket socket = new Socket("192.168.0.99", 20000);
// 키보드로부터 받아서 쓰는 것까지는 동기적임
// 1. 쓰기 분신
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
new Thread(() -> {
while (true) {
try {
String reqBody = keyboard.readLine();
bw.write(reqBody);
bw.write("\n");
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
// 2. 읽기 분신
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
new Thread(() -> {
while (true) {
try {
String respBody = br.readLine();
System.out.println("서버로부터 받은 메세지 : " + respBody);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Share article