Java 웹 개발

21.09.23 - 웹 개발 입문 29일차

개발이란 2021. 9. 23. 22:44

네트워크 - 서버 클라이언트 일대일 채팅 구현

package api.net.tcp09;

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.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;

import javax.swing.JOptionPane;

public class ChatServer3 {
	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(30000);

		try (Socket socket = server.accept();) {

			// (수신) 클라이언트에서 보내는 메세지를 수신 - main
			// (발신) 클라이언트로 메세지를 전송 - thread
			// = 상대방의 접속이 종료된 경우 발신하려고 하면 SocketException이 발생한다.

			// 스레드 생성 및 구동 코드
			Runnable r = () -> {
				// 발신 작업(무한반복) - /exit라고 입력하면 중지
				// 1. 스트림 생성
				// 2. 사용자 입력 및 네트워크 전송(출력) - 반복

				try {
					OutputStream out = socket.getOutputStream();
					OutputStreamWriter converter = new OutputStreamWriter(out);
					BufferedWriter buffer = new BufferedWriter(converter);
					PrintWriter printer = new PrintWriter(buffer);

					while (true) {
						String input = JOptionPane.showInputDialog("서버 메세지 입력");
						if (input != null) {// 입력값이 있다면 전송하도록 조건을 설정
							printer.println(input);
							printer.flush();
						}
					}
				} catch (Exception e) {
					// e.printStackTrace();
				}
			};
			Thread t = new Thread(r);
			t.setDaemon(true);
			t.start();

			// 수신 작업(무한반복) - /exit라는 메세지가 수신되면 중지
			// 1. 스트림 생성
			// 2. 메세지 수신 후 출력 - 반복
			InputStream in = socket.getInputStream();
			InputStreamReader converter = new InputStreamReader(in);
			BufferedReader buffer = new BufferedReader(converter);

			while (true) {
				String line = buffer.readLine();
				if (line == null || line.trim().equals("/exit")) {// 종료명령이 전송되었다면 수신 중지
					System.out.println("수신 중지");
					System.exit(0);
				}
				System.out.println("수신 : " + line);
			}
		} catch (Exception e) {
			// e.printStackTrace();
			// 접속이 끊어지면 SocketException 발생
			System.exit(0);
		}

		server.close();
		System.out.println("서버 종료");
	}
}

 

package api.net.tcp09;

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.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.JOptionPane;

public class ChatClient3 {
	public static void main(String[] args) throws UnknownHostException, IOException {
		try (Socket socket = new Socket("localhost", 30000);
		) {

			// (수신) 서버에서 보내는 메세지를 수신 후 출력 - thread
			// (발신) 사용자가 입력한 메세지를 서버로 전송 - main
			// = 상대방의 접속이 종료된 경우 발신하려고 하면 SocketException이 발생한다.

			// 스레드 생성 및 구동 코드
			Runnable r = () -> {
				// 수신 작업(무한반복) - /exit라는 메세지가 수신되면 중지
				// 1. 스트림 생성
				// 2. 메세지 수신 후 출력 - 반복
				try {
					InputStream in = socket.getInputStream();
					InputStreamReader converter = new InputStreamReader(in);
					BufferedReader buffer = new BufferedReader(converter);

					while (true) {
						String line = buffer.readLine();
						if (line == null || line.trim().equals("/exit")) {// 종료명령이 전송되었다면 수신 중지
							socket.close();
							System.exit(0);
						}
						System.out.println("수신 : " + line);
					}
				} catch (Exception e) {
					// e.printStackTrace();
				}
			};
			Thread t = new Thread(r);
			t.setDaemon(true);
			t.start();

			// 발신 작업(무한반복) - /exit라고 입력하면 중지
			// 1. 스트림 생성
			// 2. 사용자 입력 및 네트워크 전송(출력) - 반복

			OutputStream out = socket.getOutputStream();
			OutputStreamWriter converter = new OutputStreamWriter(out);
			BufferedWriter buffer = new BufferedWriter(converter);
			PrintWriter printer = new PrintWriter(buffer);

			while (true) {
				String input = JOptionPane.showInputDialog("클라이언트 메세지 입력");
				if (input != null) {// 입력값이 있다면 전송하도록 조건을 설정
					printer.println(input);
					printer.flush();
				}

			}
		} catch (Exception e) {
			// e.printStackTrace();
			// 접속이 끊어지면 SocketException 발생
			System.exit(0);
		}
	}
}

 

 

 

