본문 바로가기

BackEnd/Java

[java] 이것이 자바다 ch19 네트워크 입출력(IP, TCP)

728x90
반응형

1. 네트워크 기초

◎ 네트워크

   ▷ 네트워크 : 여러 컴퓨터들을 통신 회선으로 연결한 것

   ▷ LAN(Local Area Network) : 가정, 회사, 건물, 특정 영역에 존재하는 컴퓨터를 연결한 것

   ▷ WAN(Wild Area Network : LAN을 연결한 것(인터넷)

 

1. IP : PC의 네트워크 연결을 위한 고유 주소(식별할 수 있는 고유 번호, 중복 될 수 없습니다.)
▷ 사설 IP : 공유기에서 내부적으로 쓰이는 IP
   ▶ 192.168.X.X 
   ▶ 172.16.X.X
   ▶ 10.X.X.X
▷ 공인 IP : 실질적인 통신을 위한 IP (인터넷 연결)
▷ PC : 사설 IP → 내부 통신 간에 사설 IP로 통신
                             외부(인터넷) 통신을 할 때는 사설 IP → 공인 IP 변환 시켜서 통신

2. SM(Subnet Mask) : 네트워크 영역 확인 : IP 주소에 subnet mask 값을 씌우면 네트워크 영역주소가 나타남
▷ (네트워크 영역 주소) : 네트워크 공유할 수 있는 영역
   ▶ 198.168.0.110
   ▶ 255.255.255.0
        -------------------
   ▶ 192.168.0.0/24 : 네트워크 영역 : 내부 통신할 그룹

3. GW(GateWay 주소 : 라우터 연결, IP) : 라우터 장비가 PC로 연결된 인터페이스의 IP 주소
▷ 모든 PC는 라우터 장비를 통해서 나간다.(GateWay)

4. DNS : 도메인 네임 서비스(Domain Name Service) : 도메인(영문) 주소를 공인 IP로 변환시켜주는 서비스
▷ KT : 168.126.63.1 : www.naver.com  요청 → DNS 서버가 분석 → 공인 IP 전달
    구글 : 8.8.8.8

 

◎ 서버와 클라이언트

   ▷ 서버 : 서비스를 제공하는 프로그램

   ▷ 클라이언트 : 서비스를 요청하는 프로그램

   ▷ 먼저 클라이언트가 서비스를 요청하고, 서비는 처리결과를 응답으로 제공

 

◎ IP 주소

   ▷ IP주소 : 네트워크 어댑터(LAN 카드)마다 할당되는 컴퓨터의 고유한 주소

   ▷ ipconfig(윈도우), ifconfig(맥OS) 명령어로 네트워크 어댑터에 어떤 IP 주소가 부여되어 있는지 확인

   ▷ 프로그램은 DNS를 이용해서 컴퓨터의 IP주소를 검색

 

Internet Protocaol : 규약, 형식

◎ IPv4 : 32bit 크기를 가지고 4개부분(옥텟)으로  8bit(0~255)씩 나누어서 사용
   ▷ 192.168.1.1
   ▷ 256.270.109.101(X) → 255가 넘기때문에 입력할 수 없는 값입니다.
   ▷ 2^32 승 : 42억
◎ IPv6 : 128bit 크기를 16진수를 8개부분 : 16bit : 16진수 4개로 하나의 영역이 표시됩니다.

 

◎ Port 번호

   ▷ 운영체제가 관리하는 서버프로그램의 연결 번호. 서버 시작시 특정 Port 번호에 바인딩

 

 

1521(오라클)

3306(mySQL)

 

netstat : 네트워크 상태보기 (port 확인)
80 : 
http://220.119.232.100:80
http://220.119.232.100
ftp://220.119.232.100:21

 

NO 구분명 범위 설명
1 Well Know Port Numbers 0~1023 국제인터넷주소관리기구(ICANN)가 특정 애플리케이션용으로 미리 예약한 Port
2 Registerd Port Numbers 1024~49151 회사에서 등록해서 사용할 수 있는 Port
3 Dynamic Or Private Port Numbers 49152~65535 운영체제가 부여하는 동적 Port 또는 개인적인 목적으로 사용할 수 있는 Port

 

 

2. IP 주소 얻기

◎ InetAddress

   ▷ 자바는 IP 주소를 java.net 패키지의 InetAddress로 표현

   ▷ 로컬 컴퓨터의 InetAddress를 얻으려면 InetAddress.getLocalHost() 메소드를 호출

   ▷ getByName() 메소드는 DNS에서 도메인 이름으로 등록된 단 하나의 IP 주소를 가져오고,

        getAllByName() 메소드는 등록된 모든 IP 주소를 배열로 가져옵니다.

   ▷ InetAddress 객체에서 IP 주소를 얻으려면 getHostAddress() 메소드를 호출합니다.

 

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

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();
		}
	}
}

