Contents
1. 통신의 기본1. 통신에 필요한 것2. 포트 번호 부여2. 소켓 통신 원리1. 서버2. 클라이언트 3. 소켓 통신 구조1. 소켓 통신 그림⭐2. 멀티스레드를 사용해서 통신하는 법 4. 방화벽 참고5. 통신 방향1. 단 방향 Simplex2. 반 이중 방향 Half duplex3. 전 이중 방향 Full duflex4. 통신 muliplex6. 소켓 통신 숙제1. 요구 사항서버클라이언트2. 풀이1. 코드의 결과를 잘 모를 경우2. 유효성 검사를 꼭 하자3. 각 처리 과정은 메서드로 분리3. 풀이를 보고 수정함서버클라이언트1. 통신의 기본

1. 통신에 필요한 것
- 프로토콜
- IP주소
- 포트 번호
- 버퍼
2. 포트 번호 부여
- 통신이 필요한 프로세스만 포트 번호를 부여 받는다.
2. 소켓 통신 원리

1. 서버
1. 요구 사항
- 소켓의 포트 번호
- 통신 프로토콜(TCP로 고정)
2. 클라이언트
1. 요구 사항
- 서버의 소켓 포트 번호
- 서버의 IP주소
- 통신 프로토콜(TCP로 고정)
3. 소켓 통신 구조
서버 소켓, 소켓
1. 소켓 통신 그림⭐

2. 멀티스레드를 사용해서 통신하는 법

4. 방화벽 참고


