19. 네트워크 입출력

19.1 네트워크기초

네트워크는 여러 컴퓨터들을 통신회선으로 연결한 것을 말한다.
LAN은 가정 회사 건물 특정영역에 존재하는 컴퓨터를 연결한것 WAN은 LAN을 연결한것임.

19.1.1 서버와 클라이언트

네트워크에서 유무선으로 컴퓨터가 연결되어 있다면 실제로 데이터를 주고받는 행위는 프로그램들이 이한다.
서비스를 제공하는 프로그램을 일반적으로 서버라고 부르고 서비스를 요총하는 프로그램을 클라이언트라고 부른다.
인터넷에서 두 프로그램이 통신하기 위해서는 먼저 클라이언트가 서비스를 요청하고 서버는 처리결과를 응답으로 제공해준다.

19.1.2 IP서버

IP주소는 네트워크 어댑터 LAN카드 마다 할당된다.
IP주소를 모르면 프로그램들은 서로 통신할 수 없다. 프로그램은 DNS를 이용해서 컴퓨터의 IP주소를 검색한다.
DNS는 도메인이름으로 IP를 등록하는 저장소이다. 대중에게 서비스를 제공하는 대부분의 컴퓨터는 DNS에 미리 등록해놓는다.
웹브라우저는 웹 서버와 통신하는 클라이언트로 사용자가 입력한 도메인 이름으로 DNS에서 IP주소를 검색해 찾은 다음 웹서버와 연결해서 웹페이지를 받는다.

19.1.3 Port번호

IP주소는 네트워크 어댑터끼리만 연결임 실제 프로그램끼리 연결하려면 port번호가 필요하다.
한대의 컴퓨터에는 다양한 서버 프로그램들이 실행될 수 있다. 예를들어 웹서버 데이터베이스 관리시스템(DBMS) FTP서버 등이 하나의 ip주소를 갖는 컴퓨터에서 동시에 실행될 수있다.
이 경우 클라이언트는 어떤 서버와 통실해야할지 결정해야한다. IP는 컴퓨터의 네트워크 어댑터까지만 갈 수있는 정보이기때문에 컴퓨터 내부에서 실행하는 서버를 선택하기 위해서는 추가적인 Port번호가 필요하다.
네트우크 어댑터가 IP와 Port번호를 알아야 연결이 된다.
Port는 운영체제가 관리하는 서버 프로그램의 연결번호이다. 운영체재가 알아서 생성해준다.
서버는 시작할때 특정 Port번호에 바인딩한다.
예를들어 웹서버는 80번(디폴트값)으로 Ftp은21번(디폴트값) DBMS는 1521번(고정xdbms마다 다름)으로 바인딩할 수있다.
따라서 클라이언트가 웹서버와 통신하려면 80번으로 DBMS는 1521번으로 요청을 해야한다.

클라이언트 서버에서 보낸 정보를 받기위해서는 Port번호가 필요한데 서버와 같이 고정적인 Port번호에 바인딩하는 것이아니라 운영체제가 자동으로 부여하는 번호를 사용한다.
클라이언트가 서버에 요청할때 ip주소와 포트번호를 제공하고 서버가 데이터를 준다.
이 번호는 클라이언트가 서버로 요청할때 함께 전송되어 서버가 클라이언트로 데이터를 보낼때 사용된다.
2byte로 표현할수 있는 범위에서 Port번호 제공함.
프로그램에서 사용할 수 있는 전체Port번호의 범위는 065535로 사용목적에 따라 세가지 범위를 가진다.
1024
49151 회사에서 돈주고 사용 49152~65535 맘대로 사용가능
port가 동일하면 충돌할수 있는데 이러면 연결이 안된다.

19.2 ip주소 얻기

