웹개발 수업/JAVA

[Day +31]네트워크 & 미니 프로젝트 3일차

Chole Woo 2021. 8. 5. 22:05
210804 수

 

<네트워크>

: 여러 대의 컴퓨터를 통신 회선으로 연결한 것(홈 네트워크, 지역 네트워크, 인터넷 등이 해당)

 

1. 네트워크 관련 단어

1) 서버(제공하는 사람)와 클라이언트(제공을 받는 사람)

: 네트워크로 연결된 컴퓨터간의 관계를 역할(role)로 구분한 개념

(1) 서버

: 서비스를 제공하는 프로그램으로 클라이언트의 연결을 수락하고 요청 내용을 처리 후 응답을 보내는 역할

(2) 클라이언트

: 서비스를 받는 프로그램으로 네트워크 데이터를 필요로 하는 모든 어플리케이션이 해당 됨

 

2) IP주소

네트워크 상에서 컴퓨터를 식별하는 번호로 네트워크 어댑터(랜카드)마다 할당 되어 있음

 

3) 포트

같은 컴퓨터 내에서 프로그램을 식별하는 번호로 클라이언트는 서버 연결 요청 시 IP주소와 포트 번호를 알아야 함

 

4) 소켓 프로그래밍

: 소켓을 이용한 통신 프로그래밍

(1)  소켓

: 프로세스 간의 통신에 사용되는 양쪽 끝 단

(2) TCP(중요)

: 데이터 전송 속도가 느리지만 정확하고 안정적으로 전달할 수 있는 연결 지향적 프로토콜

-클라이언트와 서버간의 1:1 소켓 통신 서버가 먼저 실행 되어 클라이언트의 요청을 기다려야 하고 서버용 프로그램과 클라이언트용 프로그램을 따로 구현해야 함

-자바에서는 TCP 소켓 프로그래밍을 위해 java.net패키지에서 ServetSocket과 Socket클래스 제공

(3) UDP

: 데이터 전송 속도가 빠르지만 신뢰성 없는 데이터를 전송하는 비연결 지향적 프로토콜

-실시간 live 방송 등에서 사용

 

 

<Chap01_inet>

Example

package com.kh.chap01_inet.exmaple;

import java.net.InetAddress;
import java.net.UnknownHostException;

//연결하고자 하는 대상의 네트워크 정보를 확인할 수 있는 InetAddress 클래스 테스트
public class InetSample {
	
	public void ipSample() {
		
		//LocalHost : 자신을 의미  
		try {
			//static InetAddress getLocalHost()
			// : 지역 호스트(자신의 컴포터를 의미)의 IP주소 반환
			InetAddress localIP = InetAddress.getLocalHost();
			System.out.println(localIP);
			
			//답 : DESKTOP-EGOBN14/192.168.219.107
			//내 PC명과 아이피 주소 얻어오기
			
			//String getHostName() : 호스트의 이름을 반환
			System.out.println("내 PC명 : " + localIP.getHostName());
			
			//String getHostAddress() : 호스트의 이름을 반환
			System.out.println("내 IP명 : " + localIP.getHostAddress());
			
			//답
//			내 PC명 : DESKTOP-EGOBN14
//			내 IP명 : 192.168.219.107

			
			//static InetAddress getByName(String host) : 도메인명(host)를 통해 IP주소를 얻음
			InetAddress googleIP = InetAddress.getByName("www.google.com");
			System.out.println("구글 서버명 : " + googleIP.getHostName());
			System.out.println("구글 IP명 : " + googleIP.getHostAddress());
			
			//답
//			구글 서버명 : www.google.com
//			구글 IP명 : 172.217.163.228
			
			
			//해당 도메인이 가지고 있는 IP들을 배열로도 받을 수 있음
			InetAddress[] naverIPs = InetAddress.getAllByName("www.naver.com");
			System.out.println("네이버 IP 개수 : " + naverIPs.length);
			//배열 반복문 출력
			for(InetAddress ip : naverIPs) {
				System.out.println(ip.getHostName());
				System.out.println(ip.getHostAddress());
			}
			
			//답, IP주소가 여러개일 수도 있다.
//			네이버 IP 개수 : 2
//			www.naver.com
//			125.209.222.142
//			www.naver.com
//			223.130.195.200
			
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
		
		
		
		
	}

}

Run 

package com.kh.chap01_inet.run;

import com.kh.chap01_inet.exmaple.InetSample;

public class Run {

