본문 바로가기

학원/복기

0427 - TCP 프로그램, UDP 프로그램

InetAddress 클래스 : 네트워크 식별자(IP 주소 및 호스트이름)를 저장하기 위한 클래스

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

public class InetAdressApp {
	public static void main(String[] args) throws UnknownHostException {
		InetAddress myComputer = InetAddress.getLocalHost();
	
		System.out.println("myComputer = " + myComputer);
		System.out.println("myComputer = " + myComputer.getHostName());
		System.out.println("myComputer = " + myComputer.getHostAddress());
		
	}
}
  • InetAddress.getLocalHost() : 현재 사용중인 컴퓨터의 네트워크 식별자가 저장된 InetAddress 객체를 반환하는 메소드 
  • → UnknownHostException 발생(일반예외) : 호스트이름의 컴퓨터를 검색할 수 없는 경우 발생되는 예외 
  • 현재 사용중인 컴퓨터는 기본적으로 [127.0.0.1]의 IP 주소가 제공되며 [localhost]라는 이름으로 호스트이름 설정된다.
  • InetAddess.toString() : InetAddess 객체에 저장된 네트워크 식별자를 문자열로 변환하여 반환하는 메소드
  • InetAddess.getHostName() : InetAddess 객체에 저장된 네트워크 식별자에서 호스트 이름을 문자열로 변환하여 반환하는 메소드
  • InetAddess.getHostAddress() : InetAddess 객체에 저장된 네트워크 식별자에서 IP 주소를 문자열로 변환하여 반환하는 메소드

실행 결과

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

public class InetAdressApp {
	public static void main(String[] args) throws UnknownHostException {
    
		InetAddress itwill = InetAddress.getByName("www.itwill.xyz");
        
		System.out.println("[www.itwill.xyz]의 IP 주소 = " + itwill.getHostAddress());
        
		InetAddress[] naver = InetAddress.getAllByName("www.naver.com");	
        
		for(InetAddress address : naver) {
			System.out.println("[www.naver.com]의 IP 주소 = " + address.getHostAddress());
			}
	}
}
  • InetAddress.getByName(Stirng name) : 매개변수로 전달받은 호스트이름에 대한 네트워크 식별자가 저장된 InetAddress 객체를 반환하는 메소드 
  • InetAddress.getAllByName(String name) : 매개변수로 전달받은 호스트이름에 대한 네트워크 식별자가 저장된 InetAddress 객체의 배열을 반환하는 메소드 

실행 결과 


TCP 프로그램

  • TCP 프로그램은 ServerSocket 클래스와 Socket 클래스를 이용해 작성한다.
  • TCP 프로그램은 서버와 클라이언트가 소켓을 사용하여 1:1로 연결되어 데이터를 송수신하는 네트워크 프로그램이다.
  • 서버(Server) : 외부의 컴퓨터(클라이언트)가 접속할 수 있는 환경과 서비스를 제공하기 위한 컴퓨터를 뜻한다 → ServerSocket 클래스를 사용하여 서버 프로그램 작성
  • 클라이언트(Client) : 서버에 접속하여 서비스를 제공받을 수 있는 컴퓨터 → Socket 클래스를 사용하여 클라이언트 프로그램 작성

서버-클라이언트 연결과정

  1. 서버는 포트를 열어놓고, 클라이언트가 접속하길 기다린다.
  2. 클라이언트는 Socket 클래스로 객체 만들어 IP주소와 포트 번호 통해 접속한다.
  3. 서버측 소켓과 클라이언트의 소켓이 1:1로 연결된다.
  4. 값을 송수신할 수 있는 입출력 스트림이 자동 생성된다.

ServerSocket 클래스 : 서버 프로그램을 작성하기 위해 사용하는 클래스 

  • ServerSocket 클래스의 ServerSocket(int port) 생성자를 이용하여 매개변수로 전달받은 포트번호를 활성화하여 ServerSocket 객체를 생성 
  •  ServerSocket 객체를 생성하여 클라이언트가 접속할 수 있는 환경 제공 (포트가 열려야만 클라이언트가 접속할 수 있다)
  • 매개변수로 전달받은 포트번호를 이미 다른 네트워크 프로그램에 사용중인경우 IOException 발생 

 

ServerSocket 클래스를 사용하여 사용 가능한 포트번호를 검색하는 프로그램 작성

