본문 바로가기

카테고리 없음

9주차 배운점 느낀점 - TCP, UDP, 서버동시요청, JSON

728x90
반응형

9주차

 

넓고 얕게 공부하지마라, 그렇게 안물어본다.
소켓 서버, 클라이언트 같은 코드는 비슷한 패턴이 있다. 바로 코드부터 치려고하지말고 구문분석후 원하는 내용을 끼워넣어라고 하셨다.

 

TCP, UDP 통신에 대해서 서버와 클라이언트 코드를 실습했다.
여러 클라이언트가 한 서버에 동시 요청을 하면 처리를 해주어야하다. excutors를 이용하여 처리해 주었다.
JOSN 형식을 파일에 쓰고 읽는 실습을 했다.

 

배운 점

 

netstat -a
TCP/IP 네트워크 인터페이스의 상태 조건
리스닝 상태확인 LISTENING 클라이언트 접속을 기다리는 상황
3306: mysql 확인

 

 

nslookup
도메인의 정보를 조회 하는 명령어

서버:    kns.kornet.net
Address:  168.126.63.1

학원 아이피도 kornet에 등록 되어 있다.
kornet dns: 서버 관리하는 곳

 

google 아이피 검색

> nslookup goolge.com
서버:    goolge.com
Addresses:  2404:6800:400a:80a::2004
          142.250.76.132

 

type 옵션을 추가하여 원하는 레코드를 조회 할 수 있다

>set type-a
>www.naver.com

 

네이버 실제 주소

 

ifconfig ens32 ifconfig - 리눅스에서 사용한다고 기억하라는데..

 

TCP
TCP는 연결형 프로토콜로, 상대방이 연결된 상태에서 데이터를 주고 받는 전송용 프로토콜
클라이언트가 연결 요청을 하고 서버가 연결을 수락하면 통신 회선이 고정되고, 데이터는 고정
회선을 통해 전달. TCP는 보낸 데이터가 순서대로 전달되며 손실이 발생하지 않음
ServerSocket은 클라이언트의 연결을 수락하는 서버 쪽 클래스이고, Socket은 클라이언트에서 연결 요청할 때와 클라이언트와 서버 양쪽에서 데이터를 주고 받을 때 사용되는 클래스

TCP 서버
TCP 서버 프로그램을 개발하려면 우선 ServerSocket 객체를 생성

ServerSoket serverSocket = new ServerSocket(50001);

 

기본 생성자로 객체를 생성하고 Port 바인딩을 위해 bind() 메소드를 호출해도 ServerSocket 생성
지정해주고 바인딩 시켜야함

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",5001));

 

Port가 이미 다른 프로그램에서 사용 중이라면 BindException이 발생. 다른 Port로 바인딩하거나 Port를 사용 중인 프로그램을 종료하고 다시 실행해야 함

 

ServerSocket이 생성되면 연결 요청을 수락을 위해 accept( ) 메소드를 실행
accept()는 클라이언트가 연결 요청하기 전까지 블로킹(실행 멈춘 상태) 클라이언트의 연결 요청이 들어오면 블로킹이 해제되고 통신용 Socket을 리턴

Socket socket = serverSocket.accept();

 

리턴된 Socket을 통해 연결된 클라이언트의 IP 주소와 Port 번호를 얻으려면
getRemoteSocketAddress ( ) 메소드를 호출해서 InetSocketAddress를 얻은 다음 getHostName()과 getPort() 메소드를 호출

InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
String clientIp = isa.getHostName();
int portNo = isa.getPort();

 

ServerSocket의 close() 메소드를 호출해서 Port 번호를 언바이딩해야 서버 종료

serverSocket.close();

 

TCP 클라이언트

 

클라이언트가 서버에 연결 요청을 하려면 Socket 객체를 생성할 때 생성자 매개값으로 서버 IP 주소와 Port 번호를 제공
로컬 컴퓨터에서 실행하는 서버로 연결 요청을 할 경우에는 IP 주소 대신 localhost 사용 가능

Socket socket = new Socket("127.0.0.1",50001);

 

도메인 이름을 사용하려면 DNS에서 IP 주소를 검색하는 생성자 매개값으로 InetSocketAddress 제공

Socket socket = new Socket(new InetSocketAddress("domainName",50001));

 

기본 생성자로 Socket을 생성한 후 connect() 메소드로 연결 요청 가능

socket = new Socket();
socket.connect(new InetSocketAddress("domainName",50001));

 

ServerSocket