	public static void main(String[] args) {
		InetSample is = new InetSample();
		is.ipSample();
	}

}

 

 

<Chap02_socket>

1) Part01_tcp

(1) Sample

1> TCP Client

package com.kh.chap02_socket.part01_tcp.sample;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

public class TCPClient {
	public void clientStart() {
		
		int port = 8888;
		String serverIP;
		
		try {
			
			serverIP = InetAddress.getLocalHost().getHostAddress();
			//지금은 Server, Client 모두 본인 PC로 구동하므로 local host의 ip입력
			
			
			//서버 쪽으로 연결 요청을 위해
			//*** 서버의 IP와 port 번호 ***를 매개변수로 전달하며 소켓 객체 생성
			Socket socket = new Socket(serverIP, port);
			
			//서버의 IP, port 올바르게 입력해서 연결이 완료 된 경우
			if(socket != null) {
				
				//서버와의 입출력 스트림 가져오기 & 보조 스트림 연결
				BufferedReader br = new BufferedReader
						(new InputStreamReader(socket.getInputStream()));
				PrintWriter pw = new PrintWriter(socket.getOutputStream());
				
				
				//1. 서버로 데이터 송신
				pw.println("안녕하세요. Client입니다. 반가워요!!");
				pw.flush();
				//2. 서버에서 데이터 수신
				String msg = br.readLine();
				System.out.println("서버로부터 받은 메세지 : " + msg);
				
				
				//통신 종료 시 스트림 닫기, 소켓 종료
				pw.close();
				br.close();
				
				socket.close();
				
				
			}
			
		}  catch (IOException e) {
			e.printStackTrace();
		}
		
		//문제 : 클라이언트 쪽에서 서버쪽으로 네트워크를 연결하고 싶다. 
		//이 서버가 생성해놓은 socket(양끝단) IP와 Port번호를 알아야만 
		//client쪽에서 연결 요청을 할 수 있다. 그런데 문제가 발생할 수 있다.
		//값이 다르면 못찾아간다. 따라서 값이 같아야 한다.
	}

}

(1) Sample

2> TCP Server

package com.kh.chap02_socket.part01_tcp.sample;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class TCPServer {

	public void serverStart() {
		
		// 서버는 서버 소켓 생성이 필요하다
		
		//특정 프로그램을 구분할 수 있는 포트 번호
		//포트는 호스트(컴퓨터)가 외부와 통신을 하기 위한 통로로 
       //하나의 호스트가 65536개의 포트를 가짐
		//(0~65535 사이에서 지정 가능하나 1023번 이하는 
        //이미 사용 중인 포트가 많으니 높은 숫자로 지정할 것)
		int port = 8888;
		
		//서버는 서버 소켓 생성이 필요(클라이언트의 요청을 받기 위해서)
		//포트와 연결되어 외부 요청을 기다리다 연결 요청이 들어오면 
		//Socket을 생성하여 소켓과 소켓간의 통신이 이뤄지도록 함
		//하나의 포트에 하나의 ServerSocket만 연결 가능
		try {
			ServerSocket server = new ServerSocket(port);
			
			System.out.println("클라이언트 요청을 기다리고 있습니다...");
			//클라이언트의 요청 accept(수락) 후 해당 클라이언트와 연결 된 소켓 리턴
			Socket client = server.accept();
			
			//서버 쪽으로 요청한 클라이언트의 IP출력
			System.out.println(client.getInetAddress()
					.getHostAddress() + "가 연결을 요청함...");
			
			//연결 된 클라이언트와 입출력 스트림 생성
			//소켓은 두 개의 스트림(입/출력)을 가지고 있으며 
            //이 스트림은 연결 된 상대편 소켓의 스트림과 "교차 연결"됨
			
			//클라이언트가 보낸 데이터를 입력 받는 스트림
			InputStream input = client.getInputStream();
			//클라이언트에게 보낼 데이터를 출력하는 스트림
			OutputStream output = client.getOutputStream();
			
			//보조 스트림을 통해 성능 개선(문자열 데이터 송수신을 위해)
			//좀 더 빠르게 읽어올 수 있는 스트림
			BufferedReader br = new BufferedReader(new InputStreamReader(input));
			PrintWriter pw = new PrintWriter(output);
			
			//1. 클라이언트가 서버로 보낸 메세지 읽기
			String msg = br.readLine();
			System.out.println("클라이언트로부터 받은 메시지 : " + msg);
			
			//2. 서버에서 클라이언트로 메세지 보내기
			pw.println("안녕하세요. Server입니다. 만나서 반갑습니다");
			pw.flush();
			
			//통신 종료 시
			//스트림 반납, 서버 종료
			pw.close();
			br.close();
			
			client.close();
			server.close();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
	}

}

(2) Run

1> ClientRun

package com.kh.chap02_socket.part01_tcp.run;

import com.kh.chap02_socket.part01_tcp.sample.TCPClient;

public class ClientRun {

	public static void main(String[] args) {
			
			TCPClient tc = new TCPClient();
			tc.clientStart();
			
			
//			클라이언트 요청을 기다리고 있습니다...
//			192.168.219.107가 연결을 요청함...
//			클라이언트로부터 받은 메시지 : 안녕하세요. Client입니다. 반가워요!!

	}

}

(2) Run

2> ServerRun

package com.kh.chap02_socket.part01_tcp.run;

import com.kh.chap02_socket.part01_tcp.sample.TCPServer;

public class ServerRun {

		public static void main(String[] args) {
			
			TCPServer ts = new TCPServer();
			ts.serverStart();

	}

}

 

 

1) Part02_udo 

