본문 바로가기
코딩수업/AWS 클라우드환경 네이티브

(미완) 8/26 자바(Java) 자바 입력의 전체적 개요, 채팅 프로그램 만들기

by 인생즐겜러 2022. 8. 26.
728x90
반응형

AWS 클라우드환경 네이티브 수업 70일차

 

 

 

진행

1. 채팅 프로그램 만들기

 

 

 

 

 

요약

1. 자바 입력의 전체적 개요

   ( Stream, System.in, InputStreamReader, Scanner(System.in), BufferedReader )

2. BufferedReader 클래스

3. 채팅 프로그램 만들기

 

 

 

 

 


 

 

 

 

 

자바 입력의 전체적 개요

 

- 자바는 문자열을 메모리 상에서 UTF-16으로 인코딩하여 처리.

- 문자열 송/수신을 위해 직렬화가 필요할 때에는 변형된 UTF-8 을 사용.

- 문자열을 입출력 할 때는 운영체제 기본 인코딩값,

  또는 사용자가 지정한 인코딩 값(file encoding)으로 문자열을 인코딩.

  (메모리 상에서 처리되는 것과는 다름.)

- 1 ~ 127 까지는 아스키 코드 값과 유니코드(UTF-8, UTF-16 등 ), MS계열 코드(CP949, MS949 등 ) 의 값이 같다.

 

위의 항목 3가지를 쉽게 표시하면 아래와 같다.

입력(UTF-8) -> 송수신(modified UTF-8) -> 자바 메모리 (UTF-16) -> 송수신(modified UTF-8) -> 출력(UTF-8)

 

마지막 4번째 항목의 말은

해당 값과 아스키 코드 값이 10진수로 1~127 번까지는 대응되는 문자가 같다. 라는 말이다.

char -> int 로 값을 반환할 때 나오는 값도 아스키 코드 값이 아니라

정확히는 파일 인코딩 형식의 10진수 값이 나온다.

(127 번째 문자까지는 대응되는 문자들이 같기 떄문에 아스키코드값이라도 말해도 무방한 것.)

 

 

 

 

 

UTF-8 과 UTF-16 의 구성 방식의 차이

 

UTF-8 : 영어의 경우 1Byte, 한글의 경우 3Byte 를 사용

UTF-16 : 거의 모든 문자가 2Byte 로 구성

 

 

 

 

 


 

 

 

 

 

1. Stream

 

 

 

A에서 B로의 데이터의 흐름 자체를 말한다.

데이터 고속도로 같은 것이랄까. 

고로 한 방향으로 밖에 이동이 안된다.

 

이 방향이 입력이 되면 InputStream

출력이 되면 OutputStream

이 된다.

 

Stream은 모두 바이트 단위로 데이터를 전송한다.

 

 

 

 

 


 

 

 

 

 

2. System.in

 

 

 

System 클래스의 in 이라는 변수는 사실 InputStream의 Static 변수다.

 

따라서 InputStream 타입으로 변수 a를 선언 후에 System.in을 넣을 수 있다는 말이다.

InputStream.read()는 1byte단위로만 읽는다.

그래서 a를 읽으면 1byte의 인코딩 형식의 10진수 값 (위에서 말했듯)으로 결과가 나온다.

 

 

 

위와 똑같은 내용으로 짰고

1을 입력한 후 49라는 결과를 받은

아래의 예를 보자.

 

package LectureExam;

import java.io.IOException;
import java.io.InputStream;

public class exercise1 {

	public static void main(String[] args) throws IOException {
				
		InputStream inputstream = System.in;
		int a = inputstream.read();
		System.out.println(a);
			
	}
}



결과
1
49

 

 

 

 

 


 

 

 

 

 

3. InputStreamReader

 

 

 

InputStreamReader와 InputStream의 차이를 알아보자면

 

InputStream는

- 1byte 씩 밖에 읽지 못한다.

 - int 형으로 10진수의 UTF-16 값으로 저장된다.

 

 

 

그럼 저 단점을 보완하기 위해 개발된 녀석일 것이다.

특징은 아래와 같다.

- 바이트 단위 데이터를 문자(character) 단위 데이터로 처리할 수 있도록 변환해준다.

- char 배열로 데이터를 받을 수 있다.

 

 

 

 

 


 

 

 

 

 

4. Scanner(System.in)

 

 

 

