2020.11.17 일지

0후·2020년 11월 17일
0

비트캠프

목록 보기
22/112

오늘의 요약

오늘은 1:N 채팅 만들기 시간이었다.

Client.java

import java.io.*; // io 호출
import java.net.*; // net 호출

class Client extends Thread { // 클라이언트도 I/O를 받아들여야 하기 때문에 쓰레드 여야함! 따라서, Thread 상속선언해줌
	BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); // buffered reader를 노드,브릿지,필터를 한 줄로 통합하여 사용한 선언
	Socket s; // 소켓 멤버변수 선언
	InputStream is; // 인풋 스트림 멤버변수 선언
	OutputStream os; // 아웃풋 스트림 멤버변수 선언
	DataInputStream dis; // 데이터 인풋(필터스트림) 멤버변수 선언
	DataOutputStream dos; // 데이터 아웃풋(필터스트림) 멤버변수 선언
	String chatId; // 채팅 아이디 멤버변수 선언
	
	Client(){
		connect(); // 서버와 접속 선언
	}
	void connect(){ // connect 메소드 선언
		try{ // connect 메소드의 예외 발생 구문
			p("서버IP(기본:127.0.0.1): "); // 서버IP(기본:127.0.0.1): 를 모니터에 출력
			String ip = br.readLine(); // 사용자에게 입력받는 구문
			if(ip != null) ip = ip.trim(); // ip가 null이 아니면 공백 제거
			if(ip.length() == 0) ip = "127.0.0.1"; // 입력받은 게 없으면 ip는 기본 로컬호스트로 입력
			
			p("PORT(기본:3000): "); // PORT(기본:3000): 를 모니터에 출력
			String portStr = br.readLine(); // 사용자에게 입력받는 구문
			if(portStr != null) portStr = portStr.trim(); // port 스트링이 null이 아니면 공백 제거
			if(portStr.length() == 0) portStr = "3000"; // 입력받은 게 없으면 port는 기본 포트로 입력
			int port = Integer.parseInt(portStr); // 포트 번호를 정수형으로 반환
			if(port<0 || port>65535){ // 포트번호가 0 ~ 65535 를 벗어난 경우
				pln("범위가 유효하지 않은 포트임"); // 범위가 유효하지 않은 포트임이라고 모니터에 출력
				connect(); // 그리고 다시 connect 메소드 호출하여 연결시도
				return; // 그리고 이 부분은 다시 실행 못하게 나가버림
			}

			s = new Socket(ip, port); // 입력한 ip와 port를 담은 새로운 소켓을 만들어줌
			pln("서버와 연결 성공"); // 만든 후 "서버와 연결 성공"을 모니터에 출력
			is = s.getInputStream(); // 서버가 보냈다면 소켓에 있는 값을 인풋스트림으로 가져와서 is에 넣음 
			os = s.getOutputStream(); // os에 담긴 값을 소켓에 보낸다.
			dis = new DataInputStream(is); // is값을 DataInputStream형으로 바꿔줌
			dos = new DataOutputStream(os); // os값을 DataOutputStream형으로 바꿔줌
			start(); // 여기서 run 메소드 불러와서 동시에 스레드로 돌려주기 시작함
			// run 메소드는 메세지가 오면 모니터에 띄워주는 역할을 함
			// 스레드로 돌아가야 언제든 메세지가 와도 받을 수 있음
			
			inputChatId(); // 채팅 아이디 메소드 호출
		}catch(IOException ie){
		}
		
	}
	public void run(){ // listen (socket -> monitor) 해주는 메소드 쓰레드로 돌림
		try{ // 쓰레드 실행 예외발생구문
			while(true){ // 언제 메세지가 올 지 모르니까 무한루프로 계속 돌려줌
				String msg = dis.readUTF(); // 만약에, 소켓에서 메세지가 들어오면 msg라는 string 타입 변수에 UTF로 변환한 메세지를 담음
				pln(msg); // 위에서 받은 msg 내용 모니터에 출력
			}
		}catch(IOException ie){ // 입출력 예외 발생시 처리해주는 구문 시작
			pln("서버가 다운됨.. 2초 후에 종료됩니다."); // 서버가 다운됨.. 2초 후에 종료됩니다. 모니터에 출력
			try{ // 예외 발생 구문
				Thread.sleep(2000); // 2초 후에 종료하게끔 쓰레드 주어진 시간만큼 일시정지
				System.exit(0); // 정상 종료
			}catch(InterruptedException iie){} // ie라고 적어주면 지역변수 이름이 충돌하니까 iie로 변경
		}finally{
			closeAll(); // 마침내 모든 걸 닫아주는 메소드 소환
		}
	}
	void inputChatId(){ // 채팅 ID를 입력받는 메소드
		p("채팅ID(기본:GUEST) : "); // 채팅ID(기본:GUEST) : 를 모니터에 출력
		try{ // inputChatId의 메소드 예외 발생 구문
			chatId = br.readLine(); // chatId의 사용자 입력값을 읽어줌
			if(chatId != null) chatId = chatId.trim(); // chatId가 null이 아니면 chatId의 공백 제거
			if(chatId.length() == 0) chatId = "GUEST"; // 입력받은 게 없으면 chatId는 GUEST로 입장
			dos.writeUTF(chatId); // 소켓에 출력하는 부분인데 출력스트림을 dos에 넣는 거야
			// dos에 문자열인 chatId를 파일에 쓰기위한 함수 호출 == writeUTF
			dos.flush(); // 현재 버퍼에 저장되어 있는 내용을 클라이언트로 전송하고 버퍼를 비운다. 

			inputMsg(); // inputMsg 메소드를 호출한다.
		}catch(IOException ie){ // 입출력 예외 발생시 처리해주는 구문 시작
		}
	}
	void inputMsg(){ // Speak (key -> socket) 해주는 메소드
		String msg = ""; // msg 초기값 선언
		try{ // inputMsg의 예외 발생 구문
			while(true){ // 내가 입력값 언제 입력할 지 모르니까 대기가 필요하므로 무한루프 시작
				msg = br.readLine(); // 내가 키보드에서 입력받은 값을 msg를 읽어줌
				dos.writeUTF(chatId + ">> " + msg); // 발송하긴 할건데, dos에 문자열인 chatId의 msg를 읽어줌
				dos.flush(); // 현재 버퍼에 저장되어 있는 내용을 클라이언트로 전송하고 버퍼를 비운다.
			}
		}catch(IOException ie){ // 입출력 예외 발생
		}finally{ 
			closeAll(); // 무한루프가 끝났다? 라는 의미, 모든 것을 닫아줌
		}
	}
	void closeAll(){ // 연결 객체들 닫기
		try{ // closeAll의 메소드 예외 발생 구문
			// 그냥 두면 메모리 낭비되니까, 가비지 컬렉터가 가져가라고 보내줌
			if(dis != null) dis.close(); // filter부터 닫아줘야 하므로, dis 닫아주기
			if(dos != null) dos.close(); // dos 닫아주기
			if(is != null) is.close(); // is 닫아주기
			if(os != null) os.close(); // os 닫아주기
			if(s != null) s.close(); // 소켓 닫아주기
		}catch(IOException ie){}
	}
	void pln(String str){ // 프린트 해주는 함수
		System.out.println(str);
	}
	void p(String str){ // 프린트 해주는 함수
		System.out.print(str);
	}
	public static void main(String args[]){
		new Client(); // client 생성자 호출
	}
}