(1) Sample

1> UDP Client

 

package com.kh.chap02_socket.part02_udp.sample;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.net.UnknownHostException;

public class UDPClient {
	public void clientStart() {
		try {
			
			// UDP 통신을 위한 DatagramSocket 객체 생성	
			// 매개변수 생성자로 port 번호 설정 안하면 자동 부여
			DatagramSocket socket = new DatagramSocket();
			
			// 서버 ip/port (지금은 localhost ip로 처리)
			InetAddress serverAddress = InetAddress.getLocalHost();
			int serverPort = 8888;
			
			// 데이터 송신용 바이트 배열에 문자열을 바이트 배열 형태로 변환해서 담음
			byte[] outMsg = "Hi, I'm Client".getBytes();
			
			// 전송 메세지와 서버 ip, port DatagramPacket 객체에 담음
			DatagramPacket outPacket = new DatagramPacket
            (outMsg, outMsg.length, serverAddress, serverPort);
			
			// DatagramSocket 레퍼런스를 사용하여 메시지 전송
			socket.send(outPacket);
			
			// 데이터 수신용 바이트 배열 선언
			byte[] inMsg = new byte[100];
			
			// 데이터 수신용 DatagramPacket 객체 생성
			DatagramPacket inPacket = new DatagramPacket(inMsg, inMsg.length);
			
			// 패킷을 통해 서버로부터 전달 된 데이터를 수신
			socket.receive(inPacket);
			
			// 수신된 데이터 바이트 배열을 문자열로 생성하여 출력 확인
			System.out.println("서버로부터의 메세지 : " + new String(inPacket.getData()));
			
			// 소켓 닫음
			socket.close();
			
		} catch (SocketException e) {
			e.printStackTrace();
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

}

(1) Sample

2> UDO Server

package com.kh.chap02_socket.part02_udp.sample;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.SocketException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class UDPServer {

	// 코드 진행 순서
	// 1. 클라이언트가 DatagramPacket을 생성해서 DatagramSocket으로 서버에 데이터 전송
	// 2. 서버는 전송 받은 DatagramPacket의 
    	  getAddress(), getPort()를 호출해서 클라이언트의 정보를 얻어서
	// 3. 서버시간을 DataPacket()에 담아서 클라이언트로 전송
	
	public void serverStart() {

		try {
			// 서버의 포트번호 
			int serverPort = 8888;

			// 포트번호를 매개변수 생성자에 담아 DatagramSocket 객체 생성
			DatagramSocket socket = new DatagramSocket(serverPort);

			// 데이터 수신용 바이트 배열 선언
			byte[] inMsg = new byte[100];
			
			// 데이터를 수신하기 위한 패킷 생성
			DatagramPacket inPacket = new DatagramPacket(inMsg, inMsg.length);

			// 패킷을 통해 클라이언트로부터 전달 된 데이터를 수신
			socket.receive(inPacket);
			
			// 수신된 데이터 바이트 배열을 문자열로 생성하여 출력 확인
			System.out.println("클라이언트로부터 메세지 : " + new String(inPacket.getData()));

			// 수신한 패킷으로부터 client의 ip 주소와 port를 얻음
			InetAddress clientAddress = inPacket.getAddress();
			int clientPort = inPacket.getPort();
			System.out.println("자동 부여된 client의 포트 번호 :" + clientPort);

			// 서버의 현재 시간을 시분초 형태로 반환
			SimpleDateFormat sdf = new SimpleDateFormat("hh:mm:ss");
			String time = sdf.format(new Date());
			
			// 데이터 송신용 바이트 배열에 서버의 현재 시간 바이트 배열 형태로 변환해서 담음
			byte[] outMsg = time.getBytes();

			// 전송할 메시지를 DatagramPacket 객체에 담음
			DatagramPacket outPacket = new DatagramPacket
            (outMsg, outMsg.length, clientAddress, clientPort);

			// DatagramSocket 레퍼런스를 사용하여 메시지 전송
			socket.send(outPacket);

			// 소켓 닫음
			socket.close();

		} catch (SocketException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

(2) Run

1> ClientRun

package com.kh.chap02_socket.part02_udp.run;

import com.kh.chap02_socket.part02_udp.sample.UDPClient;

public class ClientRun {

	public static void main(String[] args) {
		new UDPClient().clientStart();
	}

}

(2) Run

 

2> ServerRun

package com.kh.chap02_socket.part02_udp.run;

import com.kh.chap02_socket.part02_udp.sample.UDPServer;

public class ServerRun {

	/*
	 * < TCP와 UDP의 특징과 차이점 >
	 * 
	 * TCP(Transmission Control Protocol) 
	 * 
	 * - 연결기반으로 연결 후 통신하는 방식 (전화)  
	 * - Byte-stream으로 데이터 경계를 구분 안함 
	 * - 데이터 전송 순서가 보장 
	 * - 데이터의 수신여부를 확인하여 데이터가 손실되면 재전송 
	 * - 신뢰성 있는 데이터 전송 
	 * - UDP 보다 속도가 느림
	 * => 데이터의 전송속도보다 안정성이 중요한 경우 사용
	 * 
	 * 
	 * UDP(User Datagram Protocol)
	 * 
	 * - 비연결기반이며 연결없이 통신하는 방식 (소포) 
	 * - datagram 으로 데이터의 경계를 구분함 
	 * - 데이터의 전송 순서가 바뀔 수 있음 
	 * - 데이터의 수신여부를 확인 안하므로 데이터가 손실되어도 어쩔 수 없음 
	 * - 신뢰성 있는 통신을 하지 않음 
	 * - TCP 보다 전송속도가 빠름 
	 * => 실시간 전송처럼 데이터의 전송 속도가 중요한 경우 사용
	 */
	
	public static void main(String[] args) {
		new UDPServer().serverStart();
	}

}

 

 

3) Part03_chat

(1) Controller

1> ChatClientManeger

package com.kh.chap02_socket.part03_chat.controller;

import java.io.IOException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;

import com.kh.chap02_socket.part03_chat.thread.Receiver;
import com.kh.chap02_socket.part03_chat.thread.Sender;

public class ChatClientManager {
	
	public void startClient() {
		// 클라이언트 컴퓨터 이름
		String name = "Client";
		// 서버 포트번호
		int port = 8888;
		
		String serverIP = null; // 서버 IP
		
		try {
			// 서버, 클라이언트를 둘 다 실행하기 때문에
			// 현재 컴퓨터의 IP 주소가 서버주소
			serverIP = InetAddress.getLocalHost().getHostAddress();
		
			System.out.println("Client Start");
			
			// 클라이언트용 소켓 객체 생성
			Socket socket = new Socket(serverIP, port);
			
			System.out.println("서버와 연결되었습니다.");
			
			// 클라이언트가 사용할 송/수신 작업용 스레드 생성 후 start()
			Sender sender = new Sender(socket, name);
			Thread sth = new Thread(sender);
			
			Receiver receiver = new Receiver(socket);
			Thread rth = new Thread(receiver);
			
			sth.start();
			rth.start();
			
		
		} catch (UnknownHostException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		}
		
		
	}

}

(1) Controller

2> ChatServerManeger

package com.kh.chap02_socket.part03_chat.controller;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

import com.kh.chap02_socket.part03_chat.thread.Receiver;
import com.kh.chap02_socket.part03_chat.thread.Sender;

public class ChatServerManager {
	
	public void startServer() {
		// 서버 컴퓨터 이름
		String name = "Server";
		// 서버 포트번호
		int port = 8888;
		
		try {
			// 서버 소켓 객체 생성 후 포트 번호와 결합
			ServerSocket server = new ServerSocket(port);
			
			System.out.println("Server Start");
			
			// 클라이언트쪽에서 접속 요청이 오길 기다리다 
            //접속 요청이 오면 요청 수락 후 해당 클라이언트에 대한 소켓 객체 생성
			Socket client = server.accept();
			System.out.println("클라이언트와 연결이 되었습니다.");
			
			// * 데이터 송/수신 작업용 스레드 만들기(Sender, Receiver)
			// 채팅 프로그램은 송신과 동시에 수신도 할 수 있어야 하므로
			// 송신과 수신을 별도의 스레드로 처리
			
			// 서버가 사용할 송/수신 작업용 스레드 생성 후 start()
			Sender sender = new Sender(client, name);
			Thread sth = new Thread(sender);
			
			Receiver receiver = new Receiver(client);
			Thread rth = new Thread(receiver);
			
			sth.start();
			rth.start();
			
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

(2) Thread

1> Receiver

package com.kh.chap02_socket.part03_chat.thread;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

// 데이터 수신 작업을 처리하는 Thread 구현
public class Receiver implements Runnable {

	private DataInputStream in; // 전달된 데이터를 입력받을 스트림
	
	// 매개변수 있는 생성자
	// Receiver 객체 생성 시 매개변수로 소켓을 전달
	// 전달 받은 소켓을 이용하여 입력용 스트림과 연결
	public Receiver(Socket socket) {
		try {
			in = new DataInputStream(socket.getInputStream());
			
		} catch (IOException e) {
			e.printStackTrace();
		}
		
	}

	// 스레드 처리 작업
	// 채팅 계속 해서 출력할 수 있도록 Sender에서 출력된 내용을 무한루프를 이용해서 콘솔에 출력
	@Override
	public void run() {
		while(true) {
			// Sender에서 출력한 값을 화면에 출력
			try {
				System.out.println(in.readUTF());
			} catch (IOException e) {
				//e.printStackTrace();
				// 서버나 클라이언트 중 한쪽이 먼저 종료하면 에러 발생 
				// 무한 루프 탈출하여 스레드 종료
				break; 
			} 
		}
	}
}

(2) Thread

2> Sender

package com.kh.chap02_socket.part03_chat.thread;

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

// 데이터 송신 작업을 처리하는 Thread 구현
public class Sender implements Runnable{
	
	private String name;	// 데이터를 보내는 사용자명
	private DataOutputStream out;	// 데이터 출력용 스트림
	private Scanner sc;  // 채팅 내용으로 입력 받을 Scanner
	
	// 매개변수 있는 생성자
	// Sender 객체 생성 시 매개변수로 Socket과 name 설정
	// Server에서 생성 : 요청을 수락한 Client의 소켓, 서버명
	// Client에서 생성 : 연결된 Server의 소켓, 클라이언트명
	public Sender(Socket socket, String name) {
		this.name = name;
		try {
			out = new DataOutputStream(socket.getOutputStream());
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	// 스레드 처리 작업
	// 채팅 계속해서 입력할 수 있도록 무한루프를 이용해서 Scanner 기능으로 데이터를 입력받고
	// 입력받은 내용을 연결된 출력 스트림을 통해서 출력
	@Override
	public void run() {
		sc = new Scanner(System.in);
		
		while(true) {
			try {
				out.writeUTF(name + " > " + sc.nextLine());
				out.flush();
			} catch (IOException e) {
				// e.printStackTrace();
				// 서버나 클라이언트 중 한쪽이 먼저 종료하면 에러 발생 
				// 무한 루프 탈출하여 스레드 종료
				break; 
			} 
		}
	}

}

(3) Run

1> ClientRun

package com.kh.chap02_socket.part03_chat.run;

import com.kh.chap02_socket.part03_chat.controller.ChatClientManager;

public class ClientRun {
	
	public static void main(String[] args) {
		ChatClientManager client = new ChatClientManager();
		client.startClient();
	}

}

(3) Run

2> ServerRun

package com.kh.chap02_socket.part03_chat.run;

import com.kh.chap02_socket.part03_chat.controller.ChatServerManager;

public class ServerRun {
	
	public static void main(String[] args) {
		ChatServerManager server = new ChatServerManager();
		server.startServer();
	}

}