자바는 IP주소를 java.net 패키지의 InetAdderss로 표현한다. 로컬 컴퓨터의ip주소를 얻을수도잇고 도메인이름으로 DNS에서 검색후 IP주소를 가져올수도잇다.
InetAdderss = ip주소를 표현한 객체임.
InetAddress ia = InetAddress.getLocalHost(); 내ip가져오기
도메인은 서버를 여러개를 사용해서 배열로 가져올수 잇다.
클라이언트가 많이 연결되었을경우 서버부하를 나누기 위해서임.
InetAdrress ia = InetAddress.getByName(String domainName);
주어진도메인이름으로 등록된 ip주소를 dns에서 찾아서 줌
InetAdrress[] iaArr = InetAddress.getAllName(String domainName);
인터넷은 불특정다수 클라이언트가 접속가능 클라이언트가 많으면 서버가 좋아야함. 이게 한정적임. 그래서 서버를 여러개 사용함.
그러나 도메인은 하나 사용함. 여러개 ip주소가 도메인하나에 있을수 있다.
리턴값은 문자열로된 IP주소이다.
정적메소드로 객체를 간접적으로 생성하고 얻어야한다.
String ip = ia.getHostAddress();를 사용하면 실제 IP주소를 받을 수 있다.

public class InetAddressExample {
public static void main(String[] args) {
    try {
        InetAddress local = InetAddress.getLocalHost();
        System.out.println("내 컴퓨터 IP 주소: " + local.getHostAddress());

        InetAddress[] iaArr = InetAddress.getAllByName("www.naver.com");
        for(InetAddress remote : iaArr) {
            System.out.println("www.naver.com IP 주소: " + remote.getHostAddress());
        }
    } catch(UnknownHostException e) {
        e.printStackTrace();
    }}}

19.3 TCP네트워킹

네트워킹 = 네트워크상에서 데이터를 주고 받는 것.
TCP는 프로토콜의 이름 주고받는 언어가 있어야하듯이 데이터를 보내기 위한 규칙이있다.
즉 네트워크상에서 데이터를 주고받으려면 규칙이 있다.
IP를 사용해서 연결작업을 먼저 해야한다. tcp는 네트워크 규약이다.
신호를 어떻게 만들어서 보낼것인가?를 정해놓은것이 프로토콜이다.
데이터를 주로 전송할때 쓰는 용도로 사용해서 전송용 프로토콜이라고 한다.
인터넷에서 전송용 프로토콜은 TCP와 UDP가 있다.
차이점은 TCP는 연결되는지 확인하고 데이터 보냄 UDP는 연결확인안하고 그냥 보냄
TCP는 연결형 프로토콜로 상대방이 연결된 상태에서 데이터를 주고받는다.
클라이언트가 연결요청을하고 서버가 연결을 수락하면 통신회선이 고정되고 데이터는 고정회선을 통해 전달된다.
먼저 연결하면 통신회선이 고정되고 고정회선을 통해 데이터가 전달된다.
그렇기 때문에 TCP는 보낸 데이터를 순서대로 전달되며 손실이 발생하지 않는다.
1번->1번 2번>2번 3번>3번 이런식으로 보냄
TCP는 IP와 함께 사용되기때문에 TCP/IP라고도 한다. TCP는 웹브라우저가 웹서버에 연결할때 사용되며 이메일전송 파일전송 DB연동에도 사용된다.
대부분 TCP가 사용이된다. 데이터 손실이 없기때문임.
자바는 TCP네트워킹을 위해 java.net 패키지에서 ServerSocket과 Socket 클래스를 제공하고 있다.
ServerSocket는 클라이언트의 연결을 수락하는 서버쪽 클래스이고
Socket는클라이언트에서 연결 요청할때와 클라이언트와 서버양쪽에서 데이터를 주고받을때 사용되는 클래스이다.
서버: 서비스를 제공하는 프로그램 언제클라이언트가 요철할지 모르니 24시간 열려있게함.
처음 연결요청을할때 어떻게 되나 밑에사진
socket끼리는 서로 통신하기 serversocket은 클라이언트가 연결요청을 accept받아서 얘하고 통신할 수 있는 socket을 만들어줌.
클라이언트는 socket에서 통신과 요청 둘다함.


클라이언트 연결요청시 IP주소와 서버소켓이 가지고잇는 Port번호까지 제공
서버소켓이 accept하면 통신용객체 소켓을 만듬 서로 데이터를 주고받기를 함. 입출력스트림 사용해서.
1.서버 소켓이 연결요청을 받기 2.소켓만들어서 통신하기

19.3.1 TCP서버

