Java lang

[이것이 자바다] 자바 네트워크1 공부 정리

Razelo 2020. 12. 27. 17:07

이번 장을 공부할 때 예제에 FX가 쓰여서 FX 설치를 해주는데, 좀 문제가 생겨서 꽤나 애를 먹었다. 친구 말로는 버전? 이 차이가 나면 그럴 수 있다는데, 확실한건 그냥 마켓에서 다운받고 재시작하는 걸로는 사용할 수 가 없었다. 그런 증상이 나타나는 사람이 많은 것 같았다. 어느 블로그에 나온대로 하니 실행은 되었지만, 매번 프로젝트를 만들때마다 이렇게 해야 하나? 싶을정도로 귀찮은 과정이었다.

 

해당블로그는 다음과 같다. 

 

we-always-fight-with-code.tistory.com/36

 

JavaFX 설치와 이클립스 연동

JavaFX를 실행하기 위해서 뭐 이클립스 네온버전을 설치하라 어쩌고 하는데 하...깔기 싫어서 기존에 쓰던 오픈버전으로 세팅했다. 대신 세팅하는데 좀 애먹었다. ㅎㅛㅎ... 이 영상도 참고했다.

we-always-fight-with-code.tistory.com

 

잘 나와있다. 근데 다른 방법은 없을까? 너무 귀찮다 ㄷㄷ 

 

 

이 블로그 내용도 괜찮다

m.blog.naver.com/pmw9440/221981411851

 

 

 

 

inetaddress

package sec06.exam01_inetaddress;

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

public class InetAddressExample {

