[Java] Network

soyeon·2022년 7월 18일
0

Java_카카오클라우드

목록 보기
13/15
post-thumbnail

알고 있어야 할 Network 개념🤸‍♀️

Java에서의 Network

Java에서의 Network

  • server process는 client의 접속을 기다리고 있다. server socket이 만들어지고, client의 접속을 대기한다.
  • client process가 접속을 하기 위해 socket을 만들고, server socket에 접속을 시도한다.
  • 접속이 가능한 상태이면 그에 mapping 되는 socket이 실제로 만들어진다.
  • client에서 접속을 시도하면 server socket은 socket을 만들어낸다. 만든 후에는 새로운 접속을 기다린다.
  • 두 socket이 Stream을 열어서 연결을 한다.

코드로 알아보기

연습 문제 1

접속한 Client에게 Server가 현재 날짜를 전송한다.

  • Server 코드
package lecture0718.exam01;

import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Date;

// Server 쪽 process
public class Exam01_DateServer {

	public static void main(String[] args) {
		
		// server socket을 만든다.
		try {
			ServerSocket serverSocket = new ServerSocket(5678);
			System.out.println("Date Server 기동 - 포트번호(5678)");
			
			// client의 접속을 기다린다. 기다리다가 client가 접속하면 socket 객체를 만들어낸다.
			Socket socket = serverSocket.accept();  // blocking method
			
			// 이 코드로 내려왔다는 것은 접속이 성공했다는 것이다.
			PrintWriter pr = new PrintWriter(socket.getOutputStream());  // output stream을 열어준다.
			
			// 현재 날짜를 구한다.
			Date date = new Date();
			
			// 만들어진 stream으로 date를 보낸다.
			pr.println(date.toLocaleString());  // 버퍼를 이용한 스트림
			// 해당 코드에서 flush()가 없으면 pr을 강제로 종료하면서 pr.close() 데이터가 내보내진다.
			pr.flush();  // 이 코드가 더 안전한다.
			
			// 자원 반납
			pr.close();
			socket.close();
			serverSocket.close();
			
			System.out.println("Date Server 종료");
		} catch (Exception e) {
			
		}		
	}
}
  1. 가장 먼저 Server Socket을 만든다. Server Socket은 port 번호를 가지고 있어야 한다.
  2. Server Socket은 예외 처리가 강제된다.
  3. Server Socket이 Client의 접속을 받을 수 있게 만들어준다.
    -> serverSocket.accept(); (blocking method)
  4. 누군가 접속해서 들어오면 Socket 객체를 만든다.
  5. 접속이 성공하면, 데이터가 나가야 한다. socket에 Output Stream을 열어주어야 한다. 문자열을 내보내기에 적합한 stream으로 감싸준다.
  6. 현재 날짜를 구한다.
  7. 만들어진 stream으로 date를 보낸다.
  8. 사용한 자원을 반납해준다.

버퍼 안에 들어가 있는 데이터를 스트림을 통해 내보내는 시점
1. 스트림이 강제로 종료될 경우(close)
2. 버퍼에 공간이 다 차는 경우
3. method를 이용해서 flush() 시킬 경우 - 강제적으로 내보낸다.

  • Client 코드