운영체제가 부여하는 동적 Port혹은 개인적인 목적으로 사용할수 있는 Port인 49152~65535 범위사용해야한다.
서버소켓을 객체를 생성하고 바인딩해도됨
TCP 서버 프로그램을 개발하려면 우선 SercerSocket객체를 생성해야한다. 다음은 50001번 Port에 바인딩하는 SercerSocket을 생성하는 코드이다.
ServerSocket serverSocket = new ServerSocket(50001);
ServerSocket을 생성하는 또 다른 방법은 기본생성자로 객체를 생성하고 Port바인딩을 위해 bind()메소드를 호출하는 것이다.
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(50001));
만약 서버 컴퓨터에 여러개의 IP가 할당되어 잇을경우 특정 IP에서만 서비스를 하로 싶다면 InetSocketAddress의 첫번째 매개값으로 해당 IP를 주면된다.
네트워크 어댑터가 여러개잇을경우 위에거로하면 아무거나로 연결수락을 함.
만약 아래로하면 한쪽의 어댑터로만 들어갈 수 있음.
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("xxx,xxx,xxx,xxx",50001));

포트번호는 서버프로그램을 식별하는 용도로 사용함. 동일한 포트번호로 두개이상의 서버프로그램 안됨.
만약 Port가 이미 다른 프로그램에서 사용중이라면 BindException이 발생한다.
이경우에는 다른 Port로 바인딩하거나 사용중인 프로그램을 종료하고 다시실행해야한다.
이클립스의 경우 여러번 실행가능하니 에러발생할 수잇다.

ServerSocket 이 성공적으로 생성되었다면(위의 메소드들 사용하고) 연결요청을 수락하기 위해 accept()메소드를 실행해야한다.
accept()는 클라이언트가 연결요청하기 전까지 블로킹된다.
블로킹이란 실행을 멈춘 일시정지,대기 상태를 의미한다. 바로 값 리턴x
Socket socket = serverSocket.accept();
클라이언트의 연결요청이 들어오면 블로킹이 해제되고(수락되고) 통신용 Socket을 리턴한다. 위 그림의 2번

Socket에 어떤 정보가 들어가있나? 클라이언트의 정보가 들어가있다. 연결요쳥할때 클라이언트가 사용하는 ip와 포트번호를 같이 보내기때문
accept가 받을때 이것들을 저장해놓음. 리턴된 Socket을 사용하면 클라이언트의 ip주소와 port번호를 얻을 수 있다.
만약 리턴된 Socket을 연결된 클라이언트의 IP주소와 Port번호를 얻고 싶다면 방법은 Socket의 getRemoteSocketAddress() 메소드를 호출한다.
서버에서 생성된 Socket의 입장에선 클라이언트의 Socket가 원격이기때문에 getRemote임.
InetSocketAddress를얻은다음 getHostName()과 getPort()메소드를 호출하면된다.
클라이언트가 요청할때 클라이언트가 사용하는 IP주소와 Port번호를 같이보낸다.
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress(); //강제타입변환
String clientIp = isa.getHostName(); //클라이언트의 ip주소
String protNo = isa.getPort();

서버를 종료하려면 ServerSocket 의 close메소드를 호출해서 Port번호를 언바인딩 시켜야한다. 그래야 다른 프로그램에서 해당 Port번호를 재사용할 수있다.
사실 서버를 종료하는 일은 별로 없다. 유지보수나 기능을 추가할때, 새로운걸 실행할때만 닫는다. 만약 종료를 해야한다면 사용함.
만약 종료를 하면 port번호가 해제된다. 다른프로그램에서 이포트를 사용할 수 있게해줌. 메모리도 해제된다.

결론 SeverSocket객체를 만들어서 port번호를 바인딩해주고(3가지방식) accept를 해서 연결요청받기
연결수락하면 socket을 얻을 수있고 이것으로 클라이언트의 ip주소나 포트번호를 얻을 수 있다.

1.서버를 시작하고 클라이언트의 요청을 받는 작업
2.키보드에서 입력받아 서버를 종료하는 작업
두가지필요 ->멀티스레드 필요
메인스레드는 startServer();~stopServer까지 실행하는데
메인은 String key = scanner.nextLine();로인해서 블로킹되서 멈춰있음
새로운스레드는 startServer()에서 시작되어 서버소켓에서 50001포트 연결을 하고있음.
스레드만들고 thread.start()무조건해줘까먹음 자꾸