//	출력 : 
//	내 컴퓨터 IP 주소: 172.20.10.20
//	www.naver.com IP 주소: 223.130.195.200
//	www.naver.com IP 주소: 223.130.200.107

 

3. TCP 네트워킹

◎ TCP

   ▷ TCP는 연결형 프로토콜로, 상대방이 연결된 상태에서 데이터를 주고 받는 전송용 프로토콜

   ▷ 클라이언트가 연결 요청을 하고 서버가 연결을 수락하면 통신 회선이 고정되고, 데이터는 고정회선을 통해 전달.

        TCP는 보낸 데이터가 순서대로 전달되며 손실이 발생하지 않음

   ▷ ServerSocket은 클라이언트의 연결을 수락하는 서버 쪽 클래스이고,

       Socket은 클라이언트에서 연결 요청할 때와 클라이언트와 서버 양쪽에서 데이터를 주고 받을 때 사용되는 클래스

 

◎ TCP 서버

1. TCP 서버 프로그램을 개발하려면 우선 ServerSocket 객체를 생성
ServerSocket serverSocket = new ServerSocket(50001);​

2. 기본 생성자로 객체를 생성하고 Port 바인딩을 위해 bind() 메소드를 호출해도 ServerSocket 생성
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress(50001));​


3. 여러개의 IP가 할당된 서버 컴퓨터에서 특정 IP에서만 서비스를 하려면 InetSocketAddress의 첫 번째 매개값으로 해당 IP를 줌

ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("xxx.xxx.xxx.xxx", 50001));

 

4. Port가 이미 다른 프로그램에서 사용중이라면 BindException이 발생합니다. 다른 Port로 바인딩하거나 Port를 사용 중인 프로그램을 종료하고 다시 실행해야 합니다.
5. ServerSocket이 생성되면 연결 요청 수락을 위해 accept() 메소드 실행
6. accept()는 클라이언트가 연결 요청하기 전까지 블로킹(실행 멈춘 상태) 클라이언트의 연결 요청이 들어오면 블로킹이 해제되고 통신용 Socket을 리턴

Socket socket = serverSocket.accept();


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

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


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

serverSocket.close();

 

TCP 예제

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class ServerExample {
	private static ServerSocket serverSocket = null;
	
	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")) {
				break;
			}
		}
		scanner.close();
		
		// TCP 서버 종료
		stopServer();
	}
	
	public static void startServer() {
		// 작업 스레드 정의
		Thread thread = new Thread() {
			@Override
			public void run() {
				try {
					// ServerSocket 생성 및 Port 바인딩
					serverSocket = new ServerSocket(50001);
					System.out.println("[서버] 시작됨");
					
					while(true) {
						System.out.println("\n[서버] 연결 요청을 기다림\n");
						// 연결 수락
						Socket socket = serverSocket.accept();
						
						// 연결된 클라이언트 정보 얻기
						InetSocketAddress isa = 
								(InetSocketAddress) socket.getRemoteSocketAddress();
						System.out.println("[서버] " + isa.getHostName() + "의 연결 요청을 수락함");
						
						// 연결 끊기
						socket.close();
						System.out.println("[서버] " + isa.getHostName() + "의 연결을 끊음");
					}
				} catch (IOException e) {
					System.out.println("[서버] " + e.getMessage());
				}
			}
		};
		// 스레드 시작
		thread.start();
	}
	
	public static void stopServer() {
		try {
			// ServerSocket을 닫고 Port 언바인딩
			serverSocket.close();
			System.out.println("[서버] 종료됨 ");
		} catch (IOException e1) {}
	}
}