네트워크 - 멀티소켓 서버

 

전체에게 메세지를 보내려면 전체 사용자 정보를 알아야 한다.
= 전체 사용자 정보는 Set<TcpUser> 형태로 main에 있다.
= main에 있는 저장소를 이곳에서 사용할 수 없다.
= 저장소를 이곳으로 옮겨야 한다.
= 하지만 객체에 저장하면 안되므로 탈객체(static) 처리를 한다.
= 이 저장소와 연관된 기능은 모두다 static으로 만들어야 한다.
= 입장(enter), 퇴장(leave), 전체전송(broadcast)와 같은 기능을 만든다.

package api.net.tcp10;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashSet;
import java.util.Set;

public class MultiSocketServer3 {

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

		try (ServerSocket server = new ServerSocket(30000);) {
			while (true) {
				Socket socket = server.accept();

				TcpUser u = new TcpUser(socket);
				TcpUser.enter(u);// 입장 메소드 호출
			}
		} catch (Exception e) {
			// e.printStackTrace();
			// 연결 제거
		}
	}

}
package api.net.tcp10;

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;
import java.util.HashSet;
import java.util.Set;

//서버에서 사용자 연결(Socket)을 처리하기 위한 보조 장치들까지 포함된 클래스
public class TcpUser {

	private static Set<TcpUser> user = new HashSet<>();

	public static void enter(TcpUser u) {
		user.add(u);
		System.out.println("[사용자 접속] 현재 사용자 " + user.size() + "명 " + u.socket);
	}

	public static void leave(TcpUser u) {
		user.remove(u);
		System.out.println("[사용자 종료] 현재 사용자 " + user.size() + "명 " + u.socket);
	}

	public static void broadcast(String message) {
		for (TcpUser u : user) {
			u.send(message);
		}
	}

	private Socket socket;// 연결 정보
	private PrintWriter writer;// 출력 도구
	private BufferedReader reader;// 입력 도구

	public TcpUser(Socket socket) {
		try {
			this.socket = socket;

			// 이 사용자가 이용할 스트림들을 생성(발생하는 예외는 main이 알 수 있도록 전가 -> 연결 제거)
			this.writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())));
			this.reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

			// 스레드 생성 및 작업지시(수신 작업)
			Runnable r2 = () -> {
				try {
					receive();
				} catch (IOException e) {
					// 수신 오류가 발생했다는 것은 연결에 문제가 생겼다는 의미
					// = 저장소에서 등록된 연결정보를 제거
					TcpUser.leave(this);
				}
			};
			Thread t2 = new Thread(r2);
			t2.setDaemon(true);
			t2.start();
		} catch (Exception e) {
			// 예외가 발생했다는 것은 연결에 문제가 생겼다는 의미
			// = 저장소에서 등록된 연결정보를 제거
			TcpUser.leave(this);
		}
	}

	// 보내는 메소드 - 메세지를 수신하면 1회 실행되도록 개조
	public void send(String line) {
		writer.println(line);
		writer.flush();
	}

	// 받는 메소드
	public void receive() throws IOException {
		while (true) {
			String line = reader.readLine();
			if (line == null)
				break;
			// send(line);//보낸 사람에게 회신(Echo)
			TcpUser.broadcast(line);// 전체에게 전송(broadcast)
			System.out.println("수신 : " + line + "(" + socket + ")");
		}
	}

}

 

 

 

네트워크 - UDP 전송 예제

UDP 통신
= 비연결형 프로토콜(통신방식)
= 서버와 클라이언트의 개념이 없다.
= 보내는 주체(Sender)와 받는 주체(Receiver)만이 존재한다.
= 크기 제한이 존재한다(크기는 정하기 나름).