1.서버 소켓 만들기
서버가 요청을 받기 위해 수락(accept())받기 while무한루프에 넣기
why? 서버는 항상 요청을 받아야하기때문이다.
받은후 연결된 클라이언트의 정보얻기
socket.getRemoteSocketAddress();을 통해서정보를 얻는데
받을때는 InetSocketAddress으로 강제타입변환을 해서 받음
socket을 통해 받았기때문에 실제 얻는 것은 InetSocketAddress이기때문이다.
String clientIp = isa.getHostName(); 정보를 얻고 ip얻기
ip번호:50001로 접속이 되는지 확인하기
클라이언트 프로그램을 안만들어서 원래있는 프로그램 인터넷브라우저를 사용

2.연결끊기
socket을 얻고나서 더이상 사용하지 않으면 닫아줘야함.
서버자체를 종료할때도serverSocket을 닫아줘야하지만.
클라이언트의 socket을 닫고 연결을 끊어도되지만 서버의 socket으로 연결끊을 수도 있다.
startServer()의 역할
클라이언트이 연결요청을 기다렸다가 ip주소를 얻고 출력, 끊고, 다시 accept반복

3.q누르면 서버닫기
serverSocket닫고 포트 언바인딩
startServer()에서 accept()대기중이엇는데 종료가되서 IOException이 발생함.

이 예제의 목적 serverSocket의 역할을알아보자 serverSocket생성방법
accpet해야한다.
접속시 socket을 얻는다 클라이언트의 정보를 얻는다.
serverSocket을 종료하면 예외처리를해서 안전하게 종료될 수 있도록한다.

public class ServerExample {
private static ServerSocket serverSocket;
//메인에서 바로 사용할 수 있도록 정적필드로 선언해두기 자동초기화되서 굳이 초기화할필요는 없음.

public static void main(String[] args) {
    System.out.println("-------------------------------------------------------");
    System.out.println("서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.");
    System.out.println("-------------------------------------------------------");

    //TCP 서버시작 메소드
    startServer();

    //키보드 입력
    Scanner scanner = new Scanner(System.in);
    while(true) {
        String key = scanner.nextLine();
        if(key.toLowerCase().equals("q")) { //toLowerCase()대소문자 구분x q면 break
            break;
        }
    }
    scanner.close(); //지금까지는 JVM이 종료되서 안닫아도됫다. 서버는 24시간 돌아가기때문에 사용안하면 닫아야함.

    //TCP 서버종료 메소드
    stopServer();
}
//TCP 서버시작 메소드
private static void startServer() {
    //작업스레드 정의 익명자식객체 사용
    Thread thread = new Thread() {
        @Override
        public void run() {
            //ServerSocket 생성 및 Port 번호 바인딩
            try {
                serverSocket = new ServerSocket(50001);
                System.out.println("[서버] 시작됨");

                while(true) {
                    System.out.println("\n[서버] 연결요청을 기다림\n");
                    //연결수락
                    Socket socket = serverSocket.accept();
                    //연결된 클라이언트 정 보얻기
                    InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                    //String clientIp = isa.getHostName();
                    //호스트이름알면 이름으로 나와서 ip만 얻으려면 getHostString();사용하기
                    String clientIp = isa.getHostString(); 
                    System.out.println("[서버]"+ clientIp + "연결요청을 수락함.");

                    //연결끊기
                    socket.close();
                    System.out.println("[서버] 종료됨");
                    }
            } catch (IOException e) {
                System.out.println("[서버]" + e.getMessage());
            }
        }
    };
    thread.start();
    }
private static void stopServer() {
    try {
        //serverSocket닫고 Port 언바인딩
        serverSocket.close();
    } catch (IOException e) {}}}

19.3.2 tcp클라이언트

클라이언트가 서버에 연결요청을 하려면 Socket객체를 통해서 연결요청을 하고 통신도해야한다.
서버는 serversocket에서 만들어진 Socket을 사용하는데 클라이언트은 Socket하나로 해야함.

