JAVA
자바 63강. 프로토콜 통신
JJJAEOoni
2022. 2. 16. 17:24
반응형
통신을 할 때
아무나 서버에 접근하여 세션이 만들어지면
보안에 취약해진다.
접근을 했을 때 인증 과정을 거쳐야 한다.
처음 메시지로 아이디, 비밀번호를 입력하면
인증이 되어
세션을 만들어주게 약속을 하는 것이다.
이 약속이 프로토콜이다.
1. 구분자 ':'
2. 최초 메시지는 username으로 체킹
3. ALL : 메시지
4. CHAT : 받는 사람 아이디 : 메시지
package site.metacoding.chatapp.server;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.List;
import java.util.Vector;
/**
* ProtocolCheck 프로토콜
*
* 1. 최초 메시지는 username으로 체킹
* 2. 구분자 :
* 3. 전체채팅 ALL:메시지
* 4. 귓속말 CHAT:아이디:메시지
*/
public class ChatServer {
// 리스너(연결받기) -> 메인 스레드
ServerSocket serverSocket;
List<ClientThread> clientList;
// 서버는 메시지 여러명에게 받아서 보내기(순차적)
// 새로운 스레드, 클라이언트 수마다
// 채팅서버는 메시지(요청)를 받을 때만 동작! -> pull 서버
public ChatServer() {
try {
serverSocket = new ServerSocket(2000);
clientList = new Vector<>(); // 동기화가 처리된 ArrayList
// while 돌리기
// 여러사람이 요청할 때마다 소켓이 새로 생성되어야하기 때문에 전역변수로 생성 X
while (true) {
Socket socket = serverSocket.accept(); // 대기 -> main 스레드가 하는 일
System.out.println("클라이언트 연결됨");
// while의 stack이 종료되면 t의 값이 가비지컬렉션 되기 때문에
// 고객 socket을 기억하기 위해 전역 ArrayList에 보관하기
ClientThread t = new ClientThread(socket);
clientList.add(t); // 클라이언트 각각의 socket을 가지고있음
System.out.println("고객리스트 크기 : " + clientList.size());
new Thread(t).start();
}
} catch (Exception e) {
System.out.println("오류내용 : " + e.getMessage());
}
}
// 내부 클래스
class ClientThread implements Runnable {
// 소켓 보관 컬렉션
Socket socket;
String username;
BufferedReader reader;
BufferedWriter writer;
boolean isLogin;
public ClientThread(Socket socket) {
this.socket = socket;
// new될 때 양끝단에 버퍼달림
try {
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
} catch (Exception e) {
e.printStackTrace();
}
}
// 전체채팅 ALL:메시지
public void chatPublic(String msg) {
try {
// System.out.println("전체채팅");
// 메시지 받았으니까 List<고객전담스레드> 고객리스트 <== 여기에 담긴
// 모든 클라이언트에게 메시지 전송(컬렉션 크기만큼 for문 돌려서!!)
// 컬렉션의 1번째 데이터를 t에 넣는것
// 자신이 보낸 메세지는 자신에게 돌아오지 않기 if문
for (ClientThread t : clientList) { // 컬렉션타입 : 컬렉션
if (t != this) {
t.writer.write("[전체채팅]" + username + " : " + msg + "\n");
t.writer.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 귓속말 CHAT:아이디:메시지
public void chatPrivate(String reciever, String msg) {
// System.out.println("귓속말");
try {
for (ClientThread t : clientList) { // 컬렉션타입 : 컬렉션
if (t.username.equals(reciever)) {
t.writer.write("[귓속말]" + username + " : " + msg + "\n");
t.writer.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 프로토콜 검사기
// ALL:안녕
// CHAT:재원:안녕
public void protocolCheck(String inputData) {
// 1. 프로토콜 분리
String[] token = inputData.split(":"); // 0번지 : 프로토콜, 배열의 크기가 2 or 3 z
// 크기로 구분하지 말기 !! ALL:안녕:ㅋㅋㅋ 이것도 3이니까 !!
String protocol = token[0];
if (protocol.equals("ALL")) {
String msg = token[1];
chatPublic(msg);
} else if (protocol.equals("CHAT")) {
String reciever = token[1];
String msg = token[2];
chatPrivate(reciever, msg);
} else { // 프로토콜 통과 못함
System.out.println("프로토콜 없음");
}
}
@Override
public void run() {
// 최초 메시지는 username
try {
username = reader.readLine();
isLogin = true; // 세션 생성
} catch (Exception e) {
isLogin = false; // 세션 생성 X
System.out.println("username을 받지 못했습니다.");
}
while (isLogin) {
String inputData;
try {
inputData = reader.readLine();
// System.out.println("From 클라이언트 : " + inputData);
// 프로토콜 검사기로 던지기
protocolCheck(inputData);
} catch (Exception e) {
try {
// 클라이언트로부터 메세지를 읽는데, 클라이언트가 연결을 해지하면
// readline에서 대기하다가 Stream이 끊겨서 catch로 넘어오고
// while은 catch만 계속 반복해서 출력된다.
System.out.println("오류내용 : " + e.getMessage());
isLogin = false;
// 리스트에서 참조중이라서 사라지지 않으니까 heap에 떠있는 자신을 날림
clientList.remove(this);
reader.close();
writer.close();
socket.close();
} catch (Exception e1) {
System.out.println("연결해제 프로세스 실패 " + e1.getMessage());
}
}
}
}
}
public static void main(String[] args) {
new ChatServer();
}
}
package site.metacoding.char_v3;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
public class MyClientSocket {
String username;
Socket socket;
// write 스레드
Scanner sc;
BufferedWriter writer;
// read 스레드
BufferedReader reader;
public MyClientSocket() {
try {
// localhost = 127.0.0.1 루프백 주소
// 쌤 IP = 192.168.0.132
socket = new Socket("localhost", 2000); // 연결
sc = new Scanner(System.in);
writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
// 새로운 스레드(읽기 전용)
new Thread(new 읽기전담스레드()).start();
// 최초 username 전송 프로토콜
System.out.println("아이디를 입력하세요.");
username = sc.nextLine();
writer.write(username + "\n"); // 내 버퍼에 담기
writer.flush(); // 버퍼에 담긴 데이터 Stream으로 흘려보내기
System.out.println("username : " + username + " 이 서버로 전송되었습니다.");
// 메인 스레드(쓰기 전용)
while (true) {
String keyboardInputData = sc.nextLine();
// 중계자(서버 소켓)에게만 write하면 됨
// 끝에 "\n" 필수 -> 이거 안쓰려면 PrintWrite 쓰면 됨
writer.write(keyboardInputData + "\n"); // 내 버퍼에 담기
writer.flush(); // 버퍼에 담긴 데이터 Stream으로 흘려보내기
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 내부 클래스로 만들면 좋은점
// MyClientSocket의 전역변수를 모두 new 없이 사용가능
class 읽기전담스레드 implements Runnable {
@Override
public void run() {
try {
while (true) {
String inputData = reader.readLine();
System.out.println(inputData);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) {
new MyClientSocket();
}
}
[출처]
https://cafe.naver.com/metacoding
메타 코딩 유튜브
https://www.youtube.com/c/%EB%A9%94%ED%83%80%EC%BD%94%EB%94%A9
반응형