Notice
Recent Posts
Recent Comments
«   2025/02   »
1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28
Archives
Today
Total
관리 메뉴

SYDev

[이것이 자바다] Chapter 19. 네트워크 입출력 본문

Programming Lang/Java

[이것이 자바다] Chapter 19. 네트워크 입출력

시데브 2025. 2. 7. 21:51

1. 네트워크 기초

  • Network: 여러 컴퓨터들을 통신 회선으로 연결한 것
  • LAN(Local Area Network): 가정, 회사, 건물, 특정 영역에 존재하는 컴퓨터를 연결한 것
  • WAN(Wide Area Network): LAN을 연결한 것 -> 흔히 말하는 Internet

1.1. 서버와 클라이언트

  • server: 서비스를 제공하는 프로그램
  • client: 서비스를 요청하는 프로그램

1.2. IP 주소

  • IP(Internet Protocol): 네트워크 어댑터(LAN 카드)마다 할당되는 인터넷 환경의 고유한 주소
  • DNS(Domain Name System): 도메인 이름으로, IP를 등록하는 저장소

 

1.3. Port 번호

  • 한 대의 컴퓨터에는 다양한 서버 프로그램들이 실행
  • IP 주소를 통해 컴퓨터에 접근 후, port 번호를 통해 특정 서버에 접근

  • 프로그램에서 사용할 수 있는 전체 Port 번호의 범위는 0 ~ 65535, 사용 목적에 따라 세 가지 범위를 가짐

 

2. IP 주소 얻기

  • Java는 IP 주소를 java.net 패키지의 InetAddress로 표현
  • InetAddress를 이용하면, 로컬 컴퓨터의 IP 주소를 얻을 수 있고, 도메인 이름으로 DNS에서 검색한후 IP 주소를 가져올 수도 있음
// 로컬 컴퓨터의 InetAddress
InetAddress ia = InetAddress.getLocalHost();	

// 컴퓨터의 도메인 이름을 알고 있는 경우
InetAddress ia = InetAddress.getByName(String domainName);
InetAddress[ ] iaArr = InetAddress.getAllByName(String domainName);

// InetAddress 객체로부터 IP 주소를 얻는 방법
String ip = InetAddress.getHostAddress();

 

3. TCP 네트워킹

  • 전송용 프로토콜: IP 주소로 프로그램들이 통신할 때, 사전에 약속된 데이터 전송 규약
  • 인터넷 전송용 프로토콜 -> TPC(Transmission Control Protocol)과 UDP(User Datagram Protocol)
    • TCP: 연결형 프로토콜
      • 상대방이 연결된 상태에서 데이터 송수신
      • 클라이언트가 연결 요청 -> 서버가 연결을 수락하면 통신 회선 고정 -> 고정 회선을 통해 데이터 전달
      • 고정 회선을 이용하기 때문에, TPC는 보낸 데이터가 순서대로 전달되며 손실 발생 X
      • 웹 브라우저가 웹 서버에 연결할 때, 이메일 전송, 파일 전송, DB 연동 등에 사용
  • Java는 TCP 네트워킹을 위해 java.net 패키지에서 ServerSocker과 Socket 클래스 제공
    • ServerSocket: 클라이언트의 연결을 수락하는 서버 쪽 클래스
    • Socket: 클라이언트에서 연결 요청할 때와 클라이언트 서버 양쪽에서 데이터를 주고 받을 때 사용되는 클래스 

3.1. TCP 서버

  • TCP 서버 프로그램 개발을 위해서는 우선 ServerSocket 객체 생성
  • 기본 생성자로 객체를 생성하고, bind() method를 통해 Port 바인딩 가능
ServerSocker serverSocket = new ServerSocket(50001);

// 기본 생성자
ServerSocket serverSocket = new ServerSocket();
serverSocker.bind(new InetSocketAddress(50001));

