2023. 2. 13. 16:49ㆍJava
프로젝트 내 src 내 javabasic 패키지 내 Ex7Server.java
package javabasic;
import java.awt.Color;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;
import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
/*
* : ServerSocket 생성
* =>하는일이 서버에 접속하는 클라이언트들을 허용하기 위한 클래스
* 언제 접속할지 모르는 클라이언트를 허용하기 위해서는 항상 대기 상태로
* 있어야하므로 하나의 스레드가 필요
* =>허용한 클라이언트와의 대화를 위해서 또 하나의 스레드가 필요
* (각 클라이언트는 내부클래스로 만들어서 스레드를 상속을 받도록 한다)
*/
public class Ex7Server extends JFrame implements Runnable {
/**
*
*/
private static final long serialVersionUID = -8514243132304872271L;
JTextArea area;
ServerSocket serverSocket;
// 접속하는 클라이언트들을 저장할 벡터 선언
// Vector<ChatClient> list=new Vector<Ex7Server.ChatClient>();
Vector<ChatClient> list = new Vector<ChatClient>();
// public Ex7Server(String title) {
// // TODO Auto-generated constructor stub
// super(title);
// this.setBounds(700, 100, 500, 500);// 시작위치x,y,크기 w,h
// // super로 해도 되고 this로 해도 됨 super는 조상
// // this로 해도 상속을 받아서 괜찮음
// // this.getContentPane().setBackground(Color.orange);//프레임위에 있는 패널의 색상 변경
// this.getContentPane().setBackground(new Color(211, 225, 208));// 프레임위에 있는 패널의 색상 변경
// this.setDesign();// 디자인 코드
// this.setVisible(true);// 보이게 하기
// this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 프로그램을 종료해주는 메서드
// }
// synchronized 란
// 멀티스레드를 잘 사용하면 프로그램적으로 좋은 성능을 낼 수 있지만
// 멀티스레드 환경에서 반드시 고려해야할 점인
// 스레드간 동기화라는 문제는 꼭 해결해야 한다.
// 스레드간 서로 공유하고 수정할 수 있는 데이타가 있는데 스레드간 동기화가 되지 않은
// 상태에서 멀티스레드 프로그램을 돌리면 데이타의 안정성과 신뢰성을 보장 못한다.
// 따라서 데이타의 thread-safe 를 작동시키기 위해 Java 에서는
// synchronized 키워드를 제공해 스레드간 동기화를 시켜 데이타의 thread-safe 를
// 가능하게 만든다.
// Java 에서 지원하는 synchronized 키워드는 여러개의 스레드가 한개의 자원을 사용
// 하고자 할 때, 현재 데이타를 사용하고 있는 해당 스레드를 제외하고 나머지 스레드들은
// 데이타에 접근 할 수 없도록 막는 개념이다.
// synchronized 키워드는 변수와 함수에 사용해서 동기화 할 수 있다.
public Ex7Server(String title) {
// TODO Auto-generated constructor stub
super(title);
this.setBounds(700, 100, 500, 500);// 시작위치x,y,크기 w,h
// super로 해도 되고 this로 해도 됨 super는 조상
// this로 해도 상속을 받아서 괜찮음
// this.getContentPane().setBackground(Color.orange);//프레임위에 있는 패널의 색상 변경
this.getContentPane().setBackground(new Color(211, 225, 208));// 프레임위에 있는 패널의 색상 변경
this.setDesign();// 디자인 코드
this.setVisible(true);// 보이게 하기
this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);// 프로그램을 종료해주는 메서드
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("run 1");
// 서버 소켓 생성
// try {
// serverSocket = new ServerSocket(7000);
// // 포트 7000번에 대기하는 서버 소켓을 만든다.
// // ServerSocket(int port) 메서드는
// // 서버 소켓을 한개를 생성한다.
// // The maximum queue length for incoming connection indications (arequest to connect) is set to 50. If a connectionindication arrives when the queue is full, the connection is refused.
// // 서버 소켓으로 들어오는 연결 시도들에 대한 최대 큐 길이는 50 으로 설정된다.
// // 만약에 큐가 꽉 차서 연결시도가 최대치에 도달하면, 연결은 거부된다.
// // 하지만 최대 큐 길이 50 에 도달하기 전에 컴퓨터 메모리 용량 부족으로
// // Java Application 이 더 이상 새로운 연결을 한 다음에 클라이언트를
// // 생성해서 실행하지 않을 수 있다.
// // 네트워크 통신 포트 설명은 다음과 같다.
// // 0번 ~ 1023번 까지는 예약된(잘 알려진) 포트 이다.
// // FTP, HTTP, TELNET 등에 관련된 포트번호가 있다고 한다.
// // 1024번 ~ 49151번 : 등록된 포트 (registered port)
// // 49152번 ~ 65535번 : 동적 포트 (dynamic port)
// // 0번 부터 1023번 까지만 사용하지 않으면 문제는 없다고 한다.
// // ServerSocket 클래스는 서버단에서 port 번호를 사용하여
// // 클라이언트으 접속을 대기하기 위한 용도로 사용한다.
// // 클라이언트의 접속을 기다리기 위해서는 accept() 메서드를 호출해주어야
// // 하며 accept() 메서드는 클라이언트가 접속할 때 까지
// // 접속대기(blocking) 상태를 유지하게 된다.
// // accept() 메서드는 클라이언트가 접속하면
// // 결과로 Socket 객체를 리턴한다.
// // Socket 클래스는
// // 서버와 클라이언트 양쪽간에 TCP 통신을 위해 사용되는 클래스이다.
// // 서버단에서는 ServerSocket 클래스 객체의 accept() 메서드의 결과
// // 로써 생성되며
// // 클라이언트단에서는 직접 서버IP주소와 port번호를 사용하여 생성한다.
// area.append("서버 소켓 생성 성공\n");
// } catch (IOException e) {
// // TODO Auto-generated catch block
// area.append("서버 소켓 생성 실패\n" + e.getMessage());
// }
try (ServerSocket serverSocket = new ServerSocket(7000)) {
area.append("서버 소켓 생성 성공\n");
while (true) {
// 접속하는 클라이언트 허용하기
try {
Socket socket = serverSocket.accept();// 클라이언트의 연결을 수용한다. 연결을 대기한다.
// 윗줄에서 accept() 메서드를 호출하였다.
// accept() 메서드를 호출하면 프로그램은 accept() 메서드를 호출한
// 지점에서 실행을 멈추고 클라이언트가 서버의 포트 7000번으로 연결할 때까지
// 무한 대기 한다. 클라이언트가 연결되면 accept() 메서드는
// Socket 객체를 반환한다. 반환된 Socket 객체는
// java.net.Socket 클래스으 객체 형태로 반환되며 클라이언트에서 작성하여
// 사용하는 Socket 클래스의 객체 socket 과 같다.
// 클라이언트단에서 쓴 코드는 다음과 같다.
// Socket socket;
// ...
// socket = new Socket("서버의문자열주소", 7000);
// 윗줄에서 보면
// 클라이언트단에 작성된
// Socket(String host, int port) 생성자 메서드는
// 스트림 소켓 하나를 생성하고 나서 String host 파라메터의 argument 값으로 넣은
// 이름이 주어진 호스트의 int port 파라메터의 argument 로써 지정된 port 번호에
// 생성한 스트림 소켓을 연결한다.
// accept() 메서드는 원격 클라이언트와 로컬 서버 사이의 연결을
// 나타내는 Socket 클래스의 객체를 반환한다.
// 원격 클라이언트와의 통신은 요 반환받은 Socket 클래스으 객체를 통해
// 일어나게 한다.
// accept() 메서드는 클라이언트가 연결 요청하기 전까지 블로킹된다.
// 블로킹이란 스레드가 대기 상태가 된다는 뜻이다. 그렇기 때문에
// UI 를 생성하는 스레드 혹은 이벤트를 처리하는 스레드에서는
// accept() 메서드를 호출하면 안된다.
// 그 이유는 블로킹이 되면 UI 갱신이나 이벤트 처리가 불가능 해지기 때문이다.
InetAddress inet = socket.getInetAddress();
area.append("접속한 ip:" + inet.getHostAddress() + "\n");
// 접속한 클라이언트를 벡터에 추가하기
ChatClient client = new ChatClient(socket);
list.add(client);// 벡터에 클라이언트 정보 추가
client.start();// 각 클라이언트가 가진 run 메소드를 호출(각 클라이언트는 스레드)
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
area.append("서버 소켓 생성 실패\n" + e.getMessage());
}
}
// 클라이언트 내부 클래스
class ChatClient extends Thread {
String nickName;
Socket socket;
BufferedReader br;
PrintWriter pw;
public ChatClient(Socket socket) {
this.socket = socket;
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream());
} catch (IOException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
}
}
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
System.out.println("run 2");
while (true)// 대기중 클라이언트로부터 메세지 받기를 기다린다?
{
// 각 클라이언트가 보낸 메세지를 서버가 읽는다
try {
String msg = br.readLine();
// 클라이언트는 보낼때 처음 접속한이후는 "1,닉네임" 이런형태로 보내고
// 메세지는 "2,메세지" 이런형태로 보내도록 프로그래밍
if (msg == null)
break;
String[] data = msg.split(",");
if (data[0].equals("1")) {
// 닉네임을 멤버변수에 일단 저장
nickName = data[1];
// System.out.println("nickName:"+nickName);
// 각 클라이언트에 보낼 메세지
String sendMessage = nickName + " 님이 입장하였습니다\n";
// 서버창에도 메세지 출력
area.append(sendMessage + "\n");
// 모든 클라이언트에 보내야한다
for (ChatClient cc : list) {
cc.pw.write(list.size() + "!" + sendMessage);
cc.pw.flush();// 캐시에 저장되어있는 메세지를 네트워크를 통해서 비로소 보낸다
}
} else if (data[0].equals("2")) {
String sendMessage = nickName + ">>" + data[1] + "\n";
// 서버창에도 메세지 출력
area.append(sendMessage + "\n");
// 모든 클라이언트에 보내야한다
for (ChatClient cc : list) {
cc.pw.write(list.size() + "!" + sendMessage);
cc.pw.flush();// 캐시에 저장되어있는 메세지를 네트워크를 통해서 비로소 보낸다
}
} else if (data[0].equals("9")) {
// 목록에서 해당 닉네임을 찾아서 지우기
for (int i = 0; i < list.size(); i = i + 1) {
ChatClient cc = list.get(i);
if (nickName.equals(cc.nickName)) {
// 삭제
list.remove(i);
break;
}
}
// 밑에 줄에서 nickName 값은 사라졌다고 생각하였다.
// 왜냐하면 클라이언트 연결 한개를 닫고 시스템에서
// 클라이언트 프로그램 한개를 종료시키려면
// 서버프로그램에서도 마찬가지로
// 고 클라이언트와 연결된 소켓객체를 제거해야 하기 때문이다.
// Ex7Client 프로그램을 한개 닫으면
// Ex7Server 프로그램에 연결된 고 클라이언트
// 소켓에 해당하는 ChatClient 내부 클래스의 객체에서
// ChatClient 내부 클래스 객체들이 담긴 벡터 list
// 목록에서 해당하는 닉네임을 찾아서 지운다.
// 그런데 현재는 해당 ChatClient 의 닉네임이 존재한다.
// 아직 리스트에서 삭제하지 않았기 때문이다.
// 그래서 닉네임과 같은 벡터 list 내의 ChatClient의
// 닉네임을 발견하면 벡터 list 내에서 ChatClient 를
// 제거한다. 그런데 여기서 그렇게 되니 ChatClient 객체도
// 함께 삭제되어 날라가겠다고 생각이 든다.
// 그러면 고 ChatClient 객체에서의 nickName 도
// 삭제되니 밑줄에서 nickName 을 불러오면 당연히
// 널값이나 값이 없어서 논리적으로도 오류적으로도 문제가
// 생긴다고 생각한다.
// 그런데
// ChatClient java.util.Vector.remove(int index)
// Vector 클래스으 remove(int index) 메서드는
// 반환하는 값이 있다.
// remove(int index) 메서드는
// 그 벡터에서 제거된 그 요소를 반환한다. 즉,
// 제거된 요소를 반환한다.
// 그 말은 벡터 list 에서 안보이게 하고 싶은
// ChatClient 객체를 삭제하였을 뿐이지
// ChatClient 객체 자체를 삭제하였다는 것은 아니라는
// 것이다. 그래서 밑줄에 제거한 ChatClient 객체
// 의 nickName 변수를 가져다 쓸 수가 있다.
// 설사 remove(int index) 메서드가 벡터에서 제거된
// 그 요소를 반환하지 않더라도
// 벡터 list 에서 제거했던 ChatClient 요소는 아직
// 도 메모리상에 남아있기 때문에 아랫줄처럼
// nickName 변수를
// 가져다가 쓸 수 있다.
String sendMessage = nickName + "님이 퇴장했습니다\n";
// 서버창에도 메세지 출력
area.append(sendMessage + "\n");
// 모든 클라이언트에 보내야한다
for (ChatClient cc : list) {
cc.pw.write(list.size() + "!" + sendMessage);
cc.pw.flush();// 캐시에 저장되어있는 메세지를 네트워크를 통해서 비로소 보낸다
}
// nickName=data[1];
// String sendMessage = nickName+" 님이 나갔습니다.\n";
// area.append(sendMessage+"\n");
//
// for(int i=0;i<list.size();i=i+1)
// {
// ChatClient cc=list.get(i);
// if(cc.nickName==nickName)
// list.remove(i);
// }
//
// for(ChatClient cc:list)
// {
// cc.pw.write(sendMessage);
// cc.pw.flush();
// }
}
} catch (IOException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
}
}
}
}
public void setDesign() {
area = new JTextArea();
JScrollPane sp = new JScrollPane(area);
// this.add(area);
this.add(sp);
}
public static void main(String[] args) {
// TODO Auto-generated method stub
// Thread 가 하는 일 Thread 는 타임슬라이싱 하여
// 문맥교환(Context Switching)을 수행하면서
// CPU 에게 여러개의 일을 동시에 하도록 만드는 일을한다.
// 타임슬라이싱(Time Slicing) : 몇 초 단위로 쪼개는 것
// Thread 의 장점 : 사람이 하면 효율이 매우 떨어져 보이는 작업을
// 컴퓨터가 하면 매우 빠른 속도로 할 수 있다. 사람들 눈에는 동시에 진행되는 것처럼
// 보이기 때문에 사용자 경험(UX)을 좋게 한다는 장점이 있다.
// Thread 의 한계 : 한명의 클라이언트가 요청을 할 때 Thread 를 하나 만들어
// 주도록 코딩을 하면
// 다른 클라이언트의 요청이 동시에 들어왔을 때 대기하지 않고 접근할 수 있게 된다.
// 하지만 수천명의 클라이언트가 요청을 하게 되면 그만큼 Thread 를 많이 만들게 된다.
// 데이타베이스까지 접근하게 되면 Thread를 수 없이 만들어야 하는데
// 이렇게 되면 메모리를 많이 차지하는 문제가 생긴다.
// Thread 의 한계 극복 : 그래서 Scale up(수직적 확장) /
// Scale out(수평적 확장) 두 가지 방법으로 이 문제를 해결할 수 있다.
// Scale up은 컴퓨터의 성능을 업그레이드 하는 것이고
// Scale out은 컴퓨터를 여러대 사용하여 분산시키는 방법이다.
// 기업에서는 Thread pool이라는 기술을 사용하여
// CPU가 풀가동되도록 만들어
// 통신 속도를 빠르게 만든다.
// 사이트의 동시접속자 수를 통계 내서 Thread를 상시 가동시키는 방법이다.
// 이렇게 만들면 Blocking I/O를 하지 않아도 되기 때문이다.
// Blocking I/O 이란 I/O 작업이 진행되는 동안 유저 프로세스는 자신의 작업을
// 중단한 채 대기하는 방식이다.
// 하지만 동시 접속자 수를 통계내는데 있어 한계가 있다.
// 그래서 등장한 것이 L4 장비(라우터)를 설치해서 Thread를 나누는 방법이다.
// 이 때 클라이언트가 재요청시에 초기 요청과 다른 Thread를 만나면
// Session에 있는 유저의 정보를 찾을 수 없게 되는데 두 가지 해결 방법이 있다.
// 하나는 서로 다른 Session을 공유하는 방법이고
// 또 하나는 클라이언트가 한번 사용한 Thread를 연속해서 사용하게
// 만드는 스티키가 있다.
// 하지만 이 방법들은 부하가 걸리기 쉽고 조작하기가 어려워 Session의 한계라고 여긴다.
// Thread 특징 요약
// Thread는 static 함수로 생명 주기가 함수 안에서 시작하고 끝난다.
// Thread는 트립(trip)시 문맥을 알아야한다.
// Thread는 Runnable 타입이여햐하고 반드시 run 함수를 가지고 있어야 한다.
// Main Thread는 Sub Thread를 실행만 시키고 자기 일을 한다.
// Java 는 Thread가 하나라도 돌아가고 있으면 종료되지 않는다.
// Socket 통신
// 웹 서버에서 통신은
// 요청이 들어왔을 때
// 단발적으로 이루어지는데 반해
// 소켓 통신은 연결이 계속 이어지는 stateful 서버를 사용한다.
// Thread를 사용하여 Socket 통신하는 방법
// 1. 서버 소켓과 클라이언트 소켓이 같은 포트에 연결한다.
// 2. 클라이언트가 접속할 때 까지 락을 걸어준다.(accept 함수 사용: 내부
// 적으로 while문이 돌고 있다.)
// 3. OS의 상시 감시자 리스너에 의해 클라이언트의 접속을 확인한다.(accept
// 함수 호출)
// 4. 클라이언트가 접속하면 Thread를 새로 생성하여 서브 소켓과 연결하고
// 서버 소켓과의 연결은 끊는다.
// 5. 클라이언트와 소켓에 각각 Buffer를 달아 서로 통신할 수 있게 만든다.
// 6. 서버 소켓은 또 다른 클라이언트의 통신을 받을 수 있게 된다.
// 메인 스레드 = 데몬 스레드
// 동시에 여러 개의 클라이언트를 처리하는데 있어서 다중 스레딩을 구현한 서버 사용하는 방법
// 에 대해서 알아보자.
// 다중 스레드 서버는
// 클라이언트가 접속 할 때마다 1개 이상의 스레드를 만들어서 처리하므로 자주 사용된다.
// Client
// 값을 밖으로 보냄 - OutputStream
// 값을 읽어들임 - InputStream
// Socket 생성
// Server
// 값을 쓰기위함 - OutputStream
// 값을 읽기위함 - InputStream
// ServerSocket 생성
// Socket Class
// 생성자
// public Socket(InetAddress address, int port)
// IP주소를 나타내는 InetAddress 객체와 포트 번호로 소켓 객체 생성
// 호스트명(IP주소), 포트번호로 소켓 객체를 생성
// public Socket(String host, int port)
// 호스트명(IP주소), 포트번호롤 소켓 객체를 생성
// Client 쪽에서 소켓의 생서자를 생성
// 자신이 접속하고 싶은 서버쪽의 호스트명(IP주소), 서버의 포트번호로 접속
// 소켓을 생성하면 연결은 자동으로 이루어지며(서버측은 서버소켓을 만들고 대기한다 가정)
// 연결 시 오류가 발생하면 IOException 발생
// TCP 연결에는 원격 IP 주소, 원격 포트 등이 사용됨
// ServerSocket Class
// 생성자
// ServerSocket(int port)
// 지정한 포트에 ServerSocket 생성
// 서버 소켓은 요청이 네트워크를 통해 들어올 때까지 기다리고
// 클라이언트 요청에 따라 적업을 한 후 결과를 반환
// ServerSocket 객체를 생성하여 클라이언트가 연결해 오기를 기다림.
// 클라이언트 연결해 올 때마다 요청은 요청큐(Request Queue)에 쌓임
// 각각의 클라이언트 연결에 accept함으로써 요청을 요청큐에서 꺼내고
// Socket 객체가 리턴됨.
// 리턴되는 Socket을 활용하여 클라이언트와 데이타를 주고 받으며
// 멀티쓰레드에서는 Socket을 생성한 쓰레드에 주어서 클라이언트를 상대함.
// Java Socket 통신 구조
// 서버 -> ServerSocket 생성 -> 무한루프에서 요청 기다림
// 1. 클라이언트 request -> 서버 accept -> socket 생성
// (socket 은 client 상대함)
// client - outputstream, inputstream
// socket - outputstream, inputstream
// 스트림을 통해서 통신함
// 2. Java Thread를 통해 소켓을 통해 전달하고 클라이언트의 요청을 상대함
// 소켓통신(스레드로 양방향통신)
// 소켓통신을 처음에 작성할 때 혹시라도 다음과 같이 만들게 될 수도 있다.
// while 문을 사용하지 않을 경우다.
// 그렇게 되면 만약에 일대일로 소켓통신을 하게 만들었다고 가정하였으며 통신방향이
// 양방향이 아니고 한쪽방향으로만 되도록 만들었다고 하자. 쓰는 쪽이 클라이언트
// 이고 읽는 쪽이 서버인채로 말이다. 물론 일대일로 소켓통신을 하더라도
// 양쪽방향으로 통신은 가능하지만 말이다. 그런데 이 때 while 문을 쓰지 않으면
// 클라이언트가 메시지 한 개만 보내면 프로그램이 종료된다. 단 한번만 메세지를
// 받거나 코드를 쓸 때 수동으로 여러번 일일이 입력해야 하면 딱 입력한 그 번수 만큼만
// 메세지를 보내는 것이다. 메세지를 보내는 횟수가 제한적이게 된다. 이럴 때 메세지를
// 한 번만 혹은 제한적인 횟수로 보내고 프로그램이 종료되는 것이 아니라
// 반복적으로 무제한으로 메세지를 보내게 하기 위해서 while 문을 이용해서
// 무제한으로 메세지를 보내더라도 프로그램이 종료되지 않고 계속 돌도록 해본다.
// 일대일로 소켓통신을 하는 것은 한 방향으로만 이루어질 수 밖에 없다.
// 물론 일대일로 소켓통신을 하더라도 양방향으로 통신을 주고받게 할 수는 있지만
// 그렇게 할 때는 주고 받는 순서가 정해지게 되는 문제가 생긴다.
// 일대일로 소켓통신을 해서 양방향으로 통신을 주고받도록 하면 코드에 작성한
// 순서대로 만약에 키브드 메세지 입력을 먼저 받는 코드를 먼저 쓰고 나서 클라이언트로부터
// 받는 메세지를 읽는 코드를 다음 줄에 썼다면 그렇게 순서가 고정되서
// 프로그램을 실행할 때 항상 서버가 메세지를 먼저 쓰고 나서
// 다음에는 클라이언트가 쓴 글을 서버가 읽는 순서로만 진행이되고
// 서버가 먼저 클라이언트에게 받은 글을 읽은 수는 없어서 진행순서가 정해지게 되는 것이다.
// 그리고 계속해서 메세지를 주고받고 싶어서 while문을 썼다하더라도
// while문 내에서도 서버의 읽기 기능과 쓰기 기능을 함께 써버린다면
// 그 때 역시도 읽기기능을 먼저 쓰고 쓰기 기능을 나중에 쓰면 또 순서가 정해져서
// 다른 순서로는 실행할 수가 없다. 그래서 while 문 안에 클라이언트로 부터 받을
// 메세지를 읽는 InputStreamReader를 감싸는 BufferedReader 와
// 클라이언트에게 보내고 싶은 내용을 쓸 때 사용하는
// OutputStreamWriter 를 감싸는 BufferedWriter 그리고 키보드입력을
// 받아야 하는 System.in 을 감싸는 InputStreamReader 를 또 감싸는
// BufferedReader 를 모두 함께 while 문 안에 쓰게 되면
// 자유자재로 주고 받는 게 아니라
// 첫번째는 서버가 보내고싶은 내용을 보내기 위해 키보드입력을 받고나서 내용을
// 보내야만 하고 비로소 그 다음에야 서버가 클라이언트로부터 내용을 받아읽을 수 있다로
// 박혀버려서 거꾸로 반대로
// 클라이언트의 내용을 먼저 받고나서 키보드로 서버가 보낸 내용을 치거나
// 아니면 클라이언트 내용을 받기를 여러번 수십번 한 다음에 보낼 내용을 여러번 치거나
// 하는 활동을 전혀 할 수가 없다.
// 그래서 결국 상태가 지속되는 연결은 맞지만 양방향 통신도 맞다고 할수 있지만
// 정해진 순서가 박혀버려서 항상 지정된 순서로 통신이 되서 자유로운 정해진 순서없이
// 통신을 하게 해주어야만 한다. 이를 어떻게 개선해야 할까?
// 이 문제를 개선하기 위해서는 먼저 쓰레드에 대해서 생각을 해 봐야 한다.
// 쓰레드를 이용하기 전에는
// 프로그램 두개가 실행되고 있다. 서버프로그램과 클라이언트 프로그램 두개이다.
// 그리고 Java 가 실행하는데,
// 각각 프로그램마다 main 스레드 한개가 동기적으로
// 쓰고 읽고를 정해진 순서대로
// 서버 파일과 클라이언트 파일을 왔다 갔다 하면서 작업을 수행하는 중이다.
// 그런데 문제를 개선하기 위해서는
// 서버도 쓰고 읽는 작업을 정해진 순서 없이 무작위로 해야하고
// 클라이언트도 쓰고 읽는 작업을 순서 없이 랜덤으로 해야 한다는 것이다.
// 그래서 서버의 메인 스레드가 읽는 스레드라고 가정한다면
// 서버가 쓰는 서브 스레드가 필요하고
// 클라이언트의 메인 스레드가 쓰는 스레드라고 가정한다면
// 클라이언트가 읽는 기능도 있는 읽기서브스레드가 필요하다.
// 한개의 프로그램 안에 쓰기라는 메인 흐름(컨텍스트,문맥,맥락,주제,특징)을
// 가지고 있을 때
// 읽기라는 다른 흐름(컨텍스트,문맥,맥락,주제,특징)을 추가하고 싶다면
// 그냥 스레드생성없이 읽기라는 흐름을 쓰기 흐름 다음에 끼워 넣는 식으로 하면
// 순서가 생겨서 안되니까
// 읽기라는 다른 흐름(컨텍스트,문맥,맥락,주제,특징)를 서브 흐름, 즉 서브 스레드로
// 가지도록 서브 스레드를 생성해주어야만 한다.
// 그래서 요런 while 문을 사용하여 지속적인 메세지를 주고 받는
// 순서없이 랜덤하게 자유로운 양방향 통신에서는
// 반드시 스레드를 사용해야 하고
// Thread 클래스 보다는 Runnable 인터페이스로 모든 메서드를 다 상속받아
// 번거롭지 않게 필요한 메서드만 구현하도록 한다. Thread 클래스를 사용하지 말고
// Runnable 인터페이스를 이용하여 구현하도록 하자.
// Runnable 인터페이스를 구현하는 것은 클라이언트 파일과 서버파일 모두 다
// 구현해야만 하고, while 문도 클라이언트 파일과 서버파일 모두 다 이용해야만 한다.
// 처음에는 스레드를 쓰는 것, 즉 Runnable 인터페이스(규격) 를 쓰는 이유가
// 단지 메세지를 주고 받을 클라이언트 객체를 여러개 생성해서 동시에 실행되게 해야하므로
// Runnable 인터페이스(규격) 를 클라이언트 클래스가 구현한다고 생각했다.
// 하지만, 서버 프로그램은 한개만 있어야 하고 클라이언트는 다수개가 있어야 하므로
// 서버 파일은 Runnable 인터페이스 (규격) 를 구현해야할 필요가 없다고 생각하였었지만
// 여기서의 Thread 클래스 사용 이유는 생성할 객체 갯수의 문제가 아니라
// 글읽기 역할(흐름,맥락,문맥,주제,특징)과 글쓰기 역할(흐름,맥락,문맥,주제,특징)
// 이렇게 역할이 두 개 이상이 되어 그것을 동시에 순서없이 비동기적으로 실행하기 위해
// Thread 클래스를 쓰게
// 된다.
// 웹도 결국 소켓이다.클라이언트가 브라우저로 네이버 서버에 연결을 하게 되면 네이버에서는
// 데이타를 주는 기능을 써야 하니까 BufferedWriter가 필요하고 브라우저는
// 클라이언트라서 서버에게 받은 것을 읽어야 하니까 BufferedReader가 필요할 것이고
// 클라이언트(또는 브라우저)는 키보드로 입력해야 하니까 키보드 BufferedReader
// 가 필요하다.그리고 서로 응답과 요청을 하려면 바이트스트림이 필요하고
// 네이버에서 HTML 코드를 받아낼 때는 버퍼를 사용한 뒤 비워줘야 하니까 flush()를
// 사용할 것이라고 한다.웹에서 이루어지는 통신은 HTTP통신규칙을 따른다.이 HTTP통신
// 은 소켓통신이다.다만 차이가 나는 것들 중 하나가 HTTP통신에서는 서버 부하를 줄이기
// 위해서 많은 노력을 하는데 그 중 하나가 stateless서버로써 일정 시간이 지나거나
// 클라이언트가 나가면 연결을 끊어버리는 것이고
// 소켓통신은 stateful서버로 연결이 끊기지 않고 지속된다고 한다.
// Java 비동기 처리 방법 - Thread
// Java 에서 비동기 처리를 위해 스레드를 사용할 수 있다.
// 다른 서버로 요청을 보낼 때 비동기 방식을 사용할 수 있다.
// 하지만 비동기 처리를 하게 되면
// 병렬작업 처리량이 많아져서 성능이 저하되는데, 이를 막기 위해서 스레드 풀을 사용해야 한다.
// 스레드풀은 스레드 갯수를 미리 정해 놓고, 작업 큐에 들어오는 요청을 미리 생성해 놓은
// 스레드들에게 할당하는 방식이다.
// JDK 1.5 부터는 java.util.concurrent Package 에서
// ExecutorService 인터페이스와 Executors 클래스를 제공한다고 한다.
// 위 문제를 해결하기 위해 스레드 풀을 적용하지만 스레드 풀도 단점이 있다고 한다.
// 100 개의 스레드를 만들어 놓았지만 실제로 10 개의 요청만 들어온다면
// 나머지 90 개의 스레드는 메모리만 차지하게 되며 낭비가 발생한다.
// 또한 작업 완료 소요 시간이 다를 경우 유휴 시간이 발생한다.
// 예를 들어 A, B, C, D 스레드가 있을 경우
// A, B 는 이미 처리가 끝났는데 C, D 는 완료되지 않았을 경우
// A, B 스레드는 C, D 의 작업이 끝날 때까지 기다리게 된다.
// 이를 방지하기 위해 JDK 1.7 부터는 forJoinPool 을 제공한다.
// 쓰레드는 프로그램(프로세스) 안에서 실행되는 하나의 흐름 단위다.
// 내부에서 while 문을 돌면 엄청 오랫동안 일을 할 수도 있다.
// 쓰레드 끼리는 값 (메모리) 을 공유 할 수도 있다.
// 가끔 쓰레드 A 와 쓰레드 B 가 서로 공유하는 값을 바꾸어서 곤란에 빠지기도 한다.
// 예를 들면 스레드 A 가 알람을 6시로 맞춰 놓았는데 스레드 B 가 8시로 맞추어 놓아
// 지각을 하고 말기도 한다고 한다.
// 스레드는 필요할 때 마다 OS 공장에서 사용하고, 다 사용하고 나면 공장에서
// 수거해 간다. 스레드 A 는 DB 접속을 하고 스레드 B 는 UI 드로잉을 할 수 있다.
// 각각의 스레드는 동시에 자기가 맡은 일을 하기 때문에 빠르게 처리가 된다고 한다.
// 스레드는 동일한 메모리 영역에서 생성되고 관리되어 상태 변이 속도가 프로세스 보다
// 빠르지만 생성/수거에 드는 비용이 있다. 생성/수거에 드는 비용을 줄이고
// 재사용성을 높이기 위해 풀장에다가 여러개의 스레드를 먼저 만들어두고 사용한다.
// 큐같은 자료구조를 가진 클래스에 스레드를 미리 몇개 만들어서 놓아두면
// 그것이 스레드 풀이라고 한다. 작업 할 것이 있을 때 스레드풀에 있는 스레드들 중에
// 나 지금 한가하다고 표식을 알리는 스레드놈을 찾아서 일을 시키면 된다고 한다.
// 일하는 놈을 따로 모아두고, 일 안하는 놈을 따로 모아두면 더 일 시키기가 쉽다.
// 실제로 스레드 풀에서 일 하는 놈 일 안하는 놈을 관리하는 기술이 아주 다양하다고 한다.
// 다만 스레드가 하나 뿐이어도 충분히 일 할 수 있는데 10 개씩 미리 만들어 둘 필요
// 는 없다. Java 5 에서는 고런 것을 만들어주는 방법이 생겼다고 한다.
// 1. 1분에 한번씩 임무를 수행 시키기 위한 스레드풀
// 1초에 한 개씩 DB 에 값이 들어갈 때, 1분 평균을 구해서 다른 테이블에 집어 넣을 때
// 필요하다고 하다.
// newScheduledThreadPool(int corePoolSize)
// 2. 스레드 풀장에 스레드를 고정적으로 몇개를 놀게 해줄지 정하는 방식도 제공한다.
// 항상 비슷하게 일이 많다면 이렇게 만들어준다.
// newFixedThreadPool(int nThreads)
// 3. 어쩔때는 일이 없지만 어쩔때는 일이 많아 질때는 어떻할까?
// 그 때는 유기적(유동적)으로 스레드의 숫자가 증가하고 감소하는
// 스레드풀도 있다고 한다.
// newCachedThreadPool()
// 이런 스레드풀에게 어떤 업무가 배달되면 서로 간에 자기가 하겠다고 다투기도 한다고한다.
// 배달되는 편지(업무)를 보관하는 편지함(업무함)이 하나인데
// 서로 편지함(업무함)을 열어 보겠다고 경쟁을 벌인다고 한다.
// 그래서 어떤 경우는 쓰레드 자기 만의 고유 편지함(업무함)을 따로 만든다고 한다.
Ex7Server server = new Ex7Server("서버");
Thread th1 = new Thread(server);
th1.start();
}
}
프로젝트 내 src 내 javabasic 패키지 내 Ex7Client.java
package javabasic;
import java.awt.Color;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.WindowAdapter;
import java.awt.event.WindowEvent;
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 javax.swing.JButton;
import javax.swing.JFrame;
import javax.swing.JLabel;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;
/*
* : 서버가 언제 메세지를 보내올지 모르므로 스레드가 필요하다(while문으로 항상
* 메세지를 받을 대기상태)
*
* [문제] 만약 미자가 창을 닫고 나간다면
* 미자를 제외한 모든 클라이언트들에게
* "미자님이 퇴장하였습니다" 라고 출력되도록 하기
*/
public class Ex7Client extends JFrame implements Runnable, ActionListener {
/**
*
*/
private static final long serialVersionUID = 1L;
JTextField txtName, txtServerIp, txtMsg;
JTextArea area;
JButton btnConnect, btnSend;
Socket socket;
BufferedReader br;// 서버로부터 메세지 읽기위한 변수
PrintWriter pw;// 서버로 메세지를 보내기 위한 변수
JLabel pSu;
public Ex7Client(String title) {
// TODO Auto-generated constructor stub
super(title);
this.setBounds(700, 100, 400, 530);// 시작위치x,y,크기 w,h
// super로 해도 되고 this로 해도 됨 super는 조상
// this로 해도 상속을 받아서 괜찮음
// this.getContentPane().setBackground(Color.orange);//프레임위에 있는 패널의 색상 변경
this.getContentPane().setBackground(new Color(211, 225, 208));// 프레임위에 있는 패널의 색상 변경
this.setDesign();// 디자인 코드
this.setVisible(true);// 보이게 하기
// this.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);//프로그램을 종료해주는 메서드
// 종료시 이벤트 발생
this.addWindowListener(new WindowAdapter() {
@Override
public void windowClosing(WindowEvent e) {
// TODO Auto-generated method stub
if (pw != null) {
// 서버로 종료 코드로 9를 보내보자
pw.write("9,\n");
pw.flush();
// String sendMessage="9,"+txtName.getText()+"\n";
// pw.write(sendMessage);
// pw.flush();
}
System.out.println("시스템 종료합니다");
System.exit(0);// 시스템 종료
super.windowClosing(e);
}
});
}
@Override
public void run() {
// TODO Auto-generated method stub
System.out.println("client run");
// 서버가 보내는 메세지를 읽어서 채팅창에 출력하기
while (true)// 언제보낼지모르므로 true로 대기상태로해놓음
{
try {
String originLine = br.readLine();// 서버가 보낸 문자열 읽기
String cnt = originLine.substring(0, originLine.indexOf("!"));
String line = originLine.substring(originLine.indexOf("!") + 1);
if (line == null)
break;
// 채팅창에 추가
area.append(line + "\n");
pSu.setText(cnt + " 명이 접속중입니다");
} catch (IOException e) {
// TODO Auto-generated catch block
// e.printStackTrace();
}
}
}
@Override
public void actionPerformed(ActionEvent e) {
// TODO Auto-generated method stub
Object ob = e.getSource();
// 메세지 입력후 엔터 또는 전송 버튼 누를경우
if (ob == txtMsg || ob == btnSend) {
// 서버에 메세지 전송
String sendMessage = "2," + txtMsg.getText() + "\n";
pw.write(sendMessage);
pw.flush();
// 입력창 지우기
txtMsg.setText("");
// 포커스
txtMsg.requestFocus();
}
}
public void setDesign() {
this.setLayout(null);
JLabel lbl1 = new JLabel("닉네임");
lbl1.setBounds(10, 10, 50, 25);
this.add(lbl1);
txtName = new JTextField();
txtName.setBounds(70, 10, 80, 24);
this.add(txtName);
JLabel lbl2 = new JLabel("서버ip");
lbl2.setBounds(160, 10, 50, 25);
this.add(lbl2);
// 밑줄에 JTextField 생성자 안에 argument 는
// cmd.exe 창에서 ipconfig 명령어를 때려서
// 나오는 IPv4 주소를 쓰면 된다. 즉, 자기 아이피 주소를 쓴다.
// 여기서는 서버도 자기 아이피 주소 이거나 홈 네트워크 내에 있는
// 공유기에 연결된 다른 기기의 아이피 주소 이므로
// 즉, 사설 네트워크에 있는 사설IP 주소이므로
// ipconfig 명령을 때려 나온
// IPv4 주소를 쓰면 된다.
// 사설 IP 주소를 가진 기기들은 해당 자원이 속한 네트워크 내에서만 서로 요청이
// 가능하다.
txtServerIp = new JTextField("192.168.0.7");//argument 로는 경우에 따라 서버사설IP주소나 서버공인IP주소(외부 서버IP주소)를 넣는다.
// txtServerIp=new JTextField("127.0.0.1");//local ip 자기아이피 안될때 로컬호스트로하기
// txtServerIp=new JTextField("localhost");//local ip 자기아이피 안될때 로컬호스트로하기
txtServerIp.setBounds(220, 10, 90, 24);
this.add(txtServerIp);
pSu = new JLabel("접속 사람 수");
pSu.setBounds(10, 45, 360, 25);
this.add(pSu);
btnConnect = new JButton("접속");
btnConnect.setBounds(320, 10, 60, 25);
this.add(btnConnect);
// 접속 이벤트
btnConnect.addActionListener(new ActionListener() {
@Override
public void actionPerformed(ActionEvent arg0) {
// TODO Auto-generated method stub
String name = txtName.getText();// 접속한 닉네임
String ip = txtServerIp.getText();// 접속할 서버 아이피
// 창제목을 닉네임으로 변경
Ex7Client.this.setTitle(name);
// 서버에 접속하기
try {
// java.net.Socket 클래스
// 생성자
// 소켓을 만들어 주어진 원격지 주소와 원격지 포트에 연결한다.
// 요 생성자는 소켓을 연결한다.(즉, 생성자가 반환되기 전에
// 원격 호스트에 대한 실제 연결이 생성된다.)
// IP 주소는 네트워크 상의 컴퓨터 또는 시스템을 식별하는 주소이고
// 포트 번호를 이용하여 통신할 응용 프로그램을 식별할 수 있고
// 데이타 교환을 할 수 있다.
// Socket(String host, int port) 생성자 메서드에서
// String host 파라메터 에는
// 연결하고자 하는 서버 IP 주소를 argument로써 넣는다.
// Socket(String host, int port) 생성자 메서드는
// String host 파라메터와 int port 파라메터를 사용하여
// Socket 객체를 생성한다.
// 여기서 String host 는 서버의 주소(문자열 주소)이다.
// Socket 생성과 연결 요청: 클라이언트가 서버에 연결 요청을
// 할 때 java.net.Socket 클래스를 사용한다.
// Socket 객체를 생성함과 동시에 연결 요청을 하려면
// 생성자으 파라메터의 argument로써
// 서버의 IP주소와 바인딩포트번호를 제공하면 된다.
// Socket 클래스 객체 생성과 동시에
// 서버에 연결을 요청하는 문은 밑엣줄로 쓴다.
socket = new Socket(ip, 7000);
// 윗줄을 쓰면 클라이언트단에서 서버단에 있는
// ServerSocket 클래스 객체에 연결을 요청하게 된다.
// 즉, 서버단에서 작성한 ServerSocket 클래스의
// 객체 serverSocket 에 연결을 요청하는 것이다.
// 서버단에서는
// serverSocket.accpet() 메서드를 써서
// 클라이언트를 무한 대기중인 블로킹 상태이다.
// 그래서 socket = new Socket(ip, 7000); 줄을
// 쓰면 Socket 클래스 객체 생성을 하고 바로
// 서버단에 작성된
// serverSocket.accept() 메서드로 간다.
// serverSocket 은 accept() 메서드를 작성한 지점에서
// 클라이언트가 연결을 요청했음을 감시하다가 발견하여
// accept() 메서드로 연결을 받아들이고 통신하기위한
// Socket 객체를 생성해 반환하고, 고 Socket 객체를 통해
// 데이타를 주고 받는다.
// io 클래스 생성
try {
br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
pw = new PrintWriter(socket.getOutputStream());
// run 메소드 호출
Thread th = new Thread(Ex7Client.this);
th.start();
// 서버로 접속한 닉네임 보내기
String sendMessage = "1," + txtName.getText() + "\n";
pw.write(sendMessage);
pw.flush();// 서버로 메세지 전송
btnConnect.setEnabled(false);
} catch (IOException e0) {
// TODO Auto-generated catch block
// e0.printStackTrace();
}
} catch (UnknownHostException e1) {
// TODO Auto-generated catch block
// 도메인 네임 서버(DNS)가 동작하지 않거나 호스트네임을
// 주소로 변환할 수 없는 경우 UnknownHostException
// 예외를 던진다.
e1.printStackTrace();
System.out.println("서버주소 오류:" + e1.getMessage());
} catch (IOException e1) {
// TODO Auto-generated catch block
// UnknownHostException 예외 외에 다양한 이유로
// 소켓을 열 수 없는 경우 IOException 예외를 던진다고 한다.
// 연결을 시도하는 대상 호스트의 포트가 연결을 허용하지 않을 경우
// 호텔 와이파이 서비스가 호텔 웹 사이트에 로그인하고 돈을 결제
// 할 때 까지 차단한 경우 그래서 돈을 결제해야지만 연결이 성공하며
// , 또는 라우터의 경로 제어 문제로 패킷을 목적지로 보낼 수 없는
// 경우도 있다고 한다.
// IOException 이 던져 질 때는 Socket 생성자 메서드를
// 실행 할 때 네트워크의 연결 실패, 방화벽 때문에 서버에 접근
// 할 수 없을 때 던져진다.
e1.printStackTrace();
}
}
});
// 채팅창과 메세지 입력하는곳 추가하기
area = new JTextArea();
JScrollPane sp = new JScrollPane(area);
sp.setBounds(10, 70, 360, 350);
this.add(sp);
txtMsg = new JTextField();
txtMsg.setBounds(10, 430, 300, 25);
this.add(txtMsg);
btnSend = new JButton("send");
btnSend.setBounds(310, 430, 70, 25);
this.add(btnSend);
// 이벤트
txtMsg.addActionListener(this);// 입력후 엔터 누르면 발생하는 이벤트
btnSend.addActionListener(this);// 전송 누르면 발생
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Ex7Client ex = new Ex7Client("client");
}
}
Ex7Client.java 파일을 Ex7Server.java 파일을 실행시키지 않은 상태로
단독으로 실행시키면 프로그램 GUI 가 화면에 뜬다.
하지만 접속 버튼을 누르면 접속이 되지 않고
Ex7Client.java 코드 내에
socket = new Socket(ip, 7000);
윗줄에서
예외가 발생한다.
게다가 그 상태에서 Ex7Client.java 로 띄어진 창으 닫기 버튼을 누르면
PrintWriter 객체인 pw 변수가 할당되지 않아 NullPointerException 도 발생한다.
클라이언트용 소켓과 서버용 소켓이 있을 때
클라이언트용 소켓 단독으로 생성 할 수 없다.
반드시 서버용 소켓을 먼저 생성해야지만 클라이언트용 소켓도 생성할 수 있다.
Ex7Server.java 를 먼저 실행한 다음에
Ex7Client.java 를 실행시켜 접속버튼을 누른다.
java.net.ConnectException: Connection refused: connect
at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:75)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:476)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:218)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:200)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:162)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:394)
at java.net.Socket.connect(Socket.java:606)
at java.net.Socket.connect(Socket.java:555)
at java.net.Socket.<init>(Socket.java:451)
at java.net.Socket.<init>(Socket.java:228)
at javabasic.Ex7Client$2.actionPerformed(Ex7Client.java:144)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2022)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2348)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:262)
at java.awt.Component.processMouseEvent(Component.java:6539)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
at java.awt.Component.processEvent(Component.java:6304)
at java.awt.Container.processEvent(Container.java:2239)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2297)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4904)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4535)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4476)
at java.awt.Container.dispatchEventImpl(Container.java:2283)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:760)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:84)
at java.awt.EventQueue$4.run(EventQueue.java:733)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:730)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Exception in thread "AWT-EventQueue-0" java.lang.NullPointerException
at javabasic.Ex7Client$1.windowClosing(Ex7Client.java:57)
at java.awt.Window.processWindowEvent(Window.java:2054)
at javax.swing.JFrame.processWindowEvent(JFrame.java:305)
at java.awt.Window.processEvent(Window.java:2013)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2297)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:760)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:84)
at java.awt.EventQueue$4.run(EventQueue.java:733)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:730)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
Ex7Client.java 프로그램은 여러 번 실행할 수 있다. 이미 실행되고 있어도 Eclipse IDE 메뉴에
Run > Run 을 누르거나
단축키인 Ctrl + F11 을 여러번 눌러 여러 개의 Java Application 을 실행해도 된다.
그러나
Ex7Server.java 프로그램은 논리적으로 여러 번 실행할 수 없다.
실제로는 여러 번 실행 할 수는 있지만 ServerSocket 객체를 여러 개 생성하지는 않는다.
실제로 Ex7Server.java 를 실행하면 처음에는 서버 소켓 생성 성공이라는 메세지도 창과 함께
뜨지만, 두번째 부터 실행하면 Ex7Server.java Java Application GUI 창은 뜨지만
서버 소켓을 여러개 생성하지를 않게 해놓았기 때문에 창에는 다음과 같이 예외를 던져 출력한다.
서버 소켓 생성 실패
Address already in use: JVM_Bind
계속해서 Ex7Server.java 를 실행하면 GUI 창은 제대로 뜨지만 서버 소켓 생성 실패라는 메세지와 예외를 던지고
Ex7Server.java 코드 안에서도 ServerSocket 클래스의 객체는 오로지 한 개만 생성해 놓는다. Server 는 논리적으로 하나만 둔다.
즉, Ex7Client.java 클라이언트 프로그램은 여러번 생성 할 수 있고,
Ex7Server.java 클라이언트 프로그램은 여러번 실행해서 여러 개의 객체를 만들지 않는다. 이럴 때는 서버 프로그램은 싱글톤 패턴으로 만든다. 하지만 싱글톤 패턴은 문제가 있다. 싱글톤 패턴을 구현하는 코드 자체가 많이 필요하다고 하다. 멀티스레딩 환경에서 발생할 수 있는 동시성 문제 해결을 위해 synchronized 키워드를 사용해야 한다고 한다. Ex7Server 는 Runnable 인터페이스 (규격) 를 구현한 클래스 이므로 싱글톤 패턴을 구현해도 스레드를 여러개를 생성할 수 있어서 스레드마다 생성되는 Ex7Server 클래스의 객체는 다르므로 싱글톤 패턴을 구현해도 여러 프로그램 창을 띄울 수 있다.
Ex7Server.java 를 다른 컴퓨터에서 실행시킨다.
Ex7Client.java 를 Ex7Server.java 를 실행시킨 다른 컴퓨터에서 마찬가지로 실행시킨다.
연결이 된다.
이번에는 Ex7Client.java 를 Ex7Server.java 를 실행시킨 기기와 다른 컴퓨터에서 실행시킨다.
java.net.ConnectException: Connection timed out: connect
at java.net.DualStackPlainSocketImpl.connect0(Native Method)
at java.net.DualStackPlainSocketImpl.socketConnect(DualStackPlainSocketImpl.java:75)
at java.net.AbstractPlainSocketImpl.doConnect(AbstractPlainSocketImpl.java:476)
at java.net.AbstractPlainSocketImpl.connectToAddress(AbstractPlainSocketImpl.java:218)
at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:200)
at java.net.PlainSocketImpl.connect(PlainSocketImpl.java:162)
at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:394)
at java.net.Socket.connect(Socket.java:606)
at java.net.Socket.connect(Socket.java:555)
at java.net.Socket.<init>(Socket.java:451)
at java.net.Socket.<init>(Socket.java:228)
at javabasic.Ex7Client$2.actionPerformed(Ex7Client.java:192)
at javax.swing.AbstractButton.fireActionPerformed(AbstractButton.java:2022)
at javax.swing.AbstractButton$Handler.actionPerformed(AbstractButton.java:2348)
at javax.swing.DefaultButtonModel.fireActionPerformed(DefaultButtonModel.java:402)
at javax.swing.DefaultButtonModel.setPressed(DefaultButtonModel.java:259)
at javax.swing.plaf.basic.BasicButtonListener.mouseReleased(BasicButtonListener.java:262)
at java.awt.Component.processMouseEvent(Component.java:6539)
at javax.swing.JComponent.processMouseEvent(JComponent.java:3324)
at java.awt.Component.processEvent(Component.java:6304)
at java.awt.Container.processEvent(Container.java:2239)
at java.awt.Component.dispatchEventImpl(Component.java:4889)
at java.awt.Container.dispatchEventImpl(Container.java:2297)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.LightweightDispatcher.retargetMouseEvent(Container.java:4904)
at java.awt.LightweightDispatcher.processMouseEvent(Container.java:4535)
at java.awt.LightweightDispatcher.dispatchEvent(Container.java:4476)
at java.awt.Container.dispatchEventImpl(Container.java:2283)
at java.awt.Window.dispatchEventImpl(Window.java:2746)
at java.awt.Component.dispatchEvent(Component.java:4711)
at java.awt.EventQueue.dispatchEventImpl(EventQueue.java:760)
at java.awt.EventQueue.access$500(EventQueue.java:97)
at java.awt.EventQueue$3.run(EventQueue.java:709)
at java.awt.EventQueue$3.run(EventQueue.java:703)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:84)
at java.awt.EventQueue$4.run(EventQueue.java:733)
at java.awt.EventQueue$4.run(EventQueue.java:731)
at java.security.AccessController.doPrivileged(Native Method)
at java.security.ProtectionDomain$JavaSecurityAccessImpl.doIntersectionPrivilege(ProtectionDomain.java:74)
at java.awt.EventQueue.dispatchEvent(EventQueue.java:730)
at java.awt.EventDispatchThread.pumpOneEventForFilters(EventDispatchThread.java:205)
at java.awt.EventDispatchThread.pumpEventsForFilter(EventDispatchThread.java:116)
at java.awt.EventDispatchThread.pumpEventsForHierarchy(EventDispatchThread.java:105)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:101)
at java.awt.EventDispatchThread.pumpEvents(EventDispatchThread.java:93)
at java.awt.EventDispatchThread.run(EventDispatchThread.java:82)
위와 같이
java.net.ConnectException: Connection timed out: connect
예외를 던진다.
이는 연결이 안된 것이다.
Connection refused 같은 예외를 던진다면 IP주소에는 연결이 되나 port번호에 연결이 안된 경우가 많다고 하며
위 코드블럭에서 보는 바처럼 Connection timed out 예외를 던지는 경우는 대부분 서버(Ex7Server.java 를 실행시키는 서버단 컴퓨터) 방화벽을 막은 경우가 대부분이라고 한다.
IntelliJ IDEA 에서 Ex7Server.java 와 Ex7Client.java 코드를 실행시킨다.
Ex7Server.java 파일로 작성된 클래스와 Ex7Client.java 파일로 작성된 클래스 모두 Runnable 인터페이스 (규격) 를 구현하였으므로 메인 스레드와 동시에 병렬로 Ex7Server 클래스의 다른 여러개의 인스턴스를 더 실행할 수 있고, 마찬가지로 Ex7Client 클래스의 인스턴스를 실행하고 나서도 메인 스레드와 동시에 병렬로 Ex7Client 클래스의 인스턴스를 더 많이 여러개 동시에 실행할 수 있다.
IntelliJ IDEA 에서는 처음 실행 하면 Runnable 인터페이스 (규격) 를 구현했더라도 그 클래스의 인스턴스를 오직 한개만 돌릴 수 있다. Runnable 인터페이스 (규격) 를 구현한 클래스를 한 번 실행하고 나서 그 이상 실행하려고 하면 알림 표시가 뜬다. Process Is Running 제목과 밑에 Ex7Server.java is not allowed to run in parallel. Would you like to stop the running one? 과 같은 식으로 알려주면서 원래 실행한 한개의 객체를 정지하고 나서 작동시키겠는지를 선택하라고 알려준다.
IntelliJ IDEA 메뉴에서 병렬로 클래스 객체 여러개를 동시에 실행하는 것을 허용해야 한다.
IntelliJ IDEA 오른쪽 상단 바에 Build Project 아이콘 옆에 javabasic.Ex7Client 라고 패키지이름.클래스이름 이라고 쓰여져
있고 그 바로 옆에 뒤집어진 삼각형이 있다. javabasic.Ex7Client 와 뒤집어진 삼각형을 포함한 영역을 클릭하면 drop down
메뉴가 나온다. Edit Configurations... 를 클릭한다.
Build and run 영역에서 보면 오른쪽에 Modify options 와 아랫쪽화살표가 쓰여져 있다.
Modify options 를 클릭하면 drop down 메뉴가 나온다.
Add Run Options 드롭다운 메뉴 중에
Operating System 영역에서 보면
Allow multiple instances 라고 쓰여져 있다.
Allow multiple instances 를 클릭하면 체크되면서 체크표시가 앞에 붙는다.
Apply > OK 를 선택한다.
Ex7Server.java 클래스 파일도 위에 쓴 과정과 마찬가지로
IntelliJ IDEA 오른쪽 상단 바에 Build Project 옆에 있는 네모칸에 쓰인 클래스이름과 뒤집어진 삼각형이 있는 영역을 선택해서 Edit Configurations... 을 눌러서 설정을 한다.
위의 코드를 실행하면 사설 네크워트 내에서는 클라이언트에서 서버로 접속할 때 예외가 발생하지 않는다.
사설 IP (Private IP) 은 일반 가정이나 회사 내 등에 할당된 네트워크의 IP 주소이며, 로컬 IP주소, 가상 IP주소 라고도 한다.
서브넷팅된 IP주소 이며 라우터(공유기)에 의해서 로컬 네트워크 내에 있는 PC 나 장치(기기)에 할당되는 IP주소이다.
사설 IP 주소대역은 고정된다.
Class A : 10.0.0.0 ~ 10.255.255.255
Class B : 172.16.0.0 ~ 172.31.255.255
Class C : 192.168.0.0 ~ 192.168.255.255
가정에서는 주로 Class C 를 사설 IP 주소대역으로 사용하는 듯 하다..
사설 IP 과 공인 IP 으 차이는
공인 IP(Public IP) 을 할당하는 주체는 인터넷 서비스 공급자인 ISP 이고
사설 IP(Private IP) 을 할당하는 주체는 라우터(공유기) 이다.
공인 IP 이 할당되는 대상에는 개인이나 회사의 서버(라우터)가 있다.
사설 IP 이 할당되는 대상에는 개인이나 회사의 기기가 있다.
공인 IP 은 인터넷 상에서 유일한 주소라는 고유성을 가지고
사설 IP 은 하나의 네트워크 안에서 유일하다라는 고유성이 있다.
공인 IP 은 내부/외부에서 모두 접근 가능하지만
사설 IP 은 외부에서는 접근이 가능하지 않고 내부에서만 접근할 수 있다.
사설 IP 주소만 가지고는 인터넷에 직접 연결할 수 없다. 라우터(공유기)를 통해 1개의 공인(Public) IP 만 할당받고,
라우터(공유기)에 연결된 개인 PC 나 다른 기기(장치)들은 사설 IP를 각각 하나씩 할당 받아서
공유기(우편사서함같은 역할을 하는 라우터)를 통해서
인터넷에 접속할 수 있게 된다.
고정 IP 과 유동 IP
고정 IP 은 컴퓨터에 고정적으로 부여된 IP 로 한번 부여되면 IP 주소를 반납하기 전까지는 다른 장비에 부여할 수 없는 IP 주소라고 한다.
유동 IP 은 장비에 고정적으로 IP 주소를 부여하지 않고 컴퓨터를 사용할 때 남아 있는 IP 중에서 돌아가면서 부여하는 IP 주소를 말한다고 한다.
인터넷 내에서 서버를 운영하고자 할 때는 공인 IP 주소를 고정 IP 주소로 부여해야 한다는 것이 중요하다고 한다.
즉, 공인 IP 주소를 부여받지 못하면 다른 사람이 내 서버에 접속하지 못하고, 고정 IP 주소를 부여하지 않으면 내 서버가 아닌 다른 사람의 서버로 접속이 될 수 있기 때문이다.
하지만 일반 가정집에서 사용하는 주소를 보면, 인터넷 서비스 제공자 업체는 각 가정마다 공인 IP 주소를 유동 IP 주소로 부여한다고 한다. 그리고 Home Router(공유기) 내부에서는 사설 IP 주소를 유동 IP 주소로 부여하는 것이 일반적이다라고 보면 된다.
즉, 위 코드는
1 클라이언트와 서버를 실행하는 컴퓨터가 똑같은 컴퓨터일 경우
2 하나의 공유기(라우터) 내에서, 즉 같은 네트워크에서 서버를 실행하는 컴퓨터와 클라이언트를 실행하는 컴퓨터가 서로 다른 컴퓨터 일 경우
위 2가지 경우에는 클라이언트에서 서버로 접속이 되지만
클라이언트를 실행하는 컴퓨터와 서버를 실행하는 컴퓨터가 각각 다른 네트워크에 속해있을 때는 접속이 안되어 예외를 던진다.
A 네트워크에 있는 클라이언트를 실행하는 컴퓨터에서
B 네트워크에 있는 서버를 실행하는 컴퓨터에 접속을 시도할 때는
네트워크가 달라서 라우터(공유기)가 할당한 사설 ip 주소로 접속을 하면
프로그램에서 예외를 던진다.
가정에서는 Home Router(공유기) 를 사용하기 때문에
다른 네트워크에 있는 클라이언트 프로그램이
가정 내 Home Router(공유기)가 만든 다른 사설 네트워크에 존재하는 컴퓨터에서 실행되는 서버 프로그램에 접속을 할 때
공유기가 사설 가정 네트워크에서 서버단인 컴퓨터에 할당한 주소로
클라이언트가 접속을 시도하면 접속이 되지않는 예외가 발생한다.
그래서 포트 포워딩을 해줘야 한다고 한다.
포트 포워딩은 외부 네트워크에서 공유기와 연결된 기기에 접속을 하려고 할 때
공유기의 특정(지정된) 포트로 접속하면
공유기가 만든 사설 네트워크 내의 특정(지정된) 컴퓨터의 설정된 포트로 접속하게 해주는 기술이라고 한다.
ipTIME 공유기를 사용하기 때문에
공유기 설정을 하기 위해
브라우저 주소창에 192.168.0.1 을 때린다.
로그인 후
관리도구 아이콘을 클릭한다.
메뉴탐색기 내에
고급 설정 > NAT/라우터 관리 > 포트포워드 설정
을 클릭한다.
오른쪽 화면 하단에
규칙이름에는 자신이 원하는 아무 이름을 입력한다.
옆에 드롭다운 메뉴목록에서는 포트포워드 사용자정의 를 그대로 선택한다.
내부 IP주소 는 서버 프로그램을 실행할 서버단 컴퓨터 주소를 입력한다.
이 때 서버단 컴퓨터 주소는 ipTIME 공유기가 할당한 사설 IP 주소를 쓴다.
아니면 현재 컴퓨터를 서버단 컴퓨터로 사용하고자 한다면
옆에 쓰여진 현재 접속된 IP 주소 에 체크한다.
프로토콜 은 자신이 쓴 프로그램에 맞는 통신의 프로토콜을 설정해 주면
된다고 한다. 거의 대부분이 TCP 이라고 한다. 드롭다운 메뉴목록에서
TCP 을 선택한다.
외부 포트는 공유기의 포트번호이다. 원하는 포트번호 범위를 선택해 주면
된다고 한다. 본인은 외부 네트워크의 컴퓨터에서 Home Router(공유기)의
7000 포트로 접속하였다면
공유기로 만들어진 사설 네트워크의 서버단
컴퓨터의 7000 포트번호로 접속시키도록 할 것이라서
외부 포트 란에 적기위한 두개의 빈칸을
7000 ~ 7000
이라고 썼다.
내부 포트 란에는 외부 포트로 접속을 하였을 때 공유기(우편사서함 같은 역할)로 하여금
비로소 사설네트워크의 서버단 컴퓨터의 몇 번 포트번호에 접속하게 할 것인지를
쓰는 것이다. 본인은 두개의 빈칸에
7000 ~ 7000
이라고 쓴 후 오른쪽 하단에 적용 버튼을 눌렀다.
적용 후에는 ipTIME 공유기 GUI 화면 상단 우측에 저장 아이콘을 눌러 설정을
반드시 저장하도록 한다.
포트 포워드 설정 후에는
Ex7Server.java 를 Home Router(공유기) 로 만들어진 사설 네트워크의 서버단 컴퓨터, 즉
위에서 설정한 내부IP주소를 할당받은 컴퓨터에서 먼저 실행하고 나서
Ex7Client.java 를 다른 네트워크의 기기에서 실행한다.
Ex7Client 프로그램 GUI 에서 서버ip 를 적는 텍스트상자에는 접속할 서버의 ip주소를 쓰는데,
서버ip 주소는 공유기의 ip 주소를 입력한다. 이 때 공유기의 ip 주소는 Home Router(공유기)가 만든 사설 네트워크
내에서의 공유기 ip 주소가 아니다. 공유기의 사설 ip 주소가 아닌 공인 ip 인 듯 하다..
ipTIME 공유기를 브라우저로 접속한다.
192.168.0.1 을 때린다.
로그인 > 관리도구 > 메뉴탐색기 > 기본 설정 > 시스템 요약 정보
를 선택한다.
인터넷 정보 영역에 외부 IP 주소가 있는데 요 외부 IP 주소가
Home Router(공유기) 으 외부IP주소로
Ex7Client.java 를 실행해서 서버ip 를 적는 텍스트 상자에 외부 IP 주소를 적고 접속을 한다.
https://luminitworld.tistory.com/45
서로 다른 인터넷에서 채팅 프로그램 구현하기(포트포워딩)
(1) 클라이언트, 서버 컴퓨터가 똑같은 컴퓨터일 경우 (가상 환경 x) java의 네트워크 프로그래밍을 공부하고 나서 채팅 프로그램이 너무 만들고 싶어서 여기저기 알아보다가 채팅 프로그램을 따
luminitworld.tistory.com
[JAVA] 비동기 처리 방법 - Thread
JAVA에서 비동기 처리를 위해 스레드를 사용할 수 있다. 다른 서버로 요청을 보낼 때 비동기 방식을 사용한다면 아래와 같이 작성하면 된다. public void request() { Thread thread = new Thread(new Runnable() { @Ov
ynzu-dev.tistory.com
https://hamait.tistory.com/612
쓰레드풀 과 ForkJoinPool
쓰레드똑똑똑!누구니?쓰레드 에요.. 프로그램(프로세스) 안에서 실행 되는 하나의 흐름 단위에요. 내부에서 while 을 돌면 엄청 오랬동안 일을 할 수 도 있답니다.쓰레드 끼리는 값 (메모리) 을 공
hamait.tistory.com
'Java' 카테고리의 다른 글
Java - java.util.Random class 와 Math class 의 random() 함수 (1) | 2023.02.15 |
---|---|
Java 난수 (0) | 2023.02.15 |
Java Class URL (0) | 2023.02.11 |
Java UnknownHostException (0) | 2023.02.11 |
Java InetAddress (0) | 2023.02.11 |