Notice
Recent Posts
Recent Comments
Link
«   2024/11   »
1 2
3 4 5 6 7 8 9
10 11 12 13 14 15 16
17 18 19 20 21 22 23
24 25 26 27 28 29 30
Tags
more
Archives
Today
Total
관리 메뉴

개발자입니다

[Java] 예제 소스 정리 - 네트워킹(Client/Server, 연결 방식) 본문

네이버클라우드 AIaaS 개발자 양성과정 1기/Java

[Java] 예제 소스 정리 - 네트워킹(Client/Server, 연결 방식)

끈기JK 2023. 2. 2. 19:30

com.eomcs.net.ex01~05

 

 

예제 소스 정리

 

네트워킹

 

 

com.eomcs.net.ex01

 

 

상대편에 연결을 요청하기 - 클라이언트(client)
package com.eomcs.net.ex01;

import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Sender {

  public static void main(String[] args) throws Exception {
    System.out.println("클라이언트 실행!");

    // 1) 다른 컴퓨터와 네트워크로 연결한다.
    // => 서버와 연결되면 Socket 객체가 생성된다.
    // => 서버와 연결될 때까지 리턴하지 않는다.
    // => 서버에 연결할 수 없으면 예외가 발생한다.
    //    - 기본으로 설정된 타임아웃 시간까지 연결되지 않으면 예외가 발생한다.
    // 
    // new Socket(원격 호스트의 IP 주소/도메인이름, 원격 호스트 프로그램의 포트번호)
    //    - 로컬 호스트(애플리케이션을 실행하는 현재 컴퓨터)일 경우: 127.0.0.1 또는 localhost
    Socket socket = new Socket("localhost", 8888); // 서버의 대기열에 등록된다면 리턴한다.
    System.out.println("서버와 연결된 Socket 생성!");

    // 2) 소켓 객체를 통해 읽고 쓸 수 있도록 입출력 스트림을 얻는다.
    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());
    System.out.println("서버와 데이터를 송수신할 스트림 준비!");

    // 3) 상대편으로 문자열을 한 줄 보낸다.
    out.println("엄진영입니다. 안녕하세요!");

    // 4) 상대편에서 보낸 문자열을 한 줄 읽는다.
    // => 상대편이 한 줄 데이터를 보낼 때까지 리턴하지 않는다.
    // => 이런 메서드를 블로킹 메서드라 부른다.
    String str = in.nextLine();
    System.out.println(str);

    // 5) 항상 입출력 도구는 사용 후 닫아야 한다.
    in.close();
    out.close();

    // 6) 네트워크 연결도 닫는다.
    socket.close();

  }

}
상대편으로부터 연결 요청 받기 - 서버(server)
package com.eomcs.net.ex01;

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Receiver {

  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행!");

    // 1) 다른 컴퓨터의 연결 요청을 기다린다.
    // - new ServerSocket(포트번호)
    // - 포트번호: 
    //   - 호스트에서 실행 중인 서버 프로그램을 구분하는 번호이다.
    //   - 1024 ~ 49151 사이의 값 사용한다.
    //      - 1 ~ 1023 사이의 포트 번호는 특정 서버가 사용하기 위해 미리 예약된 번호다.
    //      - 가능한 이 범위의 포트 번호는 사용하지 않는 것이 좋다.
    //   - 유명 프로그램의 포트 번호도 가능한 사용하지 말라.
    //      - 예) Oracle DBMS(1521), MySQL DBMS(3306) 등
    //   - 같은 컴퓨터에서 다른 프로그램이 이미 사용중인 포트 번호는 지정할 수 없다.
    //      - 포트 번호는 중복으로 사용될 수 없다.
    ServerSocket serverSocket = new ServerSocket(8888);
    System.out.println("ServerSocket 생성!");

    // 2) 연결을 기다리고 있는 클라이언트가 있다면 맨 먼저 접속한 클라이언트의 연결을 승인한다. 
    // - 클라이언트가 서버에 접속을 요청하면 그 정보를 "대기열"이라고 불리는 목록으로 관리한다.
    // - accept()를 호출하면 대기열에서 순서대로 꺼내 해당 클라이언트와 연결된 소켓을 만든다.
    Socket socket = serverSocket.accept();
    System.out.println("클라이언트와 연결된 Socket 생성!");

    // 3) 소켓 객체를 통해 읽고 쓸 수 있도록 입출력 스트림을 얻는다.
    // - 연결된 클라이언트로 데이터를 보내고 받으려면 입출력 스트림을 꺼내야 한다.
    // - 소켓이 리턴해준 입출력 스트림에 적절한 데코레이터를 붙여서 사용한다.
    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());
    System.out.println("데이터 송수신을 위한 입출력 스트림 준비!");

    // 4) 상대편이 보낸 문자열을 한 줄 읽는다.
    // => 상대편이 한 줄의 데이터를 보낼 때까지 리턴하지 않는다.
    // => 이런 메서드를 블로킹 메서드라 부른다.
    String str = in.nextLine();
    System.out.printf("상대편> %s\n", str);

    // 5) 상대편으로 문자열을 한 줄 보낸다.
    out.println("나는 엄진영입니다. 반갑습니다!");

    // 6) 항상 입출력 도구는 사용 후 닫아야 한다.
    in.close();
    out.close();

    // 7) 네트워크 연결도 닫는다.
    socket.close(); // 클라이언트와 연결을 끊는다.
    serverSocket.close(); // 클라이언트의 연결 요청을 받지 않는다.

  }

}

 

 

클라이언트 + 키보드 입력
package com.eomcs.net.ex01;

import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Sender2 {

  public static void main(String[] args) throws Exception {
    System.out.println("클라이언트 실행!");

    Scanner keyScan = new Scanner(System.in);

    Socket socket = new Socket("192.168.0.16", 8888);

    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());

    // 키보드 입력을 받아서 서버에게 전송한다.
    System.out.print("입력> ");
    String input = keyScan.nextLine();
    out.println(input);

    // 서버가 보낸 데이터를 수신한다.
    String str = in.nextLine();
    System.out.println(str);

    in.close();
    out.close();
    socket.close();
    keyScan.close();
  }

}
서버 + 키보드 입력
package com.eomcs.net.ex01;

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Receiver2 {

  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행!");

    Scanner keyScan = new Scanner(System.in);

    ServerSocket serverSocket = new ServerSocket(8888);
    Socket socket = serverSocket.accept();

    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());

    // 클라이언트가 보낸 문자열을 수신한다.
    String str = in.nextLine();
    System.out.println(str);

    // 키보드 입력을 받아서 클라이언트로 송신한다.
    System.out.print("입력> ");
    str = keyScan.nextLine();
    out.println(str);

    in.close();
    out.close();
    socket.close();
    serverSocket.close();
    keyScan.close();
  }

}

 

 

클라이언트 + 키보드 입력 + 무한 반복
package com.eomcs.net.ex01;

import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Sender3 {

  public static void main(String[] args) throws Exception {
    System.out.println("클라이언트 실행!");

    Scanner keyScan = new Scanner(System.in);

    Socket socket = new Socket("localhost", 8888);

    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());

    while (true) {
      // 키보드 입력을 받아서 서버에게 전송한다.
      System.out.print("입력> ");
      String input = keyScan.nextLine();
      out.println(input);

      // 서버가 보낸 데이터를 수신한다.
      String str = in.nextLine();
      System.out.println(str);

      if (input.equals("quit"))
        break;
    }

    in.close();
    out.close();
    socket.close();
    keyScan.close();
  }

}
서버 + 키보드 입력 + 무한 반복
package com.eomcs.net.ex01;

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Receiver3 {

  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행!");

    Scanner keyScan = new Scanner(System.in);

    ServerSocket serverSocket = new ServerSocket(8888);
    Socket socket = serverSocket.accept();

    PrintStream out = new PrintStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());

    while (true) {
      // 클라이언트가 보낸 문자열을 수신한다.
      String str = in.nextLine();
      System.out.println(str);

      // 키보드 입력을 받아서 클라이언트로 송신한다.
      System.out.print("입력> ");
      String input = keyScan.nextLine();
      out.println(input);

      if (str.equals("quit"))
        break;
    }

    in.close();
    out.close();
    socket.close();
    serverSocket.close();
    keyScan.close();
  }

}

 

 

파일 보내기
package com.eomcs.net.ex01;

import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.Socket;
import java.util.Scanner;

public class Sender4 {

  public static void main(String[] args) throws Exception {
    File file = new File("temp/jls18.pdf");

    FileInputStream fileIn = new FileInputStream(file);

    System.out.println("서버에 연결 중...");
    Socket socket = new Socket("192.168.0.3", 8888);
    System.out.println("서버에 연결 완료!");

    DataOutputStream out = new DataOutputStream(socket.getOutputStream());
    Scanner in = new Scanner(socket.getInputStream());

    System.out.println("서버에 데이터 송신 중...");

    long startTime = System.currentTimeMillis();

    // 1) 파일 크기 보내기
    out.writeLong(file.length());

    // 2) 파일 이름 보내기
    out.writeUTF(file.getName());

    // 3) 파일 데이터 보내기
    int b;
    while ((b = fileIn.read()) != -1) {
      out.write(b);
    }

    long endTime = System.currentTimeMillis();

    System.out.printf("서버에 데이터 송신 완료!(%d밀리초)\n", endTime - startTime);

    // 4) 서버의 응답받기
    String response = in.nextLine();
    System.out.println(response);

    in.close();
    out.close();
    socket.close();
    fileIn.close();
  }

}
파일 받기
package com.eomcs.net.ex01;

import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Receiver4 {

  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행 중...");

    ServerSocket serverSocket = new ServerSocket(8888);
    Socket socket = serverSocket.accept();
    System.out.println("클라이언트가 연결됨.");

    PrintStream out = new PrintStream(socket.getOutputStream());
    DataInputStream in = new DataInputStream(socket.getInputStream());

    System.out.println("클라이언트로부터 데이터 수신 중...");

    //1) 파일 크기 읽기
    long filesize = in.readLong();

    //2) 파일 이름 읽기
    String filename = in.readUTF();

    //3) 파일 데이터 읽기
    File file = new File("temp/ok_" + filename);
    FileOutputStream fileOut = new FileOutputStream(file);

    for (long i = 0; i < filesize; i++) {
      fileOut.write(in.read());
    }
    System.out.println("클라이언트로부터 데이터 수신 완료!");

    //4) 클라이언트에게 응답하기
    out.println("OK!");

    in.close();
    out.close();
    socket.close();
    serverSocket.close();
    fileOut.close();
  }

}

 

 

파일 보내기 + 버퍼
package com.eomcs.net.ex01;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.net.Socket;
import java.util.Scanner;

public class Sender5 {