// 서버 컴퓨터에 여러 개의 IP 할당된 경우, 특정 IP에서만 서비스
ServerSocket serverSocket = new ServerSocket();
serverSocker.bind(new InetSocketAddress("xxx.xxx.xxx.xxx", 50001));
  • ServerSocket이 생성되었다면, 연결 요청 수락을 위해 accept() method 실행
    • accept(): 클라이언트가 연결 요청하기 전까지 블로킹(실행을 멈춘 상태)
    • 클라이언트 요청이 들어오면 블로킹이 해제되고, 통신용 Socket을 리턴
  • InetSocketAddress 객체를 통해 IP 주소와 Port 번호 get
Socket socket = serverSocker.accept();

// 리턴된 Socket을 통해 연결된 클라이언트의 IP 주소와 Port 번호
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
String clientIP = isa.getHostName();
String portNo = isa.getPort();
  • ServerSocket의 close() method를 호출하여 Port 번호를 언바인딩 -> 서버 종료
serverSocket.close();

- 예제

package ch19.sec3.exam1;

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("Press Q or q to quit");
		
		// 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) {}
	}
}

3.2. TCP 클라이언트

  • 클라이언트가 서버에 연결 요청을 하려면 Socket 객체를 생성할 때, 생성자 매개값으로 서버 IP 주소와 Port 번호를 제공
Socket socket = new Socket( "IP", 50001 );

// IP 주소 대신 도메인 이름 사용
Socket socket = new Socket( new InetAddress.getByName("domainName", 50001) );

// Socket 생성과 연결 요청을 분리(connect() method 이용)
socket = new Socket();
socket.connect( new InetSocketAddress("domainName", 50001) );
  • 연결 요청 시에 두 가지 예외가 발생할 수 있음
    • UnkownHostException: IP 주소가 잘못 표기된 경우
    • IOException: 제공된 IP와 Port 번호로 연결할 수 없는 경우
try {
    Socket socket = new Socket("IP", 50001);
} catch (UnknownHostException e) {
    // IP 표기 방법이 잘못되었을 경우
} catch (IOException e) {
    // IP와 Port로 서버에 연결할 수 없는 경우
}

// 클라이언트에서 서버 연결을 끊고 싶은 경우
socket.close();

- 예제

package ch19.sec3.exam1;

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) {
			// 해당 포트의 서버에 연결할 수 없는 경
		}
	}
}

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

  • client가 연결 요청(connect())을 하고 서버가 연결 수락(accept())했다면, 양쪽의 Socket 객체로부터 각각 InputStream과 OutputStream을 얻을 수 있음

InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();

// 데이터 전송
String data = "보낼 데이터";
byte[] bytes = data.getBytes("UTF-8");
OutputStream os = socket.getOutputStream();
os.wirte(bytes);
os.flush();

// 데이터 전송 간편화 (보조 스트림 DataOutputStream 연결)
String data = "보낼 데이터";
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());
dos.writeUTF(data);
dos.flush();

// 데이터 수신
byte[ ] bytes = new byte[1024];
InputStream is = socket.getInputStream();
int num = is.read(bytes);
String data = new String(bytes, 0, num, "UTF-8");

// 데이터 수신 간편화 (보조 스트림 DataInputStream 연결)
DataInputStream dis = new DataInputStream(socket.getInputStream());
String data = dis.readUTF();

- 예제

package ch19.sec3.exam2;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
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("Press Q or q to quit");
		
		// 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() + "의 연결 요청을 수락함");
						
						// 데이터 받기
						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) {}
	}
}
package ch19.sec3.exam2;

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

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

 

