소켓 통신

[Java] 단방향, 반이중, 전이중
Jan 12, 2024
소켓 통신
양끝단에 소켓을 달고 소켓으로 하는 통신(외부 컴퓨터와 통신하기 위해 파일은 소켓이 되어야합니다.)
Simplex(단방향) : 데이터를 보내기만 하는 방식
Half Duplex(반이중) : 데이터를 보내면 응답(ACK)해주는 방식 (웹)
  • Client와 Server 각각 2개의 버퍼가 필요합니다.
  • Stateless (상태를 보존하지 않습니다.)
Full Duplex(전이중, 양방향) : 두 장치 서로 동시에 데이터를 주고 받는 방식(카카오톡)
  • Client와 Server Thread가 각각 2개 필요합니다..
  • Stateful (상태가 사라지지 않습니다..)

포트 : 어떤 프로세스와 통신을 할지 설정하는 값 (0~65535)
ServerSocket : 특정 포트에서 클라이언트의 연결 요청을 수신하고, 이를 수락하여 클라이언트와의 통신을 위한 Socket 객체를 생성합니다.(클라이언트 수만큼 Socket을 생성)
  • 이때 만들어지는 Socket은 포트가 랜덤으로 생성됩니다.(신경 X)
루프백 IP : Loopback 아이피는 컴퓨터 자체를 가리키는 주소로, 일반적으로 "127.0.0.1" 로 표기됩니다. localhost 라고도 합니다.

단방향 통신

Client : 요청을 보내는 host Server : 요청을 받는 host