Server.java

import java.io.*; // io를 불러들임
import java.net.*; // net을 불러들임
import java.util.*; // util을 불러들임

class Server {
	ServerSocket ss; // 서버니까 서버소켓을 ss로 잡고,
	Socket s; // 연결되면 소켓이 생성되니까 소켓을 s로 잡는다
	int port = 3000; // port도 정수형태로 받는다.
	Vector<OneClientModule> v = new Vector<OneClientModule>(); 
	// oneclientmodule을 vector로 만들어주기, 왜?
	// 클라이언트가 접속할때 소켓을 만들면서 모듈로 묶어줌
	// 앞으로 생기는 클라이언트와의 소통을 모듈안에서 하게 함
	OneClientModule ocm; 
	// 모듈을 안에서 불러야 하니까 객체 만들어줌
	// oneclientmodule 적어주면 너무 기니까 ocm으로 줄여버리기

	Server(){ // 서버를 생성자로 선언하고
		try{ // 서버 생성자의 예외 발생 구문
			ss = new ServerSocket(port); // 서버 소켓에 포트번호를 받아 선언해준다. 
			pln(port+ "번 포트에서 서버 대기중..."); // ~번 포트에서 대기중이라고 출력되게끔 해준다.
			while(true){ // 무한루프 시작
				// 무한루프 이유 - 계속 돌아야 언젠가 접속이 들어오면 바로 생성할 수 있게 하기 위함
				s = ss.accept(); // 접속 들어오면 소켓 s 생성, 하나의 클라이언트와 통신하는 서버 모듈클래스
				ocm = new OneClientModule(this); 
				// 위의 경우는, (s)로 할 경우 읽을 순 있는데, 모든 사람한테 v를 뿌려줄 때 문제가 됨
				// 따라서 (v, s)혹은 (this)를 넘겨줘야 함
				v.add(ocm); 
				// 이 모듈이 여러개 만들어질 예정이므로, 언제든 갯수가 추가되거나 삭제가 가능한 가변배열에 넣어줌
				// v안에는 연결된 클라이언트 모듈들이 들어갈 예정 그래야 각 모듈별로 io를 주고받을 수 있음
				ocm.start(); 
				// 그리고 쓰레드로 해주지 않으면 한 모듈에 붙잡혀서 동시 진행이 안 됨
				// 그래서 미리 그 모듈이 스레드로 돌아가는 모듈로 만들어놓고 start 해서 동시에 스레드로 돌아가게 해줌
			}
		}catch(IOException ie){ // IOException 예외처리, 여기서 발생하는 예외는 api보니까 서버와 연결이 안 되는 것
			// 이유는 포트가 겹치는 경우이므로, 포트가 겹칠경우 밑의 멘트를 출력해줌
			pln(port+"번 포트는 이미 사용중임");
		}finally{ // 위의 걸로도 못잡으면 마지막 유언을 남기는 데 그게 뭐냐면
			try{
				if(ss != null) ss.close(); // 서버소켓이 null이 아닌 경우 닫아주기
				// @@@@@@ 더 이상 서버소켓이 사용되지 않을때 == 연결 안할 때 만들었던 서버소켓을 메모리 줄이게 반환시킴
			}catch(IOException ie){} // IOException 예외처리
		}
	}
	void pln(String str){
		System.out.println(str);
	}
	void p(String str){
		System.out.print(str);
	}
	public static void main(String[] args){
		new Server();
	}
}