Socket socket = new Socket("서버ip주소", 서버Port번호)"
서버와 연결하기
서버의 ip주소를 모를때? 도메인주소는 알때
Socket socket = new Socket(new InetSocketAddress("domainName", 서버Port번호));
직접적으로 서버로 연결하지않고 dns로 연결후 도메인에 해당되는 ip얻고 ip에 연결해서 이용하는 포트와 연결

성공이되면 Socket객체가 생성이 된다.
연결이안되면? ip,도메인에 해당되는 pc가 없거나 port에서 프로그램이 실행되고 있지 않을 경우 예외가 발생한다.

기본생성자로 socket을 만들고나서 connect()호출해서 연결할수도 있다.
Socket socket = new Socket();
socket.connect(new InetSocketAddress("domainName", 서버Port번호));
UnknownHostException(ip,도메인문제)이나 IOException(port번호문제)이 발생할 수 있다.
데이터를 주고받은후 데이터를 끊고싶다면?
양쪽 Socket 어디서든 닫을 수 있는데 여기서도 socket.close(); 가능

public class ClientExample {
public static void main(String[] args) {
    try {
        //소켓 생성 및 포트와 연결
        Socket socket = new Socket("localhost", 50001);
        System.out.println("[클라이언트] 연결 성공");

        //연결해제
        socket.close();
        System.out.println("[클라이언트] 연결 끊음");
    } catch (UnknownHostException e) {
        //IP 또는 도메인 표기방법이 잘못되었을 경우
        System.out.println("UnknownHostException" + e.toString());
    } catch (IOException e) {
        //IP 또는 Port번호가 존재하지 않을 경우
        System.out.println("IOException" + e.getMessage());
    }}}

19.3.3 입출력 스트림으로 데이터 주고받기

연결하는 방법은 알았다.
연결하고 나서 데이터 주고받기
입출력 스트림을 사용함.
이미 18장에서함.InputStream과 OutputStream을 얻을수있다면 뒤의 코드는 같다.

클라이언트가 연결요청 connect()하고 서버가 연결 수락accept()햇다면 양쪽 Socket객체로부터 가각 입력 스트림과 출력스트림을 얻을 수잇다.
Socket으로부터 InputStream과 OutputStream을 얻는 코드이다.
서버쪽, 클라이언트의 socket일 수 있음.
socket의 스트림을 얻어내는 getter를 사용해서 얻는다. 이것으로 주고받기 함.
InputStream is = socket.getInputStream();
OutputStream os = socket.OutputStream();
InputStream is = new FileInputStream("파일주소"); 하듯이 socket의 스트림을 얻는것임.

데이터 출력(보내기)
상대방에게 데이터를 보낼때에는 보낼 데이터를 byte90배열로 새성하고 이것을 매개값으로 해서 OutputStream의 write()메소드를 호출하면된다.
String data = "보낼데이터";
데이터를 바이트로 보내기때문에 문자열을 바이트 배열로 바꿔야함.
byte[] bytes = data.getBytes("UTF-8"); //UTF-8 문자셋으로 인코딩후 바이트배열 얻기
OutputStream os = socket.getOutputStream();
os.write(bytes);
os.flush();

편의상 보조스트림 달수 있음 18.8 기본타입스트림 사용 성능보다는 편리함을 위해 사용함.
String data = "보낼데이터";
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(data);
dos.flush();

데이터 입력(받기)
데이터를 받기위해서는 받은 데이터를 저장할 byte[]배열을 하나 생성하고 이것을 매개값으로 해서 InputStream의 read()메소드를 호출하면된다.
byte[] bytes = new byte[1024];
InputStream is = socket.getInputStream();
int num = is.read(bytes);
String data = new String(bytes, 0, num, "UTF-8"); //배열받고 String으로 복원필요 보낼때UTF-8이엇으니 받을때도 인코딩

편의상 보조스트림
DataInputStream dis = new DataInputStream(socket.getInputStream());
String data = dis.readUTF();

//데이터 보내기
String sendmessage = "나는 자바가 좋아~~";
OutputStream os = socket.getOutputStream();
byte[] bytes = sendmessage.getBytes("UTF-8"); //메시지(스트링)에서 바이트를 얻어서 바이트 배열로 변환
os.write(bytes);
os.flush();
System.out.println("[클라이언트] 데이터를 보냄:" + sendmessage);

