[Java] 자바의 정석2 네트워킹 요약

yujeongkwon·2023년 11월 19일
0

CS / Network

목록 보기
27/27

🌐 네트워킹


1.3 InetAddress

  • 자바에서 IP 주소를 다루기 위한 클래스인 InetAddress 제공

1.4 URL(Uniform Resource Locator)

  • 자바에서 URL을 다루기 위한 클래스인 URL 클래스

1.5 URLConnection

  • URLConnection : 어플리케이션과 URL 간의 통신 연결을 나타내는 클래스의 최상위 클래스로 추상클래스
    • URLConnection을 상속받아 구현한 클래스들
      • HttpURLConnection : ex) URL 프로토콜이 http 프로토콜이라면 openConnection()은 HttpURLConnection을 반환
      • JarURLConnection
  • 해당 클래스를 사용하여 연결하고자하는 자원에 접근, 읽기, 쓰기 가능


📩 소켓 프로그래밍

  • 🍀 위 네트워크 블로그 정리 참조
  • TCP 관련 클래스
    • Socket
    • SecerSocket
  • UDP 관련 클래스
    • DatagramSocket
    • DatagramPacket
    • MulticastSocket

2.2 TCP 소켓 프로그래밍

  • 서버와 클라이언트간의 일대일 통신
  • 서버는 클라이언트의 연결 요청을 기다려야함
  • 통신 과정
      1. 서버 프로그램 : 서버 소켓(welcome socket)을 사용해서 서버 컴퓨터의 특정 포트에서 클라이언트의 연결요청 처리 준비
      1. 클라이언트 프로그램 : 접속할 서버의 (IP주소, 포트 정보)를 가지고 소켓을 생성 -> 서버에 연결 요청
      1. 서버 소켓 : 클라이언트의 연결 요청 수신 -> 서버에 새로운 소켓(connection socket)을 생성 -> 클라이언트의 소켓과 연결
      1. 이제 클라이언트의 소켓과 새로 생성된 서버의 소켓(connection socket)은 서버 소켓(welcome socket)과 관계없이 일대일 통신을 한다.
  • 여러개의 소켓(connection socket)은 하나의 포트를 공유해서 사용할 수 있음
  • but 서버 소켓(welcome socket)은 포트 하나 독점
    • 서버 소켓(welcome socket)이 여러개면 클라이언트가 어디로 연결해야할 지 모름
    • +) 두 서버 소켓(welcome socket)이 서로 다른 프로토콜을 사용하는 경우 같은 포트 사용 가능
    • ㄴ-> 프로토콜로 구분 가능하기 때문에 but 하나의 포트는 하나의 서버 소켓(welcome socket)이 사용하도록 하는 것이 바람직
  • 보통 포트 번호는 1023번 이상의 번호 주에서 사용하지 않는 포트 사용
  • 서버 소켓(welcome socket) : 소켓간의 연결만 처리
  • 소켓(connection socket) : 실제 데이터는 소켓들끼리 서로 주고받음
    • 두 개의 스트림을 가짐 : 입력스트림과 출력스트림
      • 데이터를 주고받는 연결통로 : 입출력스트림
    • 위 스트림들은 연결된 상대편 소켓의 스트림들과 교차연결
  • JAVA에서의 TCP 소켓 프로그래밍을 위한 클래스
    • Socket (connection socket)
      • 프로세스간의 통신 담당
      • InputStream과 OutputStream을 가짐
      • 이 두 스트림을 통해 프로세스간의 통신(입출력)이 이루어짐.
    • ServerSocket (welcome socket)
      • 포트와 연결(bind)되어 외부의 연결요청을 기다림
      • ㄴ-> 연결요청이 들어오면, Socket을 생성 -> 소켓과 소켓간의 통신을 이루어 지도록 함
      • 한 포트에 하나의 ServerSocket만 연결할 수 있다.
        (프로토콜이 다르면 같은 포트를 공유할 수 있다.)

예시 코드 : TcpIpServer.java, TcpIpClient.java

  • +) socket.getPort() : 상대편 소켓의 포트 가져오기
  • +) socket.getLocalPort() : 자신이 사용하는 포트 가져오기
    • 서버소켓이 7777번 포트를 사용하고 있어도, 서버소켓이 아닌 소켓은 7777번 포트를 사용할 수 있음
  • +) serverSocket.setSoTimeout() : 서버소켓의 대기시간 지정 가능