public class ServerSocketApp {
	public static void main(String[] args) {
		for(int i = 2000; i <= 9000; i+=1000) {
			try {
				ServerSocket serverSocket = new ServerSocket(i);
                
				System.out.println("[메시지]" + i + "번 포트는 네트워크 프로그램에서 사용 가능");
               
				serverSocket.close(); //클라이언트가 접속할 수 있는 환경 소멸 
			} catch (IOException e) { //포트번호를 이미 다른 네트워크 프로그램에 사용중인경우 IOException 발생 
				System.out.println("[메시지]" + i + "번 포트는 이미 다른 네트워크 프로그램에서 사용중입니다.");				
			}
		}
	}
}
  • serverSocket.close() : serverSocket 객체를 제거하는 메소드 →  클라이언트가 접속할 수 있는 환경이 소멸된다 

실행 결과

실습

접속된 클라이언트에게 서버 컴퓨터의 현재 날짜와 시간을 전달하는 서버 프로그램 작성

 NTP(Network Time Protocol) Server : 날짜와 시간을 제공하는 서버 컴퓨터 

import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

public class TimeServerApp {
	public static void main(String[] args) {
		ServerSocket ntpServer = null;
        
		try {
			//ServerSocket 객체 생성 : 포트를 활성화하여 클라이언트가 접속할 수 있는 환경 제공
			ntpServer = new ServerSocket(2000); //2000번 포트 활성화 
			
			//ServerSocket.toString() : ServerSocket 객체에 저장된 접속 관련 정보를 문자열로
			//반환하는 메소드 
			//System.out.println("ntpServer = " + ntpServer);
			
			System.out.println("[메세지]NTP Server Running...");
			
			//서버 프로그램에 다수의 클라이언트 접속을 허용하도록 무한루프 사용 
			while(true) {
				Socket socket = ntpServer.accept();	
                
				//OutputStream은 원시데이터를 전달하기 때문에, 날짜와 시간을 전달하기 위해서는
				//ObjectOutputStream 클래스로 확장시켜줘야 한다.
				OutputStream stream = socket.getOutputStream();
				
				//OutputStream 객체를 전달받아 객체를 전달할 수 있는 출력스트림으로 확장 
				ObjectOutputStream out = new ObjectOutputStream(stream);
				
				//출력스트림을 이용하여 시스템의 현재 날짜와 시간이 저장된 Date 객체를 생성
				//하여 전달 - 클라이언트에게 날짜와 시간을 전송 
				out.writeObject(new Date()); 

				//한번만 호출할 것이기 때문에 참조변수 생성 없이 만들어주는 것이 더 효율적
				// - 불필요한 변수는 만들지 않는 것을 권장한다
				//new ObjectOutputStream(socket.getOutputStream()).writeObject(new Date());
				
				//로그 처리 - 기록 
				System.out.println("[정보]클라이언트["+socket.getInetAddress()
					.getHostAddress()+"]에게 날짜와 시간을 제공 하였습니다.");
				
				//클라이언트와의 접속 해제 >> 더 이상 받을것도, 보낼것도 없는 상태이기 때문에
				socket.close();
			}
		} catch (IOException e) {
			System.out.println("[에러]서버 네트워크에 문제가 발생 되었습니다.");
		}
	}
}
  • ServerSocket.accept() : 클라이언트가 접속되면 클라이언트와 값을 주고 받을 수 있는 Socket 객체를 반환하는 메소드 
    → 클라이언트가 접속되기 전까지 스레드 일시 중지되며, 클라이언트가 접속되면 클라이언트의 소켓과 연결될 소켓을 생성하여 반환하고 스레드를 재실행한다. 
  • Socket.getOutputStream() : Socket 객체에 저장된 출력스트림(OutputStream 객체)을 반환하는 메소드 
  • Socket.getInetAddress() : 소켓과 연결된 외부 컴퓨터의 네트워크  식별자가 저장된 InetAddress 객체를 반환하는 메소드 

Socket 클래스 : TCP 프로그램에서 다른 컴퓨터와의 연결을 위한 정보를 저장하기 위한 클래스 

  • 다른 컴퓨터의 소켓과 연결되어 값을 송수신할 수 있는 입출력 스트림 제공 
    → Socket 클래스의 Socket(String Host, int port) 생성자를 이용하여 매개변수에 접속 컴퓨터(서버)의 네트워크 식별자(호스트이름 또는 IP 주소)와 활성화된 포트번호를 전달하여 Socket 객체 생성 - 서버 접속 
    → UnknownHostException 및 IOException 발생 - 일반 예외이므로 반드시 예외 처리 

 

