티스토리 뷰
1. 채팅의 종류 :
- (1) 1 : 1 채팅 : Unicast
- (2) N : M 채팅 : Muticast
2. 채팅구현에 있어 ServerSocket의 단점 :
- ServerSocket을 활용한 네트워킹을 할 경우, 모든 작업을 Main이라는 Thread가 모든 작업을 처리해야 하기 때문에 다른 작업을 동시에 할 수 없다.
- 그렇기에, 동시에 다양한 작업을 하기 위해 멀티 스레드를 사용한다.
3. 멀티 스레드 ( 12장 )
- 프로세스(process) : 실행 중인 하나의 프로그램을 의미하며, 하나의 프로그램이 다중 프로세스를 만들기도 한다. ( ex. 브라우저 )
- 멀티 프로세스 : 독립적으로 프로그램들을 실행하고, 여러 가지 작업을 처리한다.
- 멀티 태스킹[ (Muti-Tasking) : 동시에 여러 작업을 하는 것 ]을 위해서는 멀티 스레드나 멀티 프로세스를 구현해야 한다.
- 이때 스레드는 하나의 실행 흐름을 의미한다.
- 멀티 스레드 : 한 개의 프로그램을 실행하고, 내부적으로 여러 가지 작업을 처리
- + 멀티 프로세스 : 다양한 프로세스, 멀티 스레드 : 하나의 프로세스 안의 2개 이상의 스레드
4. Main 스레드
- 모든 자바 프로그램은 메인 스레드가 main( )메소드를 실행하면서 시작한다.
- main( ) 메소드는 첫 코드부터 아래로 순차적으로 실행한다.
- 실행 종료 조건 : 1) 마지막 코드를 실행할 때 2) return문을 만났을 때
- Main 스레드는 작업 스레드를 만들어서 병렬로 코드를 실행할 수 있음:멀티 스레드
- + JVM의 스레드가 아닌, 개발자가 직접 생성하는 스레드를 “User-Thread”라고 일반적으로 호칭하며, “Worker Thread”라고 부르기도 한다.
- 프로세스의 종료 시점 :
- 1 ) 싱글 스레드 : 메인 스레드가 종료되면, 프로세스도 종료
- 2 ) 멀티 스레드 : 실행 중인 스레드가 1개라도 있다면, 프로세스 미종료
5. Worker Thread 생성 방법
- 1 ) Thread 클래스로부터 직접 생성하는 방법
- : Runnable을 매개값으로 갖는 생성자 호출
- 2 ) Thread 하위 클래스로부터 생성
- : Thread 클래스 상속 후 run 메소드를 재정의 해서 스레드가 실행할 코드 작성
6. TCP 기반의 네트워킹에 사용된 메소드 종류
- 1 ) accept ( ) 2 ) read ( ) 3 ) write ( ) 4 ) connect ( )
01. 객체입출력를 통한 서버 구현 ( New 버전 )
package tcp_practice;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class S08_ObjectServer {
public static void main (String[] args) throws IOException { // 객체입출력으로 서버구현 ( new )
// 객체의 직렬화와 역직렬화로 구현하게 되면, 좀 더 안정적으로 운영할 수 있다.
log.debug("main({}) invoked.", Arrays.toString(args));
int listenPort = 7777;
int backLog = 100;
// ===========================================================================
ServerSocket severSocket = new ServerSocket(listenPort,backLog);
log.debug("1. severSocket", severSocket);
// ===========================================================================
try(severSocket){
while(true) { // 서버는 계속 주고 받고 해야 하기 때문에 무한루프
log.info("------------------------------------------------------------------------------");
log.info("2. Server Listening address : {}",severSocket.getLocalSocketAddress());
log.info("------------------------------------------------------------------------------");
Socket sock = severSocket.accept(); // Blocking IO
log.info("3. Client connected from address : {}",sock.getRemoteSocketAddress());
try(sock;){
// ===============================================================
// RECV : 받을 준비
// ===============================================================
InputStream is = sock.getInputStream();
// 객체의 직렬화 / 역직렬화를 이용한 입력 / 출력을 위해 Object 기반의 입력 보조스트림 생성
ObjectInputStream ois = new ObjectInputStream(is);
// 객체를 읽어드림 ( 이때 readobject 메소드의 리턴 타입은 object이다. )
// 따라서 리턴값이 다형성1에 의해서, 조상품에 안겨서 반환되기 때문에
// 실제 객체의 역직렬화를 통해서 받은 객체를 강제형변환을 통해 부모의 품에서 빼내야 한다.
Member member = (Member) ois.readObject();
log.info("4. receive a member from client : {}",member);
// 단, 이 경우에는 멤버 객체만을 주고 받을 수 있게 된다.
// ===============================================================
// SENT : 보낼 준비
// ===============================================================
OutputStream os = sock.getOutputStream();
// 객체의 직렬화/ 역직렬화를 이용한 입/출력을 위해 Object 기반의 출력 보조스트림 생성
ObjectOutputStream oos = new ObjectOutputStream(os);
member.setId(2);
member.setName("Trinty");
member.setAge(33);
oos.writeObject(member); // 전송
oos.flush(); // 출력 스트림을 사용했기에, 강제 flush를 해야 한다.
log.info("5. Sended a modified member to client : {}",member);
} catch ( IOException | ClassNotFoundException e) {
e.printStackTrace();
}// try with : sock
} // while
} // try with :severSocket
} //main
} // end class
02. 객체입출력을 통한 클라이언트 구현
package tcp_practice;
import java.io.IOException;
import java.io.InputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class S08_ObjectClient {
public static void main (String[] args) throws IOException, ClassNotFoundException { // 객체입출력으로 클라이언트 구현 ( new )
log.debug("main({}) invoked.", Arrays.toString(args));
Member member = new Member();
member.setId(0);
member.setName("YOseph");
member.setAge(23);
String serverAddress = "localhost";
int serverPort = 7777;
int connectTimeout = 100*1; // milli
Socket socket = new Socket();
socket.connect(new InetSocketAddress(serverAddress,serverPort),connectTimeout);
log.info("1. Connected to server, socket : {}", socket);
// ===========================================================================
try(socket;){
OutputStream os = socket.getOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(os);
oos.writeObject(member);
oos.flush();
log.info("2. Sended a member to server : {}", member);
// ===========================================================================
InputStream is = socket.getInputStream();
ObjectInputStream ois = new ObjectInputStream(is);
member = (Member) ois.readObject();
log.info("3. Received a member from server : {}", member);
} // try with ; socket
} //main
} // end class
03. 멀티 스레드를 통한 서버 구현
package tcp_practice;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Arrays;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class S09_MutiThreadSocketServer {
private static final int port = 7777;
public static void main(String[] args ) throws IOException {
log.debug("main({}) invoked.", Arrays.toString(args));
ServerSocket serverSock = new ServerSocket(port);
try( serverSock;){ // severSock을 자동으로 닫게 해주고
while(true) {
log.info("Listenning on {} ......", serverSock);
Socket sock = serverSock.accept(); // Blocking IO
log.info("\t + Client connect from {}", sock);
// ====================================================
// 데이터 수신 Task를 별도의 스레드에서 수행 - task1
new Receiver01 ("Receiver01",sock).start();
// new Receiver02 ("Receiver02",sock).start();
// ====================================================
// 데이터 송신 Task를 별도의 스레드에서 수행 - task2
new Sender01("Sender01",sock).start();
// 데이터의 송신과 수신을 다른 스레드에서 시행해야지 멀티 태스킹이 가능하다!!(***)
// ====================================================
} // while
} // try - with - resource : serverSock
} // main
} // end class
04. 멀티 스레드를 통한 클라이언트 구현
package tcp_practice;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.util.Arrays;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class S09_MutiThreadSocketClient {
private static final String host = "127.0.0.1"; // 서버의 주소
private static final int port = 7777;
public static void main (String[] args) {
log.debug("main({}) invoked.", Arrays.toString(args) );
// ================================================================
try {
// 소켓 생성 및 연결 시도
Socket sock = new Socket();
sock.connect(new InetSocketAddress(host,port),1000); // 연결만료 시간 : 1초로 설정
log.info("Connected to the server addr : {}, port : {}", host,port);
// ================================================================
// new Sender07("Sender07",sock).start();
new Sender07("Sender01",sock).start();
// ================================================================
new Receiver01("Receiver01", sock).start();
// ================================================================
} catch ( Exception e) {
e.printStackTrace();
} // try - catch
// ================================================================
} // main
} // end class
05. 멀티 스레드를 위한 데이터 수신 스레드
package tcp_practice;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Receiver01 extends Thread { // 데이터 수신 - Thread 클래스의 자식 클래스 형태로 선언
private Socket sock; // ServerSocket
private InputStream is; // InputStream
// =================================================================
public Receiver01(String threadName, Socket sock ) {
log.debug("Constructor invoked.",sock);
this.sock = sock;
super.setName(threadName + "-" + super.getName());
try {this.is = this.sock.getInputStream();}
catch(IOException e) { ;; }
} // Receiver01 constructor
// =================================================================
// 부모 클래스인 Thread가 물려준, run() 메소드를 재정의 :
// 참고로 부모의 run() 메소드는 하는 일이 없다.
// 이 run() 메소드의 블록 범위가 새로운 스레드의 실행범위가 된다.
// =================================================================
@Override
public void run() {
log.debug("run() invoked.");
// 아래의 타입은 자바입출력을 위한 보조스트림 중 하나로
// 바이트 단위의 데이터를 메모리에 보관할 수 있게 해주는 보조 출력 스트림이다.
ByteArrayOutputStream baos = new ByteArrayOutputStream(); // (*****)
try (baos;){ // 보조 스트림도 닫아줘야 한다.
int ch; // char
int CR = 13, LF = 10; // Enter Key code
while( (ch = is.read()) != -1 ) { // 입력스트림 is의 EOF인 -1을 만날때 까지 반복 ( 상대편이 끊어야 -1이 들어온다. )
// log.info("ch : {}", ch);
if( (ch != CR) & (ch != LF ) ) { // 수신된 바이트 값이 CRLF가 아니면....
baos.write(ch); // 보조스트림에 저장(****)
} else {
// log.info("\t + excleded : {}", ch);
if ( ch == LF) { // 수신된 바이트 값이 CR 또는 LF라면 ( 엔터키를 쳤다면 )
// 현재까지 보조출력스트림에 저장된 모든 바이트열을 문자열로 변환
String recv = baos.toString("UTF-8"); // charset을 지정하여 바이트를 문자로 변환
log.info("RECV : {}", recv); // 콘솔에 출력
baos.reset(); // 바이트열 보조스트림 내부를 깨끗하게 지운다. (공장모드)
// 출력하는 것이 아니라 자바버추얼머신 메모리에 박스를 만들어서 보관하는 것이기에, flush는 사용하지 않아도 된다.
} // inner if
}// if - else
} // while
} catch ( Exception e ) { ;; } // 예외처리할 것은 없다.
finally {
// 순서에 맞게 닫아줘야 한다.
try {this.is.close();} catch (IOException e) { ;; } // 소켓 속에서 입력스트림을 얻었기에, 입력스트림을 먼저 닫고
try {this.sock.close();} catch (IOException e) { ;; } // 소켓을 닫는다.
} // try - catch - finally
log.info("Done.");
} // run()
} // end class
06. 멀티 스레드를 위한 데이터 송신 스레드 1
package tcp_practice;
import java.io.IOException;
import java.io.OutputStream;
import java.net.Socket;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Sender01 extends Thread {
// ===============================================
private Socket sock; // ServerSocket
private OutputStream os; // OutputStream
// ===============================================
Sender01(String threadName, Socket sock ){
log.debug("Constructor invoked.",sock);
this.sock = sock;
super.setName(threadName + "-" + super.getName());
try {this.os = this.sock.getOutputStream();}
catch(IOException e) { ;; }
} // Sender01
// ===============================================
@Override
public void run() {
log.debug("run() invoked.");
try {
int CR =13, LF =10;
// HandShake Protocol 대로 메시지를 보낸다.
for (int i=0; i<10; i++) {
String message = "보내는 메시지-"+i; // 전송 메시지 생성
os.write(message.getBytes("UTF-8")); // 전송 메시지 -> 바이트 열로 변환해서 송신
// charset이 보내는 측과 받는 측이 같아야지 문자가 깨지지 않고 출력할 수 있다.
// 명령 프롬프트에서 문자열을 바꾸는 방법은 chcp이며 UTF-8을 지정하기 위해서는 chcp 65001을 작성하면 된다.
// 연결할 때는 telnet localgost 포트번호
// Sent CRLF (***) : Enter Key도 보내자! why? 그렇지 않으면 메시지가 1줄로 쭉 나오기 때문이다.
os.write(CR);
os.write(LF);
os.flush(); // 강제 flush
log.info("SENT : {}",message);
Thread.sleep(1000*1); // 1초 간격으로
} //for
} catch (Exception e ) { // 예외처리
e.printStackTrace();
} finally {
try {this.os.close();} catch (IOException e) { ;; } // 소켓 속에서 입력스트림을 얻었기에, 입력스트림을 먼저 닫고
try {this.sock.close();} catch (IOException e) { ;; } // 소켓을 닫는다
} // try - catch -finally
// ===============================================
} // run()
} // end class
07. 멀티 스레드를 위한 데이터 송신 스레드 2
package tcp_practice;
import java.io.IOException;
import java.io.ObjectOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import lombok.extern.log4j.Log4j2;
@Log4j2
public class Sender07 extends Thread {
// ===============================================
private Socket sock; // ServerSocket
private OutputStream os; // OutputStream
// ===============================================
Sender07(String threadName, Socket sock ){
log.debug("Constructor invoked.",sock);
this.sock = sock;
super.setName(threadName + "-" + super.getName());
try {this.os = this.sock.getOutputStream();}
catch(IOException e) { ;; }
} // Sender01
// ===============================================
@Override
public void run() { // 데이터 송신 스레드의 실행진입점
log.debug("run() invoked.");
try {
ObjectOutputStream oos = new ObjectOutputStream(this.os);
// ===============================================
try (oos){
for (int i=0; i<10; i++) {
String message = "서버가 보내는 메시지-"+i;
oos.writeObject(message);
oos.flush();
log.info("SENT :{}", message);
Thread.sleep(1000*1);
} // for
} // inner try
// ===============================================
} catch (Exception e ) { // 예외처리
e.printStackTrace();
} finally {
try {this.os.close();} catch (IOException e) { ;; } // 소켓 속에서 입력스트림을 얻었기에, 입력스트림을 먼저 닫고
try {this.sock.close();} catch (IOException e) { ;; } // 소켓을 닫는다
} // try - catch -finally
// ===============================================
log.info("DONE.");
} // run()
} // end class
08. 멀티 스레드의 역할 - 스레드가 1개 일때, 2가지의 일을 동시에 할 수 있을까?
package practice_thread;
import java.awt.Toolkit;
// 이 예제는 single thread 하나로 2가지 작업을 어떻게 수행하는지 보여주는 사례
// 스레드가 1개이기 때문에, 2가지 작업을 순차적으로 수행할 수 밖에 없음을 보여준다.
//
public class BeepPrintExample1 { // 1개의 스레드로 2가지 일
public static void main(String [] args) {
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i=0; i<5; i++) { // 1st case
toolkit.beep(); // 띵 소리 출력
try { Thread.sleep(500); } // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for1
for(int i=0; i<5; i++) { // 2st case
System.out.println("띵"); // 콘솔로 출력하는 "띵" 문자
try { Thread.sleep(500); } // // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
// 코드 실행이 시간을 두며, 띵이라는 소리가 5번 난 다음에, 콘솔창에 "띵"이 5번 출력된다.
} // for2
} // main
} // end class
09. 멀티 스레드 구현
package practice_thread;
import java.awt.Toolkit;
public class BeepPrintExample2 {
// 이전 예제의 2가지 작업을 Worker Thread를 만들어서 동시에 수행시키자!
public static void main(String [] args) { // Worker Thread를 만들어서 멀티 태스킹 1
// 1. 작업 스레드 ( Worker Thread )를 만드는 첫 번째 방법
Runnable beepTask = new BeepTask();
Thread thread = new Thread(beepTask);
// Thread( ) 안에는 Runnable한 객체만 넣을 수 있다.
// =======================================================
// 1번째 작업
thread.start(); // Thread의 .start를 해야지만 진정으로 run해서 새로운 실행흐름이 생성된다.
// =======================================================
// 2번째 작업
for(int i=0; i<5; i++) {
System.out.println("띵"); // 콘솔로 출력하는 "띵" 문자
try { Thread.sleep(500); } // // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for
// 소리와 출력이 동시에 이루어지고 있다.
// =======================================================
// 출력 :
// 띵
// BeepTask::run() invoked.
// 띵
// 띵
// 띵
// 띵
// 각자 스레드가 동시에 따로 실행되다 보니, BeepTask::run() invoked.의 위치는 달라질 수 있다.
// =======================================================
// 2. Worker thread를 만드는 2번째 방법 ( 익명 구현 객체 )
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Anonymous::run() invoked.");
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i=0; i<5; i++) {
toolkit.beep(); // 띵 소리 출력
try { Thread.sleep(500); } // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for - sound 띵
} // run
}); // 익명구현객체 코딩 기법으로 만든 익명구현객체
thread2.start();
// =======================================================
// 2번째 작업
for(int i=0; i<5; i++) {
System.out.println("띵"); // 콘솔로 출력하는 "띵" 문자
try { Thread.sleep(500); } // // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for
// =======================================================
// 출력 :
// 띵
// Anonymous::run() invoked.
// 띵
// 띵
// 띵
// 띵
// =======================================================
// 3. 람다식으로 working Thread 생성 (****)
Thread thread3 = new Thread( () -> {
System.out.println("Lambda::run() invoked.");
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int k=0; k<5; k++) {
toolkit.beep(); // 띵 소리 출력
try { Thread.sleep(500); } // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for - sound 띵
}); // 람다식을 이용한 익명 구현 객체 생성
thread3.start();
// =======================================================
// 2번째 작업
for(int j=0; j<5; j++) {
System.out.println("띵"); // 콘솔로 출력하는 "띵" 문자
try { Thread.sleep(500); } // // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for
} // main
} // end class
package practice_thread;
import java.awt.Toolkit;
import lombok.NoArgsConstructor;
// 함수적 인터페이스인 Runnable을 구현하는 구현 클래스 선언 (***)
@NoArgsConstructor
public class BeepTask implements Runnable { // 멀티 스레드 만드는 방법 1
@Override
public void run() {
System.out.println("BeepTask::run() invoked.");
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i=0; i<5; i++) {
toolkit.beep(); // 띵 소리 출력
try { Thread.sleep(500); } // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for - sound 띵
} // run
} // end class
10. 멀티 스레드 구현 2
package practice_thread;
import java.awt.Toolkit;
public class BeepPrintExample3 {
public static void main(String [] args) { // Worker Thread를 만들어서 멀티 태스킹 2
// how 1 - 자식 스레드 클래스를 이용하여, 스레드 객체 사용
Thread thread1 = new BeepThread();
thread1.start();
// ====================================================================
for(int i=0; i<5; i++) {
System.out.println("띵"); // 콘솔로 출력하는 "띵" 문자
try { Thread.sleep(500); } // // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for1
// ====================================================================
// how 2 - 익명자식객체코딩 기법으로 Thread 클래스의 "자식" 객체를 빠르게 만드는 방법 사용 (***)
Thread thread2 = new Thread() {
@Override
public void run() {
Toolkit toolkit = Toolkit.getDefaultToolkit();
System.out.println("thread2::run() invoked.");
for ( int i = 0; i < 5; i++) {
toolkit.beep();
try { Thread.sleep(500); } catch (Exception e) { ;; }
} // for
} // run
}; // thread2
// 스레드의 이름 ( default naming : Thread - n ) :
thread2.setName("Yoseph-Thread"); // setName으로 메소드의 이름을 지정할 수 있다. ( start 전에 만들어야 한다. )
thread2.start();
// ====================================================================
for(int i=0; i<5; i++) {
System.out.println("띵"); // 콘솔로 출력하는 "띵" 문자
try { Thread.sleep(500); } // // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for2
// ====================================================================
// 출력 :
// 띵
// BeepThread::run() invoked.
// 띵
// 띵
// 띵
// 띵
// 띵
// thread2::run() invoked.
// 띵
// 띵
// 띵
// 띵
// ====================================================================
// 이때 thread2는 for1이 끝나고 나서 생성되고 시작되기에 나중에 발생한다.
} // main
} // end class
package practice_thread;
import java.awt.Toolkit;
// Thread 클래스를 상속받는 자식 클래스를 만들고, 물려받은 메소드인 run() 메소드를 재정의하여
// 새로운 스레드 객체를 생성한다.
public class BeepThread extends Thread { // 스레드 생성 방법 - 자식 클래스
@Override
public void run() {
System.out.println("BeepThread::run() invoked.");
Toolkit toolkit = Toolkit.getDefaultToolkit();
for(int i=0; i<5; i++) {
toolkit.beep(); // 띵 소리 출력
try { Thread.sleep(500); } // 밀리초 단위로 스레드의 실행흐름을 지정된 시간동안 잠시 멈춤
catch (Exception e) { ;; }
} // for - sound 띵
} // run
} // end class
'KH 정보교육원 [ Java ]' 카테고리의 다른 글
KH 32일차 - 데이터 베이스 ( Oracle SQL Developer ) (0) | 2022.04.12 |
---|---|
KH 31일차 - 네트워킹 (0) | 2022.04.11 |
KH 29일차 - TCP 네트워킹 (0) | 2022.04.07 |
KH 28일차 - 네트워킹 (0) | 2022.04.06 |
KH - 2차 시험 + 국비 1개월차 후기 (0) | 2022.04.05 |