아래는 Scanner의 클래스다.

 

 

 

 

Scanner 의 클래스를 보면 수많은 메소드가 오버로딩이 되어있다.

그리고 우리가 자주 쓰는 Scanner(System.in)는

 

public Scanner(InputStream source) {
	this(new InputStreamReader(source), WHITESPACE_PATTERN);
}

 

여기에 해당한다.

자 익숙한 친구가 보인다. 

InputStreamReader 이 친구가 Scanner의 주요 변환계인 것이다.

 

 

 

이후 더 파고 들어가면 Scanner는 뭘 검사하는 게 많다...

그래서 느리고 무겁다. 하지만 형변환을 함에 있어 안정성은 굉장히 높다.

 

 

 

고로, Scanner의 메카니즘은 다음과 같다.

 

InputStream 로 입력 (int 타입)

=> InputStreamReader을 통해 편하게 데이터 처리 (char 타입)

=> 입력문자는 입력 메소드( next(), nextInt() 등)  타입에 맞게 정규식을 검사

=> 정규식 문자열을 Pattern.compile() 메소드로 Pattern 타입으로 변환

=> Pattern 타입을 String으로 변환

=> String 을 입력 메소드의 타입에 맞게 반환 ( nextInt() - Integer.parseInt()  등 )

 

 

 

 

 


 

 

 

 

 

5. BufferedReader

 

 

 

아래는 BufferedReader를 사용할 때 보통 쓰는 형식의 구문이다.

 

BufferedReader aaa = new BufferedReader(new InputStreamReader(System.in));

 

System.in => byte로만 받을 수 있는 InputStream 을

InputStreamReader => char로 받을 수 있게 하고

BufferedReader로 받는다.

 

 

그렇다.

BufferedReader는

Buffer에 문자열을 한꺼번에 담은 다음 한번에 보내버린다.

String과 다름없이 처리한다는 말.

게다가 딱히 검사하는 식도 없다.

 

 

 

Buffer를 활용하고

검사식이 없으니

Scanner 보다 압도적으로 빠르다.

 

 

 

 

 


 

 

 

 

 

BufferedReader (버퍼를 이용한 입력)란?

 

(7/22 Scanner 클래스와 비교하면서 볼 것)

 

 

 

- 이름처럼 버퍼를 이용해서 작업을 하는 클래스

- 띄어쓰기와 엔터를 경계로 입력값을 인식받는 Scanner와는 다르게

  엔터만 경계로 인식하고 받는 데이터도 String으로 고정되기 때문에 데이터를 따로 가공을 해야한다는 불편함이 있다.

  하지만 Scanner보다 훨씬 빠르다.

- 많은 데이터를 입력받아야하는 상황에서 좋다.

- 사용법은 Scanner와 동일하다.

 

 

 

 

 

BufferedReader 메소드 종류

 

Type Method 설명
void close() InputStream을 닫고 사용하던 자원들을 푼다
void mark( int, readAheadLimit ) Stream의 현재 위치를 마킹
boolean markSupported() Stream이 mark를 지원하는 지 true / false로 알려줌.
int read() 한 글자만 char로 읽어서 정수형으로 변환한다.
ex) 5를 읽으면 '5'을 읽어서 int로 변환한 53을 반환한다.
=> 아스키값을 가져온다는 말
int read( char a, int b, int c ) a를 b위치부터 c정도의 길이만큼
문자를 스트림으로부터 읽어온다.
String readLine() 한 줄로 읽고 String으로 반환한다.
boolean ready() InputStream이 준비 되었는지 확인.
1이라면 준비완료.
void reset() 마킹이 있으면 해당 위치부터 시작.
그게 아니면 처음부터 시작.
long skip( long n ) n개의 문자를 건너 뛴다.

 

 

 

 

 


 

 

 

 

 

채팅 프로그램 만들기

 

 

 

1. 기본적인 채팅 프로그램의 메카니즘

 

 

 

서버단

- 서버는 어떤 포트 번호 통신이 발생하는지 나타내는 ServerSocket 객체 생성

- 서버는 ServerSocket 클래스의 accept()를 호출 => 클라이언트가 지정된 포트의 서버에 연결할 때까지 대기
- 서버 측에서 accept()는 클라이언트 소켓에 연결된 서버의 새 소켓에 대한 참조를 반환

 