//	출력 : 
//	-------------------------------------------
//	서버를 종료하려면 q또는 Q를 입력하고 Enter 키를 입력하세요
//	-------------------------------------------
//	[서버] 시작됨
//	
//	[서버] 연결 요청을 기다림
//	
//	q
//	[서버] 종료됨 
//	[서버] socket closed

 

◎ TCP 클라이언트

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

3. 도메인 이름을 사용하려면 DNS에서 IP주소를 검색하는 생성자 매개값으로 InetSocketAddress 제공
Socket socket = new Socket(new InetSocketAddress("domainName", 50001));​

4. 기본 생성자로 Socket을 생성한 후 connect() 메소드로 연결 요청 가능
Socket = new Socket();
socket.connect( new InetSocketAddress("domainName", 50001));​

 

◎ TCP 클라이언트 예제

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

public class ClientExample {
	public static void main(String[] args) {
		try {
			// Socket 생성과 동시에 localhost의 50001 Port로 연결 요청;
			Socket socket = new Socket("localhost", 50001);
			
			System.out.println("[클라이언트] 연결 성공");
			
			// Socket 닫기
			socket.close();
			System.out.println("[클라이언트] 연결 끊기");
		} catch(UnknownHostException e) {
			// IP 표기 방법이 잘못되었을 경우
		} catch(IOException e) {
			// 해당 포트의 서버에 연결할 수 없는 경우
		}
	}
}

//	출력 : 
//	[클라이언트] 연결 성공
//	[클라이언트] 연결 끊기


앞에서 작성했던 Server를 먼저 실행하고 바로 위의 코드를 실행하면 클라이언트가 연결됩니다.

서버의 콘솔창에는 아래와 같이 출력됩니다.

-------------------------------------------
서버를 종료하려면 q또는 Q를 입력하고 Enter 키를 입력하세요
-------------------------------------------
[서버] 시작됨

[서버] 연결 요청을 기다림

[서버] 127.0.0.1의 연결 요청을 수락함
[서버] 127.0.0.1의 연결을 끊음

[서버] 연결 요청을 기다림


이때 주의사항은 서버를 여러번 실행시키면 에러가 나타납니다.
또한, 제대로 강제종료를 시켜주지 않으면 통신 오류가 발생됩니다.

 

 

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

1. 클라이언트가 연결 요청(connect())을 하고 서버가 연결 수락(accept())했다면, 양쪽의 Socket 객체로부터 각각 InputStream과 OutputStream을 얻을 수 있습니다.

2. 상대방에게 데이터를 보낼 때에는 보낼 데이터를 byte[ ] 배열로 생성하고, 이것을 매개값으로해서 OutputStream의 write() 메소드를 호출

3. 문자열을 좀 더 간편하게 보내고 싶다면 보조 스트림인 DataOutputStream을 연결해서 사용

4. 데이터를 받기 위해서는 받은 데이터를 저장할 byte[ ] 배열을 하나 생성하고, 이것을 매개값으로해서 InputStream의 read() 메소드를 호출

5. read() 메소드는 읽은 데이터를 byte[ ] 배열에 저장하고 읽은 바이트 수를 리턴
6. 문자열을 좀 더 간편하게 받고 싶다면 보조 스트림인 DataInputStream을 연결해서 사용

 

◎ 입출력 스트림으로 데이터 주고 받기 예제