메시지 보내기

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); // ServerSocket 생성 Socket socket = serverSocket.accept(); // 리스너(연결 요청을 받을 때까지 기다림) System.out.println("Client 연결됨"); BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8") ); while (true) { String msg = br.readLine(); if (msg == null) break; System.out.println(msg); } } catch (IOException e) { throw new RuntimeException(e); } } }
코드 해설
  1. ServerSocket 객체를 생성하여 지정된 포트 번호(여기서는 10000)로 클라이언트의 연결을 기다립니다.
  1. accept() 메서드를 호출하여 클라이언트의 연결을 수락하고, Socket 객체를 반환받습니다.
  1. "클라이언트 연결됨" 메시지를 출력합니다.
  1. BufferedReader 객체를 생성하여 socket.getInputStream()을 통해 클라이언트로부터 전송된 데이터를 읽어들입니다. 여기서는 문자열을 읽기 위해 InputStreamReader를 사용하고, 문자 인코딩으로 UTF-8을 지정합니다.
  1. 무한 루프를 돌면서 br.readLine()을 호출하여 클라이언트로부터 전송된 메시지를 한 줄씩 읽어들입니다.
  1. 만약 읽어들인 메시지가 null이라면, 즉 클라이언트가 연결을 종료했다면 루프를 종료합니다.
  1. 읽어들인 메시지를 출력합니다.
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) { // localhost = 127.0.0.1 (루프백) try { Socket socket = new Socket("127.0.0.1",10000); // (ip,port) Scanner sc = new Scanner(System.in); String msg = sc.nextLine(); BufferedWriter bw = new BufferedWriter( // 보조 스트림을 버퍼로 생성 new OutputStreamWriter(socket.getOutputStream(), // 보조스트림 만들기 ( 아웃풋 스트림 연결 ) "UTF-8") // UTF-8(한글 3Byte, 영어 1Byte)로 안하면 1Byte씩 읽어들이기 때문에 한글이 다 깨진다. ); bw.write(msg+'\n'); bw.flush(); // flush() 메서드는 BufferedWriter 클래스의 메서드로, 버퍼에 남아있는 데이터를 강제로 출력하는 역할을 합니다. } catch (IOException e) { e.printStackTrace(); } } }
코드 해설
  1. Socket 객체를 생성하여 지정된 IP 주소와 포트 번호(여기서는 127.0.0.1:10000)로 서버에 연결합니다.
  1. Scanner 객체를 생성하여 사용자로부터 입력을 받습니다.
  1. Scanner.nextLine()을 호출하여 사용자로부터 입력받은 문자열을 읽어들입니다.
  1. BufferedWriter 객체를 생성하여 socket.getOutputStream()을 통해 서버로 데이터를 전송합니다. 여기서는 문자열을 전송하기 위해 OutputStreamWriter를 사용하고, 문자 인코딩으로 UTF-8을 지정합니다.
  1. bw.write(msg+"\n")를 호출하여 사용자로부터 입력받은 문자열을 서버로 전송합니다. 문자열의 끝에 개행 문자(\n)를 추가하여 한 줄씩 전송합니다.
  1. bw.flush()를 호출하여 버퍼에 남아있는 데이터를 서버로 전송합니다.

반이중 통신

BufferedWriter는 PrintWriter를 사용하면 더 간편하게 작성 가능합니다.
  • 자동 flush기능
  • new OutputStreamReader 안해도됩니다.
  • bw.write 대신 pw.println 을 쓰면 \n을 적지 않아도 됩니다.
 
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; public class Server { public static void main(String[] args) { try { ServerSocket serverSocket = new ServerSocket(20000); Socket socket = serverSocket.accept(); // 소켓 연결 완료됨 // 버퍼 만들기 (recevied) BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); String requestMsg = br.readLine(); System.out.println("클라이언트로부터 받은 메시지 : " + requestMsg); // 버퍼 만들기 (send) PrintWriter pw = new PrintWriter(socket.getOutputStream(), true, Charset.forName("UTF-8")); 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); } } }
코드 해설
  1. ServerSocket을 생성하여 20000번 포트에서 클라이언트의 연결을 기다립니다.
  1. accept() 메서드를 호출하여 클라이언트의 연결을 수락하고, Socket 객체를 반환받습니다.
  1. Socket의 getInputStream() 메서드를 이용하여 클라이언트로부터 데이터를 읽어올 BufferedReader 객체를 생성합니다.
  1. BufferedReader의 readLine() 메서드를 호출하여 클라이언트로부터 받은 메시지를 읽어옵니다.
  1. 받은 메시지에 따라서 조건문으로 처리하여 적절한 응답을 생성합니다.
  1. Socket의 getOutputStream() 메서드를 이용하여 클라이언트로 데이터를 보낼 PrintWriter 객체를 생성합니다
  1. .PrintWriter의 println() 메서드를 호출하여 응답을 클라이언트로 보냅니다.
package ex17.half; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class Client { public static void main(String[] args) { try { Socket socket = new Socket("localhost", 20000); PrintWriter pw = new PrintWriter(socket.getOutputStream(), true); pw.println("1"); BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream()) ); String responseMsg = br.readLine(); System.out.println("서버로 부터 받은 메시지 : " + responseMsg); } catch (IOException e) { throw new RuntimeException(e); } } }
코드 해설
  1. Socket을 생성하여 "localhost"와 20000번 포트로 서버에 연결합니다.
  1. Socket의 getOutputStream() 메서드를 이용하여 서버로 데이터를 보낼 PrintWriter 객체를 생성합니다.
  1. PrintWriter의 println() 메서드를 호출하여 서버로 메시지 "1"을 전송합니다.
  1. Socket의 getInputStream() 메서드를 이용하여 서버로부터 받은 데이터를 읽어올 BufferedReader 객체를 생성합니다.
  1. BufferedReader의 readLine() 메서드를 호출하여 서버로부터 받은 응답 메시지를 읽어옵니다.
  1. 받은 응답 메시지를 출력합니다.

전이중 통신

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("127.0.0.1",20000); PrintWriter pw = new PrintWriter(socket.getOutputStream(),true); Scanner sc = new Scanner(System.in); BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream(),"UTF-8") ); // 2. 메시지 전송 쓰레드 new Thread(() -> { while(true){ String keyboardMsg = sc.nextLine(); pw.println(keyboardMsg); } } ).start(); // 3. 메시지 읽기 스레드 new Thread(() -> { while(true) { try { String requestMsg = br.readLine(); System.out.println("서버로부터 받은 메시지 : " + requestMsg); } catch (IOException e) { e.printStackTrace(); } } }).start(); } catch (IOException e) { throw new RuntimeException(e); } } }
코드 해설
  1. 소켓과 버퍼 생성: ◦ Socket 객체를 생성하여 서버에 접속합니다. ◦ PrintWriter를 사용하여 소켓의 출력 스트림을 생성합니다. 이를 통해 서버로 메시지를 전송할 수 있습니다. ◦ Scanner를 사용하여 사용자의 입력을 받을 준비를 합니다. ◦ BufferedReader와 InputStreamReader를 사용하여 소켓의 입력 스트림을 생성합니다. 이를 통해 서버로부터 받은 데이터를 읽어올 수 있습니다.
  1. 메시지 전송 쓰레드: ◦ 람다식을 사용하여 새로운 쓰레드를 생성합니다. ◦ 무한 루프를 돌며 사용자의 입력을 받고, PrintWriter를 사용하여 서버로 메시지를 전송합니다.
  1. 메시지 읽기 쓰레드: ◦ 람다식을 사용하여 새로운 쓰레드를 생성합니다. ◦ 무한 루프를 돌며 BufferedReader를 사용하여 서버로부터 받은 데이터를 읽어옵니다. ◦ 읽은 메시지를 콘솔에 출력합니다.