  public static void main(String[] args) throws Exception {
    File file = new File("temp/jls18.pdf");

    BufferedInputStream fileIn = new BufferedInputStream(new FileInputStream(file));

    System.out.println("서버에 연결 중...");
    Socket socket = new Socket("192.168.0.3", 8888);
    System.out.println("서버에 연결 완료!");

    DataOutputStream out = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream()));
    Scanner in = new Scanner(new BufferedInputStream(socket.getInputStream()));

    System.out.println("서버에 데이터 송신 중...");

    long startTime = System.currentTimeMillis();

    // 1) 파일 크기 보내기
    out.writeLong(file.length());

    // 2) 파일 이름 보내기
    out.writeUTF(file.getName());

    // 3) 파일 데이터 보내기
    int b;
    while ((b = fileIn.read()) != -1) {
      out.write(b);
    }
    out.flush(); // 버퍼에 남아있는 데이터를 방출하기

    long endTime = System.currentTimeMillis();

    System.out.printf("서버에 데이터 송신 완료!(%d밀리초)\n", endTime - startTime);

    // 4) 서버의 응답받기
    String response = in.nextLine();
    System.out.println(response);

    in.close();
    out.close();
    socket.close();
    fileIn.close();
  }

}
파일 받기 + 버퍼
package com.eomcs.net.ex01;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.DataInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class Receiver5 {

  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행 중...");

    ServerSocket serverSocket = new ServerSocket(8888);
    Socket socket = serverSocket.accept();
    System.out.println("클라이언트가 연결됨.");

    PrintStream out = new PrintStream(
        new BufferedOutputStream(socket.getOutputStream()));
    DataInputStream in = new DataInputStream(
        new BufferedInputStream(socket.getInputStream()));

    System.out.println("클라이언트로부터 데이터 수신 중...");

    //1) 파일 크기 읽기
    long filesize = in.readLong();

    //2) 파일 이름 읽기
    String filename = in.readUTF();

    //3) 파일 데이터 읽기
    File file = new File("temp/ok_" + filename);
    BufferedOutputStream fileOut = new BufferedOutputStream( 
        new FileOutputStream(file));

    for (long i = 0; i < filesize; i++) {
      fileOut.write(in.read());
    }
    System.out.println("클라이언트로부터 데이터 수신 완료!");

    //4) 클라이언트에게 응답하기
    out.println("OK!");
    out.flush(); // 버퍼의 남아있는 데이터 방출하기

    in.close();
    out.close();
    socket.close();
    serverSocket.close();
    fileOut.close();
  }

 

 

 

com.eomcs.net.ex02

 

 

클라이언트 만들기
package com.eomcs.net.ex02;

import java.net.Socket;

public class Client0110 {

  public static void main(String[] args) throws Exception {
    // 클라이언트(client)?
    // => 연결을 요청하는 쪽을 가리키는 용어다.
    // 서버(server)?
    // => 연결 요청을 받는 쪽을 가리키는 용어다.

    // 1) 서버에 연결 요청을 할 때 사용할 도구를 준비한다.
    // => 서버와의 연결이 이루어지면 Socket 객체를 리턴한다.
    // => 클라이언트 측의 포트 번호는 OS가 자동으로 부여한다.
    //    서버 측은 개발자가 명시적으로 부여해야 한다.
    Socket socket = new Socket(
        // 서버 IP 주소(ex: 234.3.4.56) 또는 도메인명(ex: www.daum.net)
        "localhost", 
        // 특수 IP : 127.0.0.1 - 로컬 컴퓨터를 가리킨다.
        // 특수 도메인명 : localhost - 127.0.0.1을 가리킨다.
        //

        // 서버 포트번호 - 서버를 구분하는 식별 번호.
        8888 
        // IP 주소가 회사 대표 번호라면, 포트 번호는 내선 번호라 할 수 있다.
        );
    System.out.println("서버와 연결되었음!");

    // 2) 서버와 연결 해제
    // => 작업이 끝난 후에는 항상 서버와의 연결을 해제해야 한다.
    // => 물론 해제하지 않아도 서버측에서 일정 시간이 지나면 자동으로 연결과 관련된 자원을 해제한다.
    // => 그러나 가능한 명시적으로 연결을 해제하는 것이 좋다.
    //    서버측에서 해당 클라이언트와 연결하기 위해 사용한 자원을 빠르게 회수하여
    //    다른 클라이언트를 위해 사용할 수 있기 때문이다.
    //
    socket.close();
    System.out.println("서버와 연결을 끊었음!");
  }

}

 

 

대기열의 제한 확인하기
package com.eomcs.net.ex02;

import java.net.Socket;
import java.util.Scanner;

public class Client0210 {
  public static void main(String[] args) throws Exception {
    // 실행을 잠시 중단시키기 위해 사용
    Scanner keyScan = new Scanner(System.in);
    System.out.println("클라이언트 실행!");

    Socket socket = new Socket("localhost", 8888);
    System.out.println("서버에 연결됨!");
    // 서버의 대기열에 접속 순서대로 대기한다.
    // 서버에서 연결이 승인되면, 비로서 입출력을 할 수 있다.

    // 일단 멈춤!
    keyScan.nextLine();
    // 대기열의 크기에 따라 연결되는 클라이언트 수의 제한을 확인하기 위해
    // 여러 개의 클라이언트를 실행하라!

    // 자원해제
    socket.close();
    System.out.println("서버와의 연결을 끊었음.");

    keyScan.close();
  }
}

 

 

대기열의 제한 확인하기
package com.eomcs.net.ex02;

import java.net.Socket;
import java.util.Scanner;

public class Client0211 {
  public static void main(String[] args) throws Exception {
    // 실행을 잠시 중단시키기 위해 사용
    Scanner keyScan = new Scanner(System.in);
    System.out.println("클라이언트 실행!");

    int count = 0;
    while (true) {
      try {
        Socket socket = new Socket("localhost", 8888);
        System.out.printf("서버에 연결됨! - %d\n", ++count);
        // 서버의 대기열에 접속 순서대로 대기한다.
        // 서버에서 연결이 승인되면, 비로서 입출력을 할 수 있다.

        // 일단 멈춤!
        keyScan.nextLine();
        // 대기열의 크기에 따라 연결되는 클라이언트 수의 제한을 확인하기 위해
        // 반복해서 서버와 연결한다.
      } catch (Exception e) {
        e.printStackTrace();
        break;
      }
    }
    System.out.println("서버와의 연결을 끊었음.");

    keyScan.close();
  }
}

 

 

타입아웃 시간 설정하기
package com.eomcs.net.ex02;

import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Scanner;

public class Client0310 {
  public static void main(String[] args) throws Exception {
    // 실행을 잠시 중단시키기 위해 사용
    Scanner keyScan = new Scanner(System.in);
    System.out.println("클라이언트 실행!");

    // 1) 소켓을 생성한다.
    Socket socket = new Socket();
    System.out.println("소켓 생성됨.");

    // 2) 연결할 서버의 주소를 준비한다. 
    SocketAddress socketAddress = new InetSocketAddress("192.168.0.76", 8888);

    // 3) 서버와의 연결을 시도한다.
    // => 타임아웃으로 지정된 시간 안에 서버가 승인(accept())하지 않으면 즉시 예외가 발생한다.
    // => Windows의 경우, 
    //    - 로컬에 접속할 때 타임아웃 설정이 정상적으로 동작되지 않는다.(확인 할 것!)
    //    - 원격 윈도우 PC에 서버를 실행하여 접속한다면 정상적으로 동작한다.
    //
    System.out.println("서버와 연결 중...");
    socket.connect(socketAddress, 10000); // timeout : milliseconds
    System.out.println("서버와 연결되었음!");

    keyScan.nextLine(); // 사용자가 엔터를 칠 때까지 다음 코드로 이동하지 않는다.

    socket.close();
    System.out.println("서버와의 연결을 끊었음.");

    keyScan.close();
  }
}

 

 

서버의 승인 테스트
package com.eomcs.net.ex02;

import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.SocketAddress;
import java.util.Scanner;

public class Client0410 {
  public static void main(String[] args) throws Exception {
    // 실행을 잠시 중단시키기 위해 사용
    Scanner keyScan = new Scanner(System.in);
    System.out.println("클라이언트 실행!");

    Socket socket = new Socket();
    System.out.println("소켓 생성됨.");

    SocketAddress socketAddress = new InetSocketAddress("localhost", 8888);

    System.out.println("서버와 연결 중...");
    socket.connect(socketAddress, 10000);
    System.out.println("서버와 연결되었음!");

    keyScan.nextLine();

    socket.close();
    System.out.println("서버와의 연결을 끊었음.");

    keyScan.close();
  }
}

 

 

서버 만들기
// 서버(server)?
// => 네트워크 연결을 기다리는 쪽을 가리킨다.
public class Server0110 {
  public static void main(String[] args) throws Exception {
    // 중간에 실행을 잠깐 멈출 때 사용하기 위함.
    Scanner keyboard = new Scanner(System.in);

    System.out.println("서버 실행!");
    // 1) 네트워크 연결을 기다리는 역할을 수행할 객체를 준비
    // => new ServerSocket(포트번호)
    // => 현재 실행 중인 프로그램과 포트 번호가 중복되어서는 안된다.
    ServerSocket ss = new ServerSocket(8888);
    // 포트번호
    // => 한 컴퓨터에서 네트워크 연결을 기다리는 프로그램의 식별번호이다.
    // => OS는 이 번호를 가지고 데이터를 받을 프로그램을 결정한다.

    System.out.println("클라이언트 연결을 기다리는 중...");

    // 잠깐 멈추기
    keyboard.nextLine(); // 사용자가 엔터를 칠 때까지 리턴하지 않는다.

    ss.close();
    System.out.println("서버 종료!");

    keyboard.close();
  }
}

// 포트번호
// => 0 ~ 1023 (well-known port)
// - 특정 프로그램이 관습적으로 사용하는 포트 번호
// - 프로그램을 작성할 때 가능한 이 포트 번호를 사용하지 말아야 한다.
//   예) 
//     7(echo), 20(FTP 데이터 포트), 21(FTP 제어포트), 23(telnet),
//     25(SMTP), 53(DNS), 80(HTTP), 110(POP3), 143(IMAP), HTTPS(443), SSL-telnet(992) 등
// => 1024 ~ 49151 (registered port)
// - 일반적인 통신 프로그램을 작성할 때 이 범위 포트 번호를 사용한다.
// - 다만 이 범위에 번호 중에서 특정 프로그램이 널리 사용하는 번호가 있다.
//   가능한 그 번호도 피하라!
//   예)
//     8080(proxy), 1521(Oracle), 3306(MySQL) 등
// => 49152 ~ 65535 (dynamic port)
// - 통신을 하는 프로그램은 반드시 포트번호를 가져야 한다.
//   그래야 OS가 해당 프로그램을 구분할 수 있다.
// - 따라서 클라이언트 프로그램도 포트번호를 갖는데,
//   프로그램에서 결정하는 것이 아니라, 
//   OS로부터 자동 발급 받는다.
// - 이 범위의 포트번호는 클라이언트가 OS로부터 자동 발급 받는 포트 번호이다.

 

 

대기열 테스트
package com.eomcs.net.ex02;

import java.net.ServerSocket;
import java.util.Scanner;

public class Server0210 {
  public static void main(String[] args) throws Exception {
    // 중간에 실행을 잠깐 멈출 때 사용하기 위함.
    Scanner keyboard = new Scanner(System.in);

    System.out.println("서버 실행");

    // 대기열?
    // => 클라이언트가 접속을 요청하면 대기열에 클라이언트 정보를 저장한다.
    // => 저장은 큐(FIFO) 방식으로 관리한다.
    // => 대기열의 크기가 클라이언트의 연결을 허락하는 최대 개수이다.
    // => 대기열을 초과하여 클라이언트 요청을 들어 왔을 때 서버는 응답하지 않는다.
    //    클라이언트는 내부에 설정된 시간(timeout)동안 기다리다 응답을 받지 못하면
    //    예외를 던지고 연결 요청을 취소한다.
    // => new ServerSocket(포트번호, 대기열크기);
    //    다음과 같이 대기열의 개수를 지정하지 않으면, 기본이 50개이다.
    //
    ServerSocket ss = new ServerSocket(8888);
    System.out.println("서버 소켓 생성 완료!");
    System.out.println("클라이언트 연결을 기다리는 중...");

    // 잠깐 멈추기
    // => 클라이언트가 접속하게 하라!
    // => 최대 몇 개까지 접속 가능한가?
    keyboard.nextLine();

    ss.close();
    System.out.println("서버 종료!");

    keyboard.close();
  }
}

 

 