TcpIpServer.java

import java.net.*;
import java.io.*;
import java.util.Date;
import java.text.SimpleDateFormat;

public class TcpIpServer {
	public static void main(String args[]) {
		ServerSocket serverSocket = null;
		
		try {
			// 서버소켓을 생성하여 7777번 포트와 결합(bind)시킨다.
			serverSocket = new ServerSocket(7777);
			System.out.println(getTime()+"서버가 준비되었습니다.");

		} catch(IOException e) {
			e.printStackTrace();
		}
		
        //클라이언트 프로그램으 요청을 지속적으로 처리하기 위해 무한 반복
        //종료시키기 위해 ctrl + c 로 강제 종료 시키기
		while(true) {	
			try {
				System.out.println(getTime()+"연결요청을 기다립니다.");
				// 서버소켓은 클라이언트의 연결요청이 올 때까지 실행을 멈추고 계속 기다린다.
                // 클라이언트의 연결요청이 오면 클라이언트 소켓과 통신할 새로운 소켓을 생성한다.
				Socket socket = serverSocket.accept();
				System.out.println(getTime()+ socket.getInetAddress() + "로부터 연결요청이 들어왔습니다.");
				
				// 소켓의 출력스트림을 얻는다.
				OutputStream out = socket.getOutputStream();
				DataOutputStream dos = new DataOutputStream(out);

				// 원격 소켓(remote socket)에 데이터를 보낸다.
				dos.writeUTF("[Notice] Test Message1 from Server.");
				System.out.println(getTime()+"데이터를 전송했습니다.");

				// 스트림과 소켓을 닫아준다.
				dos.close();
				socket.close();
			} catch (IOException e) {
				e.printStackTrace();
			}
		} // while 다시 클라이언트 프로그램의 요청을 기다림
	} // main

	// 현재시간을 문자열로 반환하는 함수
	static String getTime() {
		SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss]");
		return f.format(new Date());
	}
} // class

TcpIpClient.java

import java.net.*;
import java.io.*;

public class TcpIpClient {
	public static void main(String args[]) {
		try {
			String serverIp = "127.0.0.1";

			System.out.println("서버에 연결중입니다. 서버IP :" + serverIp);
			// 소켓을 생성하여 연결을 요청한다.
            // ㄴ 소켓을 생성하면 자동으로 서버에 연결 요청함
			Socket socket = new Socket(serverIp, 7777); 

			// 소켓의 입력스트림을 얻는다.
			InputStream in = socket.getInputStream();
			DataInputStream dis = new DataInputStream(in);

			// 소켓으로 부터 받은 데이터를 출력한다.
			System.out.println("서버로부터 받은 메시지 :"+dis.readUTF());      
			System.out.println("연결을 종료합니다.");

			// 스트림과 소켓을 닫는다.
			dis.close();
			socket.close();
			System.out.println("연결이 종료되었습니다.");
		} catch(ConnectException ce) {
			ce.printStackTrace();
		} catch(IOException ie) {
			ie.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();  
		}  
	} // main
} // class

예시 코드 TcpIpServer4.java : 여러개의 쓰레드를 생성해 클라이언트의 요청 동시 처리

  • 서버에 접속하는 클라이언트의 수가 많을 때
    • 쓰레드를 이용 -> 클라이언트의 요청을 병렬적으로 처리
    • ㄴ 사용하지 않으면 요청순으로 서버가 처리하기 때문에 늦게온 요청은 오래 기다림
    • 위의 TcpIpClient.java 여러개 실행하여 결과를 확인할 수 있음.
import java.net.*;
import java.io.*;
import java.util.Date;
import java.text.SimpleDateFormat;

public class TcpIpServer4 implements Runnable {
	ServerSocket serverSocket;
	Thread[] threadArr;

	public static void main(String args[]) {
		// 5개의 쓰레드를 생성하는 서버를 생성한다.
		TcpIpServer4 server = new TcpIpServer4(5);
		server.start();
	} // main
	