클라이언트
-
클라이언트는 연결할 서버 이름과 포트 번호를 지정해서 Socket 객체를 인스턴스화
- Socket 클래스의 생성자는 클라이언트를 지정된 서버 및 포트 번호에 연결하려고 시도한다. 통신이 설정되면 클라이언트는 서버와 통신할 수 있는 Socket 객체를 갖게 된다

연결이 설정되면 I/O 스트림을 사용해 통신할 수 있다. 각 소켓에는 OutputStream과 InputStream이 모두 있다.

클라이언트의 OutputStream은 서버의 InputStream에 연결되고, 클라이언트의 InputStream은 서버의 OutputStream에 연결. TCP는 양방향 통신 프로토콜이므로 두 스트림을 통해 동시에 데이터를 보낼 수 있다.

 

 

 

 

 


 

 

 

 

 

2. 코드 및 설명

 

 

 

서버단 코드

 

package Network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Vector;



//-------------------------------------------------------------------------
//Main Class
//-------------------------------------------------------------------------
public class MultichatServer {

	public static void main(String[] args) {
		// 서버에서 받을 서버 소켓과
		// 각 클라이언트가 들어올 소켓을 만든다.
		ServerSocket	server = null;
		Socket			socket = null;
		
		// 각 클라이언트와 연결된 소켓들을 배열처럼 저장할 벡터 객체를 생성
		Vector<Socket> vec = new Vector<Socket>();
		
		try {
			// port를 7000으로 임의설정해서 서버를 생성
			server = new ServerSocket(7000);
			
			while(true) {
				System.out.println("접속 대기 중....");
				// 클라이언트가 연결 요청하기 전까지 블로킹(스레드가 대기 상태)
				// 이 같은 이유로 보통 UI를 생성하는 스레드나, 이벤트를 처리하는 스레드에서
				// accept()를 호출하지 않아야 함.
				socket = server.accept();
				
				// 클라이언트와 연결된 소켓을 벡터에 담는다.
				vec.add(socket);
				// 쓰레드를 생성해서 연결된 클라이언트와 통신하도록 한다.
				new EchoThread(socket, vec).start();
			}
			// 입출력 시 뭔가 잘못되었을 때 예외처리~
		} catch (IOException ie) {
			System.out.println(ie.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
} // End - public class Lecture



//----------------------------------------------------------------------
// 클라이언트로 부터 전송된 전송된 문자열을 받아서
// 다른 클라이언트들에게 문자열을 보내주는 쓰레드
//----------------------------------------------------------------------
class EchoThread extends Thread {
	
	Socket	socket;
	Vector<Socket> vec;
	
	//------------------------------------------------------------------
	// 생성자를 통해서 접속한 클라이언트의 정보를 받는다.
	//------------------------------------------------------------------
	public EchoThread(Socket socket, Vector<Socket> vec) {
		this.socket	= socket;
		this.vec	= vec;
	}
	
	//------------------------------------------------------------------
	// run()
	//------------------------------------------------------------------
	public void run() {
		BufferedReader br = null;
		
		try {
			// 자바 입력의 전체적 개요 참고.
			// 클라이언트한테 입력받는 문자열을 br에 담겠다~~
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			String	str	= null;
			
			while(true) {
				// 입력받은 문자열을 읽어와서 담는다.
				str	= br.readLine();
				
				// 상대가 접속을 끊으면 break;
				// 근데 여기서는 대화가 null일 때 break; 인데 왜지?
				if(str == null) {
					// 접속이 끊어진 클라이언트를 벡터에서 제거한다.
					vec.remove(socket);
					break;
				}
				
				// 연결된 소켓들을 통해서 다른 클라이언트들에게 문자열을 보내준다.
				sendMsg(str);
			}
		} catch (IOException ie) {
			System.out.println(ie.getMessage());
		} finally {
			try {
				// 열려있는 자원을 닫는다.
				if(br		!= null)	br.close();
				if(socket	!= null)	socket.close();
			} catch (IOException ie) {
				System.out.println(ie.getMessage());
			}
		}
	} // End - public void run()

	//--------------------------------------------------------------------
	// 클라이언트로 부터 전송받은 문자열을 다른 클라이언트들에게 전송하는 메서드
	//--------------------------------------------------------------------
	public void sendMsg(String str) {
		
		try {
			for(Socket socket:vec) {
				// for문을 돌되 문자열을 보낸 클라이언트인 경우를 제외하고
				// 나머지 socket들에게만 문자열을 보낸다.
				if(socket != this.socket) {	// 현재 쓰레드와 접속된 소켓이 아닌 경우들
					PrintWriter pw = new PrintWriter(socket.getOutputStream(), true);
					pw.println(str);
					pw.flush();
					// 단, 여기서 구한 소켓들은 접속한 클라이언트가 아니라 ,
					// 남의 것들이기 때문에 여기서 소켓을 닫으면 안된다.
				}
			}
		} catch (IOException ie) {
			System.out.println(ie.getMessage());
		}
	} // End - public void sendMsg(String str)
	
} // End - class EchoThread extends Thread

 

 

 

 

package Network;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.Socket;

//-------------------------------------------------------------------------
// 키보드로 입력한 문자열을 서버로 전송하는 클래스
//-------------------------------------------------------------------------
class WriteClass{
	Socket socket;
	ClientFrame cf;
	String str;
	String id;
	
	// 생성자
	public WriteClass(ClientFrame cf) {
		this.cf	= cf;
		this.socket	= cf.socket;
	}

	// 입력한 메세지를 전송하는 메소드
	public void sendMsg() {
		// 키보드로부터 읽어오기 위한 스트림 객체를 생성한다.
		BufferedReader br	=	new BufferedReader(new InputStreamReader(System.in));
		PrintWriter	pw	=	null;
		
		try {
			// 서버로 문자열을 전송하기 위한 스트림객체를 생성한다.
			pw = new PrintWriter(socket.getOutputStream(), true);
			
			// 전송할 첫번째 데이터는 id.
			// 상대방에게 id와 함께 나의 ip주소를 전송한다.
			if(cf.isFirst == true) {
				InetAddress iaddr	=	socket.getLocalAddress();
				String	ip	=	iaddr.getHostAddress();
				getId();
				
				str = "[" + id + "] 님 로그인 (" + ip + ")";
			} else {
				// 아이디 + 입력한 내용
				str = "[" + id + "]" + cf.txtF.getText(); 
			}
			
			// 문자열을 서버로 보낸다.
			pw.println(str);
			
		} catch(IOException ie) {
			System.out.println(ie.getMessage());
		} finally {
			try {
				// 끝나고 나면 BufferedReader을 닫아주는 게 좋다.
				if(br	!= null)	br.close();
			} catch (IOException ie) {
				System.out.println(ie.getMessage());
			}
		}
	} // End - public void sendMsg()
	
	// (로그인 시 적은)id를 가져오는 메소드
	public void getId() {
		id	= Id.getId();
	}
	
} 

//-------------------------------------------------------------------------
// 서버가 보내온 문자열을 전송받는 스레드
//-------------------------------------------------------------------------
class ReadThread extends Thread {
	Socket socket;
	ClientFrame cf;
	
	// 생성자
	public ReadThread(Socket socket, ClientFrame cf) {
		this.cf	=	cf;
		this.socket	=	socket;
	}
	
	// run()
	public void run() {
		BufferedReader	br	=	null;
		
		try {
			// 서버로부터 전송된 문자열을 읽어오기 위한 스트림 객체를 생성한다.
			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			
			while(true) {
				// 소켓으로부터 문자열을 읽어온다.
				String	str = br.readLine();
				if(str == null) {
					System.out.println("접속이 끊였습니다.");
					break;
				}
				// 서버로부터 전송 받은 문자열을 화면에 출력.
				cf.txtA.append(str + "\n");
			}
		}catch(IOException ie) {
			System.out.println(ie.getMessage());
		} finally {
			try {
				if(br		!= null)	br.close();
				if(socket	!= null)	socket.close();							
			} catch(IOException ie) {
				System.out.println(ie.getMessage());
			}
		} 
	}
	
}

//-------------------------------------------------------------------------
//------------------------------------------------------------------------- 
public class MultiChatClient {

	public static void main(String[] args) {
		Socket socket	= null;
		ClientFrame	cf;
		
		try {
			// 클라이언트는 접속할 서버의 IP 주소와 포트정보로 소켓을 생성해서 서버에 연결을 요청한다.
			// 127.0.0.1는 나의 호스트 IP, 7000은 MultichatServer 에서 내가 설정한 ServerSocket
			socket = new Socket("127.0.0.1", 7000);
			System.out.println("연결 성공");
			
			// 연결이 되었으므로 대화를 나눌 화면을 준비한다.
			cf = new ClientFrame(socket);
			
			new ReadThread(socket, cf).start();
			
			
		} catch (IOException ie) {
			System.out.println(ie.getMessage());
		} catch (Exception e) {
			e.printStackTrace();
		}

	}

} // End - public class MultiChatClient

 

 

 

 

 

 

package Network;

import javax.swing.*;
import java.awt.event.*;
import java.net.Socket;
import java.awt.*;

// 로그인 화면 : 닉네임을 입력하고 입장하는 화면
class Id extends JFrame implements ActionListener{
	
	static	JTextField	tf	=	new	JTextField(8);
			JButton		btn	=	new	JButton("입장");
	
			
	WriteClass	wc;
	ClientFrame	cf;
	
	// 기본 생성자
	public Id() {
		
	}
	
	// 매개변수 있는 생성자
	public Id( WriteClass wc, ClientFrame cf ) {
		super("아이디");
		this.wc = wc;
		this.cf = cf;
		
		setLayout(new FlowLayout());
		add(new JLabel("아이디"));
		add(tf);
		add(btn);
		
		btn.addActionListener(this);
		
		setBounds(400, 300, 250, 100);
		setVisible(true);
	}
	
	// 아이디를 입력하고 입장버튼을 눌렀을 경우 작동하는 메소드
	public void actionPerformed(ActionEvent e) {
		
		wc.sendMsg();		//	처음 입장을 알리는 메세지를 전송한다.
		cf.isFirst = false;	//	입장이 되었으므로, false 로 바꾼다.
		cf.setVisible(true);	//	채팅창을 화면에 보여준다.	
		this.dispose();	// 로그인 창(자신)은 사라진다.
	}
	
	// 아이디를 알아내는 메소드
	static public String getId() {
		return tf.getText();
	}
} // End - class Id extends JFrame implements ActionListener

// 채팅창 클래스
public class ClientFrame extends JFrame implements ActionListener{
	
	JTextArea	txtA	=	new JTextArea();
	JTextField	txtF	=	new JTextField(15);
	JButton	btnTransfer	=	new	JButton("전송");
	JButton	btnExit	=	new	JButton("닫기");
	
	// 바로 입장을 한 상태인지, 대화중인지 상태를 나타낸다.
	boolean	isFirst	=	true;
	
	JPanel	p1	= new	JPanel();
	
	Socket socket;
	WriteClass wc;
	
	// 생성자
	public ClientFrame(Socket socket){
		super("대화 나누기");
		this.socket = socket;
		wc = new WriteClass(this);
		new Id(wc,this);
		
		txtA.setFont(new Font("굴림", Font.ITALIC + Font.BOLD, 24));
		txtA.setBackground(Color.YELLOW);
		add("Center", txtA);
		
		p1.add(txtF);		// 메세지 입력란
		p1.add(btnTransfer);// 전송 버튼
		p1.add(btnExit);	// 닫기 버튼
		add("South", p1);
		
		// 버튼들에 리스너를 장착시킨다.
		btnTransfer.addActionListener(this);
		btnExit.addActionListener(this);
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		
		setBounds(300, 300, 500, 500);
		// 로그인 창을 먼저 보여주기 위해서, 대화창을 숨긴다.
		setVisible(false);
	}
	
	// 전송 버튼과 닫기 버튼을 눌렀을 경우 실행하는 메서드
	public void actionPerformed(ActionEvent e) {
		String id = Id.getId();
		
		if(e.getSource() == btnTransfer) {
			// 전송 버튼을 눌렀을 경우
			// 메세지를 입력하지 않고 전송 버튼만 눌렀을 경우는 화면으로 돌아가게 한다.
			if(txtF.getText().equals("")) {
				return;				
			}
			
			txtA.append("[ " + id + " ]" + txtF.getText() + "\n");
			wc.sendMsg();
			// 메세지를 전송했으므로 메세지 입력란을 지운다.
			txtF.setText("");
		} else {
			this.dispose();
		}
		
	}

} // End - public class ClientFrame extends JFrame implements ActionListener

 

 

 

 

 

 

 

 

 

 

 

 

728x90
반응형

댓글