4. UDP 네트워킹

  • UDP(User Diagram Protocol): 발신자가 일방적으로 수신자에게 데이터를 보내는 방식
    • TCP처럼 연결 요청 및 수락 과정이 없기 때문에, TCP보다 데이터 전송 속도가 상대적으로 빠름
    • 고정 회선이 아니라 여러 회선을 통해 데이터를 전송하기 때문에, 특정 회선의 속도에 따라 데이터가 순서대로 전달되지 않거나 잘못된 회선으로 인해 데이터 손실이 발생할 수 있음
    • 데이터 전달의 신뢰성 중시 -> TCP
    • 데이터 전달의 속도 중시 -> UDP
  • Java는 UDP 네트워킹을 위해 java.net 패키지에서 DatagramSocketDatagramPacket 클래스 제공
    • DatagramSocket: 발신점수신점
    • DatagramPacket: 주고받는 데이터

4.1. UDP 서버

  • UDP 서버를 위한 DatagramSocket 객체 생성
DatagramSocket datagramSocket = new DatagramSocket(50001);

 

  • UDP 서버는 클라이언트가 보낸 DatagramPacket을 항상 받을 준비를 해야 함 -> receive()
    • receive(): 데이터를 수신할 때까지 블로킹, 데이터가 수신되면 매개값으로 주어진 DatagramPacket에 저장
// (데이터를 저장할 배열, 수신할 수 있는 최대 바이트)
DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
datagramSocket.receive(receivePacket);

// receive() method가 실행된 후, 수신된 데이터와 바이트 수 get
byte[] bytes = receivePacket.getData();
int num = receivePacket.getLength();

// 읽은 데이터가 문자열인 경우
String data = new String(bytes, 0, num, "UTF-8");
  • UDP 서버가 클라이언트에게 처리 내용을 보내려면, 클라이언트 IP 주소와 Port 번호 필요
    • receive()로 받은 DatagramPacket에서 얻을 수 있음 -> getSocketAddress()
SocketAddress socketAddress = receivePacket.getSocketAddress();	// client 정보

// 클라이언트로 보낼 DatagramPacket (바이트 배열, 시작 인덱스, 보낼 바이트 수, client 정보)
String data = "처리 내용";
byte[] bytes= data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket( bytes, 0, bytes.length, socketAddress );

// 클라이언트로 datagramPacket 전송
datagramSocket.send( sendPacket );

// UDP 서버 종료
datagramSocket.close()

4.2. UDP 클라이언트

  • UDP 클라이언트를 위한 DatagramSocket 객체 생성
// 클라이언트를 위한 DatagramSocket 생성
DatagramSocket datagramSocket = new DatagramSocket();	// Port 번호 자동 부여

// 요청 내용을 보내기 위한 DatagramPacket 생성 
// (바이트 배열, 바이트 배열에서 보내고자 하는 바이트 수, UPD 서버의 정보를 담은 InetSocketAddress 객체)
String data = "요청 내용";
byte[] bytes = data.getBytes("UTF-8");
DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, new InetSocketAddress("localhost", 50001));

// UDP 서버로 DatagramPacket 전송
datagramSocket.send(sendPacket);

// UDP 클라이언트 종료
datagramSocket.close();

- 예제

package ch19.sec4;

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

public class NewsServer {
	private static DatagramSocket datagramSocket = null;
	
	public static void main(String[] args) throws Exception {
		System.out.println("Press Q or q to quit");
		
		// UDP 서버 시작
		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 {
					// DatagramSocket 생성 및 Port 바인딩
					datagramSocket = new DatagramSocket(50001);
					System.out.println("[서버] 시작됨");
					
					while(true) {
						// 클라이언트가 구독하고 싶은 뉴스 주제 얻기
						DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
						datagramSocket.receive(receivePacket);
						String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
						
						// 클라이언트의 IP와 Port 얻기
						SocketAddress socketAddress = receivePacket.getSocketAddress();
						
						// 10개의 뉴스를 클라이언트로 전송
						for(int i = 1; i <= 10; i++) {
							String data = newsKind + ": 뉴스" + i;
							byte[] bytes = data.getBytes("UTF-8");
							DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);
							datagramSocket.send(sendPacket);
						}
 					}
				} catch (Exception e) {
					System.out.println("[서버] " + e.getMessage());
				}
			}
		};
		//스레드 시작
		thread.start();
	}
	
	public static void stopServer() {
		//DatagramSocket을 닫고 Port 언바인딩
		datagramSocket.close();
		System.out.println("[서버] 종료됨 ");
	}
}
package ch19.sec4;

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

