Hyogi's Notebook

[JAVA] Day 01 네트워크와 소켓

by 효기’s

네트워크

다른 장치로 데이터를 이동시킬 수 있는 컴퓨터들과 주변 장치들의 집합

네트워크의 연결된 모든 장치들을 노드라고 한다.

다른 노드에게 하나 이상의 서비스를 해주는 노드를 호스트

하나의 컴퓨터에서 다른 컴퓨터로 데이터를 이동시킬 때 복잡한 계층의 대표적인 모델이 OSI 계층 모델

OSI 계층 모델은 모두 7계층

인터넷 기반의 표준 모델인 TCP/IP 계층 모델을 주로 사용

 

인터넷 주소 (IP 주소)

모든 호스트는 인터넷 주소라 불리는 유일한 32비트 숫자로 구성된 주소 체계를 이용하여 서로 구분할 수 있다.

IP주소는 8비트 씩 끊어서 표현하고 각 자리는 1바이트로 0~255 까지의 범위를 갖게 된다.

32비트 주소 체계를 IP버전 4 IPv4 주소라고 한다. (포화 상태의 버전을 극복한 버전 IPv6)

IPv6는 128 비트의 주소 체계를 관리하고 있으며 16비트 씩 8부분으로 나누어 16진수 표시한다.

각 호스트는 도메인 이름을 ip주소로 바꿔줘야 한다. 이렇게 ip주소를 도메인 이름으로 바꿔주는 시스템을 DNS라고 한다.

 

포트와 프로토콜

컴퓨터의 주변 자치를 접속하기 위한 물리적인 포트와 프로그램에서 사용되는 접속 장소인 논리적인 포트가 있다.

IANA에 의해 예약된 포트번호를 가진다. (잘 알려진 포트들)

예약된 포트번호의 대표적인 예 : 80(HTTP),  21(FTP), 22(SSH), 23(TELNET)

포트번호는 0~65535까지이며 0~1023까지는 시스템에 예약된 포트번호이기 때문에 사용하지 않는것이 바람직하다.

포로토콜은 클라이언트와 서버 간의 통신규약이다.

통신 규약이란 상호 간의 접속이나 절단방식, 통신방식, 주고받을 데이터의 형식, 오류 검출방식, 코드변환방식, 전송 속도 등에 대하여 정의하는 것을 말한다.

대표적인 예: TCP와 UDP

 

TCP와 UDP

TCP/IP 계층 모델은 4계층 구조이다.

애플리케이션, 전송, 네트워크, 데이터 링크 계층이 있다.

이 중 전송 계층에서 사용하는 프로토콜에는 TCP와 UDP가 있다.

 

InetAddress 클래스

ip 주소를 표현한 클래스

자바에서는 모든 ip주소를 위 클래스를 사용한다.

 

InetAddress 클래스의 생성자

생성자는 하나만 존재하지만 특이하게 기본 생성자의 접근 제한자 default이기 때문에 new 연산자 객체를 생성할 수 없다.

객체를 생성해 줄 수 있는 5개의 static 메서드를 제공하고 있다.

 

반환형 : static InetAddress[]

메서드 : 

getAllByName(String host) → 매개변수 host에 대응되는 InetAddress 배열을 반환한다.

getByAddress(byte[] addr) → 매개변수 addr에 대응되는 InetAddress 객체를 반환한다.

getByAddress(String host, byte[] addr) → 매개변수 host와 addr로 InetAddress 객체를 생성한다.

getByName(String host) → 매개변수 host에 대응되는 InetAddress 객체를 반환한다.

getLocalHost() → 로컬 호스트의 InetAddress 객체를 반환한다.

 

실습 코드
package 고급반;

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

public class Day01 {

	public static void main(String[] args) throws UnknownHostException {

		InetAddress iaddr = InetAddress.getLocalHost(); // 로컬 호스트의 객체를 반환
		
		System.out.printf("호스트 이름 : %s %n", iaddr.getHostName());
		System.out.printf("호스트 ip 주소 : %s %n", iaddr.getHostAddress());

		iaddr = InetAddress.getByName("java.sun.com");
		System.out.printf("호스트 이름 : %s %n", iaddr.getHostName());
		System.out.printf("호스트 ip 주소 : %s %n", iaddr.getHostAddress());

		InetAddress sw[] = InetAddress.getAllByName("www.daum.net");
		for (InetAddress temp_sw : sw) {
			System.out.printf("호스트 이름 : %s %n", temp_sw.getHostName());
			System.out.printf("호스트 ip 주소 : %s %n", temp_sw.getHostAddress());
		}
	}

}

로컬 호스트의 객체를 반환해서 getHostName()을 통해 호스트 이름을 출력하고

getHostAddress()로 ip주소를 출력한다.

 

URL 클래스