대기열 크기 조정
package com.eomcs.net.ex02;

import java.net.ServerSocket;
import java.util.Scanner;

public class Server0220 {
  public static void main(String[] args) throws Exception {
    // 중간에 실행을 잠깐 멈출 때 사용하기 위함.
    Scanner keyboard = new Scanner(System.in);

    System.out.println("서버 실행");

    // => new ServerSocket(포트번호, 대기열크기);
    //
    ServerSocket ss = new ServerSocket(8888, 2);
    System.out.println("서버 소켓 생성 완료!");
    System.out.println("클라이언트 연결을 기다리는 중...");

    // 잠깐 멈추기
    // => 클라이언트가 접속하게 하라!
    // => 최대 몇 개까지 접속 가능한가?
    keyboard.nextLine();

    ss.close();
    System.out.println("서버 종료!");

    keyboard.close();
  }
}

 

 

타임아웃 테스트
package com.eomcs.net.ex02;

import java.net.ServerSocket;
import java.util.Scanner;

public class Server0310 {
  public static void main(String[] args) throws Exception {
    // 중간에 실행을 잠깐 멈출 때 사용하기 위함.
    Scanner keyboard = new Scanner(System.in);

    System.out.println("서버 실행");

    ServerSocket ss = new ServerSocket(8888, 2);
    System.out.println("서버 소켓 생성 완료!");
    System.out.println("클라이언트 연결을 기다리는 중...");

    // 잠깐 멈추기
    // => 대기열이 꽉찼을 때 클라이언트의 접속 요청은 어떻게 되는가?
    keyboard.nextLine();

    ss.close();
    System.out.println("서버 종료!");

    keyboard.close();
  }
}

 

 

대기열에서 기다리고 있는 클라이언트와 연결하기
package com.eomcs.net.ex02;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0410 {
  public static void main(String[] args) throws Exception {
    Scanner keyboard = new Scanner(System.in);

    ServerSocket ss = new ServerSocket(8888, 2);
    System.out.println("서버 소켓 생성 완료!");
    System.out.println("클라이언트 연결을 기다리는 중...");

    // 테스트1) 대기열에 클라이언트가 없을 때,
    // => accept()는 블로킹 상태에 놓인다.
    //    즉 리턴하지 않는다.
    // 

    // 큐(queue)에 대기중인 클라이언트 중 첫 번째 클라이언트를 꺼내서 연결을 승인한다.
    // => 클라이언트가 서버에 연결을 요청하면, 서버는 대기열에 추가한다.
    // => 서버소켓에서 연결을 승인하면 클라이언트와 통신할 수 있는 소켓을 리턴한다.
    // => 대기열에 기다리고 있는 클라이언트가 없으면 접속할 때까지 기다린다.
    Socket socket = ss.accept();
    System.out.println("대기 중인 클라이언트 중 한 개의 클라이언트에 대해 연결 승인!");

    keyboard.nextLine();

    socket.close();
    System.out.println("클라이언트와의 연결 해제!");

    ss.close();
    System.out.println("서버 종료!");

    keyboard.close();
  }
}

 

 

대기열과 연결 승인 테스트
package com.eomcs.net.ex02;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0420 {
  public static void main(String[] args) throws Exception {
    Scanner keyboard = new Scanner(System.in);

    ServerSocket ss = new ServerSocket(8888, 2);
    System.out.println("서버 소켓 생성 완료!");
    System.out.println("클라이언트 연결을 기다리는 중...");

    // 테스트2) 여러 클라이언트가 대기열에 있을 때,
    // => accept()를 호출하는 순간 즉시 대기열에서 맨 앞의 클라이언트 정보를 꺼내 연결한다.
    // => Queue 방식으로 대기열을 관리하기 때문에 맨 뒤에 빈자리가 생긴다.
    // 
    // 3개의 클라이언트들이 들어올 때까지 기다린다.
    // => 3개 중에 2개는 대기열에 기다리고, 나머지 1 개는 대기열에 없는 상태다.
    keyboard.nextLine();

    // 대기열에 있는 2개 중에 한 개의 클라이언트를 승인하면,
    // => 대기열에 포함 안된 나머지 1개의 클라이언트가 대기열에 들어온다.
    Socket socket = ss.accept();
    System.out.println("대기 중인 클라이언트 중 한 개의 클라이언트에 대해 연결 승인!");

    keyboard.nextLine();

    socket.close();
    System.out.println("클라이언트와의 연결 해제!");

    ss.close();
    System.out.println("서버 종료!");

    keyboard.close();
  }
}

 

 

대기열과 연결 승인 테스트 II
package com.eomcs.net.ex02;

import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0430 {
  public static void main(String[] args) throws Exception {
    Scanner keyboard = new Scanner(System.in);

    ServerSocket ss = new ServerSocket(8888, 2);
    System.out.println("서버 소켓 생성 완료!");
    System.out.println("클라이언트 연결을 기다리는 중...");

    while (true) {
      System.out.print("> ");
      String input = keyboard.nextLine(); // 사용자가 엔터를 칠 때까지 리턴하지 않는다.
      if (input.equalsIgnoreCase("q"))
        break;

      // 대기열에서 꺼내는 순간 대기열은 한 칸 빈다.
      // 즉 다른 클라이언트 연결을 수용할 수 있다.
      Socket socket = ss.accept();
      System.out.println("대기 중인 클라이언트 중 한 개의 클라이언트에 대해 연결 승인!");
    }

    ss.close();
    System.out.println("서버 종료!");

    keyboard.close();
  }
}

 

 

 

com.eomcs.net.ex03

 

 

서버와 입출력 테스트 - byte stream
package com.eomcs.net.ex03;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0110 {
  public static void main(String[] args) {
    Scanner keyScan = null;
    Socket socket = null;
    OutputStream out = null;
    InputStream in = null;

    try {
      keyScan = new Scanner(System.in);
      socket = new Socket("localhost", 8888);
      System.out.println("서버와 연결되었음!");

      // 서버랑 데이터를 주고 받을 수 있도록 입출력 스트림 객체를 준비한다.
      // => 출력 스트림 객체를 준비하기
      out = socket.getOutputStream();

      // => 입력 스트림 객체를 준비하기
      in = socket.getInputStream();
      System.out.println("소켓을 통해 입출력 스트림을 준비하였음!");

      // 서버에 데이터를 보내기 전에 잠깐 멈춤!
      System.out.print("데이터를 보내기 전에 잠깐!>");
      keyScan.nextLine();

      // 서버에게 데이터 보내기
      // - 보통 클라이언트 쪽에서 먼저 서버로 데이터를 보낸다.
      // - 실제 write()는 소켓의 내부 버퍼로 출력한다.
      //   따라서 write() 호출 후 즉시 리턴될 것이다.
      // - 즉 상대편에서 읽을 때까지 기다리는 것이 아니다.
      // - 보낼 데이터를 랜카드의 메모리에 올려 놓으면 바로 리턴한다.
      out.write(100);
      // out.flush();
      // byte stream 을 사용할 때는 바로 출력한다.
      // 따라서 flush()를 호출하지 않아도 된다.
      System.out.println("서버에 데이터를 보냈음!");

      // 서버의 응답을 받는다.
      // - 서버가 응답을 할 때까지 리턴하지 않는다.
      // 즉 blocking 모드로 작동한다.
      int response = in.read();
      System.out.println(response);

    } catch (Exception e) {
      e.printStackTrace();

    } finally {
      try {out.close();} catch (Exception e) {}
      try {in.close();} catch (Exception e) {}
      try {socket.close();} catch (Exception e) {}
    }

    keyScan.close();
  }
}

 

 

서버와 입출력 테스트 - byte stream + try with resource 문법 사용
package com.eomcs.net.ex03;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0111 {
  public static void main(String[] args) {
    try (Scanner keyScan = new Scanner(System.in);

        // 서버와 연결
        Socket socket = new Socket("localhost", 8888);

        // 서버랑 데이터를 주고 받을 수 있도록 입출력 스트림 객체를 준비한다.
        // => 출력 스트림 객체를 준비하기
        OutputStream out = socket.getOutputStream();

        // => 입력 스트림 객체를 준비하기
        InputStream in = socket.getInputStream()) {

      System.out.println("서버와 연결되었고, 입출력 스트림도 준비되었음!");

      // 서버에 데이터를 보내기 전에 잠깐 멈춤!
      System.out.print(">");
      keyScan.nextLine();

      // 서버에게 데이터 보내기
      // - 보통 클라이언트 쪽에서 먼저 서버로 데이터를 보낸다.
      // - 실제 write()는 소켓의 내부 버퍼로 출력한다.
      // 따라서 write() 호출 후 즉시 리턴될 것이다.
      out.write(100);
      // out.flush();
      // byte stream 을 사용할 때는 바로 출력한다.
      // 따라서 flush()를 호출하지 않아도 된다.
      System.out.println("서버에 데이터를 보냈음!");

      // 서버의 응답을 받는다.
      // - 서버가 응답을 할 때까지 리턴하지 않는다.
      // 즉 blocking 모드로 작동한다.
      int response = in.read();
      System.out.println(response);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 

 

서버와 입출력 테스트 - byte stream : 바이트 배열 보내고 받기
package com.eomcs.net.ex03;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0120 {
  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);

    try (Socket socket = new Socket("localhost", 8888);
        OutputStream out = socket.getOutputStream();
        InputStream in = socket.getInputStream()) {

      System.out.println("서버와 연결되었음!");

      // 서버에 데이터를 보내기 전에 잠깐 멈춤!
      System.out.print(">");
      keyScan.nextLine();

      // 서버에 보낼 바이트 배열을 준비한다.
      // => 0 ~ 99 의 값을 가진 배열이다.
      byte[] bytes = new byte[100];
      for (int i = 0; i < 100; i++) {
        bytes[i] = (byte) i;
      }

      // 서버에 바이트 배열을 전송한다.
      out.write(bytes);
      // out.flush();
      // byte stream 을 사용할 때는 바로 출력한다.
      // 따라서 flush()를 호출하지 않아도 된다.
      System.out.println("서버에 데이터를 보냈음!");

    } catch (Exception e) {
      e.printStackTrace();
    }

    keyScan.close();
  }
}

 

 

서버와 입출력 테스트 - byte stream : 바이트 배열 보내고 받기
package com.eomcs.net.ex03;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0121 {
  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);

    try (Socket socket = new Socket("localhost", 8888);
        OutputStream out = socket.getOutputStream();
        InputStream in = socket.getInputStream()) {

      System.out.println("서버와 연결되었음!");

      // 서버에서 보낸 바이트 배열을 읽는다.
      byte[] buf = new byte[100];
      int size = in.read(buf);
      System.out.printf("바이트 배열의 크기: %d\n", size);

      for (int i = 0; i < size; i++) {
        if (i > 0 && (i % 20) == 0) {
          System.out.println(); // 20바이트 출력한 후 줄 바꾼다.
        }
        System.out.printf("%x ", buf[i]);
      }

    } catch (Exception e) {
      e.printStackTrace();
    }

    keyScan.close();
  }
}

 

 