public class NewsClient {
	public static void main(String[] args) {
		try {
			// DatagramSocket 생성
			DatagramSocket datagramSocket = new DatagramSocket();
			
			// 구독하고 싶은 뉴스 주제 보내기
			String data = "정치";
			byte[] bytes = data.getBytes("UTF-8");
			DatagramPacket sendPacket = new DatagramPacket(bytes, bytes.length, new InetSocketAddress("localhost", 50001));
			datagramSocket.send(sendPacket);
			
			while(true) {
				// 뉴스 받기
				DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
				datagramSocket.receive(receivePacket);
				
				// 문자열로 변환
				String news = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
				System.out.println(news);
				
				// 10번째 뉴스를 받으면 while문 종료
				if(news.contains("뉴스 10")) {
					break;
				}
			}
			
			// DatagramSocket 닫기
			datagramSocket.close();
		} catch(Exception e) {
		}
	}
}

 

5. 서버의 동시 요청 처리

  • 일반적으로 서버는 다수의 클라이언트와 통신, clients로부터 동시에 요청을 받아서 처리하고, 처리 결과를 개별 클라이언트로 전송

-> 기존 19.3, 19.4의 동작 방식으로는, 먼저 연결한 클라이언트의 요청 처리 시간이 길어질수록, 다음 클라이언트의 요청 처리 작업 지연

-> accept()와 receive()를 제외한 요청 처리 코드를 별도의 스레드에서 작업하는 것이 좋음

  • 클라이언트의 퐁증으로 인한 서버의 과도한 스레드 생성 방지를 위해 ThreadPool 사용 -> threads 수 제한

5.1. TCP EchoServer 동시 요청 처리

package ch19.sec5.exam1;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class EchoServer {
	private static ServerSocket serverSocket = null;
	// 10개의 threads로 요청을 처리하는 threadPool 생성
	private static ExecutorService executorService = Executors.newFixedThreadPool(10);
	
	public static void main(String[] args) {
		System.out.println("Press Q or q to quit");
		
		// 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) {
						// 연결 수락
						Socket socket = serverSocket.accept();
					
						// 작업 큐에 처리 작업 넣기, Runnable은 함수형 인터페이스이므로 람다식으로 표현 가
						executorService.execute(() -> {
							try {
								// 연결된 클라이언트 정보 얻기
								InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
								System.out.println("[서버] " + isa.getHostName() + "의 연결 요청을 수락함");
								
								// 데이터 받기
								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) {
							}
						});
					}
				} catch(IOException e) {
					System.out.println("[서버] " + e.getMessage());
				}
			}
		};
		// 스레드 시작
		thread.start();
	}
	
	
	public static void stopServer() {
		try {
			// ServerSocket을 닫고 Port 언바인딩
			serverSocket.close();
			executorService.shutdownNow(); // 스레드풀 종료
			System.out.println("[서버] 종료됨 ");
		} catch (IOException e1) {}
	}
}

5.2. UDP NewsServer 동시 요청 처리

package ch19.sec5.exam1;

