JAVA,객체지향

소켓통신 실습 (스레드로 양방향통신)

25G 2021. 12. 3. 10:50

일대일로 소켓통신을 하는 것은 한 방향으로만 이루어진다.

쓰는 쪽(클라이언트)과 읽는 쪽(서버).

일대일 방식의 소켓통신을 그림으로 표현해 봤는데 여기서 먼저 개선해야 할 부분은 

클라이언트가 메시지 한 개만 보내면 프로그램이 종료되는 것이다.

이럴 때 종료되지 않고 "반복"되게 하기 위해서 while을 사용해서 메시지를 보내더라도 프로그램이 계속 돌도록 해본다.

while사용해서 클라이언트가 메시지를써도 끝나지 않도록

자 그럼 이제 하나의 문제점을 개선했는데

개선을 해놓고 보니 클라이언트만 일방적으로 메시지를 보내고 서버는 그 메시지를 읽기만 한다.

완전한 단방향 통신이다.

이를 어떻게 개선해야 할까?

 

이문제를 개선하기 위해서는 먼저 스레드에 대해서 생각을 해 봐야 한다.

지금 현제 위 프로그램에서 java는 main스레드 하나가 동기적으로 열심히 쓰고 읽고를 서버 파일과 클라이언트 파일을 왔다 갔다 하면서 작업을 수행하는 중이다.

 

근데 문제를 개선하기 위해서는 서버도 쓰고 읽는 작업을 해야 하고 클라이언트도 쓰고 읽는 작업을 해야 한다는 것이다.

그래서 서버는 쓰는 서브 스레드가 필요하고 클라이언트는 읽을수 있는 서브스레드가 필요하다.

 


양방향 통신 코드 리펙터링

 

 

요렇게 설계한다.

 

서버 소켓 파일

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;

public class ServerFile {

	// 클라이언트 연결을 받는소켓
	ServerSocket serverSocket;
	// 실재 통신을 하는 소캣
	Socket socket;
	BufferedReader br;

	// 서버에서 쓰기 버퍼
	// 이미 서버는 읽기를 하고 있기때문에 이를 수행하기위해선 새로운 스레드가 필요하다.
	BufferedWriter bw;
	BufferedReader keyboard;// 키보드로 읽는 버퍼 이역시 새로운 스레드가 필요하다.

	public ServerFile() {
		System.out.println("1. 서버소켓 시작-------------------------------------------------------");
		try {
			serverSocket = new ServerSocket(10000);

			System.out.println("2. 서버소켓 생성완료 : 클라이언트 접속 대기중 ----------------------------------------------");
			socket = serverSocket.accept();// 클라이언트 접속 대기중....
			System.out
					.println("3. 클라이언트 연결완료 -buffer 연결완료 (읽기 버퍼)-----------------------------------------------------");

			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 읽는 버퍼
			// socket.getInputStream()는 클라이언트 소켓을 ByteSream으로 연결후 br에 버퍼를 달았다.
			// 버퍼를 계속 만들필요는없기때문에 메시지를 받는곳만 반복

			keyboard = new BufferedReader(new InputStreamReader(System.in)); // 키보드 버퍼
			bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));// 쓰는버퍼
			// 스레드 생성
			// 글 쓰기 스레드
			WriteThread wt = new WriteThread();
			Thread t1 = new Thread(wt);
			t1.start();

			// 메인스래드
			// 글읽기 스레드
			while (true) {

				String msg = br.readLine();
				System.out.println("4. 클라이언트로 부터 받은 메시지 : " + msg);
			}

		} catch (Exception e) {
			System.out.println("서버소켓 에러 발생함 : " + e.getMessage());
		}
	}

	// 내부 클래스
	class WriteThread implements Runnable {

		@Override
		public void run() {
			while (true) {

				try {
					System.out.println("키보드 메시지 입력 대기중--------------------------------");
					String keyboardMsg = keyboard.readLine();

					bw.write(keyboardMsg + "\n");// 메시지끝에 \n으로 끝을 알려준다 통신규칙!
					bw.flush();// 버퍼 비우기
				} catch (Exception e) {
					System.out.println("서버소켓쪽에서 키보드 입력받는 중 오류가 발생했습니다 : " + e.getMessage());
					e.printStackTrace();
				}
			}
		}

	}

	public static void main(String[] args) {

		new ServerFile();

	}

}

 