URL이란 인터넷에서 접근 가능한 자원의 주소를 표현할 수 있는 형식이다.

URL의 구성요소

URL클래스URL를 추상화 하여 만든 클래스이다.

URL 클래스final 클래스로 되어있기 때문에 상속 불가능하다.

모든 생성자는 반드시 예외처리 해줘야한다.

 

URLConnection 클래스

원격 자원에 접근하는 데 필요한 정보를 가지고 있다.

필요한 정보란 원격 서버의 헤더 정보, 해당 자원의 길이와 타입 정보, 언어 등을 얻어 올 수 있다.

원격 서버 자원의 결과와 원격 서버의 헤더 정보를 가져 올 수 있다.

 

URLConnection 클래스의 생성자

단독적으로 객체를 생성 불가능하다.

URL 클래스의 객체를 생성해서 URL 클래스의 openConnection() 메서드를 이용해서 객체를 생성해야한다.

URLConnection 객체가 생성 되었다면 URLConnection 클래스의 connect() 메서드를 호출해야 객체가 완성된다.

 

소켓

자바 프로그램은 소켓을 통해 네트워크 통신을 한다.

소켓은 네트워크 부분의 끝 부분을 나타내며, 실제 데이터가 어떻게 전송되는지 상관하지 않고 읽기/쓰기 인터페이스를 제공한다.

네트워크 계층과 전송 계층이 캡슐화 되어 있기 때문에 두개의 계층을 신경 쓰지 않고 프로그램을 만들 수 있다.

TCP/IP 계층의 TCP를 지원하기 위해서 Socket, ServerSocket 클래스를 제공하고 있다.

클라이언트는 Socket 객체를 생성하여 TCP 서버와 연결을 시도한다.

서버는 SocketServer객체를 생성하여 Tcp 연결을 청취하여 클라이언트와 서버가 연결된다.

 

소켓 클래스

TCP 소켓은 java.net.Socket 클래스를 의미한다.

Socket(InetAddress address, int port) InetAddress 객체와 port를 이용하여 Socket 객체를 생성한다.

Socket(String host, int port) host와 port를 이용하여 Socket 객체를 생성한다.

 

소켓 생성자는 두가지 예외 처리가 발생한다.

첫번째는 호스트를 찾을 수 없거나, 서버의 포트가 열려 있지 않은 경우 UnknownHostException 예외 발생한다.

두번째는 네트워크 실패, 방화벽 때문에 서버에 접근 할 수 없을때 IOException 예외가 발생한다.

 

소켓을 이용한 입출력 스트림 생성

tcp 소켓은 두개의 네트워크 사이에서 바이트 스트림 통신을 제공한다.

Socket 클래스바이트를 읽기 위한 메서드쓰기 위한 메서드를 제공한다.

두가지 메서드를 이용하여 클라이언트와 서버간에 통신을 할 수 있다.

 

소켓 종료

소켓 사용이 끝나면 연결 끊기 위해 close() 메서드를 호출

finally 블록에서 소켓 종료 처리하고 예외처리 반드시 해야한다.

소켓은 시스템에 의해 자동으로 종료되는 경우가 있다.

프로그램이 종료되거나 자동으로 닫히는것은 바람직 하지않고 close() 메서드를 호출해서 정확히 소켓을 종료해야 한다.

소켓이 닫히더라도 getIntAddress(), getPort() 메서드는 사용할 수 있으나,

getInputStream(), getOutputStream() 메서드는 사용할 수 없다.

 

 

유니 캐스팅과 멀티 캐스팅

클라이언트와 서버 간 지속적으로 일대일 통신하는 개념을 유니 캐스팅이라고 한다.

일대 다의 통신을 멀티 캐스팅이라고 한다.

 

멀티 캐스팅

실시간 프로그램에서 서버의 정보를 모든 클라이언트가 공유할 때 문제점이 있다.

한명의 클라이언트가 서버의 정보를 변경했을 경우 모든 클라이언트에게 전송함으로써 서로가 변경된 정보를 공유할 수 있는 애플리케이션을 만들때 적합하다.

멀티 캐스팅 프로그램을 작성하기 위해서 유니캐스트에서 생성된 스레드를 저장하기 위한 공간(ArrayList)가 필요하며 클라이언트에는 자신이 보낸 메시지나 다른 클라이언트가 보낸 메시지를 받기위한 스레드가 필요하다.

 

호스트 이름, ip주소 보는방법
package 고급반;

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

public class Day01 {