- 기본적으로 OS는 모든 포트 번호를 닫아 놓는다.
- 통신이 필요한 포트 번호를 따로 허가해준다.
- 특정 프로세스가 포트를 요청하면 항상 열어둘 것 인지 사용자에게 물어본다.(방화벽 관리자 권한 허가)
5. 통신 방향
1. 단 방향 Simplex
- 메시지를 보내고 끝
서버
package ex20.ch01;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
// 단방향 통신 one way
public class MyServer01 {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(20000); // 요청만 받는 소켓
System.out.println("서버 소켓이 요청 대기중 입니다. 연결을 시도해 주세요.");
Socket socket = serverSocket.accept(); // 프로세스 대기, 포트번호는 랜덤으로 지정해줌
System.out.println("통신 소켓이 연결 되었습니다.");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String body = "";
body = br.readLine(); // 버퍼값 소비, 버퍼가 가득 차면 read 그 전까지 대기, \n까지 읽는다
System.out.println("서버측 Body 확인 : " + body);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
클라이언트
package ex20.ch01;
import java.io.*;
import java.net.Socket;
// 단방향 통신 one way
public class MyClient01 {
public static void main(String[] args) {
try {
// localhost라는 이름은 자기자신의 ip주소를 키워드로 만들어 둔것. 숫자로된 ip주소는 외부인터넷으로 나가야 확인 가능
Socket socket = new Socket("localhost", 20000); // 소켓 연결
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
System.out.println("키보드 입력 대기중...");
String msg = keyboard.readLine(); // \n까지 키보드 입력 대기중
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
bw.write(msg);
bw.write("\n");
bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
2. 반 이중 방향 Half duplex
- 메시지를 보내고 응답을 받으면 끝
서버
package ex20.ch02;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
// 반이중 방향 통신 (주고 난 뒤 받기) half way
public class MyServer02 {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(20000); // 요청만 받는 소켓
System.out.println("서버 소켓이 요청 대기중 입니다. 연결을 시도해 주세요.");
Socket socket = serverSocket.accept(); // 프로세스 대기, 포트번호는 랜덤으로 지정해줌
System.out.println("통신 소켓이 연결 되었습니다.");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
String reqBody = "";
String respBody = "";
reqBody = br.readLine(); // 버퍼값 소비, 버퍼가 가득 차면 read 그 전까지 대기, \n까지 읽는다
// 프로토콜
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);
}
}
}
클라이언트
package ex20.ch02;
import java.io.*;
import java.net.Socket;
// 반이중 방향 통신 (주고 난 뒤 받기) half way
public class MyClient02 {
public static void main(String[] args) {
try {
// localhost라는 이름은 자기자신의 ip주소를 키워드로 만들어 둔것. 숫자로된 ip주소는 외부인터넷으로 나가야 확인 가능
Socket socket = new Socket("localhost", 20000); // 소켓 연결
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
System.out.println("키보드 입력 대기중...");
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);
}
}
}
3. 전 이중 방향 Full duflex
- 메시지를 보내면서 응답을 받는 것
서버
package ex20.ch03;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
// full duplex way
public class MyServer03 {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(20000); // 요청만 받는 소켓
System.out.println("서버 소켓이 요청 대기중 입니다. 연결을 시도해 주세요.");
Socket socket = serverSocket.accept(); // 프로세스 대기, 포트번호는 랜덤으로 지정해줌
System.out.println("통신 소켓이 연결 되었습니다.");
// 1. 쓰기 분신
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
BufferedReader keyboard = new BufferedReader(new InputStreamReader(System.in));
new Thread(() -> {
while (true) {
try {
String reqBody = keyboard.readLine();
bw.write(reqBody);
bw.write("\n");
bw.flush();
System.out.println("나 : " + reqBody);
} 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);
}
System.out.println("메인 스레드 종료");
}
}
클라이언트
package ex20.ch03;
import java.io.*;
import java.net.Socket;
// 반이중 방향 통신 (주고 난 뒤 받기) full duplex
public class MyClient03 {
public static void main(String[] args) {
try {
// localhost라는 이름은 자기자신의 ip주소를 키워드로 만들어 둔것. 숫자로된 ip주소는 외부인터넷으로 나가야 확인 가능
Socket socket = new Socket("localhost", 20000); // 소켓 연결
System.out.println("소켓에 연결되었습니다.");
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();
System.out.println("나 : " + reqBody);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
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);
}
}
}
4. 통신 muliplex
서버
package ex20.ch04;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;
// multiflex (채팅)
public class MyServer04 {
private List<ManagerThread> threads = new ArrayList<>();
class ManagerThread extends Thread {
private String username;
private Socket socket;
private BufferedReader br;
private BufferedWriter bw;
public ManagerThread(Socket socket) {
this.socket = socket;
try {
this.br = new BufferedReader(new InputStreamReader(this.socket.getInputStream()));
this.bw = new BufferedWriter(new OutputStreamWriter(this.socket.getOutputStream()));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public void setUsername() {
try {
this.username = this.br.readLine();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
@Override
public void run() {
send("당신의 아이디를 전달해 주세요");
setUsername();
send("당신의 아이디는 : " + this.username);
while (true) {
try {
String msg = this.br.readLine();
// 파싱 & 프로토콜은 서비스다
// 1. 파싱
String[] split = msg.split(":"); // All:msg | s1:msg
String protocol = split[0];
String body = split[1];
// 2. 프로토콜
if (protocol.equals("ALL")) {
for (ManagerThread thread : threads) {
thread.send(body);
}
} else {
for (ManagerThread thread : threads) {
if (protocol.equals(thread.getUsername())) {
thread.send(body);
}
}
}
} catch (Exception e) {
}
}
}
private String getUsername() {
return this.username;
}
public void send(String msg) {
try {
this.bw.write(msg);
this.bw.write("\n");
this.bw.flush();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
public MyServer04() {
try {
ServerSocket serverSocket = new ServerSocket(20000);
while (true) {
Socket socket = serverSocket.accept();
ManagerThread managerThread = new ManagerThread(socket);
this.threads.add(managerThread);
managerThread.start();
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public static void main(String[] args) {
new MyServer04();
}
}
절차
- 소켓 스레드 리스트 생성
- 서버 소켓 생성
- 소켓 연결 요청 대기
- 담당 스레드 생성
- 스레드의 이름, 소켓, 쓰기버퍼, 읽기버퍼 생성
- 스레드 리스트에 저장
- 스레드 실행
- run() 실행
- 버퍼읽기 대기
- 파싱
- 프로토콜 확인
- 대상지정
- 메시지 날리기
클라이언트
package ex20.ch04;
import java.io.*;
import java.net.Socket;
// 반이중 방향 통신 (주고 난 뒤 받기) full duplex
public class MyClient04 {
public static void main(String[] args) {
try {
// localhost라는 이름은 자기자신의 ip주소를 키워드로 만들어 둔것. 숫자로된 ip주소는 외부인터넷으로 나가야 확인 가능
Socket socket = new Socket("192.168.0.99", 20000); // 소켓 연결
System.out.println("소켓에 연결되었습니다.");
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();
System.out.println("나 : " + reqBody);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}).start();
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. 소켓 통신 숙제
1. 요구 사항
- ch02 반 이중 통신을 이용하라(스레드 필요 없음)
- 서버, 클라이언트 1:1 구조
- HashMap 만들기(서버)
- 프로토콜 GET, POST, PUT, DELETE
- 클라이언트는 메시지를 아래와 같이 보낸다
GET:name -> HashMap에서 name의 값을 찾아서 클라이언트에게 전달. 없으면 -> 404 전달
POST:age/20 -> HashMap에 age키 20값 저장. -> 클라이언트에게 ok 전달
PUT:name/홍길동 -> HashMap에 name키 홍길동값 변경. 없으면 -> 404 전달
DELETE:phone -> HashMap에서 phone키 삭제. 없으면 -> 404 전달
서버
package ex20.homework;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class Server {
public static void main(String[] args) {
// 저장소 생성
Map<String, String> map = new HashMap<>();
try {
// 서버 준비
ServerSocket serverSocket = new ServerSocket(20000);
System.out.println("✅ 소켓 서버 준비 완료.");
Socket socket = serverSocket.accept();
System.out.println("🆗 소켓 서버 연결됨.");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while (true) {
// 읽기 대기
System.out.println("🔄 요청 대기중...");
String msg = br.readLine();
// 파싱
String[] parsedData = msg.split(":");
if (parsedData.length < 2) {
bw.write("400");
bw.write("\n");
bw.flush();
continue;
}
String protocol = parsedData[0].toUpperCase();
String body = parsedData[1];
//프로토콜
if (protocol.equals("GET")) {
String value = map.get(body);
if (value != null) {
bw.write(value);
bw.write("\n");
bw.flush();
} else {
bw.write("404");
bw.write("\n");
bw.flush();
}
} else if (protocol.equals("POST")) {
String[] keyAndValue = body.split("/");
map.put(keyAndValue[0], keyAndValue[1]);
bw.write("ok");
bw.write("\n");
bw.flush();
} else if (protocol.equals("PUT")) {
String[] keyAndValue = body.split("/");
Object result = map.replace(keyAndValue[0], keyAndValue[1]);
if (result == null) {
bw.write("404");
bw.write("\n");
bw.flush();
} else {
bw.write("ok");
bw.write("\n");
bw.flush();
}
} else if (protocol.equals("DELETE")) {
Object result = map.remove(body);
if (result == null) {
bw.write("404");
bw.write("\n");
bw.flush();
} else {
bw.write("ok");
bw.write("\n");
bw.flush();
}
} else {
bw.write("400");
bw.write("\n");
bw.flush();
}
System.out.println("❗ 응답 완료.");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

클라이언트
package ex20.homework;
import java.io.*;
import java.net.Socket;
public class Client {
public static void main(String[] args) {
try {
// 클라이언트 준비
Socket socket = new Socket("localhost", 20000);
BufferedReader keyBoard = new BufferedReader(new InputStreamReader(System.in));
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while (true) {
// 요청 메시지 작성
System.out.println("🔄 요청 메시지를 작성해 주세요...");
String reqMsg = keyBoard.readLine();
// 요청 메시지 보내기
bw.write(reqMsg);
bw.write("\n");
bw.flush();
// 응답 메시지 읽기
String resMsg = br.readLine();
System.out.println(resMsg);
System.out.println("❗ 요청 완료.");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}

2. 풀이
1. 코드의 결과를 잘 모를 경우
Test 파일을 만들어 부분 테스트를 해보자
- Map 자료형의 메서드 결과값을 잘 모를 경우
package ex20.ch05;
import java.util.HashMap;
import java.util.Map;
public class MyTest {
public static void main(String[] args) {
Map<String, Object> map = new HashMap<>();
String respBody = (String) map.get("name");
System.out.println(respBody);
}
}

- String 자료형의 split메서드의 결과값을 잘 모를 경우
package ex20.ch05;
import java.util.HashMap;
import java.util.Map;
public class MyTest02 {
public static void main(String[] args) {
String reqBody = "GET~name";
String[] method = reqBody.split(":");
System.out.println(method.length);
System.out.println(method[0]);
}
}

2. 유효성 검사를 꼭 하자
서버
// 유효성 검사
try {
msg = reqBody.split(":")[1];
switch (method) {
case "GET":
respBody = doGet(msg);
break;
case "POST":
respBody = doPost(msg);
break;
case "PUT":
respBody = doPut(msg);
break;
case "DELETE":
respBody = doDelete(msg);
break;
default:
respBody = "404";
}
} catch (Exception e) {
respBody = "프로토콜이 잘못됐어요. GET:key, POST:key/value, PUT:key/value, DELETE:key 중 하나를 사용해 주세요.";
}
클라이언트
// 유효성 검사
while (true) {
reqBody = keyIn.readLine();
if (reqBody.startsWith("GET:") ||
reqBody.startsWith("POST:") ||
reqBody.startsWith("PUT:") ||
reqBody.startsWith("DELETE:")) break;
else System.out.println("GET:, POST:, PUT:, DELETE:");
}
3. 각 처리 과정은 메서드로 분리
// name
static private String doGet(String msg) {
String respBody = (String) datas.get(msg);
return respBody != null ? respBody : "404";
}
// age/20
static private String doPost(String msg) {
String key = msg.split("/")[0];
String value = msg.split("/")[1];
datas.put(key, value);
return "ok";
}
// name/홍길동
static private String doPut(String msg) {
return "";
}
// phone
static private String doDelete(String msg) {
return "";
}
3. 풀이를 보고 수정함
서버
package ex20.ch05;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;
public class MyServer {
// 저장소 생성
static Map<String, Object> map = new HashMap<>();
static String doGet(String body) {
return map.get(body) != null ? (String) map.get(body) : "404";
}
static String doPost(String body) {
try {
String key = body.split("/")[0];
String value = body.split("/")[1];
map.put(key, value);
} catch (Exception e) {
return "404";
}
return "ok";
}
static String doPut(String body) {
try {
String key = body.split("/")[0];
String value = body.split("/")[1];
map.replace(key, value);
} catch (Exception e) {
return "404";
}
return "ok";
}
static String doDelete(String body) {
return map.remove(body) != null ? "ok" : "404";
}
public static void main(String[] args) {
try {
// 서버 준비
ServerSocket serverSocket = new ServerSocket(20000);
System.out.println("✅ 소켓 서버 준비 완료.");
Socket socket = serverSocket.accept();
System.out.println("🆗 소켓 서버 연결됨.");
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String respBody = "";
while (true) {
// 읽기 대기
System.out.println("🔄 요청 대기중...");
String msg = br.readLine();
// 파싱
String[] parsedData = msg.split(":");
String protocol = parsedData[0];
String body = "";
//프로토콜
try {
body = parsedData[1];
switch (protocol) {
case "GET":
respBody = doGet(body);
break;
case "POST":
respBody = doPost(body);
break;
case "PUT":
respBody = doPut(body);
break;
case "DELETE":
respBody = doDelete(body);
break;
default:
respBody = "404";
}
} catch (Exception e) {
respBody = "프로토콜이 잘못됐어요. GET:key, POST:key/value, PUT:key/value, DELETE:key 중 하나를 사용해 주세요.";
}
bw.write(respBody);
bw.write("\n");
bw.flush();
System.out.println("❗ 응답 완료.");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
클라이언트
package ex20.ch05;
import java.io.*;
import java.net.Socket;
public class MyClient {
public static void main(String[] args) {
try {
// 클라이언트 준비
Socket socket = new Socket("localhost", 20000);
BufferedReader keyIn = new BufferedReader(new InputStreamReader(System.in));
BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
String reqMsg = "";
while (true) {
// 유효성 검사
while (true) {
// 요청 메시지 작성
System.out.println("🔄 요청 메시지를 작성해 주세요...");
reqMsg = keyIn.readLine();
if (reqMsg.startsWith("GET:") ||
reqMsg.startsWith("POST:") ||
reqMsg.startsWith("PUT:") ||
reqMsg.startsWith("DELETE:")) break;
else System.out.println("GET:, POST:, PUT:, DELETE: 로 시작해 주세요.");
}
// 요청 메시지 보내기
bw.write(reqMsg);
bw.write("\n");
bw.flush();
// 응답 메시지 읽기
String resMsg = br.readLine();
System.out.println(resMsg);
System.out.println("❗ 요청 완료.");
}
} catch (IOException e) {
throw new RuntimeException(e);
}
}
}
Share article