import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class NewsServer {
	private static DatagramSocket datagramSocket = null;
	// 10개의 스레드로 요청을 처리하는 스레드풀 생성
	private static ExecutorService executorService = Executors.newFixedThreadPool(10);
	
	public static void main(String[] args) throws Exception {
		System.out.println("Press Q or q to quit");
		
		// UDP 서버 시작
		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 {
					// DatagramSocket 생성 및 Port 바인딩
					datagramSocket = new DatagramSocket(50001);
					System.out.println("[서버] 시작됨");
					
					while(true) {
						// 클라이언트가 구독하고 싶은 뉴스 주제 얻기
						DatagramPacket receivePacket = new DatagramPacket(new byte[1024], 1024);
						datagramSocket.receive(receivePacket);
						
						// 작업 큐에 처리 작업 넣기, Runnable은 함수형 인터페이스이므로 람다식으로 표현 가
						executorService.execute(() -> {
							try {
								String newsKind = new String(receivePacket.getData(), 0, receivePacket.getLength(), "UTF-8");
								
								// 클라이언트의 IP와 Port 얻기
								SocketAddress socketAddress = receivePacket.getSocketAddress();
								
								// 10개의 뉴스를 클라이언트로 전송
								for(int i = 1; i <= 10; i++) {
									String data = newsKind + ": 뉴스" + i;
									byte[] bytes = data.getBytes("UTF-8");
									DatagramPacket sendPacket = new DatagramPacket(bytes, 0, bytes.length, socketAddress);
									datagramSocket.send(sendPacket);
								}
							} catch (Exception e) {
							}
						});
 					}
				} catch (Exception e) {
					System.out.println("[서버] " + e.getMessage());
				}
			}
		};
		//스레드 시작
		thread.start();
	}
	
	public static void stopServer() {
		//DatagramSocket을 닫고 Port 언바인딩
		datagramSocket.close();
		System.out.println("[서버] 종료됨 ");
	}
}

 

6. JSON 데이터 형식

  • 네트워크 통신에서 가장 많이 사용되는 데이터 형식은 JSON(JavaScript Object Notation)

{
    "id": "winter",
    "name": "한겨울",
    "age": 25,
    "student": true,
    "tel": { "home": "02-123-1234", "mobile": "010-123-1234" },
    "skill": [ "java", "C", "c++" ]
}

 

-> 프로젝트에 JSON-java 라이브러리 추가

- 예제

package ch19.sec6;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import java.nio.charset.Charset;

import org.json.JSONArray;
import org.json.JSONObject;

public class CreateJsonExample {
	public static void main(String[] args) throws IOException {
		// JSON 객체 생성
		JSONObject root = new JSONObject();
		
		// 속성 추가
		root.put("id", "winter");
		root.put("name", "한겨울");
		root.put("age", 25);
		root.put("student", true);
		
		// 객체 속성 추가
		JSONObject tel = new JSONObject();
		tel.put("home", "02-123-1234");
		tel.put("mobile", "010-123-1234");
		root.put("tel", tel);
		
		// 배열 속성 추가
		JSONArray skill = new JSONArray();
		skill.put("java");
		skill.put("c");
		skill.put("c++");
		root.put("skill", skill);
		
		// JSON 얻기
		String json = root.toString();
		
		// 콘솔에 출력
		System.out.println(json);
		
		// 파일로 저장
		Writer writer = new FileWriter("src/ch19/sec6/member.json", Charset.forName("UTF-8"));
		writer.write(json);
		writer.flush();
		writer.close();
	}
}

package ch19.sec6;

import java.io.BufferedReader;
import java.io.FileReader;
import java.io.IOException;
import java.nio.charset.Charset;

import org.json.JSONArray;
import org.json.JSONObject;

public class ParseJsonExample {
	public static void main(String[] args) throws Exception {
		// 파일로부터 JSON 읽기
		BufferedReader br = new BufferedReader(
				new FileReader("src/ch19/sec6/member.json", Charset.forName("UTF-8"))
		);
		String json = br.readLine();
		br.close();
		
		// JSON 파싱
		JSONObject root = new JSONObject(json);
		
		// 속성 정보 읽기
		System.out.println("id: " + root.getString("id"));
		System.out.println("name: " + root.getString("name"));
		System.out.println("age: " + root.getInt("age"));
		System.out.println("student: " + root.getBoolean("student"));
		
		// 객체 속성 정보 읽기
		JSONObject tel = root.getJSONObject("tel");
		System.out.println("home: " + tel.getString("home"));
		System.out.println("mobile: " + tel.getString("mobile"));
		
		// 배열 속성 정보 읽기
		JSONArray skill = root.getJSONArray("skill");
		System.out.print("skill: ");
		for(int i = 0; i < skill.length(); i++) {
			System.out.print(skill.get(i) + ", ");
		}
	}
}

 