	public static void main(String[] args) throws UnknownHostException {

		InetAddress iaddr = InetAddress.getLocalHost(); // 로컬 호스트의 객체를 반환

		System.out.printf("호스트 이름 : %s %n", iaddr.getHostName());
		System.out.printf("호스트 ip 주소 : %s %n", iaddr.getHostAddress());

		iaddr = InetAddress.getByName("java.sun.com");
		System.out.printf("호스트 이름 : %s %n", iaddr.getHostName());
		System.out.printf("호스트 ip 주소 : %s %n", iaddr.getHostAddress());

		InetAddress sw[] = InetAddress.getAllByName("www.daum.net");
		for (InetAddress temp_sw : sw) {
			System.out.printf("호스트 이름 : %s %n", temp_sw.getHostName());
			System.out.printf("호스트 ip 주소 : %s %n", temp_sw.getHostAddress());
		}
	}

}

 

Echo 소켓 코드 구현
package 고급반;

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

public class EchoClient {
	private String ip;
	private int port;
	private String str;
	BufferedReader file;

	public EchoClient(String ip, int port) throws IOException {
		this.ip = ip;
		this.port = port;
		Socket tcpSocket = getSocket();
		OutputStream os_socket = tcpSocket.getOutputStream();
		InputStream is_socket = tcpSocket.getInputStream();

		BufferedReader bufferR = new BufferedReader(new InputStreamReader((is_socket)));
		BufferedWriter bufferW = new BufferedWriter(new OutputStreamWriter(os_socket));
		System.out.println("입력 : ");
		file = new BufferedReader(new InputStreamReader(System.in));
		str = file.readLine();
		str += System.getProperty("line.separator");
		bufferW.write(str);
		bufferW.flush();
		str = bufferR.readLine();
		System.out.println("Echo Reault : " + str);
		file.close();
		bufferW.close();
		bufferR.close();
		tcpSocket.close();
	}

	public Socket getSocket() {
		Socket tcpSocket = null;
		try {
			tcpSocket = new Socket(ip, port);
		} catch (IOException ioe) {
			ioe.printStackTrace();
			System.exit(0);
		}
		return tcpSocket;
	}

	public static void main(String[] args) throws IOException {
		new EchoClient("ip주소", 2000);
	}

}
package 고급반;

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

public class EchoServer {
	private BufferedReader bufferR;
	private BufferedWriter bufferW;
	private InputStream is;
	private OutputStream os;
	private ServerSocket serverS;

	public EchoServer(int port) {
		try {
			serverS = new ServerSocket(port);
		} catch (IOException ioe) {
			ioe.printStackTrace();
			System.exit(0);
		}
		while (true) {
			try {
				System.out.println("클라이언트의 요청을 기다리는 중");
				Socket tcpSocket = serverS.accept();
				System.out.println("클라이언트의 IP 주소: " + tcpSocket.getInetAddress().getHostAddress());
				is = tcpSocket.getInputStream();
				os = tcpSocket.getOutputStream();
				bufferR = new BufferedReader(new InputStreamReader(is));
				bufferW = new BufferedWriter(new OutputStreamWriter(os));
				String message = bufferR.readLine();
				System.out.println("수신메세지 : " + message);
				message += System.getProperty("line.separator");
				bufferW.write(message);
				bufferW.flush();
				bufferR.close();
				bufferW.close();
				tcpSocket.close();
			} catch (IOException ioe) {
				ioe.printStackTrace();
			}
		}

	}

	public static void main(String[] args) {
		new EchoServer(2000);
	}

}

 

소켓 코드 구현
package multi;

import java.net.Socket; // 네트워크 통신

class ClientExample4 {
	public static void main(String[] args) {
		// 명령행 인수의 개수를 나타낸다.
		if (args.length != 1) {
			// 최소 한명의 명령행 인수(사용자 이름)이 필요하다고 판단해서 그렇지 않으면 프로그램 종료
			System.out.println("Usage: java ClientExample4 <user-name>");
			return;
		}
		try {
			// 소켓을 생성하고 지정된 ip주소와 포트번호로 서버에 연결한다.
			Socket socket = new Socket("ip주소", 8000);

			// 메세지를 전송하는 스레드를 생성하고 시작한다. 클라이언트 소켓과 사용자 이름이 전달된다.
			Thread thread1 = new SenderThread(socket, args[0]);

			// 메시지를 수신하는 스레드를 시작한다. 이 스레드는 서버로부터 오는 메시지를 수신하는 역할을 한다.
			Thread thread2 = new ReceiverThread(socket);

			thread1.start(); // 메시지 전송 스레드 시작
			thread2.start(); // 메시지 수신 스레드 시작
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}
}
package multi;

import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

class PerClinetThread extends Thread {

	// List는 클라이언트 간에 공유되는 출력 스트림 ('PrintWriter') 목록을 관리한다.
	// Collections.synchronizedList를 사용하여 스레드 안전한 리스트로 만듭니다.
	static List<PrintWriter> list = Collections.synchronizedList(new ArrayList<PrintWriter>());