package lecture0718.exam01;

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

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class Exam01_DateClient extends Application{

	TextArea textarea;
	Button connBtn;
	
	@Override
	public void start(Stage primaryStage) throws Exception {
		
		// 화면 구성
		BorderPane root = new BorderPane();
		root.setPrefSize(700, 500);  // window 크기
		
		textarea = new TextArea();
		root.setCenter(textarea);  // 화면 center에 textarea를 붙인다.
		
		connBtn = new Button("Date 서버 접속");
		connBtn.setPrefSize(150, 40);
		connBtn.setOnAction(e -> {
			textarea.clear();
			
			try {
				// server process에 접속을 시도한다.
				Socket socket = new Socket("localhost", 5678);
				
				// 접속에 성공했다. input stream을 열어준다.
				InputStreamReader ir = new InputStreamReader(socket.getInputStream());
				BufferedReader br = new BufferedReader(ir);  // 한줄씩 읽을 수 있는 stream으로 감싼다.
				
				// msg를 읽어들인다.
				String msg = br.readLine();  // blocking method
					
				// textarea에 msg를 붙인다.
				textarea.appendText(msg + "\n");  // 나중에 문제가 생길 수 있다!
				
				// 사용한 자원을 반납한다.
				br.close();
				ir.close();
				socket.close();
				
				textarea.appendText("서버와의 연결이 종료되었어요!");
			} catch (Exception e2) {}			
		});
		
		FlowPane flowPane = new FlowPane();
		flowPane.setPadding(new Insets(10,10,10,10));  // 여백을 준다.
		flowPane.setPrefSize(700, 40);
		flowPane.setHgap(10);
		flowPane.getChildren().add(connBtn);  // 버튼 부착
		
		root.setBottom(flowPane);
		
		Scene scene = new Scene(root);
		primaryStage.setScene(scene);
		primaryStage.show();
	}
	
	public static void main(String[] args) {
		launch();  // thread가 하나 만들어지면서 start를 실행하게 된다.
	}
}
  1. 화면 구성
  2. Date 서버 접속 버튼을 누르면 실행될 이벤트 객체를 만든다.
    • textarea를 초기화 시킨다.
    • server socket에 접속을 시도한다.
    • socket 객체를 하나 만든다.
      : 어느 컴퓨터에 접속을 시도할 것인지 ip, port 번호를 넣어준다.
    • 알맞은 곳에 접속하게 되면 client와 server socket이 만들어진다.
  3. socket에 Input Stream을 열어주어야 데이터를 받을 수 있다.
  4. server가 보내기 전에 br이 readline()을 하면 안된다.
    하지만 readline은 blocking method라서 읽을 수 있는 것이 들어올 때까지 기다리기 때문에 server가 보내지 않으면 계속해서 대기하게 된다.
  5. textarea에 받은 msg를 append 시켜준다.
  6. 사용한 자원을 반납해준다. 안해주면 메모리 leak가 발생하게 된다. 사용한 것 역순으로 반납하면 된다.

연습 문제 2

간단한 Echo Program 작성해보기.
: client에서 문자열을 입력하면 server에게 갔다가 돌아와서 화면에 찍힌다.
-> client가 "/exit"를 입력하면 종료된다.

  • Server 코드
package lecture0718.exam02;

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

public class Exam02_EchoServer {

	public static void main(String[] args) {
		
		ServerSocket serverSocket = null;
		Socket socket = null;
		PrintWriter pr = null;
		BufferedReader br = null;
		
		try {
			serverSocket = new ServerSocket(5678);
			System.out.println("Echo Server 기동 - 클라이언트 접속 대기");
			
			socket = serverSocket.accept();  // blocking method
			
			// 접속에 성공했다. stream을 열어준다.
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));  // 한줄씩 읽을 수 있는 stream으로 감싼다.
			pr = new PrintWriter(socket.getOutputStream());
			
			String msg = null;
			
			while(true) {
				msg = br.readLine();  // blocking method
				
				if(msg.equals("/exit") || (msg == null)) {
					break;
				}																	
				pr.println(msg);
				pr.flush();			
			}			
		} catch (Exception e) {
			
		} finally {
			// 사용된 resource를 해제
			try {
				// close하기 전에 객체가 존재하는지 확인한다.
				if(br != null) br.close();
				if(pr != null) pr.close();
				if(socket != null) socket.close();
				if(serverSocket != null) serverSocket.close();
				System.out.println("Echo Server 종료");
			} catch (Exception e2) {
				
			}			
		}
	}
}
  • Client 코드
package lecture0718.exam02;

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