	public TcpIpServer4(int num) {
		try {
			// 서버소켓을 생성하여 7777번 포트와 결합(bind)시킨다.
			serverSocket = new ServerSocket(7777);
			System.out.println(getTime()+"서버가 준비되었습니다.");

			threadArr = new Thread[num];
		} catch(IOException e) {
			e.printStackTrace();
		}
	}

	public void start() {
		for(int i=0; i < threadArr.length; i++) {
			threadArr[i] = new Thread(this);
			threadArr[i].start();
		}
	}

	public void run() {
		while(true) {
			try {
				System.out.println(getTime()+ "가 연결요청을 기다립니다.");

				Socket socket = serverSocket.accept();
				System.out.println(getTime()+ socket.getInetAddress() + "로부터 연결요청이 들어왔습니다.");

				// 소켓의 출력스트림을 얻는다.
				OutputStream out = socket.getOutputStream();
				DataOutputStream dos = new DataOutputStream(out);

				// 원격 소켓(remote socket)에 데이터를 보낸다.
				dos.writeUTF("[Notice] Test Message1 from Server.");
				System.out.println(getTime()+"데이터를 전송했습니다.");

				// 스트림과 소켓을 닫아준다.
				dos.close();
				socket.close();
		    } catch (IOException e) {
				e.printStackTrace();
			}
		} // while	
	} // run

	// 현재시간을 문자열로 반환하는 함수
	static String getTime() {
		String name = Thread.currentThread().getName();
		SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss]");

		return f.format(new Date()) + name ;
	}
} // class

예시 코드 TcpIpServer5.java, TcpIpClient5.java : 1:1 채팅

  • 소켓으로 데이터를 송수신 하는 작업을 별도의 쓰레드 Sender와 Receiver로 처리
  • 송/수신이 동시에 가능
  • 아래와 같이 1:1 채팅이 가능

TcpIpServer5.java

import java.net.*;
import java.io.*;
import java.util.Scanner;

public class TcpIpServer5 {
	public static void main(String args[]) {
		ServerSocket serverSocket = null;
		Socket socket = null;

		try {
			// 서버소켓을 생성하여 7777번 포트와 결합(bind)시킨다.
			serverSocket = new ServerSocket(7777);
			System.out.println("서버가 준비되었습니다.");

			socket = serverSocket.accept();

			Sender   sender   = new Sender(socket);
			Receiver receiver = new Receiver(socket);

			sender.start();
			receiver.start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	} // main
} // class

class Sender extends Thread {
	Socket socket;
	DataOutputStream out;
	String name;

	Sender(Socket socket) {
		this.socket = socket;
		try {
			out = new DataOutputStream(socket.getOutputStream());
			name = "["+socket.getInetAddress()+":"+socket.getPort()+"]";
		} catch(Exception e) {}
	}

	public void run() {
		Scanner scanner = new Scanner(System.in);
		while(out!=null) {
			try {
				out.writeUTF(name+scanner.nextLine());		
			} catch(IOException e) {}
		}
	} // run()
}

class Receiver extends Thread {
	Socket socket;
	DataInputStream in;

	Receiver(Socket socket) {
		this.socket = socket;
		try {
			in = new DataInputStream(socket.getInputStream());
		} catch(IOException e) {}

	}

	public void run() {
		while(in!=null) {
			try {
				System.out.println(in.readUTF());
			} catch(IOException e) {}
		}
	} // run
}

TcpIpClient5.java

import java.net.*;
import java.io.*;

public class TcpIpClient5 {
	public static void main(String args[]) {
		try {
			String serverIp = "127.0.0.1";
            
			// 소켓을 생성하여 연결을 요청한다.
			Socket socket = new Socket(serverIp, 7777); 

			System.out.println("서버에 연결되었습니다.");
			Sender sender = new Sender(socket);
			Receiver receiver = new Receiver(socket);

			sender.start();
			receiver.start();
		} catch(ConnectException ce) {
			ce.printStackTrace();
		} catch(IOException ie) {  
			ie.printStackTrace();
		} catch(Exception e) {
			e.printStackTrace();  
		}  
	} // main
} // class

예시 코드 TcpIpMultichatServer.java, TcpIpMultichatClient.java: 멀티채팅 서버 프로그램

  • 위 1:1 채팅 발전시킨 버전
  • 여러 클라이언트가 서버에 접속해서 채팅 가능

TcpIpMultichatServer.java

import java.net.*;
import java.io.*;
import java.util.*;

public class TcpIpMultichatServer {
	HashMap clients;
	