public static void main(String[] args) {

    try {
      //1. 서버소켓 생성
      ServerSocket serverSocket = new ServerSocket(50001);

      //2. 클라이언트의 socket과 접속 할 수 있도록 accept 서버소켓에 바인드
      System.out.println("server start");
      Socket socket = serverSocket.accept();

      //client가 보낸 메세지 받기
      BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      String message = br.readLine();
      System.out.println("client message :" + message);

      //client에 메시지 보내기
      BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
      bw.write("i like you too!!!");
      bw.newLine(); // 한줄 띄기
      bw.flush();


      //3.소켓 종료
      socket.close();

      //4.서버소켓 종료
      serverSocket.close();
      System.out.println("server close");
      br.close();
      bw.close();
    } catch (Exception e) {
      e.printStackTrace();
    }
  }

 

ClientSocket

public static void main(String[] args) {
    try{
      Socket socket = new Socket("127.0.0.1",50001);

      //Server에 보낼 데이터 준비하여 보내기
      BufferedWriter bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
      bw.write(" i like you");
      bw.newLine(); // 한줄 띄기
      bw.flush();

      //Server에서 보낸 데이터를 받아 출력 하기
      BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
      String message = br.readLine();
      System.out.println("server message :" + message);

      socket.close();
      bw.close();
      br.close();
    }catch (Exception e){
      e.printStackTrace();
    }
  }

 

메세지 도착

 

UDP

 

포트지정, 서버소켓 이런거 없음, 채널만 있으면됨,
listen을 하지 않아, accept가 없다. 연결 요청과 수락이없다.
FTP(파일서버)같은건 속도를 위해 UDP 사용

 

발신자가 일방적으로 수신자에게 데이터를 보내는 방식. TCP처럼 연결 요청 및 수락 과정이 없기 때문에 TCP보다 데이터 전송 속도가 상대적으로 빠름
데이터 전달의 신뢰성보다 속도가 중요하다면 UDP를 사용하고, 데이터 전달의 신뢰성이 중요하다면 TCP를 사용
DatagramSocket은 발신점과 수신점에 해당하고 DatagramPacket은 주고받는 데이터에 해당

 

 

UDP 서버

DatagramSocket 객체를 생성할 때에는 다음과 같이 바인딩할 Port 번호를 생성자 매개값으로 제공

 

DatagramSocket datagramSocket = new DatagramSocket(50001);

 

receive() 메소드는 데이터를 수신할 때까지 블로킹되고, 데이터가 수신되면 매개값으로 주어진 DatagramPacket에 저장

DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);

 

DatagramPacket 생성자의 첫 번째 매개값은 수신된 데이터를 저장할 배열이고 두 번째 매개값은 수신할 수 있는 최대 바이트 수

byte[] bytes = receivePacket.getData();
int num = receivePacket.getLength();

String data = new String(bytes, 0, num, "UTF-8");

 

getSocketAddress () 메소드를 호출하면 정보가 담긴 SocketAddress 객체를 얻을 수 있음

SocketAddress socketAddress = receivePacket.getSocketAddress();

 

SocketAddress 객체는 클라이언트로 보낼 DatagramPacket을 생성할 때 네 번째 매개값으로 사용

Stirng data = "처리 내용";
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);

 

DatagramPacket을 클라이언트로 보낼 때는 DatagramSocket의 send() 메소드를 이용

datagramSocket.send(sendPacket);

 

UDP 서버를 종료하고 싶을 경우에는 DatagramSocket의 close() 메소드를 호출

datagramSocket.close();

 

UDP 클라이언트
서버에 요청 내용을 보내고 그 결과를 받는 역할
UDP 클라이언트를 위한 DatagramSocket 객체는 기본 생성자로 생성. Port 번호는 자동 부여

String data = "요청 내용";
byte[] bytes = data.getBytes("UTF-8");
DatadgramPacket sendPacket = new DatagramPacket(
	bytes, bytes.length, new InetSocketAddress("localhost",50001)
);

 

생성된 DatagramPacket을 매개값으로해서 DatagramSocket의 send() 메소드를 호출하면 UDP 서버로 DatagramPacket이 전송

datagramSocket.send(sendPacket);

 

DatagramSocket을 닫으려면 close() 메소드를 호출

atagramSocket.close();

 

서버의 동시 요청 처리

 

일반적으로 서버는 다수의 클라이언트와 통신. 서버는 클라이언트들로부터 동시에 요청을 받아서 처리하고, 처리 결과를 개별 클라이언트로 보내줌
accept()와 receive()를 제외한 요청 처리 코드를 별도의 스레드에서 작업

 