//데이터 받기
InputStream is = socket.getInputStream();
bytes = new byte[1024];
int readByteCount = is.read(bytes);
String receivemessage = new String(bytes, 0, readByteCount, "UTF-8");
System.out.println("[클라이언트] 데이터를 받음:" + receivemessage);
*/
//보조스트림 사용하기
//데이터 보내기
String sendmessage = "나는 자바가 좋아~~";
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(sendmessage);
dos.flush();
System.out.println("[클라이언트] 데이터를 보냄:" + sendmessage);

//데이터 받기
DataInputStream dis = new DataInputStream(socket.getInputStream());
String receivemessage = dis.readUTF();
System.out.println("[클라이언트] 데이터를 받음:" + receivemessage);

//데이터 받기
InputStream is = socket.getInputStream();
byte[] bytes = new byte[1024];
int readByteCount = is.read(bytes);
String message = new String(bytes, 0, readByteCount, "UTF-8");


//데이터 보내기
OutputStream os = socket.getOutputStream();
bytes = message.getBytes("UTF-8"); //메시지(스트링)에서 바이트를 얻어서 바이트 배열로 변환
os.write(bytes);
os.flush();
System.out.println("[서버] 받은 데이터를 다시보냄:" + message);
*/

//보조스트림 사용하기
//데이터 받기
//InputStream is = socket.getInputStream();
//DataInputStream dis = new DataInputStream(is);
DataInputStream dis = new DataInputStream(socket.getInputStream());
String message = dis.readUTF();

//데이터보내기
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(message);
dos.flush();
System.out.println("[서버] 받은 데이터를 다시보냄:" + message);

보낸메시지를 다시 돌려보내는 에코(메아리) TCP서버를 구현한 예제
연결성공후 끊기 사이에 작업 을 넣으면 됨.
클라이언트가 데이터를 주고 -> 서버가 받고 ->서버가 주고 -> 클라이언트가 받고
서버가 받는거부터 작성
이걸 연장되면 채팅프로그램임 내가 보낸걸 상대방들도 받고 연결을끊지않고 대기해서 다시 보낼수있으면

19.4 UDP네트워킹

UDP user datagram protocol
연결과정을 거치지 않음 받는 쪽을 생각안하고 일방적으로 보냄
UDP는 발신자가 일방적으로 수신자에게 데이터를 보내는 방식으로 TCP처럼 연결 요청 및 수락 과정이 없기 때문에 TCP보다 데이터 전송속도가 상대적으로 빠르다.
UDP는 고정회선이아니라 여러 회선을 통해 데이터가 전송되기 때문에 특정 회선의 속도에 따라 데이터가 순서대로 전달되지 않거나 잘못된 회선으로 인해 데이터 손실 발생가능성이 있다.
데이터를 보낼때마다 선로가 달라진다. 어쩔때는 빠르게 가거나 어쩔때는 돌아서 가면 회선마다 속도가 달라짐. 그래서 순서가 유지되지 않을 수있다.
또 가다가 네트워크가 끊기면 데이터 손실이 발생한다.
불안해서 사용할 수 없을 것 같은데 어떻게 쓰나? 중요하지 않은 데이터를 빠르게 전달하고자할때 사용함. 꼭 중요x -> 실시간 스트리밍 영상
실시간 영상 스트리밍에서 한 컷의 영상이 손실되더라도 영상데이터는 꾸준하게 계속해서 수신되므로 문제가 되지는 않는다.
대부분의 데이터는 잘 전달되어야해서 TCP를 사용하지만 실시간 영상같은건 상관없다.
따라서 데이터 전달의 신뢰성보다 속도가 더 중요하다면 UDP를 사용하고 데이터 전달의 신뢰성이 중요하다면 TCP를 사용해야한다.
자바는 UDP네트워킹을 위해 java.net 패키지에서 'DatatgramSocket'과 'DatagramPacket' 클래스를 제공하고 있다.
DatatgramSocket은 클라이언트와 서버 양끝단에서 데이터를 받고보내는 기능 발신점과 수신점에 해당하고 실제 흘러가는 것은 DatagramPacket은 주고 받는 데이터에 해당한다.