import javafx.application.Application;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class Exam02_EchoClient extends Application{
	
	TextArea textarea;
	Button connBtn;
	TextField idField, textfield;
	
	Socket socket;
	BufferedReader br;
	PrintWriter pr;
	
	@Override
	public void start(Stage primaryStage) throws Exception {
		
		// 화면 구성
		BorderPane root = new BorderPane();
		root.setPrefSize(700, 500);  // window 크기
		
		textarea = new TextArea();
		root.setCenter(textarea);  // 화면 center에 textarea를 붙인다.
		
		connBtn = new Button("Echo Server 접속");
		connBtn.setPrefSize(150, 40);
		connBtn.setOnAction(e -> {
			textarea.clear();			
			try {
				socket = new Socket("localhost", 5678);
				
				// stream을 연다.
				br = new BufferedReader(new InputStreamReader(socket.getInputStream()));  // 한줄씩 읽을 수 있는 stream으로 감싼다.
				pr = new PrintWriter(socket.getOutputStream());
				
				textarea.appendText("Echo Server 접속 성공!!" + "\n");
			} catch (Exception e2) {}			
		});
		
		idField = new TextField();
		idField.setPrefSize(100, 40);

		textfield = new TextField();
		textfield.setPrefSize(200, 40);
		textfield.setOnAction(e -> {
			try {				
				String msg = idField.getText() + " : " + textfield.getText();			
				// 만들어진 stream으로 date를 보낸다.
				pr.println(msg);
				pr.flush();
				
				if(textfield.getText().equals("/exit")) {
					textarea.appendText("Echo Server와의 연결을 종료합니다.");
					textfield.setDisable(true);
				} else {
					try {
						String serverMsg = br.readLine();  // blocking method
						textarea.appendText(serverMsg + "\n");		
					} catch (Exception e2) {

					}						
				}
				textfield.clear();									
			} catch (Exception e1) {
				
			}
		});  // 입력 상자에 글 입력 후 enter 입력하면 이벤트 처리
		
		FlowPane flowPane = new FlowPane();
		flowPane.setPadding(new Insets(10,10,10,10));  // 여백을 준다.
		flowPane.setPrefSize(700, 40);
		flowPane.setHgap(10);
		flowPane.getChildren().add(connBtn);  // 버튼 부착
		flowPane.getChildren().add(idField);  // 아이디 필드 부착
		flowPane.getChildren().add(textfield);  // 입력 상자 부착
			
		root.setBottom(flowPane);
		
		Scene scene = new Scene(root);
		primaryStage.setScene(scene);
		primaryStage.show();
	}
	
	public static void main(String[] args) {
		launch();  // thread가 하나 만들어지면서 start를 실행하게 된다.
	}
}

-> 화면 구성(연습 문제 1 + server에게 보내는 화면)

=> 문제: 여러 명의 client를 받을 수 없다. (방 한 개짜리 채팅 프로그램)
server는 client가 들어오면 thread를 만들어서 client와 통신하게 만들어 주고, 자신은 다시 새로운 클라이언트를 기다려야 한다.
chat
: 받은 데이터를 공유해야 하기 때문에 공유 자원으로 해야 한다.

연습 문제 3

채팅 프로그램

  • Server 코드