서버와 입출력 테스트 - byte stream : Data 주고 받기
package com.eomcs.net.ex03;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0130 {
  public static void main(String[] args) {

    try (Scanner keyScan = new Scanner(System.in);
        Socket socket = new Socket("localhost", 8888);
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        DataInputStream in = new DataInputStream(socket.getInputStream())) {

      System.out.println("서버와 연결되었고, 입출력 스트림도 준비되었음!");

      // 서버에 데이터를 보내기 전에 잠깐 멈춤!
      System.out.print(">");
      keyScan.nextLine();

      // 서버에 int 값을 전송한다.
      out.writeInt(1567891234); // 랜카드의 메모리로 출력한다. 전송이 완료될 때까지 기다리는 것이 아니다.
      // out.flush();
      // byte stream 을 사용할 때는 바로 출력한다.
      // 따라서 flush()를 호출하지 않아도 된다.
      System.out.println("서버에 데이터를 보냈음!");

      // 서버에서 보낸 int 값을 읽는다.
      System.out.println("서버의 응답을 기다리고 있음!");
      int value = in.readInt(); // 수신된 데이터가 메모리에 저장될 때까지 기다린다. 그래서 블로킹한다.
      System.out.println(value);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 

 

서버와 입출력 테스트 - byte stream : Data 주고 받기 II
package com.eomcs.net.ex03;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0140 {
  public static void main(String[] args) {

    try (Scanner keyScan = new Scanner(System.in);
        Socket socket = new Socket("localhost", 8888); // 서버의 대기열에 등록되는 순간 객체가 생성된다.
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());
        DataInputStream in = new DataInputStream(socket.getInputStream())) {

      System.out.println("서버와 연결되었음!");

      // 서버에 데이터를 보내기 전에 잠깐 멈춤!
      System.out.print(">");
      keyScan.nextLine();

      // 서버에 Data를 전송한다.
      out.writeInt(1567891234);
      out.writeByte(100);
      out.writeFloat(3.14f);
      out.writeUTF("ABC가각간");

      // out.flush();
      // byte stream 을 사용할 때는 바로 출력한다.
      // 따라서 flush()를 호출하지 않아도 된다.
      System.out.println("서버에 데이터를 보냈음!");

      // 서버에서 보낸 Data를 읽는다.
      System.out.println("서버로부터 데이터가 오기를 기다림!");
      int value = in.readInt();
      byte value2 = in.readByte();
      float value3 = in.readFloat();
      String value4 = in.readUTF();

      System.out.printf("%d, %d, %f, %s\n", value, value2, value3, value4);

    } catch (Exception e) {
      e.printStackTrace();
    }

  }
}

 

 

서버와 입출력 테스트 - byte stream: 문자열 주고 받기
package com.eomcs.net.ex03;

import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0150 {
  public static void main(String[] args) {

    try (Scanner keyScan = new Scanner(System.in);
        Socket socket = new Socket("localhost", 8888);
        PrintStream out = new PrintStream(socket.getOutputStream());
        Scanner in = new Scanner(socket.getInputStream())) {

      System.out.println("서버와 연결되었음!");

      // 서버에 데이터를 보내기 전에 잠깐 멈춤!
      System.out.print(">");
      keyScan.nextLine();

      out.println("ABC가각간");
      // out.flush();
      // byte stream 을 사용할 때는 바로 출력한다.
      // 따라서 flush()를 호출하지 않아도 된다.
      System.out.println("서버에 데이터를 보냈음!");

      String str = in.nextLine();
      System.out.println(str);

    } catch (Exception e) {
      e.printStackTrace();
    }
  }
}

 

 

서버와 입출력 테스트 - byte stream + buffer
package com.eomcs.net.ex03;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0160 {
  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);

    try (Socket socket = new Socket("localhost", 8888);
        PrintStream out = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
        Scanner in = new Scanner(new BufferedInputStream(socket.getInputStream()))) {

      System.out.println("서버와 연결되었음!");

      // 서버에 데이터를 보내기 전에 잠깐 멈춤!
      System.out.print(">");
      keyScan.nextLine();

      out.println("ABC가각간");
      out.flush();
      // 버퍼를 사용할 때는
      // 데이터를 보내기 위해 반드시 flush()를 호출해야 한다.
      // => 버퍼에 남아 있는 데이터를 연결된 출력 스트림을 이용하여 내보낸다.
      System.out.println("서버에 데이터를 보냈음!");

      String str = in.nextLine();
      System.out.println(str);

    } catch (Exception e) {
      e.printStackTrace();
    }

    keyScan.close();
  }
}

 

 

서버와 입출력 테스트 - character stream
package com.eomcs.net.ex03;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client0210 {
  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);

    try (Socket socket = new Socket("localhost", 8888);
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

      System.out.println("서버와 연결되었음!");

      // 서버에 데이터를 보내기 전에 잠깐 멈춤!
      System.out.print("데이터를 보내기 전에 잠깐!>");
      keyScan.nextLine(); // blocking : 키보드에서 한 줄을 읽을 때까지

      out.println("ABC가각간");
      out.flush();
      // character stream 클래스의 경우
      // 출력 데이터를 내부 버퍼에 보관하고 있다가
      // 버퍼가 꽉차거나 flush()를 호출할 때 출력을 수행한다.
      // 따라서 위 출력 문자열은 서버에 보내지 않는다.
      // - BufferedWriter를 붙이지 않아도 이렇게 동작하기 때문에 주의하라!
      System.out.println("서버에 데이터를 보냈음!");

      String str = in.readLine();
      System.out.println(str);

    } catch (Exception e) {
      e.printStackTrace();
    }

    keyScan.close();
  }
}

 

 

클라이언트와 입출력 테스트 - byte stream
      keyboard = new Scanner(System.in);
      serverSocket = new ServerSocket(8888);
      System.out.println("클라이언트의 연결을 기다리고 있음.");

      Socket socket = null;
      OutputStream out = null;
      InputStream in = null;

      try {
        // accept()
        // - 대기열에서 클라이언트 정보를 한 개 꺼내 소켓을 만들어 클라이언트와 연결한다.
        // - 만약 대기열에 클라이언트 정보가 없다면 클라이언트가 접속할 때까지 기다리다.
        socket = serverSocket.accept();
        System.out.println("대기열에서 클라이언트 정보를 꺼내 소켓을 생성하였음.");

        // 클라이언트와 데이터를 주고 받을 입출력 스트림 객체를 준비한다.
        // => 출력 스트림 객체를 준비하기
        out = socket.getOutputStream();

        // => 입력 스트림 객체를 준비하기
        in = socket.getInputStream();
        System.out.println("클라이언트와 통신할 입출력 스트림이 준비되었음.");

        // Client와 Server의 통신 규칙에 따라 순서대로 입출력 해야 한다.
        // 왜? 
        // 입출력은 blocking 모드로 작동하기 때문이다.
        // 즉 read()라는 메서드는 클라이언트가 보낸 데이터를 읽기 전까지 리턴하지 않는다.
        //
        // 클라이언트와 서버 간의 데이터를 주고 받는 통신 규칙을 "프로토콜(protocol)"이라 한다.
        // 클라이언트에서 한 줄의 문자열을 보내면
        // 서버는 한 줄의 문자열을 읽은 후에 응답해야 한다.

        System.out.print("데이터를 읽기 전에 잠깐!");
        keyboard.nextLine();

        System.out.println("클라이언트가 보낸 1바이트를 기다리고 있음!");
        // => 클라이언트가 1바이트를 보낼 때까지 리턴하지 않는다.
        int request = in.read(); // blocking 모드로 작동한다.

        System.out.println(request);

        // 서버가 데이터를 보내지 않으면 클라이언트의 read()는 리턴하지 않는다.
        // 이를 확인하기 위해 잠시 실행을 멈춘다.
        System.out.print("데이터를 보내기 전에 잠깐!");
        keyboard.nextLine();

        // => 클라이언트에게 받은 문자열을 그대로 보낸다.
        // 물론 클라이언트가 보낸 데이터를 다 읽을 때까지 리턴하지 않는다.
        out.write(request);
        // out.flush();
        // byte stream 을 사용할 때는 바로 출력한다.
        // 따라서 flush()를 호출하지 않아도 된다.
        System.out.println("클라인트에게 데이터를 보냈음.");

      } catch (Exception e) {
        e.printStackTrace();

      } finally {
        try {out.close();} catch (Exception e) {}
        try {in.close();} catch (Exception e) {}
        try {socket.close();} catch (Exception e) {}
        System.out.println("클라이언트와의 연결을 끊었음.");
      }


    } catch (Exception e) {
      System.out.println("상세 예외 정보:");
      e.printStackTrace();

    } finally {
      System.out.println("키보드 자원 해제 및 서버 소켓 자원 해제!");
      keyboard.close();
      try { serverSocket.close();} catch (IOException e) {}
    }
    System.out.println("서버 종료!");
  }

}

 

 

클라이언트와 입출력 테스트 - byte stream + try with resource 문법 사용
package com.eomcs.net.ex03;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0111 {
  public static void main(String[] args) {


    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("클라이언트의 연결을 기다리고 있음.");

      try (Socket socket = serverSocket.accept();

          // 클라이언트와 데이터를 주고 받을 입출력 스트림 객체를 준비한다.
          // => 출력 스트림 객체를 준비하기
          OutputStream out = socket.getOutputStream();

          // => 입력 스트림 객체를 준비하기
          InputStream in = socket.getInputStream()) {

        System.out.println("대기열에서 클라이언트 정보를 꺼내 소켓을 생성하였음.");
        System.out.println("클라이언트와 통신할 입출력 스트림이 준비되었음.");

        // Client와 Server의 통신 규칙에 따라 순서대로 입출력 해야 한다.
        // 왜? 입출력은 blocking 모드로 작동하기 때문이다.
        // 클라이언트와 서버 간의 데이터를 주고 받는 통신 규칙을 "프로토콜(protocol)"이라 한다.
        // 클라이언트에서 한 줄의 문자열을 보내면
        // 서버는 한 줄의 문자열을 읽은 후에 응답해야 한다.

        System.out.println("클라이언트가 보낸 1바이트를 기다리고 있음!");
        // => 클라이언트가 1바이트를 보낼 때까지 리턴하지 않는다.
        int request = in.read(); // blocking 모드로 작동한다.
        System.out.println(request);

        // 서버가 데이터를 보내지 않으면 클라이언트의 read()는 리턴하지 않는다.
        // 이를 확인하기 위해 잠시 실행을 멈춘다.
        System.out.print("데이터를 보내기 전에 잠깐!");
        keyboard.nextLine();

        // => 클라이언트에게 받은 문자열을 그대로 보낸다.
        // 물론 클라이언트가 보낸 데이터를 다 읽을 때까지 리턴하지 않는다.
        out.write(request);
        // out.flush();
        // byte stream 을 사용할 때는 바로 출력한다.
        // 따라서 flush()를 호출하지 않아도 된다.
        System.out.println("클라인트에게 데이터를 보냈음.");
      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();

    }
    System.out.println("서버 종료!");
  }

}

 

 