import java.io.*; import java.net.ServerSocket; import java.net.Socket; import java.util.Scanner; public class Server { public static void main(String[] args){ try { // 1. 소켓과 버퍼 만들기 ServerSocket serverSocket = new ServerSocket(20000); Socket socket = serverSocket.accept(); BufferedReader br = new BufferedReader( new InputStreamReader(socket.getInputStream(),"UTF-8") ); Scanner sc = new Scanner(System.in); PrintWriter pw = new PrintWriter(socket.getOutputStream(),true); // 2. 메시지 받기 쓰레드 new Thread(() -> { while(true) { String requestMsg = null; try { requestMsg = br.readLine(); } catch (IOException e) { e.printStackTrace(); } System.out.println("클라이언트로부터 받은 메시지 : " + requestMsg); } }).start(); // 3. 메시지 전송 쓰레드 new Thread(() -> { while(true){ String keyboardMsg = sc.nextLine(); pw.println(keyboardMsg); } } ).start(); } catch (IOException e) { throw new RuntimeException(e); } } }
코드 해설
  1. 소켓과 버퍼 생성: ◦ ServerSocket 객체를 생성하여 지정된 포트 번호(20000)로 클라이언트의 연결을 대기합니다. ◦ Socket 객체를 생성하여 클라이언트와의 소켓 연결을 수립합니다. ◦ InputStreamReader를 사용하여 소켓의 입력 스트림을 생성하고, BufferedReader를 사용하여 데이터를 읽어옵니다. ◦ PrintWriter를 사용하여 소켓의 출력 스트림을 생성하고, 서버가 클라이언트로 메시지를 보낼 수 있도록 합니다.
  1. 메시지 받기 쓰레드: ◦ 람다식을 사용하여 새로운 쓰레드를 생성합니다. ◦ 무한 루프를 돌며 BufferedReader를 사용하여 클라이언트로부터 받은 데이터를 읽어옵니다. ◦ 읽은 메시지를 콘솔에 출력합니다.
  1. 메시지 전송 쓰레드: ◦ 람다식을 사용하여 새로운 쓰레드를 생성합니다. ◦ 무한 루프를 돌며 사용자의 입력을 받고, PrintWriter를 사용하여 클라이언트로 메시지를 전송합니다.
 
Share article
RSSPowered by inblog