	TcpIpMultichatServer() {
    	/**
    	-  클라이언트가 멀티채팅서버에 접속하면 HashMap에 저장
        - 접속을 해제하면 HashMap에서 제거
        - 서버에 접속한 클라이언트(TcpIpMultichatClient.java)를 HashMap에 저장해서 관리
        - 클라이언트가 데이터를 입력하면, 
          멀티채팅서버는 HashMap에 저장된 모든 클라이언트에게 데이터를 전송
        **/
		clients = new HashMap();
		Collections.synchronizedMap(clients);	// 동기화 처리
	}

	public void start() {
		ServerSocket serverSocket = null;
		Socket socket = null;

		try {
			serverSocket = new ServerSocket(7777);
			System.out.println("서버가 시작되었습니다.");

			while(true) {
				socket = serverSocket.accept();
				System.out.println("["+socket.getInetAddress()+":"
                				+socket.getPort()+"]"+"에서 접속하였습니다.");
				ServerReceiver thread = new ServerReceiver(socket);
				thread.start();
			}
		} catch(Exception e) {
			e.printStackTrace();
		}
	} // start()

	void sendToAll(String msg) {
    	/** 멀티채팅서버의 ServerReceiver 쓰레드는 
        클라이언트가 추가될 때마다 생성되며 
        클라이언트의 입력을 서버에 접속된 모든 클라이언트에게 전송 **/
		Iterator it = clients.keySet().iterator();
		
		while(it.hasNext()) {
			try {
				DataOutputStream out = (DataOutputStream)clients.get(it.next());
				out.writeUTF(msg);
			} catch(IOException e){}
		} // while
	} // sendToAll

	public static void main(String args[]) {
		new TcpIpMultichatServer().start();
	} 
	class ServerReceiver extends Thread {
		Socket socket;
		DataInputStream in;
		DataOutputStream out;

		ServerReceiver(Socket socket) {
			this.socket = socket;
			try {
				in  = new DataInputStream(socket.getInputStream());
				out = new DataOutputStream(socket.getOutputStream());
			} catch(IOException e) {}
		}

		public void run() {
			String name = "";
			try {
				name = in.readUTF();
				sendToAll("#"+name+"님이 들어오셨습니다.");
				
                /**
                HashMap clients : (key : 클라이언트 이름, value : 출력 스트림)
                clients는 다른 클라이언트가 입력한 데이터를 전송하는데 사용
                **/
				clients.put(name, out);
				System.out.println("현재 서버접속자 수는 "+ clients.size()+"입니다.");
				
                /**
                클라이언트가 종료되어 클라이언트의 입력스트림(in)이 null이 되면
                while문을 빠져나가서 clients의 목록에서 해당 클라이언트를 제거
                **/
				while(in!=null) {
					sendToAll(in.readUTF());
				}
			} catch(IOException e) {
				// ignore
			} finally {
				sendToAll("#"+name+"님이 나가셨습니다.");
				clients.remove(name);
				System.out.println("["+socket.getInetAddress() +":"+socket.getPort()+"]"+"에서 접속을 종료하였습니다.");
				System.out.println("현재 서버접속자 수는 "+ clients.size()+"입니다.");
			} // try
		} // run
	} // ReceiverThread
} // class

TcpIpMultichatClient.java

import java.net.*;
import java.io.*;
import java.util.Scanner;

public class TcpIpMultichatClient {
	public static void main(String args[]) {
		if(args.length!=1) {
			System.out.println("USAGE: java TcpIpMultichatClient 대화명");
			System.exit(0);
		}

		try {
			String serverIp = "127.0.0.1";
            // 소켓을 생성하여 연결을 요청한다.
			Socket socket = new Socket(serverIp, 7777); 
			System.out.println("서버에 연결되었습니다.");
			Thread sender   = new Thread(new ClientSender(socket, args[0]));
			Thread receiver = new Thread(new ClientReceiver(socket));

			sender.start();
			receiver.start();
		} catch(ConnectException ce) {
			ce.printStackTrace();
		} catch(Exception e) {}
	} // main

	static class ClientSender extends Thread {
		Socket socket;
		DataOutputStream out;
		String name;