사용되는 클래스 : DatagramSocket, DatagramPacket

package api.net.udp01;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;

public class Receiver {
	public static void main(String[] args) throws IOException {

		//수신 도구 생성 - 포트를 반드시 지정해줘야 한다.
		DatagramSocket ds = new DatagramSocket(30000);

		//수신하기 위한 저장공간 생성
		//= 사용자가 얼마만큼의 데이터를 보낼지는 모르지만 크기를 미리 가정해서 생성

		byte[] data = new byte[80];//80 byte 저장소
		//DatagramPacket dp = new DatagramPacket(받을데이터공간, 받을크기);
		DatagramPacket dp = new DatagramPacket(data, data.length);
		System.out.println("수신 준비 완료");

		//수신
		ds.receive(dp);
		System.out.println("수신 완료");

		//확인
		System.out.println(Arrays.toString(data));//있는 그대로의 배열을 출력

		//데이터를 복원하기 위해서는 반드시 읽은 크기를 알아야 한다.
		int size = dp.getLength();
		System.out.println("size = " + size);

		//정리
		ds.close();
		System.out.println("정리 완료");
	}
}

 

UDP 수신 코드 작성 시 주의사항
= 사용자가 전송하는 양과 관계 없이 수신할 단위 크기를 지정해야 한다.

package api.net.udp01;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;

public class Receiver {
	public static void main(String[] args) throws IOException {

		// 수신 도구 생성 - 포트를 반드시 지정해줘야 한다.
		DatagramSocket ds = new DatagramSocket(30000);

		// 수신하기 위한 저장공간 생성
		// = 사용자가 얼마만큼의 데이터를 보낼지는 모르지만 크기를 미리 가정해서 생성

		byte[] data = new byte[80];// 80 byte 저장소
		// DatagramPacket dp = new DataFamPacket(data, data.length);
		System.out.println("수신 준비 완료");

		// 수신
		ds.receive(dp);
		System.out.println("수신 완료");

		// 확인
		System.out.println(Arrays.toString(data));// 있는 그대로의 배열을 출력

		// 데이터를 복원하기 위해서는 반드시 읽은 크기를 알아야 한다.
		int size = dp.getLength();
		System.out.println("size = " + size);

		// 정리
		ds.close();
		System.out.println("정리 완료");
	}
}

 

 

 

네트워크 - UDP 대용량 전송 예제

UDP 통신에서 대용량 데이터를 보낼 경우 처리방법
= 수신측에 설정된 크기만큼 잘라서 보낸다
= 수신측에서 설정된 크기보다 큰 데이터가 전송되면 버려진다

package api.net.udp01;

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 Sender3 {
	public static void main(String[] args) throws IOException {

		// 사용되는 클래스 : DatagramSocket, DatagramPacket

		// 보내는 쪽에서는 전송을 위한 도구와 데이터를 준비
		DatagramSocket ds = new DatagramSocket();

		// 데이터를 준비해서 ds를 이용하여 전송
		String text = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";// 124byte

		// 준비한 데이터를 byte로 변환
		byte[] data = text.getBytes();

		// DatagramPacket dp = new DatagramPacket(보내는데이터, 보내는크기, 주소객체, 포트번호);
		// DatagramPacket dp = new DatagramPacket(data, data.length, "localhost",
		// 30000);//error

		InetAddress address = InetAddress.getByName("localhost");// 127.0.0.1
		System.out.println(address);

		// 전송할 데이터의 시작위치와 전송개수를 관리하기 위한 변수
		int start = 0;
		int block = 80;

		while (start < data.length) {// 시작지점이 배열크기보다 작으면
			// 남은개수 = 전체크기 - 시작지점 = data.length - start
			int remain = data.length - start;
			System.out.println("남은 데이터 : " + remain + "개");
			// 전송개수 = 남은개수와 블록크기(80) 중에서 작은것
			int size = Math.min(block, remain);
			System.out.println("전송개수 : " + size + "개");

			// 전송할 만큼의 공간을 생성한 뒤 data에서 해당 부분만큼 copy를 수행
			byte[] buffer = new byte[size];
			// System.arraycopy(어느배열에서, 어느지점부터, 어느배열로, 어느지점부터, 몇개);
			System.arraycopy(data, start, buffer, 0, size);

			DatagramPacket dp = new DatagramPacket(buffer, size, address, 30000);

			// 전송 코드
			ds.send(dp);
			System.out.println("전송 완료");

			// 시작지점을 block에 설정된 크기만큼 증가 처리
			start += block;
		}

		// 정리 코드
		ds.close();
		System.out.println("사용 종료");
	}
}

 

 