NTP 서버에 접속하여 서버에서 보내온 날짜와 시간을 제공받아 출력하는 클라이언트 프로그램 작성

import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.net.Socket;
import java.net.UnknownHostException;
import java.text.SimpleDateFormat;
import java.util.Date;

public class TimeClientApp {
	public static void main(String[] args) throws ClassNotFoundException {
		try {
        		//Socket(String Host, int port) 생성자 이용해 객체 생성 - 서버 접속 
			Socket socket = new Socket("192.168.13.7", 2000);
			
			InputStream stream = socket.getInputStream();
			
			//InputStream 객체를 전달받아 객체를 받을 수 있는 입력스트림으로 확장 
			ObjectInputStream in = new ObjectInputStream(stream);
			
			//입력스트림을 이용하여 서버에서 보내온 Date 객체를 반환받아 저장 
			Date date = (Date)in.readObject(); //Object 타입으로 받아오기 때문에 명시적객체형변환 필요
		
			//Date 객체에 저장된 날짜와 시간을 원하는 형식의 문자열로 변환하여 출력
			System.out.println("[결과]서버에서 보내온 날짜와 시간 = "
					+ new SimpleDateFormat("yyyy년 MM월 dd일 HH시 mm분 ss초").format(date));
			
			//접속 해제
			socket.close();
			
		} catch (UnknownHostException e) {
			System.out.println("[에러]서버를 찾을 수 없습니다.");
		} catch (IOException e) {
			System.out.println("[에러]서버에 접속할 수 없습니다.");
		}
	}
}
  • Socket.getInputStream() : Socket 객체의 입력스트림(InputStream 객체)을 반환하는 메소드
  • Socket.close() : Socket 객체를 제거하는 메소드 - 접속 해제 


클라이언트에서 보내온 메세지를 제공받아 출력하는 서버 프로그램 작성

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

public class EchoServerApp {
	public static void main(String[] args) {
		ServerSocket echoServer=null;
		
		try {
			echoServer=new ServerSocket(3000);
			System.out.println("[메세지]Echo Server Running...");
			
			while(true) {
				Socket socket=echoServer.accept();
				
				//소켓의 입력스트림을 제공받아 대략의 문자데이터를 입력받을 수 있는 입력스트림 확장
				BufferedReader in=new BufferedReader
						(new InputStreamReader(socket.getInputStream()));
				
				//클라이언트의 소켓과 연결된 입력스트림을 이용하여 문자열(메시지)를 반환받아 출력
				System.out.println("["+socket.getInetAddress().getHostAddress()
						+"]님이 보내온 메세지 = "+in.readLine());
				
				socket.close();
			}
		} catch (IOException e) {
			System.out.println("[에러]서버 네트워크에 문제가 발생 되었습니다.");
		}
	}
}

키보드로 메세지를 입력받아 접속 서버에 전달하는 클라이언트 프로그램 작성

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;

public class EchoClientApp {
	public static void main(String[] args) throws IOException {
		//키보드 입력스트림을 대량의 문자데이터를 입력받을 수 있는 입력스트림으로 확장 
		// 원시데이터만 받아들일 수 있기 때문에 문자 데이터 읽을 수 있게 확장 >> 문자열 데이터 읽을 수 있도록 확장
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		
		System.out.print("전달 메세지 입력 >> ");
		String message = in.readLine(); //키보드로 메세지 입력 받기
		
		try {
			Socket socket = new Socket("192.168.13.X", 3000);
			
			/*
			//소켓의 출력스트림을 제공받아 대량의 문자데이터를 전달할 수 있는 출력스트림으로 확장 
			BufferedWriter out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			
			//서버의 소켓과 연결된 출력스트림을 이용하여 문자열(메시지) 전달 
			out.write(message);
			
			//출력 스트림의 버퍼에 존재하는 문자데이터를 출력스트림으로 전달 
			out.flush();
			*/
			
			//BufferedWriter 대신 PrintWriter 사용 가능 (더 효율적)
			
			//소켓의 출력스트림을 제공받아 모든 자료형의 값을 문자열로 변환하여 전달할 수 
			//있는 기능의 출력스트림으로 확장
			PrintWriter out = new PrintWriter(socket.getOutputStream());
			
			//PrintWriter.println(Object o) : 매개변수로 전달받은 모든 자료형의 값(객체)를 
			//문자열로 변환하여 전달하는 메소드
			out.println(message);
			out.flush();
			
			socket.close();
		} catch (IOException e) {
			System.out.println("[에러]서버에 접속할 수 없습니다.");
		}
	}
}