package lecture0718.exam03;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class Exam03_ChatServer extends Application {

	TextArea textarea;
	Button startBtn, stopBtn;
	ServerSocket serverSocket;
	ExecutorService executorService = Executors.newCachedThreadPool();
	SharedObject sharedObject = new SharedObject();
	
	private void printMsg(String name) {
		Platform.runLater(()->{
			textarea.appendText(name + "\n");
		});
	}
	
	class SharedObject {
		
		List<ClientRunnable> clients = new ArrayList<ClientRunnable>();
		
		public synchronized void broadcast(String msg) {
			clients.stream().forEach(t -> {
				t.pw.println(msg);
				t.pw.flush();
			});
		}		
	}
	
	
	class ClientRunnable implements Runnable {

		private SharedObject sharedObject;		
		private Socket socket;
		private BufferedReader br;
		private PrintWriter pw;

		public ClientRunnable(SharedObject sharedObject,Socket socket) throws IOException {
			this.sharedObject = sharedObject;
			this.socket = socket;
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			pw = new PrintWriter(socket.getOutputStream());						
		}
		
		@Override
		public void run() {
			
			String msg = null;

			try {
				while(true) {
					msg = br.readLine();
					if( msg == null ) {
						break;
					}
					sharedObject.broadcast(msg);					
				}
			} catch (IOException e) {
				e.printStackTrace();
			}
			
			try {
				if( pw != null ) pw.close();
				if( br != null ) br.close();				
				if( socket != null ) socket.close();
			} catch (Exception e) {
				e.printStackTrace();
			}			
			sharedObject.clients.remove(this);
		}
		
	}
	
	@Override
	public void start(Stage primaryStage) throws Exception {

		BorderPane root = new BorderPane();
		root.setPrefSize(700, 500);
		
		textarea = new TextArea();
		textarea.setEditable(false);
		root.setCenter(textarea);
		
		
		startBtn = new Button("Chat Server 기동");
		startBtn.setPrefSize(150, 40);
		startBtn.setOnAction(e->{
			textarea.clear();
			printMsg("[ 채팅서버 기동 - 5555 ]");
			Runnable runnable = new Runnable() {
				
				@Override
				public void run() {
					try {
						serverSocket = new ServerSocket(5555);
						while(true) {
							printMsg("[ 클라이언트 접속 대기중 ]");
							Socket socket = serverSocket.accept();
							ClientRunnable cRunnable = new ClientRunnable(sharedObject,socket);
							synchronized (this) {
								sharedObject.clients.add(cRunnable);
							}
							printMsg("[ 클라이언트 접속 - 현재 클라이언트 수 : " + sharedObject.clients.size() + " ]");							
							executorService.execute(cRunnable);							
						}
					} catch (IOException e) {
						e.printStackTrace();
					}
				}
			};
			executorService.execute(runnable);
		});

		stopBtn = new Button("Chat Server 중지");
		stopBtn.setPrefSize(150, 40);
		stopBtn.setOnAction(e->{
			printMsg("[ 서버 중지 - 프로그램 재시작 필요 ]");
			executorService.shutdownNow();
		});
		
		
		FlowPane flowPane = new FlowPane();
     	flowPane.setPadding(new Insets(10, 10, 10, 10));
		flowPane.setColumnHalignment(HPos.CENTER);
		flowPane.setPrefSize(700, 40);
		flowPane.setHgap(10);
		flowPane.getChildren().add(startBtn);
		flowPane.getChildren().add(stopBtn);
		
		root.setBottom(flowPane);
		
		Scene scene = new Scene(root);
		primaryStage.setScene(scene);
		primaryStage.setTitle("Chat Server ( Java IO )");
		primaryStage.setOnCloseRequest(e->{
			executorService.shutdownNow();
		});
		primaryStage.show();	
		
		 
	}
	 
	public static void main(String[] args) {
		launch();
	}
	
}
  • Client 코드
package lecture0718.exam03;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javafx.application.Application;
import javafx.application.Platform;
import javafx.geometry.HPos;
import javafx.geometry.Insets;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TextArea;
import javafx.scene.control.TextField;
import javafx.scene.layout.BorderPane;
import javafx.scene.layout.FlowPane;
import javafx.stage.Stage;

public class Exam03_ChatClient extends Application {

	TextArea textarea;
	TextField idfield, msgfield;
	Button connBtn, disconnBtn;
	
	Socket socket;
	BufferedReader br;
	PrintWriter pw;
	ExecutorService executorService = Executors.newCachedThreadPool();
	
	
	private void printMsg(String name) {
		Platform.runLater(()->{
			textarea.appendText(name + "\n");
		});
	}
	
