이번 장을 공부할 때 예제에 FX가 쓰여서 FX 설치를 해주는데, 좀 문제가 생겨서 꽤나 애를 먹었다. 친구 말로는 버전? 이 차이가 나면 그럴 수 있다는데, 확실한건 그냥 마켓에서 다운받고 재시작하는 걸로는 사용할 수 가 없었다. 그런 증상이 나타나는 사람이 많은 것 같았다. 어느 블로그에 나온대로 하니 실행은 되었지만, 매번 프로젝트를 만들때마다 이렇게 해야 하나? 싶을정도로 귀찮은 과정이었다.
해당블로그는 다음과 같다.
we-always-fight-with-code.tistory.com/36
잘 나와있다. 근데 다른 방법은 없을까? 너무 귀찮다 ㄷㄷ
이 블로그 내용도 괜찮다
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();
*/
반응형
'Java' 카테고리의 다른 글
[이것이 자바다] 자바 네트워크2 공부 정리 (0) | 2021.01.01 |
---|---|
[이것이 자바다] 자바 NIO 공부 정리 (0) | 2020.12.30 |
[이것이 자바다] 자바 IO패키지 공부 정리 (0) | 2020.12.26 |
[이것이 자바다] 자바 병렬처리 공부 정리 (0) | 2020.12.25 |
[이것이 자바다] 자바 스트림 공부 정리 (0) | 2020.12.24 |