19.4.1 UDP서버

UDP서버를 위한 DatatgramSocket객체를 생성할때에는 바인딩할 Port번호를 생성자 매개값으로 제공해야한다.
DatatgramSocket datagramSocket = new DatatgramSocket(Port번호);

서버쪽DatatgramSocket은 항상 받을 준비를 하고 있어야함.
UDP서버는 클라이언트가 보낸 DatagramPacket을 항상 받을 준비를 해양한다. 이 역할으르 하는 메소드가 receive()이다.
receive()메소드는 데이터를 수신할때까지 블로킹되고 데이터가 수신되면 매개값으로 주어진 DatagramPacket에 저장한다.

receive 매개값으로는 바로 받기 위해 비어있는 데이터 패킷을 준비해서 넣음
DatatgramPacket receivePacket = new DatatgramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);
그림을 보면 네트워크 상에서 DatatgramPacket가 흘러가는 것처럼 보이지만 모든 회선안에는 byte가 흘러가는 것임. 이 byte를 DatatgramPacket객체로 만드는 것이다.
그래서 앞으로 도착하는 것을 빈DatatgramPacket에 담아줌
(배열, 몇개까지담아라);
DatatgramPacket 생성자의 첫번째 매개값은 수신된 데이터를 저장할 배열이고 두번째 매개값은 수신할수 있는 최대 바이트 수이다.
보통 첫번째 바이트배열의 크기를 준다.

데이터가 흘러와서 실제 흐러온걸 받으려면 getData();사용함.
(new byte[1024], 1024);배열에 넣었으니 배열로 열음
byte[] bytes = receviePacket.getData();
int num = receivePacket.getLength(); //실제로 읽은 바이트수 리턴

읽은 데이터가 문자열이라면 String 생성자을 이용해서 문자열을 얻을 수 있다.
bytes에서 0부터 읽은 만큼만 문자열로 바꾸기 UTF-8로 디코딩
String data = new String(bytes, 0 , num , "UTF-8");

서버가 받을걸 다시 클라이언트로 보내줘야함. 그러려면 서버가 클라이언트의 ip주소 port번호 알아야함. 그런데 클라이언트가 데이터를 보낼때 얻엇음
반대로 UDP서버가 클라이언트에게 처리 내용을 보내려면 클라이언트 IP주소와 Port번호가 필요한데 이것은 receive()로 받은 DatagramPacket에서 얻을 수 있다.
클라이언트가 서버가 되고 서버가 클라이언트가 되는 느낌으로 보내는 것임.
getSocketAddress()메소드를 호출하면 정보가 담긴 SocektAddress객체를 얻을 수 있다.
SocketAddress socketAddress = receivePacket.getSocketAddress();

이렇게 얻은 SocketAddress 객체는 클라이언트로 보낼 DatagramPacket을 생성하고 네번째 매개값으로 사용된다.
얻은 SocketAddress 객체를 DatagramPacket에 담아서 클라이언트한테 보냄
클라이언트에서 보낼때 포함한 정보를 위에서 담은것을 매개값으로 사용
DatagramPacket 첫번째 매개값은 바이트 배열이고 두번째는 시작 인덱스 세번째는 보낼 바이트 수이다.
String data= "처리 내용";
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(bytes, 0 ,bytes.length, socketAddress);

DatagramPacket를 클라이언트로 보낼때는 send()메소드를 이용
datagramPacket.send(sendPacket);

사용종료시
datagramPacket.close();

예제 뉴스주는 서버만들기
1.서버 보내기
2.q입력받으면 서버 끄기 2가지로 해야하니 작업 스레드 만들어주기