	class ReceiveRunnable implements Runnable {

		private BufferedReader br;
		
		public ReceiveRunnable(BufferedReader br) {
			this.br = br;
		}
		
		@Override
		public void run() {
			String msg = null;
			try {
				while(true) {
					msg = br.readLine();
					if( msg == null ) {
						break;
					}
					printMsg(msg);					
				}
			} catch (IOException e) {
				// e.printStackTrace();
			} finally {
				try {
					if( br != null ) br.close();
				} catch (Exception e2) {
					e2.printStackTrace();
				}
			}
		}
		
	}
	
	@Override
	public void start(Stage primaryStage) throws Exception {

		BorderPane root = new BorderPane();
		root.setPrefSize(700, 500);
		
		textarea = new TextArea();
		textarea.setEditable(false);
		root.setCenter(textarea);
		
		connBtn = new Button("Chat 서버 접속");
		connBtn.setPrefSize(150, 40);
		connBtn.setOnAction(e->{
			textarea.clear();
			disconnBtn.setDisable(false);
			try {
				socket = new Socket("localhost",5555);
				br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
				pw = new PrintWriter(socket.getOutputStream());		
				printMsg("Chat Server 연결 성공");
				
				idfield.setDisable(false);
				msgfield.setDisable(false);
				
				ReceiveRunnable receiver = new ReceiveRunnable(br);
				executorService.execute(receiver);
				connBtn.setDisable(true);
			} catch (UnknownHostException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			}			
		});

		idfield = new TextField();
		idfield.setPrefSize(70, 40);
		idfield.setDisable(true);
		
		msgfield = new TextField();
		msgfield.setPrefSize(200, 40);
		msgfield.setDisable(true);
		msgfield.setOnAction(e->{
			
			String msg = "[" + idfield.getText() + "] : " + msgfield.getText();
			pw.println(msg);
			pw.flush();
			msgfield.clear();
			
		});
		
		disconnBtn = new Button("Chat 서버 접속 종료");
		disconnBtn.setPrefSize(150, 40);
		disconnBtn.setDisable(true);
		
		disconnBtn.setOnAction(e->{
			connBtn.setDisable(false);
			idfield.setDisable(false);
			msgfield.setDisable(false);
			try {
				printMsg("[서버연결 종료]");
				// br은 blocking중이므로 pw를 먼저 close.
				if( pw != null ) pw.close();
				if( br != null ) br.close();				
				if( socket != null ) socket.close();
			} catch (UnknownHostException e1) {
				e1.printStackTrace();
			} catch (IOException e1) {
				e1.printStackTrace();
			}	
			disconnBtn.setDisable(true);
		});
		
		FlowPane flowPane = new FlowPane();
     	flowPane.setPadding(new Insets(10, 10, 10, 10));
		flowPane.setColumnHalignment(HPos.CENTER);
		flowPane.setPrefSize(700, 40);
		flowPane.setHgap(10);
		flowPane.getChildren().add(connBtn);
		flowPane.getChildren().add(idfield);
		flowPane.getChildren().add(msgfield);
		flowPane.getChildren().add(disconnBtn);
		
		root.setBottom(flowPane);
		
		Scene scene = new Scene(root);
		primaryStage.setScene(scene);
		primaryStage.setTitle("Echo Server에 접속");
		primaryStage.setOnCloseRequest(e->{
			try {
				// br은 blocking중이므로 pw를 먼저 close.
				if( pw != null ) pw.close();
				if( br != null ) br.close();				
				if( socket != null ) socket.close();
				executorService.shutdownNow();
			} catch (Exception e2) {
				e2.printStackTrace();
			}
		});
		primaryStage.show();		
	}
	 
	public static void main(String[] args) {
		launch();
	}		
}

0개의 댓글