클라이언트 소켓 파일

 

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;

public class ClientFile {

	Socket socket;
	BufferedWriter bw;
	BufferedReader keyboard;

	// 읽기 버퍼
	// 세로운 스레드 필요
	BufferedReader br;

	public ClientFile() {
		System.out.println("1. 클라이언트소켓 시작-------------------------------------------------------");
		try {
			socket = new Socket("localhost", 10000);// 해당 소스코드 실행시 서버소켓의 accept() 메서드 호출
			// 내컴퓨터가 서버와 클라이언트를 같이 일을하니까 localhost로 설정
			// 포트번호는 서버와 같이 10000으로 잡아준다.

			System.out.println("2. 버퍼(write)연결 완료-------------------------------------------------------");
			bw = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
			// 클라이언트는 써야하기때문에 Output해줘야한다.

			// 키보드 연결
			System.out.println("3.키보드 스트림 + 버퍼(read) 연결완료-------------------------------------------------------");
			keyboard = new BufferedReader(new InputStreamReader(System.in));

			br = new BufferedReader(new InputStreamReader(socket.getInputStream()));// 읽는 버퍼

			// 서브스레드(글 읽기 역할)
			ReadThread rt = new ReadThread();
			Thread t1 = new Thread(rt);
			t1.start();

			// 메인스레드(글쓰기 역할)
			// 버퍼를 계속 만들 필요는 없기때문에 메시지를 쓰는부분만 반복
			while (true) {
				System.out.println("4. 키보드 메시지 입력 대기중-------------------------------------------------------");
				String keyboardMsg = keyboard.readLine();
				// 통신의 규칙!!
				// 메세지 끝을 알려줘야한다. \n

				bw.write(keyboardMsg + "\n");
				// 버퍼의 남는 공간을 비워줘야한다.
				bw.flush();

			}

		} catch (Exception e) {
			System.out.println("클라이언트 소켓 에러 발생함 : " + e.getMessage());
			e.printStackTrace();
		}
	}

	class ReadThread implements Runnable {

		@Override
		public void run() {
			// 이번에는 글을 읽어야한다.
			while (true) {
				try {
					String msg = br.readLine();
					System.out.println("서버로 부터 받은 메시지 : " + msg);
				} catch (IOException e) {
					System.out.println("클라이언트 소켓쪽에서 서버소켓 메시지를 입력받는중 오류가 발생했습니다." + e.getMessage());
					e.printStackTrace();
				}
			}

		}

	}

	public static void main(String[] args) {

		new ClientFile();
	}

}

각자 필요한 기능에 서브 스레드를 달아줘서 다음과 같이 서로 양방향 통신을 할 수 있게 됐다

 

소켓통신 완료


소켓통신이 중요한 이유

 

웹 공부를 하게 되면 웹도 결국 소켓이다.

클라이언트가 브라우저로 네이버 서버에 연결을 하게 되면 네이버에서는 데이터를 주는 쪽이니까 bufferedwriter가 필요하고 브라우저는 읽어야 하니까 bufferedread가 필요할 것이고 클라이언트는 키보드로 입력해야 하니까 키보드 bufferedread가 필요하다.

그리고 서로 응답과 요청을 하려면 byteStream이 필요하고 네이버에서 html 코드를 받아낼 때는 버퍼를 사용하고 비워줘야 하니까 flush()를 사용할 것이다.

즉 웹에서 이루어지는 통신은 http통신에 규칙을 따르는데

이 http통신의 뿌리가 되는 통신은 소켓통신이 되는 것이다.

다만 차이가 나는 것들 중 하나가 http통신에서는 서버 부하를 줄이기 위해 많은 노력을 하는데 그중 하나가 stateless서버로 일정 시간이 지나거나 클라이언트가 나가면 연결을 끊어버린 것이고

소켓통신은 stateful서버로 연결이 끊기지 않고 지속된다.

 

 

'JAVA,객체지향' 카테고리의 다른 글

어댑터 패턴과 인터페이스default  (0) 2021.12.06
이벤트 리스너 원리  (0) 2021.12.05
소켓통신 실습 (일대일)  (0) 2021.12.03
소켓통신 개념(포트(port)개념)  (0) 2021.12.02
버퍼(Buffer)란?  (0) 2021.12.02