		ClientSender(Socket socket, String name) {
			this.socket = socket;
			try {
				out = new DataOutputStream(socket.getOutputStream());
				this.name = name;
			} catch(Exception e) {}
		}

		public void run() {
			Scanner scanner = new Scanner(System.in);
			try {
				if(out!=null) {
					out.writeUTF(name);
				}	

				while(out!=null) {
					out.writeUTF("["+name+"]"+scanner.nextLine());					}
			} catch(IOException e) {}
		} // run()
	} // ClientSender

	static class ClientReceiver extends Thread {
		Socket socket;
		DataInputStream in;

		ClientReceiver(Socket socket) {
			this.socket = socket;
			try {
				in = new DataInputStream(socket.getInputStream());
			} catch(IOException e) {}
		}

		public void run() {
			while(in!=null) {
				try {
					System.out.println(in.readUTF());
				} catch(IOException e) {}
			}
		} // run
	} // ClientReceiver
} // class

2.3 UDP 소켓 프로그래밍

  • UDP = DatagramSocket과 DatagramPacket 사용
  • ㄴ <-> TCP = Socket과 SercerSocket 사용
    • ㄴ UDP 는 연결 지향이 아니기 때문에 Socket 필요x
    • 데이터를 DatagramPacket에 담아서 전송
  • DatagramPacket
    • 헤더 : DatagramPacket을 수신할 호스트의 정보 (주소와 포트) 저장
    • 데이터 : 전송할 데이터
  • DatagramPacket을 전송하면 DatagramPacket에 지정된 주소(호스느의 포트)의 DatagramSocket과에 도착

예시 코드 UdpClient.java, UdpServer.java

UdpClient

import java.net.*;
import java.io.*;

public class UdpClient {
	public void start() throws IOException, UnknownHostException {
		DatagramSocket datagramSocket = new DatagramSocket();
		InetAddress    serverAddress  = InetAddress.getByName("127.0.0.1");

		// 데이터가 저장될 공간으로 byte배열을 생성한다.
		byte[] msg = new byte[100];

		DatagramPacket outPacket = new DatagramPacket(msg, 1, serverAddress, 7777);
		DatagramPacket inPacket  = new DatagramPacket(msg, msg.length);

		datagramSocket.send(outPacket);   // DatagramPacket을 전송한다.
		datagramSocket.receive(inPacket); // DatagramPacket을 수신한다.

		System.out.println("current server time :" + new String(inPacket.getData()));

		datagramSocket.close();
	} // start()

	public static void main(String args[]) {
		try {
			new UdpClient().start();
		} catch(Exception e) {
			e.printStackTrace();
		}
	} // main 
}

UdpServer.java

import java.net.*;
import java.io.*;
import java.util.Date;
import java.text.SimpleDateFormat;

public class UdpServer {
	public void start() throws IOException {
		// 포트 7777번을 사용하는 소켓을 생성한다.
		DatagramSocket socket = new DatagramSocket(7777);
		DatagramPacket inPacket, outPacket;

		byte[] inMsg = new byte[10];
		byte[] outMsg;

		while(true) {
			// 데이터를 수신하기 위한 패킷을 생성한다.
			inPacket = new DatagramPacket(inMsg, inMsg.length);

			// 패킷을 통해 데이터를 수신(receive)한다.
			socket.receive(inPacket);

			// 수신한 패킷으로 부터 client의 IP주소와 Port를 얻는다.
			InetAddress address = inPacket.getAddress();
			int port = inPacket.getPort();

			// 서버의 현재 시간을 시분초 형태([hh:mm:ss])로 반환한다.			
			SimpleDateFormat sdf = new SimpleDateFormat("[hh:mm:ss]");
			String time = sdf.format(new Date());
			outMsg = time.getBytes(); // time을 byte배열로 변환한다.

			// 패킷을 생성해서 client에게 전송(send)한다.
			outPacket = new DatagramPacket(outMsg, outMsg.length, address, port);
			socket.send(outPacket);
		}
	} // start()

	public static void main(String args[]) {
		try {
			new UdpServer().start(); // UDP서버를 실행시킨다.
		} catch (IOException e) {
			e.printStackTrace();
		}
	} // main
}
profile
인생 살자.

0개의 댓글

관련 채용 정보