스레드를 처리할 때 클라이언트의 폭증으로 인한 서버의 과도한 스레드 생성을 방지하기 위해 스레드풀을 사용하는 것이 바람직

 

스레드풀:
여러 개의 스레드를 효율적으로 관리하여 작업을 수행하는 데 사용되는 소프트웨어 디자인 패턴
스레드를 미리 생성하고 풀에 저장해 두었다가 필요할 때 꺼내와 작업을 할당하는 방식

 

TCP EchoServer 동시 요청 처리
스레드풀을 이용해서 클라이언트의 요청을 동시에 처리

 

Executors class

 

스레드를 사용자가 직접 관리하는 것은 어렵기때문에 스레드를 만들고 관리하는 작업을 Executors를 사용한다.

 

Thread를 만들고 관리하는 것을 고수준의 API Executors에게 위임한다.
Runnable만 개발자가 만들고 생성, 종료, 없애기 작업(일련의 작업)들은 Executors가 해줌
주로 ExecutorService 사용함

 

쓰레드 만들기: 애플리케이션이 사용할 Thread pool을 만들어 관리한다.
쓰레드 관리: 쓰레드 생명 주기를 관리한다.
작업 처리 및 실행: 쓰레드로 실행할 작업을 제공할 수 있는 API를 제공한다.

 

Executor interface:
execute(Runnable)

 

ExecutorService interface:
Executor 상속 받은 인터페이스로, Callable도 실행할 수 있으며, Executor를 종료 시키거나, 여러 Callable을 동시에 실행하는 등의 기능을 제공한다.
Runnable 은 반환값이 void 이고 Callable<V>는 반환 값이 있다.

 

Runnable: 인자를 받지 않고 리턴값이 없다. Runnable의 run() 메소드에 정의된 코드를 수행할뿐이다.

public interface Runnable {
    public abstract void run();
}

 

Callable: 인자를 받지 않으며, 특정 타입의 객체를 리턴한다. call() 메소드 수행 중 Exception 발생시킬 수 있다.

public interface Callable<V> {
    V call() throws Exception;
}

 

Callable 예시:
Callable<String>을 implements한 Mycallable 클래스 정의 그 안에call() 메소드 정의
Thread는 FutureTask를 통해서 Callable을 호출
FutureTask.get() 으로 call()의 리턴값을 받는다.

public class CallableExample1 {

    static class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {  // runable에 run 처럼 내용입력
            String result = "Called at " + LocalTime.now();
            return result;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable callable = new MyCallable(); // 
        FutureTask futureTask = new FutureTask(callable); //
        Thread thread = new Thread(futureTask);
        thread.start(); // 스레드 시작

        // 결과가 리턴되기를 기다립니다.
        System.out.println("result : " + futureTask.get());
    }
}

 

ExecutorService에서 Callable 예시:
Callable을 Job으로 등록하고 수행시킬 수 있다. Future를 이용하여 결과를 리턴받는다.

 static class MyCallable implements Callable<String> {
        @Override
        public String call() throws Exception {
            String result = "Called at " + LocalTime.now();
            return result;
        }
    }

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        MyCallable callable = new MyCallable();
        ExecutorService executor = Executors.newSingleThreadExecutor();
        //ExecutorService 구현체를 SingleThread 형태로 리턴
        
        Future<String> future = executor.submit(callable);
        // 스레드가 활용할 작업 실행
        // 작업을 큐에 넣고 작업을 수행하는 스레드 풀에서 해당 작업을 실행
        // runnable에 start 같은 애
        // Future 객체 반환함

     
        System.out.println("result : " + future.get());//리턴
    }

 

ScheduledExecutorService interface:
ExecutorService를 상속 받은 인터페이스로 특정 시간 이후에 또는 주기적으로 작업을 실행할 수 있다.


ExecutorService 구현

 

SingleThread:

 

Executors.newSingleThreadExecutor() - 구현체 생성 , SingleThread형태로 리턴

 

submit() : - 작업 제출 , Thread를 활용할 작업을 제출합니다.(해당 스레드가 대기 중인 경우 제출한 작업을 처리함) 작업을 큐에 넣고 작업을 수행하는 스레드 풀에서 해당 작업을 실행

 

shutdown() - 작업 종료 , 현재 진행 중인 작업을 마치고 Thread를 종료. (종료 안해줄 시 무한 대기)

 

shutdownNow() - 즉시 종료 , 진행중인 작업 마치지 않아도 종료

 

MultiThread:

 

Executors.newFixedThreadPool(Thread 개수) - 구현체 생성 , Executors 클래스의 Static Method를 활용하여

ExecutorService 구현체를 MultiThread 형태로 리턴

 