package api.net.udp01;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import java.util.Arrays;

public class Receiver3 {
	public static void main(String[] args) throws IOException {
		// UDP 수신 코드 작성 시 주의사항
		// = 사용자가 전송하는 양과 관계 없이 수신할 단위 크기를 지정해야 한다.

		// 수신 도구 생성 - 포트를 반드시 지정해줘야 한다.
		DatagramSocket ds = new DatagramSocket(30000);

		// 수신하기 위한 저장공간 생성
		// = 사용자가 얼마만큼의 데이터를 보낼지는 모르지만 크기를 미리 가정해서 생성

		byte[] data = new byte[80];// 80 byte 저장소

		StringBuilder builder = new StringBuilder();

		while (true) {
			// DatagramPacket dp = new DatagramPacket(받을데이터공간, 받을크기);
			DatagramPacket dp = new DatagramPacket(data, data.length);
			System.out.println("수신 준비 완료");

			// 수신
			ds.receive(dp);
			System.out.println("수신 완료");

			// 확인
			int size = dp.getLength();
			System.out.println("size = " + size);
			System.out.println(Arrays.toString(data));

			// 수신한 데이터를 문자열로 복원
			// = 준비한 공간보다 큰 데이터를 받아야 하는 경우 반복적으로 수신을 실행
			String text = new String(data, 0, size);
			builder.append(text);

			System.out.println(builder);
		}

		// 정리
		// ds.close();
		// System.out.println("정리 완료");
	}
}

 

 

 