클라이언트와 입출력 테스트 - byte stream : 바이트 배열 주고 받기
package com.eomcs.net.ex03;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0120 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("클라이언트의 연결을 기다리고 있음.");

      try (Socket socket = serverSocket.accept();
          OutputStream out = socket.getOutputStream();
          InputStream in = socket.getInputStream()) {

        System.out.println("대기열에서 클라이언트 정보를 꺼내 소켓을 생성하였음.");
        System.out.println("클라이언트와 통신할 입출력 스트림이 준비되었음.");

        System.out.println("클라이언트가 보낸 100바이트를 기다리고 있음!");
        // => 클라이언트가 100바이트를 보낼 때까지 리턴하지 않는다.
        byte[] buf = new byte[100];
        int size = in.read(buf);
        System.out.printf("읽은 바이트 수: %d\n", size);

        for (int i = 0; i < size; i++) {
          if (i > 0 && (i % 20) == 0) {
            System.out.println(); // 20바이트 출력한 후 줄 바꾼다.
          }
          System.out.printf("%x ", buf[i]);
        }

      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

클라이언트와 입출력 테스트 - byte stream : 바이트 배열 주고 받기
package com.eomcs.net.ex03;

import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0121 {
  public static void main(String[] args) {
    // 소켓을 연결한 후에 꼭 클라이언트가 먼저 데이터를 보낼 필요는 없다.
    // 서버가 먼저 데이터를 보내도 된다.
    // 즉 읽고 쓰기 순서만 맞으면 누가 먼저 보내든지 상관없다.
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("클라이언트의 연결을 기다리고 있음.");

      try (Socket socket = serverSocket.accept();
          OutputStream out = socket.getOutputStream();
          InputStream in = socket.getInputStream()) {

        System.out.println("대기열에서 클라이언트 정보를 꺼내 소켓을 생성하였음.");
        System.out.println("클라이언트와 통신할 입출력 스트림이 준비되었음.");

        System.out.print("데이터를 보내기 전에 잠깐!");
        keyboard.nextLine();

        byte[] buf = new byte[100];
        for (int i = 0; i < buf.length; i++) {
          buf[i] =  (byte) i;
        }

        // 클라이언트에서 받은 바이트 개수 만큼 배열을 출력한다.
        out.write(buf);
        // out.flush();
        // byte stream 을 사용할 때는 바로 출력한다.
        // 따라서 flush()를 호출하지 않아도 된다.
        System.out.println("클라인트에게 데이터를 보냈음.");

      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

클라이언트와 입출력 테스트 - byte stream : Data 주고 받기
package com.eomcs.net.ex03;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0130 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("클라이언트의 연결을 기다리고 있음.");

      try (Socket socket = serverSocket.accept();
          DataOutputStream out = new DataOutputStream(socket.getOutputStream());
          DataInputStream in = new DataInputStream(socket.getInputStream())) {

        System.out.println("클라이언트와 연결되었고, 입출력 스트림도 준비되었음!");
        System.out.println("read() 호출 전에 잠깐 대기!");
        keyboard.nextLine();

        System.out.println("read() 호출: 클라이언트가 보낸 int 값을 기다리고 있음!");
        int value = in.readInt(); // 랜카드에 있는 메모리에서 수신된 4바이트를 읽을 때까지 기다린다.
        System.out.println(value);


        System.out.println("데이터를 보내기 전에 잠깐!");
        keyboard.nextLine();

        // 클라이언트에서 받은 int 값을 그대로 리턴한다.
        out.writeInt(value);
        // out.flush();
        // byte stream 을 사용할 때는 바로 출력한다.
        // 따라서 flush()를 호출하지 않아도 된다.
        System.out.println("클라인트에게 데이터를 보냈음.");

      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

클라이언트와 입출력 테스트 - byte stream : Data 주고 받기 II
package com.eomcs.net.ex03;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0140 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("클라이언트의 연결을 기다리고 있음.");

      try (Socket socket = serverSocket.accept();
          DataOutputStream out = new DataOutputStream(socket.getOutputStream());
          DataInputStream in = new DataInputStream(socket.getInputStream())) {

        System.out.println("클라이언트가 보낸 Data를 기다리고 있음!");

        int value = in.readInt();
        byte value2 = in.readByte();
        float value3 = in.readFloat();
        String value4 = in.readUTF();
        System.out.printf("%d, %d, %f, %s\n", value, value2, value3, value4);


        System.out.println("데이터를 보내기 전에 잠깐!");
        keyboard.nextLine();

        // 클라이언트에서 받은 Data를 그대로 리턴한다.
        out.writeInt(value);
        out.writeByte(value2);
        out.writeFloat(value3);
        out.writeUTF(value4);
        // out.flush();
        // byte stream 을 사용할 때는 바로 출력한다.
        // 따라서 flush()를 호출하지 않아도 된다.
        System.out.println("클라인트에게 데이터를 보냈음.");

      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

클라이언트와 입출력 테스트 - byte stream: 문자열 주고 받기
package com.eomcs.net.ex03;

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0150 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("클라이언트의 연결을 기다리고 있음.");

      try (Socket socket = serverSocket.accept();
          Scanner in = new Scanner(socket.getInputStream());
          PrintStream out = new PrintStream(socket.getOutputStream())) {

        System.out.println("클라이언트가 보낸 한 줄의 문자열을 기다리고 있음!");

        String str = in.nextLine();
        System.out.println(str);

        // 서버가 데이터를 보내지 않으면 클라이언트의 read()는 리턴하지 않는다.
        // 이를 확인하기 위해 잠시 실행을 멈춘다.
        System.out.print(">");
        keyboard.nextLine();

        out.println(str);
        // out.flush();
        // byte stream 을 사용할 때는 바로 출력한다.
        // 따라서 flush()를 호출하지 않아도 된다.
        System.out.println("클라인트에게 데이터를 보냈음.");

      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

클라이언트와 입출력 테스트 - byte stream + buffer
package com.eomcs.net.ex03;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0160 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("클라이언트의 연결을 기다리고 있음.");

      try (Socket socket = serverSocket.accept();
          Scanner in = new Scanner(new BufferedInputStream(socket.getInputStream()));
          PrintStream out = new PrintStream(new BufferedOutputStream(socket.getOutputStream()))) {

        System.out.println("클라이언트가 보낸 한 줄의 문자열을 기다리고 있음!");

        String str = in.nextLine();
        System.out.println(str);

        // 서버가 데이터를 보내지 않으면 클라이언트의 read()는 리턴하지 않는다.
        // 이를 확인하기 위해 잠시 실행을 멈춘다.
        System.out.print(">");
        keyboard.nextLine();

        out.println(str);
        out.flush();
        // 버퍼를 사용할 때는
        // 데이터를 보내기 위해 반드시 flush()를 호출해야 한다.
        // => 버퍼에 남아 있는 데이터를 연결된 출력 스트림을 이용하여 내보낸다.
        System.out.println("클라인트에게 데이터를 보냈음.");

      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

클라이언트와 입출력 테스트 - character stream
package com.eomcs.net.ex03;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0210 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("클라이언트의 연결을 기다리고 있음.");

      try (Socket socket = serverSocket.accept(); // blocking : 클라이언트가 대기열에 들어올 때까지
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          PrintWriter out = new PrintWriter(socket.getOutputStream())) {

        System.out.println("클라이언트가 보낸 한 줄의 문자열을 기다리고 있음!");

        String str = in.readLine();
        System.out.println(str);

        // 서버가 데이터를 보내지 않으면 클라이언트의 read()는 리턴하지 않는다.
        // 이를 확인하기 위해 잠시 실행을 멈춘다.
        System.out.print("데이터를 보내기 전에 잠깐!>");
        keyboard.nextLine();

        out.println(str);
        out.flush();
        // character stream 클래스의 경우
        // 출력 데이터를 내부 버퍼에 보관하고 있다가
        // flush()가 호출되면 비로서 출력을 수행한다.
        // 따라서 위 출력 문자열은 클라이언트에 보내지 않는다.
        // 주의!
        // => 단 close()를 호출하면 close() 내부에서 flush()를 실행한다.
        System.out.println("클라인트에게 데이터를 보냈음.");

      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

 

com.eomcs.net.ex04

 

 

통신 방식 - Stateful
package com.eomcs.net.ex04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

// stateful 방식
// => 서버와 연결한 후, 
//    클라이언트에서 연결을 끊을 때까지(또는 서버에서 연결을 끊을 때까지) 
//    계속해서 연결을 유지하며 클라이언트 요청을 처리한다.
// => 단점: 
//    - 한 번 연결하면 클라이언트가 연결을 끊을 때까지 계속 유지한다.
//    - 클라이언트가 작업을 요청하지 않더라도 계속 
//      서버에 연결정보를 계속 유지하기 때문에 
//      서버 메모리를 많이 차지하고 
//      동시에 많은 클라이언트의 요청을 처리하기 힘들다.
//    - 만약 서버가 순차적으로 클라이언트와 연결을 수행한다면
//      이전에 연결했던 클라이언트가 연결을 끊기 전까지는 
//      다음 클라이언트와 연결되지 못하는 문제가 있다. 
// => 장점:
//    - 한 번 서버에 연결되면 클라이언트가 연결을 끊을 때까지 유지되기 때문에
//      요청할 때 마다 매번 연결 작업을 수행할 필요가 없다.
//    - 따라서 요청에 대한 응답이 빠르다.
//    - 또한 연결된 상태에서 수행한 작업  정보를 서버에 유지할 수 있어
//      영속성이 필요한 작업을 처리하는데 유리하다.
// => 작업 시간: 
//    - = 데이터 전송 시간 + 작업 처리 시간 + 데이터 수신 시간
//    - 즉 작업을 요청할 때마다 연결할 필요가 없기 때문에 연결하는데 시간이 걸리지 않는다. 
//
// => 대표적인 예:
//    - 게임 서버 연결: 서버에 한 번 연결되면 게임을 마칠 때까지 데이터를 주고 받는다.
//    - 화상 통신: 한 번 연결하면 연결을 끊을 때까지 데이터를 주고 받는다.
//    - 채팅 서버: 전용 클라이언트를 이용한 채팅. 예) 유튜브 라이브 방송 채팅
//    - 텔렛: 원격 제어 프로그램
//    - FTP: 파일 전송 프로그램
//    - 오프라인 예: 건강보험문의, 상담 등 
//
// => 통신
// 클라이언트            서버
//     |------------------>| 연결
//     |------------------>| 데이터 수신
//     |<------------------| 데이터 전송
//     |------------------>| 데이터 수신
//     |<------------------| 데이터 전송
//     |------------------>| 데이터 수신
//     |<------------------| 데이터 전송
//     |--------X--------->| 끊기
// - 즉 한 번 연결하면 연결을 끊을 때까지 계속 데이터를 주고 받는다.
//

public class Client0110 {
  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);

    try (Socket socket = new Socket("localhost", 8888);
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

      System.out.println("서버와 연결되었음!");

      String name = null;
      do {
        System.out.print("이름? ");
        name = keyScan.nextLine();

        out.println(name);
        out.flush();

        String str = in.readLine();
        System.out.println(str);

      } while (!name.equalsIgnoreCase("quit"));

    } catch (Exception e) {
      e.printStackTrace();
    }

    keyScan.close();
  }
}

 

 

통신 방식 - Stateful + 서버 종료
package com.eomcs.net.ex04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

public class Client0130 {
  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);

    try (Socket socket = new Socket("localhost", 8888);
        PrintWriter out = new PrintWriter(socket.getOutputStream());
        BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

      System.out.println("서버와 연결되었음!");

      String name = null;
      do {
        System.out.print("이름? ");
        name = keyScan.nextLine();

        out.println(name);
        out.flush();

        String str = in.readLine();
        System.out.println(str);

      } while (!name.equalsIgnoreCase("quit") && !name.equalsIgnoreCase("stop"));

    } catch (Exception e) {
      e.printStackTrace();
    }

    keyScan.close();
  }
}

 

 

통신 방식 - Stateless
package com.eomcs.net.ex04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.Scanner;

// stateless
// => 서버에 작업을 요청할 때 연결하고, 서버로부터 응답을 받으면 연결을 끊는다.
// => 즉 요청/응답을 한 번만 수행한다.
// => 단점:
//    - 요청할 때마다 매번 서버에 연결해야 하기 때문에 실행 시간이 많이 걸린다.
//    - 실행시간 = 연결하는데 걸린 시간 + 데이터 전송 시간 + 작업 처리 시간 + 데이터 수신 시간
//    - 서버와 연결할 때,
//      사용자 인증(authentication; 아이디, 암호 유효 여부 검사)이나
//      사용권한 확인(authorization; 사용자에게 허락된 작업 범위를 확인) 같은
//      작업을 반드시 수행하는 경우 연결하는데 더더욱 많은 시간이 걸린다.
// => 장점:
//    - 서버에 계속 연결된 상태가 아니기 때문에 서버 입장에서는 메모리를 많이 사용하지 않는다.
//    - 왜?
//      클라이언트와 연결을 계속 유지하지 않기 때문에
//      작업을 처리하는 동안만 연결정보를 유지한다.
//      그래서 같은 메모리라도 stateful 방식 보다는
//      더 많은 클라이언트의 요청을 처리할 수 있다.
// => 대표적인 예:
//    - HTTP 통신: 웹브라우저가 서버에 연결한 후 요청을 하고 서버가 응답한 후 연결을 끊는다.
//    - 메신저: 메신저 서버에 연결하고 메시지 전송 후 연결 끊는다.
//    - 메일 전송: 메일서버에 데이터 전송 후 연결 끊는다.
//    - 오프라인 예: 114 안내, 배달 등
//
// => 통신
// 클라이언트            서버
//     |------------------>| 연결
//     |------------------>| 데이터 수신
//     |<------------------| 데이터 전송
//     |--------X--------->| 끊기
//     |------------------>| 연결
//     |------------------>| 데이터 수신
//     |<------------------| 데이터 전송
//     |--------X--------->| 끊기
//     |------------------>| 연결
//     |------------------>| 데이터 수신
//     |<------------------| 데이터 전송
//     |--------X--------->| 끊기
// - 서버에 요청할 때 마다 연결하고 응답을 받으면 즉시 연결을 끊는다.
// - 따라서 비영속적인 단일 작업을 처리할 때 적합한 통신 방식이다.

public class Client0210 {
  public static void main(String[] args) {
    Scanner keyScan = new Scanner(System.in);

    while (true) {
      System.out.print("이름? ");
      String name = keyScan.nextLine();

      if (name.equalsIgnoreCase("quit")) {
        break;
      }

      // 요청할 때 마다 서버와 연결한다.
      try (Socket socket = new Socket("localhost", 8888);
          PrintWriter out = new PrintWriter(socket.getOutputStream());
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()))) {

        System.out.println("서버와 연결되었음!");

        // 한 번 요청한다.
        out.println(name);
        out.flush();

        // 응답 받으면 서버와 연결을 끊는다.
        String str = in.readLine();
        System.out.println(str);

        System.out.println("서버와 연결 끊음!");

        if (name.equals("stop"))
          break; 

      } catch (Exception e) {
        e.printStackTrace();
      }
    }

    keyScan.close();
  }
}

 

 

통신 방식 - Stateful
package com.eomcs.net.ex04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0110 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("서버 실행!");

      try (Socket socket = serverSocket.accept();
          BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
          PrintWriter out = new PrintWriter(socket.getOutputStream())) {

        System.out.println("클라이언트가 연결되었음!");

        // stateful 방식은 클라이언트와 데이터를 주고 받는 동안 
        // 그 클라이언트의 작업 상태를 유지할 수 있다.
        // 예를 들어 다음과 같이 몇 번 요청했는지 요청 횟수를 유지할 수 있다.
        int count = 0;

        while (true) {
          String name = in.readLine();
          if (name.equalsIgnoreCase("quit")) {
            out.println("Goodbye!");
            out.flush();
            break;
          }
          count++;
          out.printf("%s 님 반갑습니다!(%d)\n", name, count);
          out.flush();
        }
      }
      System.out.println("클라이언트와의 연결을 끊었음.");

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

통신 방식 - Stateful + 여러 클라이언트 요청 처리
package com.eomcs.net.ex04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0120 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("서버 실행!");

      while (true) {
        // 한 클라이언트와 대화가 끝다면 다음 클라이언트와 대화를 한다.
        try (Socket socket = serverSocket.accept();
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream())) {

          System.out.println("클라이언트가 연결되었음!");

          while (true) {
            String name = in.readLine();
            if (name.equalsIgnoreCase("quit")) {
              out.println("Goodbye!");
              out.flush();
              break;
            }
            out.printf("%s 님 반갑습니다!\n", name);
            out.flush();
          }
        } catch (Exception e) {
          System.out.println("클라이언트와 통신 도중 오류 발생!");
        }
        System.out.println("클라이언트와의 연결을 끊었음.");
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

통신 방식 - Stateful + 여러 클라이언트 요청 처리 + 서버 종료
package com.eomcs.net.ex04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0130 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("서버 실행!");

      loop: while (true) {
        // 한 클라이언트와 대화가 끝다면 다음 클라이언트와 대화를 한다.
        try (Socket socket = serverSocket.accept();
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream())) {

          System.out.println("클라이언트가 연결되었음!");

          while (true) {
            String name = in.readLine();
            if (name.equalsIgnoreCase("quit")) { // 클라이언트와 연결 끊기
              out.println("Goodbye!");
              out.flush();
              break;
            } else if (name.equalsIgnoreCase("stop")) { // 서버 종료하기
              out.println("Goodbye!");
              out.flush();
              break loop;
            }

            out.printf("%s 님 반갑습니다!\n", name);
            out.flush();
          }
        } catch (Exception e) {
          System.out.println("클라이언트와 통신 도중 오류 발생!");
        }
        System.out.println("클라이언트와의 연결을 끊었음.");
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

통신 방식 - Stateful + 여러 클라이언트 요청 처리 + 서버 종료 + 로컬에서만 가능
package com.eomcs.net.ex04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class Server0140 {
  public static void main(String[] args) {
    try (Scanner keyboard = new Scanner(System.in);
        ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("서버 실행!");

      loop: while (true) {
        // 한 클라이언트와 대화가 끝다면 다음 클라이언트와 대화를 한다.
        try (Socket socket = serverSocket.accept();
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream())) {

          System.out.println("클라이언트가 연결되었음!");

          // 접속한 클라이언트의 IP 주소 알아내기
          InetAddress inetAddr = socket.getInetAddress();
          System.out.printf("접속자: %s\n", inetAddr.getHostAddress());

          while (true) {
            String name = in.readLine();
            if (name.equalsIgnoreCase("quit")) { // 클라이언트와 연결 끊기
              out.println("Goodbye!");
              out.flush();
              break;
            } else if (name.equalsIgnoreCase("stop")
                // localhost 에서만 서버를 멈출 수 있다.
                && inetAddr.getHostAddress().equals("127.0.0.1")) { // 서버 종료하기
              out.println("Goodbye!");
              out.flush();
              break loop;
            }

            out.printf("%s 님 반갑습니다!\n", name);
            out.flush();
          }
        } catch (Exception e) {
          System.out.println("클라이언트와 통신 도중 오류 발생!");
        }
        System.out.println("클라이언트와의 연결을 끊었음.");
      }

    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

통신 방식 - Stateless
package com.eomcs.net.ex04;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class Server0210 {
  public static void main(String[] args) {
    try (ServerSocket serverSocket = new ServerSocket(8888)) {

      System.out.println("서버 실행!");


      loop: while (true) {
        // 클라이언트와 연결된 후 요청을 받아서 응답하면 바로 연결을 끊는다.
        try (Socket socket = serverSocket.accept();
            BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
            PrintWriter out = new PrintWriter(socket.getOutputStream())) {

          System.out.println("클라이언트가 연결되었음!");

          String name = in.readLine();

          if (name.equals("stop")) {
            out.println("Goodbye!");
            out.flush();
            break loop;
          } else {
            out.printf("%s 님 반갑습니다!\n", name);
            out.flush();
          }
        } catch (Exception e) {
          System.out.println("클라이언트와 통신 중 오류 발생!");
        }
        System.out.println("클라이언트와의 연결을 끊었음.");
      }
    } catch (Exception e) {
      e.printStackTrace();
    }
    System.out.println("서버 종료!");
  }

}

 

 

stateful 방식 - 계산기 클라이언트 만들기
package com.eomcs.net.ex04.stateful;

import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class CalcClient {
  public static void main(String[] args) throws Exception {
    Scanner keyScan = new Scanner(System.in);

    Socket socket = new Socket("localhost", 8888);
    Scanner in = new Scanner(socket.getInputStream());
    DataOutputStream out = new DataOutputStream(socket.getOutputStream());

    while (true) {
      System.out.print("값1? ");
      out.writeInt(Integer.parseInt(keyScan.nextLine()));

      System.out.print("연산자? ");
      out.writeUTF(keyScan.nextLine());

      System.out.print("값2? ");
      out.writeInt(Integer.parseInt(keyScan.nextLine()));
      out.flush();

      String str = in.nextLine();
      System.out.println(str);

      if (str.equals("Goodbye!"))
        break;
    }

    in.close();
    out.close();
    socket.close();
    keyScan.close();
  }
}
stateful 방식 - 계산기 서버 만들기
package com.eomcs.net.ex04.stateful;

import java.io.DataInputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class CalcServer {
  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행 중...");

    ServerSocket ss = new ServerSocket(8888);

    while (true) {
      try (Socket socket = ss.accept()) {
        processRequest(socket);
      } catch (Exception e) {
        System.out.println("클라이언트 요청 처리 중 오류 발생!");
        System.out.println("다음 클라이언트의 요청을 처리합니다.");
      }
    }
    // ss.close();
  }

  static void processRequest(Socket socket) throws Exception {
    try (Socket s = socket;
        DataInputStream in = new DataInputStream(socket.getInputStream());
        PrintStream out = new PrintStream(socket.getOutputStream());) {

      loop: while (true) {
        int a = in.readInt();
        String op = in.readUTF();
        int b = in.readInt();
        int result = 0;

        switch (op) {
          case "+":
            result = a + b;
            break;
          case "-":
            result = a - b;
            break;
          case "*":
            result = a * b;
            break;
          case "/":
            result = a / b;
            break;
          case "quit":
            break loop;
          default:
            out.println("해당 연산을 지원하지 않습니다.");
            continue;
        }

        out.printf("%d %s %d = %d\n", a, op, b, result);
      }
      out.println("Goodbye!");
    }
  }
}

 

 

stateful 방식의 이점 활용 - 계산기 클라이언트 만들기
package com.eomcs.net.ex04.stateful2;

import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class CalcClient {
  public static void main(String[] args) throws Exception {
    Scanner keyScan = new Scanner(System.in);

    Socket socket = new Socket("localhost", 8888);
    Scanner in = new Scanner(socket.getInputStream());
    DataOutputStream out = new DataOutputStream(socket.getOutputStream());

    while (true) {
      System.out.print("연산자? ");
      out.writeUTF(keyScan.nextLine());

      System.out.print("값? ");
      out.writeInt(Integer.parseInt(keyScan.nextLine()));

      String str = in.nextLine();
      System.out.println(str);

      if (str.equals("Goodbye!"))
        break;
    }

    in.close();
    out.close();
    socket.close();
    keyScan.close();
  }
}
stateful 방식의 이점 활용 - 계산기 서버 만들기
package com.eomcs.net.ex04.stateful2;

import java.io.DataInputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class CalcServer {
  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행 중...");

    ServerSocket ss = new ServerSocket(8888);

    while (true) {
      // stateful을 사용할 때 이점:
      // => 연결되어 있는 동안 클라이언트의 작업 결과를 계속 유지할 수 있다.
      try (Socket socket = ss.accept()) {
        processRequest(socket);
      } catch (Exception e) {
        System.out.println("클라이언트 요청 처리 중 오류 발생!");
        System.out.println("다음 클라이언트의 요청을 처리합니다.");
      }
    }
    // ss.close();
  }

  static void processRequest(Socket socket) throws Exception {
    try (Socket socket2 = socket;
        DataInputStream in = new DataInputStream(socket.getInputStream());
        PrintStream out = new PrintStream(socket.getOutputStream());) {

      // 작업 결과를 유지할 변수
      int result = 0;

      loop: while (true) {
        String op = in.readUTF();
        int a = in.readInt();

        switch (op) {
          case "+":
            result += a;
            break;
          case "-":
            result -= a;
            break;
          case "*":
            result *= a;
            break;
          case "/":
            result /= a;
            break;
          case "quit":
            break loop;
          default:
            out.println("해당 연산을 지원하지 않습니다.");
            continue;
        }

        out.printf("계산 결과: %d\n", result);
      }
      out.println("Goodbye!");
    }
  }
}

 

 

stateful 방식 - 다중 클라이언트의 요청 처리 시 문제점과 해결책
package com.eomcs.net.ex04.stateful3;

import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class CalcClient {
  public static void main(String[] args) throws Exception {
    Scanner keyScan = new Scanner(System.in);

    Socket socket = new Socket("localhost", 8888);
    Scanner in = new Scanner(socket.getInputStream());
    DataOutputStream out = new DataOutputStream(socket.getOutputStream());

    while (true) {
      System.out.print("연산자? ");
      out.writeUTF(keyScan.nextLine());

      System.out.print("값1? ");
      out.writeInt(Integer.parseInt(keyScan.nextLine()));

      String str = in.nextLine();
      System.out.println(str);

      if (str.equals("quit"))
        break;
    }

    in.close();
    out.close();
    socket.close();
    keyScan.close();
  }
}
stateful 방식 - 다중 클라이언트의 요청 처리 시 문제점과 해결책
package com.eomcs.net.ex04.stateful3;

import java.io.DataInputStream;
import java.io.PrintStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class CalcServer {

  // 클라이언트와 통신하는 부분을 별도의 스레드(실행흐름)로 분리하여
  // 독립적으로 실행하게 한다.
  static class RequestHandler extends Thread {

    Socket socket;

    public RequestHandler(Socket socket) {
      this.socket = socket;
    }

    @Override
    public void run() {
      // main() 메서드 호출과 분리하여 별도로 실행할 코드가 있다면
      // 이 메서드에 둔다.
      try (Socket socket2 = socket;
          DataInputStream in = new DataInputStream(socket.getInputStream());
          PrintStream out = new PrintStream(socket.getOutputStream());) {

        // 작업 결과를 유지할 변수
        int result = 0;

        loop: while (true) {
          String op = in.readUTF();
          int a = in.readInt();

          switch (op) {
            case "+":
              result += a;
              break;
            case "-":
              result -= a;
              break;
            case "*":
              result *= a;
              break;
            case "/":
              result /= a;
              break;
            case "quit":
              break loop;
            default:
              out.println("해당 연산을 지원하지 않습니다.");
              continue;
          }

          out.printf("계산 결과: %d\n", result);
        }
        out.println("quit");

      } catch (Exception e) {
        System.out.println("클라이언트 요청 처리 중 오류 발생!");

      } finally {
        System.out.println("클라이언트 연결 종료!");
      }
    }
  }

  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행 중...");

    ServerSocket ss = new ServerSocket(8888);

    while (true) {
      System.out.println("클라이언트의 연결을 기다림!");
      Socket socket = ss.accept();
      InetSocketAddress remoteAddr = (InetSocketAddress) socket.getRemoteSocketAddress();
      System.out.printf("클라이언트(%s:%d)가 연결되었음!\n", //
          remoteAddr.getAddress(), remoteAddr.getPort());

      // 연결된 클라이언트가 연결을 끊기 전까지는
      // 대기하고 있는 다른 클라이언트의 요청을 처리할 수 없다.
      // 이것이 스레드를 사용하기 전의 문제점이다.
      // 해결책?
      // 클라이언트와 대화하는 부분을 스레드로 분리하여 실행하라!
      RequestHandler requestHandler = new RequestHandler(socket);
      requestHandler.start();
      // 스레드를 실행하려면 start() 를 호출하라.
      // start() 내부에서 run()을 호출할 것이다.
      // start() 호출한 후에 즉시 리턴한다.
      System.out.printf("%s 클라이언트와의 대화를 별도의 스레드에서 담당합니다!\n", //
          remoteAddr.getAddress());
    }
    // ss.close();
  }

}

 

 

stateless 방식 - 계산기 클라이언트 만들기
package com.eomcs.net.ex04.stateless;

import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class CalcClient {
  public static void main(String[] args) throws Exception {
    Scanner keyScan = new Scanner(System.in);

    while (true) {
      System.out.print("값1? ");
      int a = Integer.parseInt(keyScan.nextLine());

      System.out.print("연산자? ");
      String op = keyScan.nextLine();

      System.out.print("값2? ");
      int b = Integer.parseInt(keyScan.nextLine());

      try (Socket socket = new Socket("localhost", 8888);
          Scanner in = new Scanner(socket.getInputStream());
          DataOutputStream out = new DataOutputStream(socket.getOutputStream())) {

        out.writeInt(a);
        out.writeUTF(op);
        out.writeInt(b);

        String str = in.nextLine();
        System.out.println(str);

      } catch (Exception e) {
        System.out.println("서버와 통신 중 오류 발생!");
      }

      System.out.print("계속하시겠습니까?(Y/n)");
      if (keyScan.nextLine().equalsIgnoreCase("n")) {
        break;
      }
    }

    keyScan.close();
  }
}
stateless 방식 - 계산기 서버 만들기
package com.eomcs.net.ex04.stateless;

import java.io.DataInputStream;
import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;

public class CalcServer {
  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행 중...");

    ServerSocket ss = new ServerSocket(8888);

    while (true) {
      Socket socket = ss.accept();
      System.out.println("클라이언트 요청 처리!");
      try {
        processRequest(socket);
      } catch (Exception e) {
        System.out.println("클라이언트 요청 처리 중 오류 발생!");
        System.out.println("다음 클라이언트의 요청을 처리합니다.");
      }
    }
    // ss.close();
  }

  static void processRequest(Socket socket) throws Exception {
    try (Socket socket2 = socket;
        DataInputStream in = new DataInputStream(socket.getInputStream());
        PrintStream out = new PrintStream(socket.getOutputStream());) {

      int a = in.readInt();
      String op = in.readUTF();
      int b = in.readInt();
      int result = 0;

      switch (op) {
        case "+":
          result = a + b;
          break;
        case "-":
          result = a - b;
          break;
        case "*":
          result = a * b;
          break;
        case "/":
          result = a / b;
          break;
        default:
          out.println("해당 연산을 지원하지 않습니다.");
          return;
      }
      out.printf("%d %s %d = %d\n", a, op, b, result);
    }
  }
}

 

 

stateless 방식에서 클라이언트를 구분하고 작업 결과를 유지하는 방법
package com.eomcs.net.ex04.stateless2;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class CalcClient {
  public static void main(String[] args) throws Exception {
    Scanner keyScan = new Scanner(System.in);

    // 서버에서 이 클라이언트를 구분할 때 사용하는 번호이다.
    // => 0 번으로 서버에 요청하면, 서버가 신규 번호를 발급해 줄 것이다.
    long clientId = 0;

    while (true) {
      System.out.print("연산자? ");
      String op = keyScan.nextLine();

      System.out.print("값? ");
      int value = Integer.parseInt(keyScan.nextLine());

      try (Socket socket = new Socket("localhost", 8888);
          DataOutputStream out = new DataOutputStream(socket.getOutputStream());
          DataInputStream in = new DataInputStream(socket.getInputStream())) {

        // => 서버에 클라이언트 아이디를 보낸다.
        out.writeLong(clientId);

        // => 서버에 연산자와 값을 보낸다.
        out.writeUTF(op);
        out.writeInt(value);
        out.flush();

        // => 서버에서 보낸 클라이언트 아이디를 읽는다.
        clientId = in.readLong();

        // => 서버에서 보낸 결과를 읽는다.
        System.out.println(in.readUTF());

      } catch (Exception e) {
        System.out.println("서버와 통신 중 오류 발생!");
      }

      System.out.print("계속하시겠습니까?(Y/n)");
      if (keyScan.nextLine().equalsIgnoreCase("n")) {
        break;
      }
    }

    keyScan.close();
  }
}
stateless 방식에서 클라이언트를 구분하고 작업 결과를 유지하는 방법
package com.eomcs.net.ex04.stateless2;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class CalcServer {

  // 각 클라이언트의 작업 결과를 보관할 맵 객체
  // => Map<clientID, result>
  static Map<Long, Integer> resultMap = new HashMap<>();

  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행 중...");

    ServerSocket ss = new ServerSocket(8888);

    while (true) {
      Socket socket = ss.accept();
      System.out.println("클라이언트 요청 처리!");
      try {
        processRequest(socket);
      } catch (Exception e) {
        System.out.println("클라이언트 요청 처리 중 오류 발생!");
        System.out.println("다음 클라이언트의 요청을 처리합니다.");
      }
    }
    // ss.close();
  }

  static void processRequest(Socket socket) throws Exception {
    try (Socket socket2 = socket;
        DataInputStream in = new DataInputStream(socket.getInputStream());
        DataOutputStream out = new DataOutputStream(socket.getOutputStream());) {

      // 클라이언트를 구분하기 위한 아이디
      // => 0: 아직 클라이언트 아이디가 없다는 의미
      // => x: 서버가 클라이언트에게 아이디를 부여했다는 의미
      long clientId = in.readLong();

      // 연산자와 값을 입력 받는다.
      String op = in.readUTF();
      int value = in.readInt();

      // 클라이언트를 위한 기존 값 꺼내기
      Integer obj = resultMap.get(clientId);
      int result = 0;

      if (obj != null) {
        System.out.printf("%d 기존 고객 요청 처리!\n", clientId);
        result = obj; // auto-unboxing
      } else {
        // 맵에 해당 클라이언트 ID로 저장된 값이 없다는 것은
        // 한 번도 서버에 접속한 적이 없다는 의미다. 
        // 따라서 새 클라이언트 아이디를 발급한다.
        // => 예제를 간단히 하기 위해 현재 실행 시점의 밀리초를 사용한다.
        clientId = System.currentTimeMillis();
        System.out.printf("%d 신규 고객 요청 처리!\n", clientId);
      }

      String message = null;
      switch (op) {
        case "+":
          result += value;
          break;
        case "-":
          result -= value;
          break;
        case "*":
          result *= value;
          break;
        case "/":
          Thread.sleep(30000);
          result /= value;
          break;
        default:
          message = "해당 연산을 지원하지 않습니다.";
      }

      // 계산 결과를 resultMap에 보관한다.
      resultMap.put(clientId, result);

      // 클라이언트로 응답할 때 항상 클라이언트 아이디와 결과를 출력한다.
      // => 클라이언트 아이디 출력
      out.writeLong(clientId);

      // => 계산 결과 출력
      if (message == null) {
        message = String.format("계산 결과: %d", result);
      }
      out.writeUTF(message);
      out.flush();

    }
  }
}

 

 