	Socket socket; // 클라이언트와의 통신을 위한 소켓을 저장하는 변수
	PrintWriter writer; // 클라이언트에게 메시지를 전송하는데 사용되는 PrintWriter 객체를 저장하는 변수

	PerClinetThread(Socket socket) { // 클라이언트 스레드를 생성할 때 호출되는 생성자
		this.socket = socket; // 클라이언트 소켓을 인자로 받아서 초기화
		try {
			// 소켓의 출력 스트림을 얻어서 writer에 저장
			writer = new PrintWriter(socket.getOutputStream());
			list.add(writer); // 리스트에 추가
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}

	// 스레드가 실행되는 메서드
	@Override
	public void run() {
		String name = null;
		try {
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

			name = reader.readLine(); // 이름을 읽어옵니다.
			sendAll("#" + name + "님이 들어오셨습니다."); // 모두에게 알림
			while (true) {
				String str = reader.readLine(); // reader을 사용하여 클라이언트 소켓으로부터 메시지를 읽어온다.
				if (str == null) // 소켓이 닫혔거나 끊어지면 루프를 종료
					break;
				sendAll(name + ">" + str); // 메시지를 모든 클라이언트에게 전송
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		} finally { // 스레드가 종료될때
			list.remove(writer); // 리스트에서 writer을 제거
			sendAll("#" + name + "님이 나가셨습니다."); // 모든 클라이언트에게 전송
			try {
				socket.close();
			} catch (Exception ignored) {
			}
		}
	}

	// 모두에게 알리는 메서드
	private void sendAll(String str) {
		for (PrintWriter writer : list) {
			writer.println(str); // writer을 이용하여 메시지를 전송
			writer.flush(); // 출력 스트림을 강제로 비운다.
		}
	}
}
package multi;

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

class ReceiverThread extends Thread {
	Socket socket; // 통신을 위한 소켓을 저장하는 변수

	ReceiverThread(Socket socket) { // 생성자
		this.socket = socket; // 소켓을 인자로 받아서 초기화
	}

	@Override
	public void run() { // 스레드가 실행되는 메서드
		try {
			// 클라이언트 소켓의 입력 스트림을 읽기 위한 bufferedReader을 생성한다.
			BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			while (true) {

				// 서버로부터 메시지를 계속해서 수신하고 출력한다.
				String str = reader.readLine(); // 클라이언트 소켓으로부터 한줄의 메시지를 읽어온다.
				if (str == null) // 읽을 메시지가 없다면 null을 반환하고 루프를 종료
					break;
				System.out.println(str); // 읽어온 메시지를 콘솔에 출력
			}
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}
}
package multi;

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

class SenderThread extends Thread {
	Socket socket; // 클라이언트와의 통신을 위한 소켓을 저장하는 변수
	String name; // 이름을 저장

	SenderThread(Socket socket, String name) { // 클라이언트 소켓과 클라이언트 이름을 인자로 받아 초기화
		this.socket = socket;
		this.name = name;
	}

	// 서버로 전송
	@Override
	public void run() {

		try {
			// 키보드 입력을 읽어온다.
			BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
			// 소켓의 출력 스트림을 이용하여 서버에 메시지를 전송하는 printWriter을 생성

			// 클라이언트 소켓의 출력 스트림을 이용하여 서버에 메시지를 전송하는 printWriter을 생성한다.
			PrintWriter writer = new PrintWriter(socket.getOutputStream());

			writer.println(name); // 이름을 서버로 전송한다.
			writer.flush();

			while (true) {
				String str = reader.readLine(); // 사용자가 엔터를 누를때 까지 입력을 대기
				if (str.equals("bye")) // bye를 누르면
					break; // 종료
				writer.println(str); // 입력받은 메시지를 printwriter을 사용하여 소켓을 통해 서버로 전송
				writer.flush(); // (즉시 서버로 전송)
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		} finally {
			try {
				socket.close(); // 소켓 닫음
			} catch (Exception ignored) {
			}
		}
	}
}
package multi;

import java.net.ServerSocket;
import java.net.Socket;

class ServerExample4 {
	public static void main(String[] args) {
		ServerSocket serverSocket = null;
		try {
			serverSocket = new ServerSocket(8000); // 서버 소켓 생성
			while (true) { // 계속해서 연결을 대기
				Socket socket = serverSocket.accept(); // 연결을 수락하고 클라이언트와 통신할 새로운 소켓 객체을 반환
				Thread thread = new PerClinetThread(socket); // 새로 연결된 클라이언트를 처리하기 위한 스레드를 생성
				thread.start(); // 새로운 클라이언트 스레드를 시작
			}
		} catch (Exception e) {
			System.out.println(e.getMessage());
		}
	}
}

블로그의 정보

감성 개발자 효기

효기’s

활동하기