1. EchoServer 클래스
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class EchoServer {
	private static ServerSocket serverSocket = null;
	
	
	public static void main(String[] args) {
		System.out.println("---------------------------------------");
		System.out.println("서버를 종료하려면 q를 입력하고 Enter 키를 입력하세요");
		System.out.println("---------------------------------------");
		
		// TCP 서버 시작
		startServer();
		
		// 키보드 입력
		Scanner scanner = new Scanner(System.in);
		while(true) {
			String key = scanner.nextLine();
			if(key.toLowerCase().equals("q")) {
				break;
			}
		}
		scanner.close();
		
		// TCP 서버 종료
		stopServer();
	}
	
	public static void startServer() {
		// 작업 스레드 정의
		Thread thread = new Thread() {
			@Override
			public void run() {
				try {
					// ServerSocket 생성 및 Port 바인딩
					serverSocket = new ServerSocket(50001);
					System.out.println("[서버] 시작됨");
					
					// 연결 수락 및 데이터 통신
					while(true) {
						System.out.println("\n[서버] 연결 요청을 기다림\n");
						// 연결 수락
						Socket socket = serverSocket.accept();
						
						// 연결된 클라이언트 정보 얻기
						InetSocketAddress isa =
								(InetSocketAddress) socket.getRemoteSocketAddress();
						System.out.println("[서버] " + isa.getHostName() + "의 연결 요청을 수락함");
						
						/*
				//------------------------------------------------------------------
				// 데이터 받기
				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);
				*/
					
				//------------------------------------------------------------------
						// 데이터 받기
						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);
						//------------------------------------------------------------------
						
						// 연결 끊기
						socket.close();
						System.out.println("[서버] " + isa.getHostName() + "의 연결을 끊음");
					}
				} catch(IOException e) {
					System.out.println("[서버] " + e.getMessage());
				}
			}
		};
		// 스레드 시작
		thread.start();
	}
	
	public static void stopServer() {
		try {
			// ServerSocket을 닫고 Port 언바인딩
			serverSocket.close();
			System.out.println("[서버] 종료됨 ");
		} catch(IOException e1) {}
	}
}


2. EchoClient 클래스

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

public class EchoClient {

	public static void main(String[] args) {
		// TODO Auto-generated method stub
		try {
			// Socket 생성과 동시에 localhost의 50001 포트로 연결 요청;
			Socket socket = new Socket("localhost", 50001);
			
			System.out.println("[클라이언트] 연결 성공");
			
			/*
			//--------------------------------------------------------------------
			// 데이터 보내기
			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);
			//--------------------------------------------------------------------
			
			// 연결 끊기
			socket.close();
			System.out.println("[클라이언트] 연결 끊음");
		} catch(Exception e) {
		}
	}
}



서버를 먼저 실행하고 클라이언트를 실행하면 아래와 같은 출력을 나타낼 수 있습니다.

1. 서버 출력

---------------------------------------
서버를 종료하려면 q를 입력하고 Enter 키를 입력하세요
---------------------------------------
[서버] 시작됨

[서버] 연결 요청을 기다림

[서버] 127.0.0.1의 연결 요청을 수락함
[서버] 받은 데이터를 다시 보냄: 12345
[서버] 127.0.0.1의 연결을 끊음

[서버] 연결 요청을 기다림


2. 클라이언트 출력

[클라이언트] 연결 성공
[클라이언트] 데이터 보냄: 12345
[클라이언트] 데이터 받음: 12345
[클라이언트] 연결 끊음


이때도 Server를 여러번 실행시키면 오류가 나타납니다.
EchoClient 클래스의 localhost를 상대방 IP 주소로 작성하면 상대방에게 제가 보낸 메세지를 전달할 수 있습니다.

이렇게 연결할 때 DataInputStream, DataOutputStream을 사용하면 상대방이 데이터로만 전달해야 연결이 가능합니다.
따라서, InputStream, OutputStream을 사용하면 모든 데이터를 주고 받을 수 있습니다.

 

TCP : Transmission Control Protocol(신뢰성 있는 통신)
   → 데이터를 주고 받을 때 확인 과정, 상대방이 잘 받았는지 중간 확인
   → 중간확인 : 속도가 조금 느리게 동작
   → 파일을 정확하게 주고 받을 경우
   → 데이터를 정확하게 주고 받을 경우
   → ServerSocket 객체 생성 필요

UDP : User Datagram Protoclol(비신뢰성 있는 통신)
   → 데이터를 그냥 뿌려주고 상대방이 잘 받았는지 확인 안함
   → 중간확인 x : 속도가 빠르다.
   → dhcp 서버 : ip 할당
   → 전화, 동영상 스트리밍
   → DatagramSocket 객체 생성 필요

 

TCP를 이용하면 상대방이 연결을 수락하면 통신 회선이 고정되고 데이터는 고정 회선을 통해서 전달됩니다.

순서대로 데이터가 전달되며 데이터 손실이 발생하지 않는다고 하네요!

 

다음은 UDP를 알아보죠!!

 

많은 분들의 피드백은 언제나 환영합니다! 많은 댓글 부탁드려요~~

 

728x90
반응형