OneClientModule.java

import java.io.*;
import java.net.*;

// 하나의 클라이언트와 통신(특정 클라이언트에서 들어서 나머지 클라이언트들에게 뿌려줌)하는 서버 모듈 클래스
class OneClientModule extends Thread { 
	//각 클라이언트마다 모듈을 하나씩 가지게 됨.
	//이 모듈을 통해서 서버와 클라이언트가 소통을 원활히 할 수 있게 함!
	//동시수행 되어야 하므로 스레드 상속 받아줌.

	Server server;
	// 먼저 내가 만든 서버를 다른 클래스에서 가져와야 하니까 서버 불러줌
	Socket s; 
	InputStream is;
	OutputStream os; 
	DataInputStream dis;
	DataOutputStream dos;
	String chatId = "GUEST";

	OneClientModule(Server server){
		// 서버랑 연결해서 사용할거니까, 매개변수로 서버를 가져옴
		this.server = server; // 이거는 서버
		this.s = server.s; // 이거는 소켓
		try{
			is = s.getInputStream(); // 소켓에서 인풋값 받아서 is에 저장 
			os = s.getOutputStream(); // os에 있는거 아웃풋 시켜줌
			dis = new DataInputStream(is); // is에 있는거 datainputstream으로 바꿔줌
			dos = new DataOutputStream(os); // os에 있는거 dataoutputstream으로 바꿔줌
		}catch(IOException ie){}
	}
	public void run(){ // listen -> broadcasting
		listen(); // start를 선언하면, 리슨메소드를 스레드로 돌려줌
	}
	void listen(){ // listen 메소드 호출
		String msg = ""; // msg 메세지에 빈값으로 초기화 시켜줌
		try{ // listen 메소드에서 예외 발생 구문
			chatId = dis.readUTF(); // 소켓에서 chatId를 UTF파일로 읽어준 값으로 아이디를 설정해줌
			String inMsg = chatId + "님 입장!! (총인원:" + server.v.size()+ "명)"; // 누가 들어오면 뜰 메세지를 inMsg에 넣어줌
			broadcast(inMsg); // 미리 설정해둔 inMsg를 broadcast 시킴
			server.pln(inMsg); // 서버에도 inMsg를 broadcast 시킴
			while(true){ // 무한루프 시작, 계속 실행하게끔 해주려고 무한루프 시작
				msg = dis.readUTF(); // 소켓에 메세지가 도착하면 (누군가 보내거나 서버가 보내면) 그 내용을 is>dis>msg에 담아서
				broadcast(msg); // broadcast 해준다
				server.pln(msg); // 서버도 그 메세지를 받아서 똑같이 모니터에 출력 해준다
			}
		}catch(IOException ie){
			server.v.remove(this); // 누군가 사라지면(끄면), 이 모듈 자체가 그 사람의 모듈이니까 이 모듈을 삭제시킴
			String outMsg = chatId+"님 퇴장!!(총인원: " + server.v.size() + "명)"; // 퇴장시 채팅 아이디와 서버의 인원을 outMsg에 담아줌
			broadcast(outMsg); // outMsg를 담아 broadcast 해줌
			server.pln(outMsg); // 서버도 그 메세지를 받아서 똑같이 모니터에 출력 해준다
		}finally{
			closeAll();
		}
	}
	void closeAll(){ // 연결객체들 닫기
		try{
			if(dis != null) dis.close();
			if(dos != null) dos.close();
			if(is != null) is.close();
			if(os != null) os.close();
			if(s != null) s.close();
		}catch(IOException ie){}
	}
	void broadcast(String msg){ // broadcast 하는 메소드, msg 스트링을 받아와서 모두에게 뿌려주는 메소드
		try{
			for(OneClientModule ocm: server.v){ // 벡터 v안에 있는 모든 모듈들(즉 누군가 접속했기때문에 생긴모듈들에게)
				ocm.dos.writeUTF(msg); // 내가 쓴 메세지를 output시킴
				ocm.dos.flush(); //끝까지 밀어넣어서 보내줌
			}
		}catch(IOException ie){}
	}
}

알게된 개념

ServerSocket은 java에서 서버 프로그램을 개발할 때 쓰이는 클래스고, Socket 클래스는 client에서 서버로 접속하거나 Server에서 accept 하는데 필요한 클래스다.

profile
휘발방지

0개의 댓글