7. TCP 채팅 프로그램

  • ChatServer
    • 채팅 서버 실행 클래스
    • ServerSocket을 생성하고 50001에 바인딩
  • SocketClient
    • ChatClient와 1:1로 통신
  • ChatClient
    • 채팅 클라이언트 실행 클래스
    • ChatServer에 연결 요청
    • SocketClient와 1:1로 통신

7.1. 채팅 서버

package ch19.sec7;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
import java.util.Scanner;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.json.JSONObject;

public class ChatServer {
	// field
	ServerSocket serverSocket;
	ExecutorService threadPool = Executors.newFixedThreadPool(100);
	Map<String, SocketClient> chatRoom = Collections.synchronizedMap(new HashMap<>());
	
	// method: 서버 시작
	public void start() throws IOException {
		serverSocket = new ServerSocket(50001);
		System.out.println("[서버] 시작됨");
		
		Thread thread = new Thread(() -> {
			try {
				while(true) {
					Socket socket = serverSocket.accept();
					SocketClient sc = new SocketClient(this, socket);
				}
			} catch (IOException e) {
			}
		});
		thread.start();
	}
	
	// method: 클라이언트 연결 시 SocketClient 생성 및 추가
	public void addSocketClient(SocketClient socketClient) {
		String key = socketClient.chatName + "@" + socketClient.clientIp;
		chatRoom.put(key, socketClient);
		System.out.println("입장: " + key);
		System.out.println("현재 채팅자 수: " + chatRoom.size() + "\n");
	}

	// method: 클라이언트 연결 종료 시 SocketClient 제거
	public void removeSocketClient(SocketClient socketClient) {
		String key = socketClient.chatName + "@" + socketClient.clientIp;
		chatRoom.remove(key);
		System.out.println("나감: " + key);
		System.out.println("현재 채팅자 수: " + chatRoom.size() + "\n");
	}
	
	// method: 모든 클라이언트에게 메시지 보냄
	public void sendToAll(SocketClient sender, String message) {
		JSONObject root = new JSONObject();
		root.put("clientIp", sender.clientIp);
		root.put("chatName", sender.chatName);
		root.put("message", message);
		String json = root.toString();
		
		Collection<SocketClient> socketClients = chatRoom.values();
		for(SocketClient sc : socketClients) {
			if(sc == sender) continue;
			sc.send(json);
		}
	}
	
	// method: 서버 종료
	public void stop() {
		try {
			serverSocket.close();
			threadPool.shutdownNow();
			// Collection<SocketClient>의 요소 스트림을 이용해서 전체 SocketClient의 close() method 호출
			chatRoom.values().stream().forEach(sc -> sc.close());
			System.out.println("[서버] 종료됨 ");
		} catch (IOException e1) {}
	}
	
	// method: main
	public static void main(String[] args) {
		try {
			ChatServer chatServer = new ChatServer();
			chatServer.start();
			
			System.out.println("Press Q or q to quit");
			
			Scanner scanner = new Scanner(System.in);
			while(true) {
				String key = scanner.nextLine();
				if(key.equals("q")) break;
			}
			scanner.close();
			chatServer.stop();
		} catch (IOException e) {
			System.out.println("[서버] " + e.getMessage());
		}
	}
}
package ch19.sec7;

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

import org.json.JSONObject;

public class SocketClient {
	// field
	ChatServer chatServer;
	Socket socket;
	DataInputStream dis;
	DataOutputStream dos;
	String clientIp;
	String chatName;