public class NewsServer {
private static DatagramSocket datagramSocket = null;
public static void main(String[] args)  {
    System.out.println("-------------------------------------------------------");
    System.out.println("서버를 종료하려면 q 또는 Q를 입력하고 Enter 키를 입력하세요.");
    System.out.println("-------------------------------------------------------");

    //UDP 서버시작
    startServer(); //이부분에서 새로운 스레드 만들어서 서버 시작 실행됨

    //키보드 입력
    Scanner scanner = new Scanner(System.in);
    while (true) {
        String key = scanner.nextLine();
        if(key.toLowerCase().equals("q")) { 
            break;
        }
    }
    scanner.close();

    //UDP서버 종료
    stopServer();
}

public static void startServer() {
    //작업스레드 정의
    Thread thread = new Thread() {
        @Override
        public void run() {
            try {
                //datagramSocket생성 및 Port바인딩
                datagramSocket = new DatagramSocket(50001);
                System.out.println("[서버] 시작됨");

                //서버는 항상 클라이언트의 요청을 받아야하니 무한반복
                while (true) {
                    //클라이언트가 구독하고 싶은 뉴스 주제 얻기
                    DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024); 
                    //클라이언트에게 데이터오면 1024개까지 저장하겠다.
                    //비어있는 DatagramPacket 받고 블로킹하고 있기
                    System.out.println("클라이언트의 희망 뉴스 종류를 얻기 위해 대기함");
                    datagramSocket.receive(receivePacket); 

                    //데이터를 받았을때 블로킹 풀림
                    byte[] data = receivePacket.getData(); //데이터를 얻어서 배열에 저장
                    int num = receivePacket.getLength(); // 받은 데이터의 길이를 num에 저장
                    String newsKind = new String(data, 0, num , "UTF-8"); //0~num까지 문자열로 변환

                    //클라이언트의 IP와 Port정보가 있는 SocketAddress 얻기
                    SocketAddress socketAddress = receivePacket.getSocketAddress();

                    //10개의 뉴스를 클라이언트로 전송
                    for(int i = 1 ; i <= 10 ; i++) {
                        String sendData = newsKind + " 뉴스" + i;
                        byte[] sendBytes = sendData.getBytes("UTF-8");
                        //DatagramPacket sendPacket = new DatagramPacket
                        //(보낼배열, 0 , 보낼배열.length, 클라이언트의 ip및 port정보);
                        DatagramPacket sendPacket = new DatagramPacket(sendBytes, 0 , sendBytes.length, socketAddress);
                        //datagramSocket이 send함.
                        datagramSocket.send(sendPacket);
                    }
                }
            } catch (SocketException e) {
                System.out.println("[서버]" + e.getMessage());
            } catch (IOException e) {
                System.out.println("[서버]" + e.getMessage());
            }}

    };
    //작업스레드 시작
    thread.start();

}

public static void stopServer() {
    //DatagramSocket을 닫고 Port 언바인딩
    datagramSocket.close();
    System.out.println("[서버] 종료됨");
}}

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

        try {
            //DatagramSocket생성 자동으로 내부에서 만들어져서 할당안해도됨.
            DatagramSocket datagramSocket = new DatagramSocket();


            //보낼 DatagramPacket 생성
            String data = "정치";
            byte[] bytes = data.getBytes();
            //InetSocketAddress serverAddress = new InetSocketAddress("localhost", 50001);
            DatagramPacket sendPacket = new DatagramPacket
            (bytes, 0, bytes.length, new InetSocketAddress("localhost", 50001));
            datagramSocket.send(sendPacket);

            //클라이언트도 항상 받을 준비 해야함.
            while (true) {
                //서버로부터 DatagramPacket 받기
                DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
                datagramSocket.receive(receivePacket); 

                //문자열로 변환
                byte[] receivedata = receivePacket.getData(); //데이터를 얻어서 배열에 저장
                int num = receivePacket.getLength(); // 받은 데이터의 길이를 num에 저장
                String news = new String(receivedata, 0, num , "UTF-8"); //0~num까지 문자열로 변환
                System.out.println(news);

                //news에 뉴스10이 포함되어있다면 탈출
                if (news.contains("뉴스10")) {
                    break;
                }
            }

            //연결 끊기 DatagramSocket닫기
            datagramSocket.close();

        } catch (SocketException e) {
            System.out.println("[클라이언트]" + e.getMessage());
        } catch (IOException e) {
            System.out.println("[클라이언트]" + e.getMessage());
        }} }

2022.12.22 리뷰

네트워크 채팅프로그램 프로젝트가 이해가 어려워서 이전 세팅을 잘 알기 위해 분리해서 공부하기로 했다.
중요한 부분이라 잘 보고 싶다.
중요한 것은 꺾이지 않는 마음.

+ Recent posts