submit() - 작업 제출 , Thread를 활용할 작업을 제출합니다.(해당 스레드가 대기 중인 경우 제출한 작업을 처리함) 작업을 큐에 넣고 작업을 수행하는 스레드 풀에서 해당 작업을 실행

shutdown() - 작업 종료 , 현재 진행 중인 작업을 마치고 Thread를 종료. (종료 안해줄 시 무한 대기)

 

ScheduledThread:

 

특정 시간 이후에 또는 주기적(반복적)으로 작업을 실행할 수 있다. 크론이나 스케줄러처럼 사용할 수있음(크론: 유닉스기반 os에서 주기적으로 실행되어야할 작업들 예약)
예: 10초 대기 후에 작업 처리(실행), 1초 대기 후 10초에 1번씩 작업 처리(실행)

 

작업의 수보다 스레드의 갯수가 더 적을 때
-> 스레드에서 작업을 바로 처리를 못하므로 Blocking Queue에 작업을 쌓아서 대기해 둔 상태로 두고 앞의 작업이 끝난 후에 작업을 처리함

 

참고 사이트:
https://veneas.tistory.com/entry/Java-Executors-Thread-%EC%82%AC%EC%9A%A9%EB%B2%95

 

예시:
NewsServer class

public class NewsServer {
	private static DatagramSocket datagramSocket = null;

	private static ExecutorService es = Executors.newFixedThreadPool(10);
	//10개로 제한
    //고정된 크기의 스레드 풀을 생성했다. 10개 이상부터는 블로킹큐에 대기
	public static void main(String[] args) throws Exception {
		System.out.println("--------------------------------------------------------------------");
		System.out.println("서버를 종료하려면 q를 입력하고 Enter 키를 입력하세요.");
		System.out.println("--------------------------------------------------------------------");		
		
		//UDP 서버 시작
		startServer();
		
		//키보드 입력
		Scanner scanner = new Scanner(System.in);
		while(true) {
			String key = scanner.nextLine();
			if(key.toLowerCase().equals("q")) {  // q를 입력받으면 종료
				break;
			}
		}
		scanner.close();
		
        //TCP 서버 종료
        stopServer();		
	}	
		
	public static void startServer() {
		//작업 스레드 정의

		es.execute( ()-> {  // Runnable 또는 람다 표현식으로 표현되는 작업을 수행하도록 스레드 풀에 제출
			try {
				//DatagramSocket 생성 및 Port 바인딩
				datagramSocket = new DatagramSocket(50001);
				// udp 통신에는 datagramsocket 을 쓴다.
				// serversocket 을 만들어서 accept 함수를 통해 소켓을 받지 않는다.
				// 서버쪽 udp 포트만 명시해둔다 (받은 패킷에서는 ip, port를 알 수 있어서 전달가능)
				System.out.println( "[서버] 시작됨");

				while(true) {
					//클라이언트가 구독하고 싶은 뉴스 주제 얻기
					DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
					// 받을 데이터그램 패킷의 사이즈를 정의 해줘야 한다.

					datagramSocket.receive(receivePacket);
					String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");

					//클라이언트의 IP와 Port 얻기
					SocketAddress socketAddress = receivePacket.getSocketAddress();
					//  이걸로 클라이언트 패킷에서 ip 와 포트번호를 알수 있다.

					//10개의 뉴스를 클라이언트로 전송
					for(int i=1; i<=10; i++) {
						String data = newsKind + ": 뉴스" + i;
						byte[] bytes = data.getBytes("UTF-8");
						DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);

						// 보낼 데이터그램 패킷의 사이즈를 정의 해줘야한다.
						datagramSocket.send(sendPacket);
					}
				}
			} catch (Exception e) {
				System.out.println("[서버] " + e.getMessage());
			}});
		};
		//스레드 시작

		
	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();
			
			//구독하고 싶은 뉴스 주제 보내기
			String data = "정치";
			byte[] bytes = data.getBytes("UTF-8");
			DatagramPacket sendPacket = new DatagramPacket(
				bytes, bytes.length, 	new InetSocketAddress("localhost", 50001)
			);
			datagramSocket.send(sendPacket);

			while(true) {
				//뉴스 받기
				DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
				datagramSocket.receive(receivePacket);
				
				//문자열로 변환
				String news = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
				System.out.println(news);
				
				//10번째 뉴스를 받았을 경우 while 문 종료
				if(news.contains("뉴스10")) {
					break;
				}
			}
			
			//DatagramSocket 닫기
			datagramSocket.close();
		} catch(Exception e) {
		}
	}
}

 