stateless 에서 다중 클라이언트 요청 처리하기
package com.eomcs.net.ex04.stateless3;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class CalcClient {
  public static void main(String[] args) throws Exception {
    Scanner keyScan = new Scanner(System.in);

    // 서버에서 이 클라이언트를 구분할 때 사용하는 번호이다.
    // => 0 번으로 서버에 요청하면, 서버가 신규 번호를 발급해 줄 것이다.
    long clientId = 0;

    while (true) {
      System.out.print("연산자? ");
      String op = keyScan.nextLine();

      System.out.print("값? ");
      int value = Integer.parseInt(keyScan.nextLine());

      try (Socket socket = new Socket("localhost", 8888);
          DataOutputStream out = new DataOutputStream(socket.getOutputStream());
          DataInputStream in = new DataInputStream(socket.getInputStream())) {

        // => 서버에 클라이언트 아이디를 보낸다.
        out.writeLong(clientId);

        // => 서버에 연산자와 값을 보낸다.
        out.writeUTF(op);
        out.writeInt(value);
        out.flush();

        // => 서버에서 보낸 클라이언트 아이디를 읽는다.
        clientId = in.readLong();

        // => 서버에서 보낸 결과를 읽는다.
        int result = in.readInt();
        System.out.printf("계산 결과: %d\n", result);

      } catch (Exception e) {
        System.out.println("서버와 통신 중 오류 발생!");
      }

      System.out.print("계속하시겠습니까?(Y/n)");
      if (keyScan.nextLine().equalsIgnoreCase("n")) {
        break;
      }
    }

    keyScan.close();
  }
}
stateless 에서 다중 클라이언트 요청 처리하기
package com.eomcs.net.ex04.stateless3;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.Map;

