N:N 채팅방을 자바 코드로 구현해볼 예정이다. 처음부터 끝까지 프로토콜을 모두 만들어 볼것이다. 구현을 위해서 처음 배운 기능이나 설계 방식에 대해서 모두 정리해보자 그에따른 첫번째로 소켓 통신이다.
자바의 소켓은 블로킹 소켓과 논블로킹 소켓, 2가지가 있다. 블로킹과 논블로킹 개념에 대해서 짧게 얘기하고 다음 포스팅에 소켓과 논블리킹 소켓
그리고 동기와 비동기 4개의 개념을 동시에 정리하겠다. 내가 크게 헷갈렸던 개념이기 때문에 한꺼번에 다루는 게 좋을듯하다.
Non-Blocking 소켓
반면에 논 블로킹 소켓은 소켓 작업이 완료 되지 않아도 해당 작업에서 바로 반환되는 방식이다.
논 블로킹은 A함수가 B함수를 호출해도 제어권은 그대로 자신이 가지고 있는다.
_ A함수가 B함수를 호출하면, B함수는 실행되지만, 제어권은 A함수가 그대로 가지고 있는다.
_ A함수가 계속 제어권을 가지고 있기 때문에, B함수를 호출한 이후에도 자신의 코드를 계속 실행한다.
간단하게 제어권이 어떻게 옮겨가냐에 따라 블로킹과 논블로킹을 나누었다. 개념이 그렇게 어렵진않다. 동기와 비동기가 같이 들어가면 헷갈릴만한 요소가 많음으로 다음 포스팅에 묶어서 전부 정리하겠다.
참고한 사이트
https://velog.io/@nittre/블로킹-Vs.-논블로킹-동기-Vs.-비동기
public class Client {
private static final int SERVER_PORT = 8888; //원하는 포트의 번호(서버 포트와 동일 해야함)
public static void main(String[] args) {
Client client = new Client();
client.start();
}
public void start() {
Socket socket;
try {
//socket 연결 후 클라이언트 인풋,아웃풋 생성
socket = new Socket("localhost", SERVER_PORT); //소켓 객체 생성
ClientOutputThread clientoutputThread = new ClientOutputThread(socket,connectname);
ClientInputThread clientinputThread = new ClientInputThread(socket,clientoutputThread);
// 인풋과 아웃풋을 따로 스레드를 2개 만들어서 동작함.
clientoutputThread.start();
clientinputThread.start();
} catch (IOException e) {
e.printStackTrace();
}
}
위 main클래스에서 소켓을 특정포트와 연결해 생성 한 다음, 인풋과 아웃풋 스레드 2개를 만들어 통신을 준비한다.
public class ClientOutputThread extends Thread {
Socket socket;
OutputStream out = null;
Scanner scanner = new Scanner(System.in);
String clientname;
public ClientOutputThread(Socket socket,String clientname) {
this.socket = socket;
this.clientname = clientname;
}
@Override
public void run() {
try {
out = socket.getOutputStream(); //데이터 전송을 위한 스트림
startChat();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
위는 아웃풋 스레드로 받아온 소켓을 out = socket.getOutputStream(); 을 만들어, 데이터 전달을 수행한다.
public class ClientInputThread extends Thread {
static final int MAXBUFFERSIZE = 2048;
Socket socket;
InputStream in = null;
ClientOutputThread clientOutputThread;
public ClientInputThread(Socket socket, ClientOutputThread clientOutputThread) {
this.socket = socket;
this.clientOutputThread = clientOutputThread;
}
@Override
public void run() {
try {
in = socket.getInputStream(); // 데이터를 받기위한 스트림.
while (true) {
byte[] serverbytedata = new byte[MAXBUFFERSIZE];
int serverbytelength = in.read(serverbytedata);
PacketType serverpackettype = byteToPackettype(serverbytedata);//서버 헤더부분 타입추출
int serverpacketlength = byteToBodyLength(serverbytedata); //서버 헤더부분 길이추출
boolean disconnectcheck;
if (serverbytelength >= 0) {
HeaderPacket packet = makeServerPacket(serverbytedata, serverpackettype);
disconnectcheck = packetCastingAndPrint(packet, serverpackettype);
if(!disconnectcheck){
break;
}
}
}
위 클래스는 input 스레드의 일부분으로 서버보내는 데이터를 해석하는데 사용된다. 아웃풋과 마찬가지로 소켓을 받아와 in = socket.getInputStream();을 만들어 데이터를 받아온다.
public static void main(String[] args) {
Server server = new Server();
server.start();
}
public void start() {
ServerSocket serverSocket = null;
try {
serverSocket = new ServerSocket(SERVER_PORT); // 포트번호와 서버 소켓 생성
System.out.println("[Server Start]");
while (true) {
System.out.println("[Client Waiting]");
Socket socket = serverSocket.accept(); //클라이언트 연결 수락
//연결이 들어올때마다 새로운 소켓 생성
//클라이언트가 접속하면 새로운 스레드 생성.
ServerThread ServerThread = new ServerThread(socket);
ServerThread.start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
위 클래스는 클라이언트에서 요청하는 연결을 수락을 도와주는 서버이다. 코드의 주석에서 보면 알수 있듯이
Socket socket = serverSocket.accept();
이 코드에서 클라이언트의 연결요청이 들어오면 새로운 소켓을 생성하고, 밑에선 새로운 스레드를 생성하고 있다.
자바에서 위처럼 다양한 데이터 스트림이 있지만 내가 만들 프로토콜은 자바로 이루어진 서버와 클라이언트 통신의 목적이 아닌 다양한 언어들이 사용할 수 있도록 설계할 예정이다. 그렇기 때문에 모든 데이터들은 byte단위로 이루어져서 패킷을 주고받으며 통신을 할 수 있도록 설계할 예정이다.