Q. MessageSender`와 `MessageReceiver`라는 클래스를 만들어서 다음 기능을 구현
- `MessageSender`에서는 사용자가 입력한 내용을 `MessageReceiver`로 UDP 통신을 이용하여 전달
- 입력 가능한 최대 크기는 100byte로 제한하고, 그 이상을 입력하면 에러메세지를 출력
- 메세지가 전송되면 `전송 완료`라는 문구를 출력
- `MessageReceiver`에서는 `MessageSender`에서 전송된 내용을 받아서 복원하여 출력
- 수신 블록 크기는 100byte로 제한한다
- 포트는 `23456`번을 사용

 

package api.net.udp02;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.util.Scanner;

public class MessageSender4 {
	public static void main(String[] args) {

		//필요한 도구 준비
		try(
			Scanner sc = new Scanner(System.in);//표준 입력(키보드) 도구
			DatagramSocket ds = new DatagramSocket();//UDP 전송 도구
		){
			while(true) {
				//사용자 입력
				System.out.print("입력 : ");
				String line = sc.nextLine();

				//바이트 변환
				byte[] data = line.getBytes(/* 인코딩방식 */);

				//100byte가 넘는지 검사
				final int LIMIT = 100;
				if(data.length > LIMIT) {
					System.out.println("100byte를 초과하는 데이터는 전송할 수 없습니다.");
					continue;//재입력을 원할 경우
					//break;//반복 종료를 원할 경우
					//return;//main 종료를 원할 경우
					//System.exit(-1);//프로그램 종료를 원할 경우

					//throw new RuntimeException();//RuntimeException은 "실행중 발생한"이라는 의미의 예외
				}

				//비어있는 문자열 검사
				//if(line.equals("")) {
				//if(line.length() == 0) {
				//if(line.isEmpty()) {
				if(data.length == 0) {
					System.out.println("비어있는 문자열은 전송할 수 없습니다");
					continue;
				}

				//전송을 위한 그릇(DatagramPacket) 준비
				InetAddress address = InetAddress.getByName("localhost");//주소 검사 및 분석 객체
				DatagramPacket dp = new DatagramPacket(data, data.length, address, 30000);

				//전송
				ds.send(dp); 
				System.out.println("전송 완료");
			}
		}
		catch(Exception e) {
			e.printStackTrace();//예외 추적 결과 출력
		}

	}
}

 

package api.net.udp02;

import java.net.DatagramPacket;
import java.net.DatagramSocket;

public class MessageReceiver4 {
	public static void main(String[] args) {

		// 수신 도구 생성
		try (DatagramSocket ds = new DatagramSocket(23456);) {

			// 수신 공간 준비
			byte[] data = new byte[100];// 100byte

			while (true) {
				DatagramPacket dp = new DatagramPacket(data, data.length);

				// 수신
				ds.receive(dp);

				// 복원
				String text = new String(dp.getData(), 0, dp.getLength());
				System.out.println("text = " + text);
			}

		} catch (Exception e) {
			e.printStackTrace();
		}

	}
}

입력 : 가나다라
전송 완료
입력 : 
비어있는 문자열은 전송할 수 없습니다
입력 : ㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱㄱ
100byte를 초과하는 데이터는 전송할 수 없습니다.
입력 : 

 

 

 

 

네트워크 - UDP 멀티캐스트 예제

멀티캐스트(Multicast)
= 특정 채널에 가입하여 채널 단위의 메세지 전송을 하는 방식
= 특정 채널에 대한 조건이 존재한다.
= 멀티캐스트용 IP 중에서 원하는 것을 골라서 만든다.
= 범위 : (224.0.0.0 ~ 239.255.255.255)

 

package api.net.udp03;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;
import java.net.UnknownHostException;

public class MulticastSender {
	public static void main(String[] args) throws IOException {
		// 채널 정보 설정
		String channel = "224.0.0.0";
		int port = 30000;

		// 주소 객체 생성(분석 및 오류 검사)
		InetAddress address = InetAddress.getByName(channel);

		// Multicast에서는 Unicast와 다른 클래스를 사용한다
		// = MulticastSocket

		// DatagramSocket ds = new DatagramSocket();//Unicast
		MulticastSocket ms = new MulticastSocket();// Multicast
		System.out.println("멀티캐스트 도구 생성");

		// 채널 참여
		ms.joinGroup(address);
		System.out.println("채널 참여 : " + address);

		// 데이터 준비 및 전송
		String text = "Hello Multicast!";
		byte[] data = text.getBytes();
		DatagramPacket dp = new DatagramPacket(data, data.length, address, port);

		ms.send(dp);
		System.out.println("채널 메세지 전송 완료");

		// 정리
		ms.close();
		System.out.println("사용 종료");
	}
}

 

멀티캐스트 수신자

package api.net.udp03;

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.InetAddress;
import java.net.MulticastSocket;

public class MulticastReceiver {
	public static void main(String[] args) throws IOException {
		//정보 생성
		String channel = "224.0.0.0";
		int port = 30000;
		InetAddress address = InetAddress.getByName(channel);

		//도구 생성
		//DatagramSocket ds = new DatagramSocket(port);//unicast
		MulticastSocket ms = new MulticastSocket(port);//multicast

		//채널 참여
		ms.joinGroup(address);

		//데이터 수신 준비 및 수신
		byte[] data = new byte[100];
		DatagramPacket dp = new DatagramPacket(data, data.length);

		ms.receive(dp);

		//복원 및 출력
		String text = new String(dp.getData(), 0, dp.getLength());
		System.out.println("text = " + text);

		//정리
		ms.close();
	}
}

text = Hello Multicast!

 

멀티캐스트 도구 생성
채널 참여 : /224.0.0.0
채널 메세지 전송 완료
사용 종료