public class CalcServer {

  // 각 클라이언트의 작업 결과를 보관할 맵 객체
  // => Map<clientID, result>
  static Map<Long, Integer> resultMap = new HashMap<>();


  // 클라이언트와 대화하는 부분을 별도의 코드로 분리하여 실행한다.
  static class RequestHandler extends Thread {

    Socket socket;

    public RequestHandler(Socket socket) {
      this.socket = socket;
    }

    @Override
    public void run() {
      // main 스레드와는 별개로 실행해야 하는 코드가 있다면
      // 이 메서드에 둔다.
      try (Socket socket2 = socket;
          DataInputStream in = new DataInputStream(socket.getInputStream());
          DataOutputStream out = new DataOutputStream(socket.getOutputStream());) {

        // 클라이언트를 구분하기 위한 아이디
        // => 0: 아직 클라이언트 아이디가 없다는 의미
        // => x: 서버가 클라이언트에게 아이디를 부여했다는 의미
        long clientId = in.readLong();

        // 연산자와 값을 입력 받는다.
        String op = in.readUTF();
        int value = in.readInt();

        // 클라이언트는 위한 기존 값 꺼내기
        Integer obj = resultMap.get(clientId);
        int result = 0;

        if (obj != null) {
          System.out.printf("%d 기존 고객 요청 처리!\n", clientId);
          result = obj; // auto-unboxing
        } else {
          // 해당 클라이언트가 방문한적이 없다면, 새 클라이언트 아이디를 발급한다.
          clientId = System.currentTimeMillis();
          System.out.printf("%d 신규 고객 요청 처리!\n", clientId);
        }

        switch (op) {
          case "+":
            result += value;
            break;
          case "-":
            result -= value;
            break;
          case "*":
            result *= value;
            break;
          case "/":
            Thread.sleep(30000);
            result /= value;
            break;
        }
        // 클라이언트로 응답할 때 항상 클라이언트 아이디와 결과를 출력한다.
        // => 클라이언트 아이디 출력
        out.writeLong(clientId);

        // => 계산 결과 출력
        out.writeInt(result);

        out.flush();

        // 계산 결과를 다시 resultMap에 보관한다.
        resultMap.put(clientId, result);

      } catch (Exception e) {
        System.out.println("클라이언트 요청 처리 중 오류 발생!");
      } finally {
        System.out.println("클라이언트 연결 종료!");
      }
    }
  }