	public static void main(String[] args) {
		try {
			InetAddress local = InetAddress.getLocalHost(); //예외가 발생할 수 있으니 예외처리 해줍시다. 
			System.out.println("내 컴퓨터 IP주소: "+local.getHostAddress());
			
			InetAddress[] iaArr = InetAddress.getAllByName("www.naver.com"); //DNS에서 검색한다. 복수개의 IP주소를 가지고 와서 각각 InetAddress 객체로 만든 다음 배열로 리턴한다. 
			for(InetAddress remote : iaArr) {
				System.out.println("www.naver.com IP주소: "+remote.getHostAddress());
			}
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}

}

 

 

 

data_read_write

package sec07.exam02_data_read_write;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerExample {
	//서버 ->두번 실행하면 bind 예외가 발생한다. 또 서버를 실행시키니 -> 이미 사용중이다... 라는 예외. 이런예외가 발생하면 terminated 하고 다시 시작해주셈/ 
	public static void main(String[] args) {
		ServerSocket serverSocket = null;
		
		try {
			serverSocket = new ServerSocket();
			serverSocket.bind(new InetSocketAddress("localhost",5001)); //바인딩하기 / 현재 컴퓨터의 5001포트에다가 하는거지. 
			
			while(true) { //서버는 항상 클라이언트의 요청을 기다려야 하므로 무한루프를 작성함. 
				System.out.println("[연결 기다림]");
				Socket socket = serverSocket.accept();  //socket객체는 클라이언트와 통신하는 객체이다. 
				// accept() 는 클라이언트의 연결 요청을 수락하는 역할을 함. 연결요청이 오기전까지 블로킹이 됨. 즉 대기상태임. 
				//연결요청이 들어오면 socket이라는 통신용객체를 만들고 리턴한다.  		
				InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
				System.out.println("[연결 수락함]"+isa.getHostName()); //클라이언트의 IP주소가 출력됨 .
			
				
				//데이터를 받는코드 -> 
				byte[] bytes = null;
				String message = null;
				InputStream is = socket.getInputStream();
				bytes = new byte[100];
				int readByteCount = is.read(bytes);//클라이언트가 데이터를 보내기 전가지는 read()는 대기 상태가 된다. 즉 블로킹상태임.
				message = new String(bytes,0,readByteCount,"UTF-8");
				System.out.println("[데이터 받기 성공]: "+message);
				
				//데이터를 보내는 코드 ->
				OutputStream os = socket.getOutputStream();
				message = "Hello Client";
				bytes = message.getBytes("UTF-8");
				os.write(bytes);
				os.flush();
				System.out.println("[데이터 보내기 성공]");
				
				//이제 필요가 없으므로 
				is.close();
				os.close();
				socket.close(); // 연결을 끊겠다는 의미 
			}
		} catch (Exception e) {
			e.printStackTrace();
		} 
		
		
		//서버 소켓이 필요 없다면 이제 닫아주다. 지금은 별 의미 없다. 나중에 ui추가해서 버튼 눌렀을때나 써먹을 수 있다. 
		if(!serverSocket.isClosed()) {
			try {
				serverSocket.close(); //예외처리발생하므로 처리해주자. 
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}
package sec07.exam02_data_read_write;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

public class ClientExample {
	// 클라이언트
	public static void main(String[] args) {
		Socket socket = null;
		try {
			socket = new Socket(); // 소켓 생성
			System.out.println("[연결 요청]");
			socket.connect(new InetSocketAddress("localhost", 5001)); // 연결하기/ 연결 요청 
			//서버가 실행중이지 않다면, java.net.ConnectException: Connection refused: connect 와 같은 에러가 발생한다. 그래서 5001에서 서버가 돌아가고 있어야 한다. 
			System.out.println("[연결 성공]");//성공적으로 연결이 수락이 되면 이 코드가 실행이 된다.  
			
			//서버로 데이터 보내는 코드 
			byte[] bytes = null;
			String message = null;
			OutputStream os = socket.getOutputStream();
			message = "Hello Server";
			bytes = message.getBytes("UTF-8"); //UTF-8 문자셋을 이용해서 문자열을 바이트배열로 만든다. 
			os.write(bytes);
			os.flush();
			System.out.println("[데이터 보내기 성공]");
			
			//서버쪽에서 보낸 데이터 받기 
			InputStream is = socket.getInputStream();
			bytes = new byte[100];
			int readByteCount = is.read(bytes);
			message = new String(bytes,0,readByteCount,"UTF-8");
			System.out.println("[데이터 받기 성공]: "+message);
			
			os.close();
			is.close();
			//socket.close(); 어차피 밑 코드 if문에서 닫히게되있으므로 굳이 안써줘도 됨. 
		} catch (Exception e) {
			e.printStackTrace();
		}
		
		//추가적으로 서버와 연결을 끊기 위한 코드 
		if(!socket.isClosed()) {
			try {
				socket.close(); //예외 처리 필요 
			} catch (IOException e) {}
		}

	}

}

 

 

 

 

chatting  or application 

package application;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Iterator;
import java.util.List;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javafx.application.Application;
import javafx.application.Platform;
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.stage.Stage;

public class ServerExample extends Application {
	ExecutorService executorService; //스레드 풀을 생성하기 위해 선언 
	ServerSocket serverSocket;       //클라이언트의 연결 요청을 수락하기 위해 선언 
	List<Client> connections = new Vector<Client>();  //벡터로 객체를 생성한다. 서버쪽에서 클라이언트를 관리하기 위해 리스트에 저장해서 관리한다. 벡터는 멀티쓰레드에서 쓰레드에 안전하기 떄문에 상ㅇ했다. 

	void startServer() { // 서버 시작 코드/ start버튼을 눌렀을 때 동작한다. 
		executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors()); //코어의 수만큼 스레드를 생성해서 사용한다. 

		try {
			serverSocket = new ServerSocket(); //서버 소켓 생성 
			serverSocket.bind(new InetSocketAddress("localhost", 5001)); //포트에 바인딩하기 . 
		} catch (Exception e) {
			if (!serverSocket.isClosed()) { //예외가 발생하면 안전하게 서버를 닫아준다. 
				stopServer();               //밑에 있는 메소드를 호출한다. 
			}
			return;                         //startServer()를 종료한다. 
		}

		Runnable runnable = new Runnable() {          //연결 수락작업을 수행할 수 있게끔 ! 연결 수락작업객체라고 보면됨. 
			@Override
			public void run() {
				Platform.runLater(() -> {              //밑에 있는 두 코드를 실행하는 것은 (람다식) 스레드 풀의 스레드가 아니라 자바 fx어플리케이션 쓰레드가 실행한다고 보면 된다.  
					displayText("[서버 시작]");          //그냥 쉽게 UI변경을 하려면 Platform.runlater~~~ 로 이 방법으로 만든다고 보면 된다. 스레드끼리의 문제떄문인걸로 알고 있다. 자세한건 javafx스레드 공부하고 나면 이해될듯. 
					btnStartStop.setText("stop");
				});
				while (true) {//클라이언트의 연결 수락 작업을 계속할 수 있도록 무한루프로 만든다. 
					try {
						Socket socket = serverSocket.accept();  //클라이언트가 연결 요청할때까지 대기하다가(블로킹) 연결요청이 오면 socket을 리턴한다. 
						String message = "[연결 수락: " + socket.getRemoteSocketAddress() + ": " //클라이언트의 ip주소와 포트에 대한 정보를 얻는다.toString()... 이라고 보면 됨.  
								+ Thread.currentThread().getName() + "]";                      //스레드 풀의 어떤 스레드가 실행하고 있는지 보여준다. 
						Platform.runLater(() -> displayText(message)); //로그를 출력한다. 

						Client client = new Client(socket);                  //클라이언트 객체를 만든다. 매개값으로 socket을 제공한다. 
						connections.add(client);						     //벡터에 저장한다. 											
						Platform.runLater(() -> displayText("[연결 개수: " + connections.size() + "]")); //또 로그를 출력한다. 몇개가 연결되었는지 보여준다. 
					} catch (Exception e) {
						if (!serverSocket.isClosed()) { //예외가 발생하면 안전하게 종료한다. 
							stopServer();
						}
						break;                          //더 이상 while문이 실행하지 않도록 break 한다. 즉 예외가 발생하면 무한루프를 빠져나온다. 
					}
				}
			}
		};
		executorService.submit(runnable);               //StartServer()의 마지막 코드. Runnable runnable에서 생성한 연결 수락 작업 객체를 스레드 풀에 스레드에서 처리할 수 있기 위해서 submit()을 통해 작업 객체 runnable을 매개값으로 전달해준다. 
	}

	void stopServer() { //start 버튼을 누르고 나면 버튼이 stop으로 변하는데 그때 stop버튼을 누르면 실행된다. 
		try {
			Iterator<Client> iterator = connections.iterator(); // 반복자를 얻어낸다. 
			while (iterator.hasNext()) {                        //이터레이터에서 클라이언트를 하나 가져오기 위해서... 가져올게 있다면? 
				Client client = iterator.next();                //하나 가져온다. 
				client.socket.close();                          //가져온 클라리언트의 socket을 닫는다. 예외처리도 해준다. 
				iterator.remove();								//연결을 끊었으니 클라이언트를 컬렉션에서 제거해야 해서 모든 클라이언트를 리스트에서 제거해줬다. 
			}
			//이제 서버 소켓을 닫아주자. 
			if (serverSocket != null && !serverSocket.isClosed()) {  //서버 소켓을 닫아준다. 
				serverSocket.close();
			}
			if (executorService != null && !executorService.isShutdown()) { //executorService도 닫아준다. 
				executorService.shutdown();
			}
			
			//로그 출력해주기. 
			Platform.runLater(() -> {
				displayText("[서버 멈춤]");
				btnStartStop.setText("start"); //버튼은 다시 start로 바꿔야 한다. 
			});
		} catch (Exception e) {
		}
	}

	class Client { //클라이언트 통신 코드 / 데이터 통신 코드 
		Socket socket;  //필드 

		Client(Socket socket) { 
			this.socket = socket; //사용할 소켓이다. 대입해준다. 
			receive();            //클라이언트에게 데이터를 받을 준비를 항상 해놓는다. 
		}

		void receive() { //클라이언트가 보낸 데이터를 받기 위해 정의됨. 항상 서버쪽의 통신 소켓을 이용해서 읽는다. 무한루프를 돌면서!클라이언트가 정상/비정상 종료하면 catch로 이동해서 무한루프가 종료된다. 
			Runnable runnable = new Runnable() { //별도의 스레드에서 클라이언트의 데이터를 받아야 한다. 
				@Override
				public void run() { //클라이언트가 보낸 데이터를 받을 코드를 작성한다. 
					try {
						while (true) { //무한루프 생성. 계속 클라이언트에서 보낸 데이터를 받기 위해서 
							byte[] byteArr = new byte[100]; //클라이언트에서 보낸 데이터를 일단 저장할 바이트배열 
							InputStream inputStream = socket.getInputStream(); 

							// 클라이언트가 비정상 종료를 했을 경우 IOException 발생
							int readByteCount = inputStream.read(byteArr); //클라이언트가 보낸 데이터를 받는다. 

							// 클라이언트가 정상적으로 Socket의 close()를 호출했을 경우
							if (readByteCount == -1) {         
								throw new IOException(); //강제적으로 IOException을 발생시켜서 밑에 있는 데이터를 받는 코드가 실행되지 않도록 catch로 이동한다.왜냐면 정상적으로 종료된거니까! 어차피 비정상 종료면 IOException 발생해서 밑에 예외로 가게 되어있음. 
							}

							String message = "[요청 처리: " + socket.getRemoteSocketAddress() + ": " //어떤클라이언트가 보낸 데이터냐.. 를 출력하기 위해서 .. ! 클라이언트 ip주소 출력함. 
									+ Thread.currentThread().getName() + "]";                      //어떤 스레드가 요청을 처리했는지를 알기 위해서 ! 
							Platform.runLater(() -> displayText(message));                         //로그 출력 

							String data = new String(byteArr, 0, readByteCount, "UTF-8");          //읽은 데이터를 문자열로 변환함. 

							for (Client client : connections) {                                    //우리가 받은 데이터를 모든 클라이언트에게 전송을 할려고 한다. 채팅이니까. 그니까 하나의 클라이언트가 보낸 데이터를 모든 클라이언트에게 전달한다. 
								client.send(data);													//send() 메소드 호출 
							}
						}
					} catch (Exception e) {    //read()메소드를 호출하게 되면은 (114줄에서)  클라이언트가 비정상적으로 종료가 되면 IOException이 발생한다. 정상적으로 종료하면 read는 -1을 리턴한다. 그러므로 비정상/정상 인경우 둘다 이 코드로 오게 된다. 
						try {
							connections.remove(Client.this);          //클라이언트가 더 이상 연결되어 있지 않기 때문에 해당 클라이언트에 해당되는 클라이언트 객체를 컬렉션에서 제거한다./ this라고만 적으면 runnable객체을 의미한다. 그래서 바깥 클라이언트 클래스의 인스턴스다 라는 뜻에서 Client.this로 적는다.   
							String message = "[클라이언트 통신 안됨: " + socket.getRemoteSocketAddress() + ": " //로그 생성 
									+ Thread.currentThread().getName() + "]";
							Platform.runLater(() -> displayText(message)); //로그 출력한다. 
							socket.close();                                //이제 닫아준다. 클라이언트가 통신 안되니까 -> 예외가 발생했으니까 ->클라이언트가 소켓을 close했기 때문에 예외가 발생했으니 ->  socket을 닫아준다. 
						} catch (IOException e2) { //close()에 대한 예외 처리임. 
						}
					}
				}
			};
			executorService.submit(runnable);     //스레드풀이 작업큐에 위 작업객체를 저장할 수 있도록 runnable을 매개값으로 준다. executorService내부의 스레드가 결국 run작업을 실행한다.  
		}

		void send(String data) { //클라이언트로 데이터를 보낸다. 매개값으로 문자열을 받아서 이걸 클라이언트로 전송한다. 얘도 스레드풀의 스레드가 처리해야 한다. 
			Runnable runnable = new Runnable() { //작업을 정의한다. 
				@Override
				public void run() { //클라이언트로 보내는 코드를 작성해보자. 
					try {
						byte[] byteArr = data.getBytes("UTF-8"); //보내고자 하는 데이터를 바이트 배열로 만든다. 문자셋을 utf로 한다. 여기서 발생하는 예외를 처리해준다. 
						OutputStream outputStream = socket.getOutputStream(); 
						outputStream.write(byteArr); //여기서도 IOException이 발생할 수 있기 때문에 예외를 Exception으로 잡아준다. (위에 있는 예외랑 같이 사용하기 위해서 )
						outputStream.flush(); 
					} catch (Exception e) { //getBytes랑 write에서 예외가 발생해서 여기서 잡아주는데, 아마 getBytes는 정확하게 UTF-8로 줬기 때문에 예외가 안생길거다. 아마 write에서 발생하는 것일 거다. write에서 발생하는 경우는 클라이언트가 통신이 안되는 경우일 것이다.
						try {
							String message = "[클라이언트 통신 안됨: " + socket.getRemoteSocketAddress() + ": " //통신이 안되므로 다음과 같이 작성. 
									+ Thread.currentThread().getName() + "]";
							Platform.runLater(() -> displayText(message));
							connections.remove(Client.this);                         //클라이언트가 통신이 안되기 때문에 더 이상 Client객체는 필요가 없다. 그러므로 컬렉션으로 삭제한다. Client객체의 참조인 this
							socket.close();                                       //아울러 소켓도 닫아준다. 여기서도 예외가 발생할 수 있으므로 예외를 잡아준다. 
						} catch (IOException e2) {
						}
					}
				}
			};
			executorService.submit(runnable); //스레드풀의 작업큐에 저장하기 위해서 다음과 같이 작성한다. 
		}
	}
 
	//여기서부터는 ui관련 코드이다. 
	//////////////////////////////////////////////////////
	TextArea txtDisplay;
	Button btnStartStop;

	@Override
	public void start(Stage primaryStage) throws Exception {
		BorderPane root = new BorderPane();
		root.setPrefSize(500, 300);

		txtDisplay = new TextArea();
		txtDisplay.setEditable(false);
		BorderPane.setMargin(txtDisplay, new Insets(0, 0, 2, 0));
		root.setCenter(txtDisplay);

		btnStartStop = new Button("start");
		btnStartStop.setPrefHeight(30);
		btnStartStop.setMaxWidth(Double.MAX_VALUE);
		btnStartStop.setOnAction(e -> {
			if (btnStartStop.getText().equals("start")) {
				startServer();
			} else if (btnStartStop.getText().equals("stop")) {
				stopServer();
			}
		});
		root.setBottom(btnStartStop);

		Scene scene = new Scene(root);
		scene.getStylesheets().add(getClass().getResource("app.css").toString());
		primaryStage.setScene(scene);
		primaryStage.setTitle("Server");
		primaryStage.setOnCloseRequest(event -> stopServer());
		primaryStage.show();
	}

	void displayText(String text) {
		txtDisplay.appendText(text + "\n");
	}

	public static void main(String[] args) {
		launch(args);
	}
}
package application;

import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;

import javafx.application.Application;
import javafx.application.Platform;
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.stage.Stage;

public class ClientExample extends Application { //javafx 실행클래스로 만들기 위해 Application클래스를  상속받았다. 
	Socket socket; //필드 
	
	void startClient() { //연결 시작 코드 
		//소켓 객체를 생성해서 서버에 연결 요청하는 코드를 작성을 하면 된다. 근데 하나 고려해야 할점은 서버와 연결이 되기 전까지 블로킹 즉 대기상태가 되기 때문에 자바 fx 어플리케이션 스레드가 연결을 요청하는 코드를 실행하지 않도록 하는게 좋다. 
		//그래서 별도의 스레드를 생성해서 연결 요청을 하도록 만들었다. 그래서 스레드 객체를 생성을 했다.
		Thread thread = new Thread() {   //익명객체로 만들자. 
			@Override
			public void run() {
				try {
					socket = new Socket(); //소켓 객체를 생성한다. 
					socket.connect(new InetSocketAddress("localhost", 5001)); //연결을 한다. 여기서도 예외가 발생한다. 
					Platform.runLater(()->{                                                //이제 로그에 출력하자. 
						displayText("[연결 완료: "  + socket.getRemoteSocketAddress() + "]"); //어떤 서버에 연결이 완료됬는지 출력해줌. (서버의 ip주소와 포트번호)
						btnConn.setText("stop");                                            //start버튼의 글자를 바꿔준다. 
				        btnSend.setDisable(false);                                          //send버튼을 활성화시킨다. 이제 보낼 수 있다. 
					});
				} catch(Exception e) {  //5001에서 서버가 실행하고 있지 않다면 예외가 발생한다. 그러니 예외처리를 해준다. 
					Platform.runLater(()->displayText("[서버 통신 안됨]"));    
					if(!socket.isClosed()) { stopClient(); }   //소켓을 안전하게 닫아준다.
					return;                                   //run메소드를 종료해준다.   
				}
				receive(); //예외가 발생하지 않았다면 연결이 성공한 것이므로, 여기서 receive()메소드를 통해 항상 서버가 보낸 데이터를 받도록 한다. 
			}
		};
		thread.start();     //이제 스레드를 시작한다. 
	}
	
	void stopClient() {//연결 끊기 코드이다. 
		try {
			Platform.runLater(()->{ //로그 출력해준다. 
				displayText("[연결 끊음]");
				btnConn.setText("start");   //글자를 start로 바꿔주고, 
				btnSend.setDisable(true);   //send버튼을 다시 비활성화 시킨다. 보낼 수 없게. 
			});
			if(socket!=null && !socket.isClosed()) { //이제 소켓도 닫아주자.  
				socket.close();                      //소켓을 닫는다. 
			}
		} catch (IOException e) {}    //close()의 예외를 잡아준다. 
	}	
	
	void receive() { //서버에서 데이터를 보내게 되면 그 데이터를 받아서 로그창에 출력을 해준다. 
		while(true) { //항상 서버의 데이터를 받아야 하므로 무한루프로 작성한다. 
			try {
				byte[] byteArr = new byte[100]; //데이터를 받을 바이트 배열 생성 
				InputStream inputStream = socket.getInputStream(); 
				
				//서버가 비정상적으로 종료했을 경우 IOException 발생
				int readByteCount = inputStream.read(byteArr); //read()는 서버가 데이터를 보내기 전까지는 블로킹된다. 서버가 소켓을 정상 닫으면 -1 비정상 종료하면 IOExeption이 발생한다. 
				
				//서버가 정상적으로 Socket의 close()를 호출했을 경우
				if(readByteCount == -1) { throw new IOException(); } //강제적으로 발생시킨다. 
				
				String data = new String(byteArr, 0, readByteCount, "UTF-8"); //정상적으로 데이터를 읽었을 경우에만 이 코드를 실행한다. 문자열로 변환한다. 
				 
				Platform.runLater(()->displayText("[받기 완료] "  + data));  //출력해준다. 
 			} catch (Exception e) {       //예외가 발생했다면, 
				Platform.runLater(()->displayText("[서버 통신 안됨]"));
				stopClient();                     //클라이언트의 연결을 끊어준다. 
				break;                            // 무한루프를 빠져나간다. 
			}
		}
	}
	
	void send(String data) { 	//데이터를 입력하고 send버튼을  클릭하면 이게 실행된다. 서버로 데이터를 보내는 역할을 한다. 데이터를 보낼 때도 별도의 스레드를 만들어서 보내주는게 좋다. 
								//왜냐면 데이터를 보내는 시간이 오래 걸리면 UI가 멈춰있기 때문이다. 가능하면 javafx 어플리케이션 스레드가 통신코드를 실행하지 않도록 해주는게 좋다. 
		Thread thread = new Thread() {
			@Override
			public void run() {
				try {		
					byte[] byteArr = data.getBytes("UTF-8"); //매개값으로 받은 문자열을 바이트 배열로 변환 
					OutputStream outputStream = socket.getOutputStream(); 
					outputStream.write(byteArr); 
					outputStream.flush();
					Platform.runLater(()->displayText("[보내기 완료]")); //로그에 출력해준다. 
				} catch(Exception e) {//write에서 예외가 발생할 경우 -> 서버가 통신이 안될 경우 
					Platform.runLater(()->displayText("[서버 통신 안됨]")); 
					stopClient();    //안전하게 소켓을 닫아준다. 
				}				
			}
		};
		thread.start(); //스레드 시작 
	}
	
	//여기는 ui코드이다. 
	//////////////////////////////////////////////////////
	TextArea txtDisplay;
	TextField txtInput;
	Button btnConn, btnSend;
	
	@Override
	public void start(Stage primaryStage) throws Exception {
		BorderPane root = new BorderPane();
		root.setPrefSize(500, 300);
		
		txtDisplay = new TextArea();
		txtDisplay.setEditable(false);
		BorderPane.setMargin(txtDisplay, new Insets(0,0,2,0));
		root.setCenter(txtDisplay);
		
		BorderPane bottom = new BorderPane();
			txtInput = new TextField();
			txtInput.setPrefSize(60, 30);
			BorderPane.setMargin(txtInput, new Insets(0,1,1,1));
			
			btnConn = new Button("start");
			btnConn.setPrefSize(60, 30);
			btnConn.setOnAction(e->{
				if(btnConn.getText().equals("start")) {
					startClient();
				} else if(btnConn.getText().equals("stop")){
					stopClient();
				}
			});
			
			btnSend = new Button("send"); 
			btnSend.setPrefSize(60, 30);
			btnSend.setDisable(true);
			btnSend.setOnAction(e->send(txtInput.getText()));
			
			bottom.setCenter(txtInput);
			bottom.setLeft(btnConn);
			bottom.setRight(btnSend);
		root.setBottom(bottom);
		
		Scene scene = new Scene(root);
		scene.getStylesheets().add(getClass().getResource("app.css").toString());
		primaryStage.setScene(scene);
		primaryStage.setTitle("Client");
		primaryStage.setOnCloseRequest(event->stopClient());
		primaryStage.show();
	}
	
	void displayText(String text) {
		txtDisplay.appendText(text + "\n");
	}	
	
	public static void main(String[] args) {
		launch(args);
	}
}
/*text-area 배경색*/
.text-area {
	-fx-background-color: gold;
}

/*scroll-pane 배경색*/
.text-area .scroll-pane {
	-fx-background-color: transparent;
}

/*viewport 배경색*/
.text-area .scroll-pane .viewport{
	-fx-background-color: transparent;
}

/*content 배경색*/
.text-area .scroll-pane .content{
	-fx-background-color: transparent;
}

 

 

 

 

udp

package sec08.exam01_udp;

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

public class UdpSendExample {

	public static void main(String[] args) throws Exception{
		//발신자 코드 
		DatagramSocket datagramSocket = new DatagramSocket(); //예외가 발생함. 
		
		System.out.println("[발신 시작]");
		
		for(int i = 1;i<=3;i++) {
			String data = "메시지" +i;
			byte[] byteArr = data.getBytes("UTF-8");
			DatagramPacket packet = new DatagramPacket( //3개의 매개변수를 준다. 
					byteArr, byteArr.length, new InetSocketAddress("localhost",5001)
			);
			datagramSocket.send(packet); //send가 3번 실행됨. 3개의 DatagramPacket이 전송된다. 
			System.out.println("[보낸 바이트 수]: "+byteArr.length+"bytes");
		}
		System.out.println("[발신 종료]");
		
		datagramSocket.close();
	}

}
package sec08.exam01_udp;

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

public class UdpReceiveExample {

	public static void main(String[] args) throws Exception {
		// 수신자 코드 ->수신자부터 실행해도 된다. 수신자가 없어도 발신자는 실행이 된다. 왜냐면 수신자와 연결하지 않고, 일방적으로 데이터를 보내기 때문에 수신자가 받든 안받든 상관없기 때문이다. 
		// 근데 발신자가 보낸 데이터를 다 받을 수 있으려면 수신자부터 실행시키는 것이 좋다. 
		DatagramSocket datagramSocket = new DatagramSocket(5001); // 발신자와의 차이점은 수신자는 포트 번호로 바인딩해야 한다는 것이다. //예외처리해줌.

		// 데이터를 받으려면 가능하면 스레드를 생성해서 받는 것이 좋다.
		Thread thread = new Thread() {
			@Override
			public void run() {
				System.out.println("[수신 시작]");
				try {
					while (true) { // 계속해서 발신자가 보낸 데이터를 받는다. 무한루프
						DatagramPacket packet = new DatagramPacket(new byte[100], 100); // 데이터를 받을 DatagramPacket 객체
																						// 생성하기/ 매개변수는 첫번째가 데이터를 받을 바이트
																						// 배열이고, 두번쨰가 배열의 길이이다.
						datagramSocket.receive(packet); // receive는 데이터를 받고 나서 packet안에 잇는 배열에다가 데이터를 저장한다. 예외처리가 필요하다.
						String data = new String(packet.getData(),0,packet.getLength(),"UTF-8");
						System.out.println("[받은 내용:" + packet.getSocketAddress()+"]" + data); //내용을 출력해준다. 
					}
				} catch (Exception e) {
					System.out.println("[수신 종료]"); //예외가 발생하면 출력한다. 
				}
			}
		};
		
		thread.start();   //시작해준다. 
		
		Thread.sleep(10000); //10초 이후에 닫아준다. 무한정 받을 수는 없으니까 
		datagramSocket.close();
	}

}

 

 

 

 

필기

package network;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;

/*
<네트워크>
여러대의 컴퓨터를 통신 회선으로 연결한 것
홈 네트워크: 컴퓨터가 방마다 있고, 이들 컴퓨터를 유,무선 등의 통신 회선으로 연결 
지역 네트워크: 회사, 건물, 특정 영역에 존재하는 컴퓨터를 통신 회선으로 연결한 것
인터넷: 지역 네트워크를 통신 회선으로 연결한 것

<서버와 클라이언트>
서버: 서비스를 제공하는 프로그램
웹서버, FTP서버, DBMS, 메신저 서버
클라이언트의 연결을 수락하고, 요청 내용을 처리한 후 응답을 보내는 역할을 한다. 

클라이언트: 서비스를 받는 프로그램
웹브라우저, FTP클라이언트, 메신저
네트워크 데이터를 필요로  하는 모든 애플리케이션이 해당(모바일앱 포함)

클라이언트 			서버
1. 연결요청 -> 		2. 연결 수락
3. 처리 요청 -> 		4. 처리
			     <- 5. 응답(처리 결과)
			     

<IP(Internet Protocol) 주소 >
네트워크상에서 컴퓨터를 식별하는 번호 
네트워크 어댑터(랜(Lan)카드 )마다 할당 

xxx.xxx.xxx.xxx 형식으로 표현 (xxx는 0~255사이의 정수 )

<포트(Port)>
같은 컴퓨터 내에서 프로그램을 식별하는 번호 
클라이언트는 서버 연결 요청시 IP주소와 Port를 같이 제공
0~65535 범위의 값을 가진다. 세가지 범위로 구분한다. 

구분명										범위					설명
Well Know Port Numbers     					0~1023				국제 인터넷 주소관리기구가 특정 애플리케이션용으로 미리 예약한 포트 
Registered Port Numbers						1024~49151			회사에서 등록해서 사용할 수 있는 포트 
Dynamic Or Private Port Numbers 			49152~65535			운영체제가 부여하는 동적 포트 또는 개인적인 목적으로 사용할 수 있는 포트 


<InetAddress로 IP 주소 얻기 >
java.net.InetAddress
IP주소를 표현한 클래스
로컬 컴퓨터의 IP주소 뿐만 아니라 
도메인 이름을 DNS에서 검색한 후 IP 주소를 가져오는 기능을 제공 

로컬 컴퓨터에서 얻기
InetAddress ia = InetAddress.getLocalHost();

도메인 이름으로 얻기 
InetAddress ia = InetAddress.getByName(String host);
InetAddress[] isArr = InetAddress.getAllByName(String host);

InetAddress로 IP주소 얻기 
String ip = InetAddress.getHostAddress();



<TCP> Transmission Control Protocol 
특징: 
연결 지향적 프로토콜 -> 시간 소요가 됨.
통신 선로 고정 -> 전송 속도 느려질 수도 있음. 
데이터를 정확하고 안정적으로 전달 

java.net API
ServerSocket, Socket 이 있다. 

-------------------------------서버측------------------------------------------
ServerSocket 생성과 연결 수락
<ServerSocket 생성과 포트 바인딩 >
방법1:
ServerSocket serverSocket = new ServerSocket(5001);
방법2:
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("127.0.0.1",5001));

 <연결 수락>
try{
	Socket socket = serverSocket.accept(); //accept()는 클라이언트가 연결요청을 하기 전까지 대기 상태로 기다리다가 연결요청을 하면 통신용 소켓을 리턴을 한다. 그리고 이 리턴받은 소켓을 통해 여러가지를 한다. 
}catch(Exception e){}


연결된 클라이언트 IP 주소 얻기 
InetSocketAddress socketAddress = (InetSockeetAddress) socket.getRemoteSocketAddress(); 

리턴타입			메소드명				설명
String			getHostName()		클라이언트 IP 리턴
int 			getPort()			클라이언트 포트 번호 리턴
String			toString()			"IP:포트번호" 형태의 문자열 리턴


ServerSocket 포트 언바인딩 -> 종료하고 싶다면
serverSocket.close(); 를 사용하자. 
-----------------------------------------------------------------------------------------


--------------------------클라이언트 측 ------------------------------------------

Socket 생성과 연결 요청

try {
		Socket socket = new Socket("localhost",5001); //방법 1 
		Socket socket = new Socket(new InetSocketAddress("localhost",5001)); //방법 2
}catch(UnknownHostException e) {
		//IP 표기 방법이 잘못되었을 경우
}catch(IOException E) {
		//해당 포트의 서버에 연결할 수 없는 경우 
}



그리고 또 이런 방법으로도 가능하다. 
socket = new Socket();
socket.connect(new InetSocketAddress("localhost",5001));

연결을 끊을때는 다음과 같이 .
try {
	socket.close();
}catch(IOException e) {}

-------------------------------------------------------------------------------


<Socket 데이터 통신>
입출력 스트림 얻기 

//입력 스트림 얻기  
InputStream is = socket.getInputStream();

//출력 스트림 얻기 
 OutputStream os = socket.getOutputStream();
 
 <데이터 보내기> 
 String data = "보낼 데이터";
 byte[] byteArr = data.getBytes("UTF-8");
 OutputStream outputStream  = socket.getOutputStream();
 outputStream.write(byteArr);
 outputStream.flush();
 
 
 <데이터 받기 >
 byte[] byteArr = new byte[100];
 InputStream inputStream = socket.getInputStream();
 int readByCount = inputStream.read(byteArr);
 String data = new String(byteArr,0,readByCount,"UTF-8");


read()의 블로킹 해제 

블로킹이 해제되는 경우									리턴값
상대방이 데이터를 보냄									읽은 바이트 수	
상대방이 정상적으로 Socket의 close()를 호출					-1
상대방이 비정상적으로 종료 								IOException 발생 




<스레드 병렬 처리> -> 서버에서 스레드를 제한된 개수만큼 운영해야 서버 폭주를 막을 수 있다.  
<블로킹(대기상태)가 되는 메소드 >
ServerSocket의 accept()
Socket 생성자 또는 connect()
Socket의 read(),write()

<병렬 처리의 필요성>
스레드가 블로킹되면 다른 작업을 수행하지 못한다. 
-> 입출력 할 동안 다른 클라이언트의 연결 요청을 수락하지 못한다.
-> 입출력 할 동안 다른 클라이언트의 입출력을 하지 못한다. 

UI생성/변경 스레드에서 블로킹 메소드를 호출하지 않도록 주의 한다. 
-> UI생성 및 변경이 안되고 이벤트 처리가 안된다. 



<스레드풀을 사용해서 스레드 수 관리>
스레드 풀은 스레드 수를 제한해서 사용하기 때문에 갑작스런 클라이언트의 폭증은
작업 큐의 작업량만 증가시길 뿐 스레드 수는 변함이 없으므로 서버 성능은 완만히 저하된다.
다만 대기하는 작업량(작업 큐 내의) 이 많기 때문에 개별 클라이언트에서 응답을 늦게 받을 순 있다. 

------------------------------------------------------------------------------------------

UDP(User Datagram Protocol)
 특징:
 비연결 지향적 프로토콜 
  -> 연결 절차를 거치지 않고 발신자가 일방적으로 데이터를 발신하는 방식
  -> 연결 절차가 없기 때문에 TCP 보다는 빠르게 전송할 수 있다. 
  통신 선로가 고정적이지 않다. 
  -> 데이터 패킷들이 서로 다른 통신 선로를 통해 전달될 수 있다. 
  -> 먼저 보낸 패킷이 느린 선로를 통해 전송될 경우, 나중에 보낸 패킷보다 늦게 도착할 수 있다. 
  데이터 손실이 발생할 수 있다. 
  -> 일부 패킷은 잘못된 선로로 전송되어 잃어 버릴 수 있다. -> 데이터 전달 신뢰성이 떨어지낟. 
  
  {발신자 (DatagramSocket)} -> DatagramPacket -> {(DatagramSocket) 수신자}
  
  예시:
  
  발신자 구현 코드 :
  DatagramSocket datagramSocket = new DatagramSocket();
  
  String data = "전달 데이터";
  byte[] byteArr = data.getBytes("UTF-8");
  DatagramPacket packet = new DatagramPacket(byteArr, byteArr.length, new InetSocketAddress("localhost",5001));
  
  datagramSocket.send(packet);
  
  수신자 구현 코드:
  DatagramSocket datagramSocket = new DatagramSocket(5001);
  DatagramPacket datagramPacket = new DatagramPacket(new byte[100],100);
  datagramSocket.receive(datagramPacket);
  
  DatagramSocket 닫기:
  datagramSocket.close();
  
  
  





*/

 

 

반응형