기본 설정
소켓 통신의 흐름
- ServerSocket 객체 생성
- accept() 메서드 호출
- 소켓으로부터 Steam 객체 얻기
- 상호 대화
- 종료
실행 순서 : 서버 > 클라이언트
소켓 통신 과정
Server 입장
- Application데이터 생성
Application에서 데이터가 생성
이 데이터는 전송 계층으로 전달
- Segment 생성
전송 계층에서는 데이터를 Segment 로 나눔
각 Segment에는 TCP Header 와 Application데이터가 들어갑니다.
- Header 추가
각 Segment에는 TCP Header 가 추가
송신자와 수신자의 포트 번호, 시퀀스 넘버, 확인 응답 번호 등의 정보가 있음
- Packet생성
Segment에 TCP Header 가 추가
네트워크 계층으로 넘어가며 IP Header 가 추가 ⇒ Packet
- 데이터 전송
Packet은 네트워크를 통해 목적지로 전송
Packet의 전송 경로는 라우팅 등의 과정을 거쳐 목적지에 도착
- 도착지에서의 Packet처리
Packet이 도착지에 도달
네트워크 계층에서 IP Header를 제거하고 전송 계층으로 넘겨짐
- Segment 재조립
전송 계층에서는 Packet으로 도착한 데이터를 Segment로 다시 조립
- Header 제거
Segment에서는 TCP Header를 제거하고 Application 계층으로 데이터를 전달
- Application 데이터 처리
Application에서는 받은 데이터를 처리
Client 입장
- Application데이터 생성
Application에서 데이터가 생성
전송 계층으로 전달
- Segment생성
전송 계층에서는 데이터를 Segment로 나눔
각 Segment에는 TCP 헤더와 Application 데이터가 들어감
- Header추가
각 Segment에는 TCP Header가 추가
송신자와 수신자의 포트 번호, 시퀀스 넘버, 확인 응답 번호 등
- Packet생성
Segment에 TCP Header가 추가
네트워크 계층으로 넘어가며 IP Header가 추가 ⇒ Packet
- 데이터 전송
Packet은 네트워크를 통해 서버로 전송
Packet의 전송 경로는 라우팅 등의 과정을 거쳐 서버에 도착
- 도착지에서의 Packe처리
- Packet이 Server에 도착
네트워크 계층에서 IP Header를 제거하고 전송 계층으로 넘겨짐
- Segment 재조립
전송 계층에서는 Packet으로 도착한 데이터를 Segment로 다시 조립
- Header 제거
Segment에서는 TCP Header를 제거
Application 계층으로 데이터를 전달
- Application데이터 처리
Application에서는 받은 데이터를 처리
단방향 통신 : 한쪽만 보내는 것
package ex17.oneway; import java.io.BufferedWriter; import java.io.IOException; import java.io.OutputStreamWriter; import java.net.Socket; import java.util.Scanner; public class Client { public static void main(String[] args) { // 127.0.0.1 = localhost, 루프백 try { Socket socket = new Socket("127.0.0.1", 10000); Scanner sc = new Scanner(System.in); String msg = sc.nextLine(); BufferedWriter bw = new BufferedWriter( // 버퍼로 만듦 new OutputStreamWriter(socket.getOutputStream(), "UTF-8") // 보조스트림 만듦 -> 아웃풋 스트림 연결 ); bw.write(msg+"\n"); // 버퍼에 입력 bw.flush(); // 강제 푸쉬 } catch (IOException e) { System.out.println(e.getMessage()); e.printStackTrace(); // 에러 자세히 확인 } } }
package ex17.oneway; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(10000); // 소켓 생성 Socket socket = serverSocket.accept(); // 리스너 : 요청하는지 확인 System.out.println("클라이언트 연결됨"); BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8") ); String msg = br.readLine(); System.out.println(msg); } catch (IOException e) { throw new RuntimeException(e); } } }
flush를 안할 경우 전송되지 않음
- main 스레드가 리스너 역할을 하고 받은 요청을 처리함
- 소켓은 2개가 필요함
- 보통 try/catch/finally 사용
반이중 통신 : Stateless
package ex17.half; 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 Client { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 20000); // 소켓 생성 // 버퍼 만들기(send) PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); pw.println("1"); // 영화 요청 // 버퍼 만들기(receive) BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); String responseMsg = br.readLine(); System.out.println("서버로 부터 받은 메세지 : " + responseMsg); } catch (IOException e) { throw new RuntimeException(e); } } }
package ex17.half; import java.io.*; import java.net.ServerSocket; import java.net.Socket; public class Server { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(20000); // 리스너 소켓 생성 Socket socket = serverSocket.accept(); // 연결후 랜덤포트 소켓 생성 //소켓 연결 완료됨 // 버퍼 만들기(recwived) BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); String requestMsg = br.readLine(); System.out.println("클라이언트로부터 받은 메세지 : " + requestMsg); // 프로토콜 적용하기 // 버퍼 만들기(send) -> 동기적 : 순서대로 실행 PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); // 내부가 버퍼드 라이터가 구현되어 있음 if(requestMsg.equals("1")) { pw.println("영화"); // 내부에 '\'가 내제되어 있음 - 응답 } else if(requestMsg.equals("2")) { pw.println("드라마"); } else { pw.println("프로토콜을 확인하세요 : 1은 영화, 2는 드라마"); } } catch (IOException e) { throw new RuntimeException(e); } } }
- 한쪽이 보내면 응답을 하는 것
양방향 통신이 가능하지만 동시에 양쪽 방향에서 전송할 수 없음
요청에 대한 반응일 뿐 동시는 안됨
ex) 무전기
- 버퍼가 총 4개가 달림
- 요청과 응답 관계 → 트리거에 프로토콜이 작동해서 반응함
- 단기적 / 스레드 1개로 됨
- Web, Http 이 사용 → 선도 끊기고 요청자의 상태를 기억도 안 함
ex) 네이버에 주소를 검색했을 때
F5 - 재요청
요청 시에만 응답하면 됨
전이중 통신 : Stateful
1) 내가 작성한 코드
package ex17.multi; 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 Client { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 10000); // 소켓 생성 //System.out.println("클라이언트 : 소켓 생성 완료"); Thread sendThread = new Thread(() -> { //System.out.println("클라이언트 : 보내기 스레드 생성 완료"); try { // 버퍼 만들기(send) PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); //System.out.println("클라이언트 : 쓰기 버퍼 생성 완료"); System.out.println("클라이언트 : 통신 연결 완료"); while (true) { Scanner sc = new Scanner(System.in); String sendMsg = sc.nextLine(); pw.println(sendMsg); //System.out.println("클라이언트 : 메세지 키보드로 받아서 보내기 완료"); Thread.sleep(500); if(sendMsg.equals("끝")) { System.out.println("서버 : 통신 종료"); break; } } } catch (Exception e) { e.printStackTrace(); } }); // receivedThread Thread receivedThread = new Thread(() -> { //System.out.println("클라이언트 : 읽기 스레드 생성 완료"); try { // 버퍼 만들기(received) BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); //System.out.println("클라이언트 : 읽기 버퍼 생성 완료"); while (true) { String requestMsg = br.readLine(); System.out.println("서버로부터 받은 메세지 : " + requestMsg); //System.out.println("서버로부터 받은 메세지 읽기 완료"); if(requestMsg.equals("끝")) { System.out.println("서버 : 통신 종료"); break; } } } catch (Exception e) { e.printStackTrace(); } }); sendThread.start(); receivedThread.start(); } catch (IOException e) { throw new RuntimeException(e); } } }
package ex17.multi; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class Server { public static void main(String[] args) { try { // main 스레드 ServerSocket serverSocket = new ServerSocket(10000); // 리스너 소켓 생성 Socket socket = serverSocket.accept(); // 대기중 -> 연결후 랜덤포트 소켓 생성 //System.out.println("서버 : 연결 완료"); // receivedThread Thread receivedThread = new Thread(() ->{ // 스택이 달라서 readLine()에서 오류 발생 //System.out.println("서버 : 읽기 스레드 생성 완료"); try{ // 버퍼 만들기(received) BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); //System.out.println("서버 : 읽기 버퍼 생성 완료"); System.out.println("서버 : 통신 연결 완료"); while(true) { String requestMsg = br.readLine(); System.out.println("클라이언트로부터 받은 메세지 : " + requestMsg); //System.out.println("클라이언트로부터 받은 메세지 읽기 완료"); if(requestMsg.equals("끝")) { System.out.println("서버 : 통신 종료"); break; } } } catch (Exception e) { e.printStackTrace(); } }); Thread sendThread = new Thread(() -> { //System.out.println("서버 : 쓰기 스레드 생성 완료"); try { // 버퍼 만들기(send) PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); //System.out.println("서버 : 쓰기 버퍼 생성 완료"); while (true) { Scanner sc = new Scanner(System.in); String sendMsg = sc.nextLine(); pw.println(sendMsg); //System.out.println("서버 : 메세지 키보드로 받아서 보내기 완료"); if(sendMsg.equals("끝")) { System.out.println("서버 : 통신 종료"); break; } } } catch (Exception e) { e.printStackTrace(); } }); receivedThread.start(); sendThread.start(); } catch (IOException e) { throw new RuntimeException(e); } } }
2) 강사님 코드
package ex17.multi; 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 Client { public static void main(String[] args) { try { // 1. 소켓과 버퍼 만들기 Socket socket = new Socket("192.168.0.44", 10000); Scanner sc = new Scanner(System.in); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); // 2. 메시지 전송 스레드 new Thread(() -> { while (true) { String keyboardMsg = sc.nextLine(); pw.println(keyboardMsg); } }).start(); // 3. 메시지 읽기 스레드 BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); new Thread(() -> { while (true) { try { String serverMsg = br.readLine(); System.out.println("서버로부터 받은 메시지 : " + serverMsg); } catch (Exception e) { e.printStackTrace(); } } }).start(); } catch (IOException e) { throw new RuntimeException(e); } } }
package ex17.multi; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.ServerSocket; import java.net.Socket; import java.nio.charset.Charset; import java.util.Scanner; public class Server { public static void main(String[] args) { try { // 1. 소켓과 버퍼 만들기 ServerSocket serverSocket = new ServerSocket(20000); Socket socket = serverSocket.accept(); Scanner sc = new Scanner(System.in); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); // 2. 메시지 받기 스레드 new Thread(() -> { while (true) { try { String requestMsg = br.readLine(); System.out.println("클라이언트로부터 받은 메시지 : " + requestMsg); } catch (Exception e) { e.printStackTrace(); } }}).start(); new Thread(() -> { while (true) { String keyboardMsg = sc.nextLine(); pw.println(keyboardMsg); } }).start(); } catch (IOException e) { throw new RuntimeException(e); } } }
- 동시에 양방향 전송이 가능함
ex) 전화
- 보내는 스레드, 읽는 스레드 2개가 필요함
읽는 스레드는 계속 돌아야 함
- 채팅 → 선도 안 끊기고 요청자의 상태를 기억함
ex) 카카오톡 : 내가 보내기 전에 연락이 옴
- 반드시 답장을 안 해도 됨 → 트리거가 없음
계속 연결되어있어서 반이중 보다 부하가 심함
IP 확인 방법
PrintWriter → 자동 flush
UTF-8 자동 설정
자동 설정이 안되어있을 경우
PrintWriter pw = new PrintWriter(socket.getOutputStream(), true, Charset.forName("UTF-8"));
.println : \가 내장되어 있음
Share article