  public static void main(String[] args) throws Exception {
    System.out.println("서버 실행 중...");

    ServerSocket ss = new ServerSocket(8888);

    while (true) {
      System.out.println("클라이언트의 연결을 기다림!");
      Socket socket = ss.accept();
      InetSocketAddress remoteAddr = (InetSocketAddress) socket.getRemoteSocketAddress();
      System.out.printf("클라이언트(%s:%d)가 연결되었음!\n", //
          remoteAddr.getAddress(), remoteAddr.getPort());

      // 독립적으로 수행할 코드를 갖고 있는 스레드 객체를 생성한다.
      RequestHandler requestHandler = new RequestHandler(socket);

      // 그리고 작업을 실행시킨다.
      // => 스레드를 실행시킨 후 바로 리턴한다.
      requestHandler.start();
      System.out.printf("%s 클라이언트 요청을 스레드에게 맡겼습니다!\n", //
          remoteAddr.getAddress());

    }
    // ss.close();
  }
}

 

 

 

com.eomcs.net.ex05

 

 

connection-oriented 클라이언트 - 연결 후 데이터 송수신
package com.eomcs.net.ex05;

import java.io.PrintStream;
import java.net.Socket;
import java.util.Scanner;

public class Client0110 {
  public static void main(String[] args) throws Exception {
    Socket socket = new Socket("localhost", 8888); // 서버에 연결
    System.out.println("서버에 연결됨!");

    // 연결이 이루어진 후 데이터 입출력을 할 수 있다.
    Scanner in = new Scanner(socket.getInputStream());
    PrintStream out = new PrintStream(socket.getOutputStream());

    out.println("Hello!");
    System.out.println("데이터 보냄!");

    String str = in.nextLine();
    System.out.println("데이터 받음!");
    System.out.println(str);

    in.close();
    out.close();
    socket.close();
    System.out.println("서버와 연결 끊음!");
  }
}
connection-oriented 서버 - 연결 후 데이터 송수신
package com.eomcs.net.ex05;

import java.io.PrintStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

// 연결지향(connection oriented)
// => 연결 후에 데이터를 송수신 하기 때문에 데이터 송수신에 대한 신뢰를 보장한다.
// => TCP 통신 방법이 전형적인 예이다.
//    예) FTP, Telnet, SMTP, POP3, HTTP 등 
// 
public class Server0110 {
  public static void main(String[] args) throws Exception {
    Scanner keyboardScanner = new Scanner(System.in);

    System.out.println("서버 실행 중...");
    ServerSocket ss = new ServerSocket(8888);

    System.out.println("엔터를 치면 대기열에서 기다리고 있는 클라이언트의 소캣을 생성한다.>");
    keyboardScanner.nextLine();

    Socket socket = ss.accept();
    System.out.println("클라이언트와 통신할 소캣을 준비하였다!");

    Scanner in = new Scanner(socket.getInputStream());
    PrintStream out = new PrintStream(socket.getOutputStream());

    String str = in.nextLine();
    System.out.println("데이터 수신 완료!");

    out.println(str);
    System.out.println("데이터 송신 완료!");

    in.close();
    out.close();
    socket.close();
    System.out.println("클라이언트 연결 끊기!");

    ss.close();
    keyboardScanner.close();
  }
}

 

 

connectionless 클라이언트 - 연결없이 데이터 송신
package com.eomcs.net.ex05;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

// Connectionless
// => 서버와 연결없이 데이터를 보내고 받을 수 있다.
// => DatagramSocket, DatagramPacket을 사용하여 처리한다.
// => 예) 편지, ping 등
// => 응용) 모니터링 프로그램에서 많이 사용한다.
//
public class Client0210 {
  public static void main(String[] args) throws Exception {
    // connectionless 방식으로 통신을 수행할 소켓 생성
    // - 클라이언트 쪽은 포트 번호를 지정하지 않는다.
    // - 물론 OS가 자동으로 부여할 것이다.
    DatagramSocket socket = new DatagramSocket();

    // 데이터를 받을 상대편 주소와 포트 번호
    String receiver = "localhost";
    int port = 8888;

    // 보낼 데이터를 바이트 배열로 준비
    //    String message = new String("Hello"); // Heap에 String 객체 생성
    //    String message = "Hello"; // constant pool에 String 객체 생성
    //    byte[] bytes = message.getBytes("UTF-8");
    byte[] bytes = "Hello".getBytes("UTF-8");

    // 보낼 데이터를 패킷에 담는다.
    // => 패킷 = 데이터 + 데이터크기 + 받는이의 주소 + 받는이의 포트번호
    DatagramPacket packet = new DatagramPacket(
        bytes, // 데이터가 저장된 바이트 배열
        bytes.length, // 전송할 데이터 개수
        InetAddress.getByName("localhost"), // 데이터를 받을 상대편 주소
        port // 포트번호
        );

    // 데이터 전송
    socket.send(packet);
    System.out.println("데이터 전송 완료!");

    // 자원해제
    socket.close();

    // 상대편이 네트웍에 연결되었는지 따지지 않고 무조건 데이터를 보낸다.
    // 만약 상대편이 연결되어 있지 않다면, 보낸 데이터는 그 쪽 네트웍에서 버려진다.
    // => 데이터 송수신을 보장하지 않는다.
  }
}
connectionless 클라이언트 - 연결없이 데이터 수신
package com.eomcs.net.ex05;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.util.Scanner;

// 비연결(connectionless)
// => 연결없이 데이터를 송수신한다.
// => 상대편이 네트워크에 연결되어 있지 않다면, 그 데이터는 버려진다.
// => 그래서 전송 여부를 신뢰할 수 없다.
// => 실생활에서 "편지"와 같다.
// => 예) ping
// => DatagramSocket, DatagramPacket을 사용하여 처리한다.
public class Server0210 {
  public static void main(String[] args) throws Exception {
    Scanner keyScan = new Scanner(System.in);

    System.out.println("서버 실행 중...");

    System.out.println("소켓 생성 전 잠깐!>");
    keyScan.nextLine();

    // 데이터 송수신을 담당할 소켓을 먼저 준비한다.
    // => 보내는 쪽이나 받는 쪽이나 같은 소켓 클래스를 사용한다.
    //    서버 소켓이 따로 없다.
    // => 받는 쪽에서는 소켓을 생성할 때 포트번호를 설정한다.
    DatagramSocket socket = new DatagramSocket(8888);

    // 받은 데이터를 저장할 버퍼 준비
    byte[] buf = new byte[8196];

    // 빈 패킷 준비
    DatagramPacket emptyPacket = new DatagramPacket(buf, buf.length);

    System.out.println("데이터를 읽기 전에 잠깐 멈춤>");
    keyScan.nextLine();

    // 빈 패킷을 사용하여 클라이언트가 보낸 데이터를 받는다.
    // => 데이터를 받을 때까지 리턴하지 않는다.
    socket.receive(emptyPacket);
    System.out.println("데이터를 받았음!");

    socket.close();
    keyScan.close();

    // 빈 패킷에 저장된 클라이언트가 보낸 데이터를 꺼낸다.
    // 패킷에 저장된 UTF-8로 인코딩된 바이트 배열을 가지고 String 객체(UTF-16)를 만든다.

    // 1) 패킷 객체에 보관된 바이트 배열을 꺼낸다.
    byte[] bytes = emptyPacket.getData();

    // getData()가 리턴한 배열은 DatagramPacket 을 만들 당시 넘겨준 배열이다.
    System.out.println(buf == bytes);

    // 2) 바이트 배열에 보관된 데이터의 개수를 알아낸다.
    int len = emptyPacket.getLength();

    // 3) 클라이언트에서 받은 바이트 배열을 가지고 String 객체를 생성한다.
    String message = new String(bytes, 0, len, "UTF-8");

    // 실무에서는 다음과 같이 로컬 변수를 사용하지 않고 직접 패킷 객체를 사용하는 방식으로 코딩한다.
    //    String message = new String(//
    //        emptyPacket.getData(), // ==> buf, 패킷에서 바이트 배열을 꺼낸다.
    //        0, // 버퍼에서 데이터를 꺼낼 때 0번째부터 꺼낸다.
    //        emptyPacket.getLength(), // 패킷에서 받은 바이트의 개수만큼 데이터를 꺼낸다.
    //        "UTF-8" // 바이트 배열로 인코딩된 문자표의 이름을 지정한다.
    //        );

    System.out.println(message);

  }
}