PrintWriter의 println() 메소드 사용하면, 어떤 형태의 값이든 문자열로 변환해 전달할 수 있다는 장점이 존재한다. 따라서, BufferedWriter 대신 PrintWriter 사용하는 것이 더 효율적이다. 

 


UDP 프로그램

  • UDP 프로그램은 DatagramSocekt 클래스와 DatagramPacket 클래스를 이용하여 작성한다.
  • 값을 전달하는 컴퓨터와 값을 전달받는 컴퓨터로 구분한다. (서버, 클라이언트라는 개념이 없다.)
  • 접속이라는 행위 필요 없이, 그냥 전송한다. 

DatagramSocket 클래스

  • 다른 컴퓨터와 연결하기 위한 정보를 저장하기 위한 클래스
  • 데이터를 보내는 컴퓨터는 기본생성자를 이용해 DatagramSocket() 객체를 생성 

DatagramPacket 클래스

  • 연결 컴퓨터에게 보낼 패킷정보를 저장하기 위한 클래스 
  • DatagramPacket 클래스의 DatagramPacket(byte[] data, int length, InetAddress address, int port) 생성자를 사용하여 데이터를 보내기 위한 패킷정보가 저장된 DatagramPacket 객체 생성 

실습

키보드로 메세지를 입력받아 다른 컴퓨터에게 전달하는 UDP 프로그램 작성 

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;

public class MessageSendApp {
	public static void main(String[] args) throws IOException {
		BufferedReader in = new BufferedReader(new InputStreamReader(System.in));
		
		System.out.println("전달 메세지 입력 >> ");
		String message = in.readLine();
		
        	//데이터를 보내는 컴퓨터는 기본생성자를 이용해 DatagramSocket() 객체를 생성
		DatagramSocket socket = new DatagramSocket();
		
		//연결할 컴퓨터의 네트워크 식별자가 저장된 InetAddress 객체를 반환받아 저장 
		InetAddress address = InetAddress.getByName("192.168.13.X");
		
		byte[] data = message.getBytes();
		
        	//DatagramPacket 클래스의 DatagramPacket 생성자를 사용하여 데이터를 보내기 위한 
           	//패킷정보가 저장된 DatagramPacket 객체 생성 
		DatagramPacket packet = new DatagramPacket(data, data.length, address, 4000);
		
		socket.send(packet);
		
		socket.close();
		
		System.out.println("[결과]연결 컴퓨터에게 메세지를 보냈습니다.");
	}
}
  • String.getBytes() : String 객체에 저장된 문자열을 byte 배열로 변환하여 반환하는 메소드
  • DatagramSocket.send(DatagramPacket packt) : 매개변수로 전달받은 DatagramPacket 객체의 패킷정보를 이용하여 데이터(패킷)를 전달하는 메소드 
  • DatagramSocket.close() : DatagramSocket 객체를 제거하는 메소드 

다른 컴퓨터에서 보내온 메세지를 얻어와 출력하는 UDP 프로그램 

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class MessageReceiveApp {
	public static void main(String[] args) throws IOException {
		//데이터를 받는 컴퓨터는 포트를 활성화하기 위해 DatagramSocket 클래스의
		//DatagramSocket(int port) 생성자를 사용하여 객체 생성 
		DatagramSocket socket = new DatagramSocket(4000);
		
		//패킷에 의해 전달된 값(메시지)을 저장하기 위한 byte 배열 선언 
		byte[] data = new byte[1024];
		
		//데이터를 받는 컴퓨터는 DatagramPacket 클래스의 DatagramPacket(byte[] data, int length)
		//생성자를 이용하여 DatagramPacket 객체 생성 
		DatagramPacket packet = new DatagramPacket(data, data.length);
		
		System.out.print("메시지 수신 중...");
		
		socket.receive(packet);
		
		//byte 배열에 저장된 값을 제공받아 String 객체를 생성하여 저장 
		String message = new String(data);
		
		System.out.println("[결과]메세지 >> " + message);
		
		socket.close(); 
	}
}
  • DatagramSocket.receive(DatagramPacket packet) : 연결 컴퓨터에서 보내온 값(패킷)을 얻어와 DatagramPacket 객체의 byte 배열에 저장하는 메소드  →  패킷을 받기전까지 스레드 일시 중지