	// 생성자
	public SocketClient(ChatServer chatServer, Socket socket) {
		try {
			this.chatServer = chatServer;
			this.socket = socket;
			this.dis = new DataInputStream(socket.getInputStream());
			this.dos = new DataOutputStream(socket.getOutputStream());
			InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
			this.clientIp = isa.getHostName();
			receive();
		} catch(IOException e) {
		}
	}
	
	// method: JSON 받기
	public void receive() {
		chatServer.threadPool.execute(() -> {
			try {
				while(true) {
					String receiveJson = dis.readUTF();
					
					JSONObject jsonObject = new JSONObject(receiveJson);
					String command = jsonObject.getString("command");
					
					switch(command) {
						case "incoming":
							this.chatName = jsonObject.getString("data");
							chatServer.sendToAll(this, "들어오셨습니다.");
							chatServer.addSocketClient(this);
							break;
						case "message":
							String message = jsonObject.getString("data");
							chatServer.sendToAll(this, message);
							break;
					}
				}
			} catch (IOException e) {
				chatServer.sendToAll(this, "나가셨습니다.");
				chatServer.removeSocketClient(this);
			}
		});
	}
		
	// method: JSON 보내기
	public void send(String json) {
		try {
			dos.writeUTF(json);
			dos.flush();
		} catch(IOException e) {
		}
	}

	public void close() {
		try {
			socket.close();
		} catch(Exception e) {}
	}
}

7.2. 채팅 클라이언트

package ch19.sec7;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.util.Scanner;

import org.json.JSONObject;

public class ChatClient {
	// field
	Socket socket;
	DataInputStream dis;
	DataOutputStream dos;
	String chatName;
	
	// method: 서버 연결
	public void connect() throws IOException {
		socket = new Socket("localhost", 50001);
		dis = new DataInputStream(socket.getInputStream());
		dos = new DataOutputStream(socket.getOutputStream());
		System.out.println("[클라이언트] 서버에 연결됨");
	}
	
	// method: JSON 받기
	public void receive() {
		Thread thread = new Thread(() -> {
			try {
				while(true) {
					String json = dis.readUTF();
					JSONObject root = new JSONObject(json);
					String clientIp = root.getString("clientIp");
					String chatName = root.getString("chatName");
					String message = root.getString("message");
					System.out.println("<" + chatName + "@" + clientIp + "> " + message);
				}
			} catch(Exception e1) {
				System.out.println("[클라이언트] 서버 연결 끊김");
				System.exit(0);
			}
		});
		thread.start();
	}
	
	// method: JSON 보내기
	public void send(String json) throws IOException {
		dos.writeUTF(json);
		dos.flush();
	}
	
	// method: 서버 연결 종료
	public void unconnect() throws IOException {
		socket.close();
	}
	
	// method: main
	public static void main(String[] args) {
		try {
			ChatClient chatClient = new ChatClient();
			chatClient.connect();
			
			Scanner scanner = new Scanner(System.in);
			System.out.println("대화명 입력: ");
			chatClient.chatName = scanner.nextLine();
			JSONObject jsonObject = new JSONObject();
			jsonObject.put("command", "incoming");
			jsonObject.put("data", chatClient.chatName);
			String json = jsonObject.toString();
			chatClient.send(json);
			
			chatClient.receive();
			
			System.out.println("보낼 메시지를 입력하고 Enter");
			System.out.println("채팅을 종료하려면 q를 입력하고 Enter");
			
			while(true) {
				String message = scanner.nextLine();
				if(message.toLowerCase().equals("q")) {
					break;
				} else {
					jsonObject = new JSONObject();
					jsonObject.put("command", "message");
					jsonObject.put("data", message);
					json = jsonObject.toString();
					chatClient.send(json);
				}
			}
			scanner.close();
			chatClient.unconnect();
		} catch(IOException e) {
			System.out.println("[클라이언트] 서버 연결 안 됨");
		}
	}
}


참고자료