[네트워크] 멀티 쓰레드로 채팅 구현하기
2022.11.07 - [네트워크] - [네트워크] 서버와 클라이언트 코드 구현하기
[네트워크] 서버와 클라이언트 코드 구현하기
소켓 서버와 클라이언트를 연결하는 출입구. 소켓을 통해 데이터 통로가 생성되어 데이터를 주고받을 수 있다. 소켓의 구성 프로토콜(Protocal) : TCP/UDP IP주소 : 호스트들을 식별할 수 있는 고유 주
iknow-where-togo.tistory.com
이전에 서버와 클라이언트를 만들고 소켓을 통해 연결하는 것을 구현하였다.
그리고 서버에서 welcome 메세지를 보내면 클라이언트에서 받고 답장을 보내도록 구성하였다.
이번에는 소켓 프로그래밍과 멀티 쓰레드를 이용하여 전이중 통신을 기반으로 한 채팅 프로그램을 구현해 보겠다.
쓰레드(Tread)
- 프로세스 내에서 실제로 작업을 수행하는 주체이다.
- 동시에 실행될 수 있는 가장 작은 단위이다.
- 두 개 이상의 쓰레드를 가지는 프로세스를 멀티 쓰레드 프로세스(multi-threaded process)라고 한다.
블로킹(Blocking)과 논블로킹(Non-Blocking)
블로킹은 어떤 소켓이 시스템 콜을 요청했을 때, 해당 요청에 대한 응답이 오기 전까지 요청한 소켓은 대기상태가 되는 것을 의미한다. 따라서 소켓이 블록이 되면 해제되기 전까지는 다음 일 처리를 할 수 없다.
논블로킹이란 소켓이 시스템 콜을 요청했을 때, 바로 해당 일 처리가 되는 것은 아니더라도 응답 시스템 콜은 보내 소켓이 블록 되는 것을 막는 방식이다. 대기 시간이 없어 일처리가 바로바로 가능하다.
멀티 쓰레드를 이용하여 프로그래밍하는 이유는 소켓은 기본적으로 블로킹(Blocking) 방식으로 구동되기 때문이다. 블로킹 방식으로 작동하면 클라이언트 소켓이 연결 요청을 하면 결과나 응답이 오기 전까지 무한정 기다리는 방식이다. listen(), connect(), accept(), recv(), send(), read(), write(), recvfrom(), sento(), close() 등의 메서드는 모두 블로킹될 수 있는 메서드이다.
블로킹 방식으로 구동할 때의 문제점은 서버와 클라이언트는 지속적으로 연결 요청 - 연결 수락이 가능해야 하는데, 입출력에서 블로킹이 되면 기능이 정확하게 구현되지 않게 된다. 또한 클라이언트 1과 클라이언트2가 있을 때, 클라이언트1과 데이터 송수신을 하고 있다면 클라이언트 2는 송수신을 할 수 없게 된다. 그러므로 멀티 쓰레드 프로세스로 구성하여 논블로킹(Non-Bloking)방식처럼 병렬처리가 되도록 구현하여야 한다.
쓰레드 생성 방법
- 러너블(Runnable) 인터페이스로 구현하기
- 쓰레드(thread) 클래스를 상속받아 구현하기
쓰레드 생성 방법은 두 가지가 있는데 러너블을 이용하여 구현해보았다.
- EchoSver.java (서버)
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class Main {
public static void main(String[] args) {
Scanner scn = new Scanner(System.in);
try(
ServerSocket ss = new ServerSocket(8200) )
{
while (true) {
//연결처리
System.out.println("EchoServer is started and waiting for client..");
Socket client = ss.accept();
System.out.println("Client is connected : " + client);
//환영메세지 보내기
BufferedReader in = new BufferedReader(new InputStreamReader(client.getInputStream()));
PrintWriter out = new PrintWriter(new OutputStreamWriter(client.getOutputStream()));
out.println("Welcome! This is a EchoServer.");
out.flush();
// 보내고 나면 프로그램 종료 -> 연결이 끊김
System.out.println("message is sent.");
while (true) {
//클라이언트에게 메세지 수신
String msg = in.readLine();
System.out.println("From Client :" + msg);
if (msg == null) break; //클라이언트 소켓을 종료하면 null이 생김
//클라이언트에게 메세지 송신
System.out.print("Your message : ");
msg = scn.nextLine();
out.println(msg);
out.flush();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
-EchoClient.java (클라이언트)
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class EchoClient {
public static void main(String[] args) {
Scanner scn = new Scanner(System.in);
try {
//연결요청 보내기
Socket socket = new Socket("127.0.0.1", 8200);
System.out.println("Connection is successfull !!" + socket);
BufferedReader in = new BufferedReader(
new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWriter(
new OutputStreamWriter(socket.getOutputStream()));
//수신하기
String msg = in.readLine();
System.out.println(msg);
//쓰레드 생성
//서버에게 메세지 보내기
Thread sendMsg = new Thread(new Runnable() {
public void run() { //run안에 있는 명령문을 쓰레드가 실행
while (true) {
System.out.print("Your message : ");
String msg = scn.nextLine();
if (msg.equals("Bye")) break;
out.println(msg);
out.flush();
}
try {
// 접속을 종료하고 프로그램을 끝낸다.
socket.close();
System.out.println("Connection is ended");
}catch(IOException e){
throw new RuntimeException(e);
}
}
});
//서버에게 답신 보내기
Thread recvMsg = new Thread(new Runnable() {
@Override
public void run() {
while (true){
String msg = null;
try {
msg = in.readLine();
System.out.println("From Server :" + msg);
}catch(IOException e){
throw new RuntimeException(e);
}
}
}
});
//쓰레드실행
sendMsg.start();
recvMsg.start();
}catch (IOException e){
e.printStackTrace();
}
}
}
실행결과