JSON
네트워크로 전달하는 데이터 형식
두 개 이상의 속성이 있으면 객체 { }로 표기. 두 개 이상의 값이 있으면 배열 [ ]로 표기

 

 

JSON 라이브러리 다운로드: https://github.com/stleary/JSON-java
인텔리제이: 메뉴 - Project Structure - Modules - '+'

 

 

JSON데이터형식 파일에 쓰기

public static void main(String[] args) throws IOException {
    //JSON 객체 생성
    JSONObject object = new JSONObject();

    //jSON 객체에 속성 추가
    object.put("id","sin");
    object.put("password",1234);
    object.put("student",true);

    JSONObject tel = new JSONObject();
    tel.put("home","02-455-8989");
    tel.put("mobile","010-123-1234");
    object.put("tel",tel); // 객체를 객체한테 put

    JSONArray hobby = new JSONArray();
    hobby.put("swimming");
    hobby.put("sleeping");
    object.put("habbit",hobby);


    //속성 확인하기
    String data = object.toString();


    //콘솔에 출력
    System.out.println(data);

    //파일에 저장
    Writer writer = new FileWriter("member.json", Charset.forName("UTF-8"));
    writer.write(data);
    writer.flush();
    writer.close();
  }

 


값에 객체넣으면 {} 표기, 배열 넣으면 []표기

 

JSON 파일 형식 읽기

public static void main(String[] args) throws IOException {
    BufferedReader br = new BufferedReader(new FileReader("member.json", Charset.forName("UTF-8")));
    String data = br.readLine();
    br.close();

    //JSON 파싱
    JSONObject root = new JSONObject(data);
    System.out.println(root.getString("id")); // 데이터 타입마다 get다름
    System.out.println(root.getInt("password"));
    System.out.println(root.getString("id"));
    System.out.println(root.getBoolean("student"));

    JSONObject tel = root.getJSONObject("tel"); // tel 의 값: 객체 받기
    System.out.println(tel.getString("mobile"));

    //배열 속성
    JSONArray hobby = root.getJSONArray("habbit"); // habbit의 값: 배열 받기
    System.out.println(tel.getString("home"));
    for(int i = 0; i<hobby.length() ; i++){
      System.out.println(hobby.get(i)+",");
    }
  }

 

json과 xml 차이
둘 다 데이터를 표현하고 전송하는 데 사용되는 텍스트 기반의 데이터 형식

 

JSON:
데이터를 표현하는 데에는 JavaScript 객체와 배열의 형식을 사용
키-값 쌍으로 이루어진 객체와 배열을 중첩하여 사용할 수 있다.

{"name": "John", "age": 30, "city": "New York"}

 

일반적으로 작성하기가 더 간단하며, 가독성이 좋다.
데이터를 표현하는 데 필요한 구문이 더 간결하기 때문에 크기가 더 작다.
네임스페이스와 스키마를 지원하지 않는다.(xml에서 사용)
기본 데이터 타입으로는 문자열, 숫자, 불리언, 배열, 객체, null

 

XML:
데이터를 표현하는 데 태그의 형태 사용
태그는 중첩될 수 있으며, 속성을 추가할 수 있다.

<person>
    <name>John</name>
    <age>30</age>
    <city>New York</city>
</person>

 

태그 중첩이 많아질수록 가독성이 떨어질 수 있다.
더 많은 태그와 구조적 정보로 인해 JSON에 비해 더 클 수 있다.
모든 데이터는 문자열 형태로 저장되며, 특별한 파싱이나 형 변환이 필요
네임스페이스 및 스키마를 사용하여 데이터를 더 엄격하게 정의하고 구조화할 수 있다.
(네임스페이스: XML 문서에서 요소 및 속성의 이름 충돌을 방지하기 위한 메커니즘
스키마: XML 문서의 구조와 유효성을 정의하여 데이터 일관성을 유지하고 유효성 검사 수행)

 

회고

 

서버 동시 요청 처리할 때 ExecutorService 를 사용하여 스레드풀의 스레드 갯수를 제한해 주었다. 이 부분에 관해서 어떻게 돌아가는 지 , 스레드풀이 무엇인지, 블로킹큐가 무엇인지등을 찾아보았다. 나는 모르는내용에 대해서 점점 깊게 찾는방법을 잘 모른다.
그래도 어제 TCP/IP찾아보면서 모르는 부분을 타고타고 찾아서 이해하는 시간을 가졌더니 좀 늘었다.
모르는 부분에대해서 길게 물어서 찾아알아가는 습관을 길러야한다.

반응형