본문 바로가기
공부/java

16-17일 통신

by 샤샤샤샤 2022. 12. 6.

통신

통신은 HTTP와 소켓(socket)통신으로 나눠진다. 

HTTP통신은 웹 브라우저와 같이 클라이언트(사용자)가 서버에 요청을 보낼때만 응답하는 통신이다. 예를 들어 우리가 특정 사이트에 로그인 할때, 우리는 "이 정보(ID/PW)가 맞는지 확인해주세요." 하는 식의 요청을 보내고 그에 맞춰 웹사이트에서는 "맞네요" 또는 "아닙니다" 와 같은 정보를 돌려주는 것이다.

 

반면 소켓 통신은 양방향 통신이다. 특정 포트(창구)를 통해 서버와 클라이언트가 실시간으로 정보를 주고받으며 소통할 수 있다. 채팅, 게임과 같이 응답 흐름이 양방향인 경우는 소켓통신이다. 연결지향적이며, 포트가 공개되어 있지 않아 제3자가 접근하기 힘들다. 달리 TCP/IP통신이라고 말하기도 한다.

포트, ip주소등, 서로 연결하기 위해서 필요한 요소들을 통틀어 소켓이라고 지칭한다.

 

stream이란?

사전상의 정의는 연속, 흐름, 개울과 같은 뜻을 가지고 있다. 소켓통신에서 stream은 데이토 이동 통로를 지칭한다. 최근 우크라이나-러시아 전쟁으로 유명해진 노르드 스트림에서의 stream과 같은 맥락에서 사용되었다.

자바에는 입력 통로 역할을 하는 InputStream 클래스와 출력 통로 역할을 하는 OutStream클래스가 있다. 이 둘은 단일 방향으로 연속적으로 흘러간다. 이때 정보가 오기를 기다리는 쪽이 server가 되며, 정보를 보내오는 쪽이 client가 된다.

 

통신의 흐름

소켓으로 서로 연결하기 위해서는 서버의 ip주소와 포트번호를 알아야 한다. 비유하자면 네트워크 세상은 수백개의 문이 달린 집(컴퓨터)들이 존재하는 세상이다. 만약 내가 어떤 특정한 집에 찾아고 싶다면, 집의 주소(ip주소)와 그 집에 달린 수백개의 문중에 열린 문(포트)를 알아야 입장할수 있는 것이다.

 

서버와 클라이언트 두가지 경우 모두의 코드를 살펴보자

 

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;

public class ex124_client {
    public static void main(String[] args) {
        Socket socket = null;
        socket = new Socket();
        try {
            InetAddress localhost = InetAddress.getLocalHost();
            socket.connect(
                    //127.0.0.1 : 접속하려는 컴퓨터 주소를 말함. localhost와 같은 말.
                    new InetSocketAddress(localhost,5001)   // 그럼 자기가 자기 컴퓨터에 접속하는건가?
            );
            System.out.println( "서버 접속 성공!");

            // 서버에 문자열형으로 데이터를 보내보자.
            byte[] bytes = null;
            String message = null;

            OutputStream os = socket.getOutputStream();
            message = "Hi! This is Client~";
            // UTF8 : 문자인코딩(부호화, 암호화) 타입
            // 문자 인코딩 타입 : ASCII(영문), EUC-KR(완성형 한글), UTF-8(유니코드 - 전세계 문자)
            //                : cp949(ms949) 윈도우즈 한글 인코딩
            bytes = message.getBytes("UTF-8");
            os.write(bytes);
            os.flush(); // 버퍼에 있는 데이터를 다 내보낸다.
            System.out.println("데이터 보내기 성공!");

            // 서버로부터 데이터 받기
            InputStream is = socket.getInputStream();
            bytes = new byte[1024];
            int readByteCount = is.read(bytes);
            message = new String(bytes, 0, readByteCount, "UTF-8");
            System.out.println("데이터 받기 성공 :" + message);

            os.close();
            is.close();

        } catch (Exception e) {
            System.out.println( "서버접속 오류!" );
        }
    }
}

클라이언트 코드였다.

import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;

public class ex124_server {
    public static void main(String[] args) {
        ServerSocket serverSocket = null;
        try {
            InetAddress localhost = InetAddress.getLocalHost();
            serverSocket = new ServerSocket();
            serverSocket.bind(
                    new InetSocketAddress(localhost, 5001) );
            while (true){
                System.out.println("서버가 연결을 기다리는 중.....");
                Socket socket = serverSocket.accept(); // Blocking... 대기중..
                InetSocketAddress isa =
                        (InetSocketAddress)socket.getRemoteSocketAddress();
                System.out.println("서버가 연결을 수락함!" + isa.getHostName() );

                // 클라이언트 데이터를 수신한다.
                byte[] bytes = null;
                String message = null;
                InputStream is = socket.getInputStream();
                bytes = new byte[1024]; // 1024바이트만큼 버퍼 메모리 생성
                int readByteCount = is.read(bytes);
                message = new String(bytes, 0, readByteCount, "UTF-8");
                System.out.println("서버가 데이터 받기 성공함.");
                System.out.println("서버가 받은 데이터 : " + message);

                // 서버에서 클라로 데이터 보내기
                OutputStream os = socket.getOutputStream();
                message = "Hi~ this is Server~";
                bytes = message.getBytes("UTF-8");
                os.write(bytes);
                os.flush(); // 버퍼에 있는 데이터 밀어내기.
                System.out.println("서버가 클라에게 데이터를 보내기 성공!");

                is.close();
                os.close();
                socket.close();
                if(message.equals("exit")){// "exit" 문자열이면 서버 종료!
                    break;
                }
            }
        } catch (Exception e) {
            System.out.println("서버 초기화 오류");
        }
    }
}

서버의 코드다.

 

정보를 보낼려고 하면, 받는 대상이 존재해야 한다. 그러니 먼저 서버의 코드부터 보자.

import java.io.InputStream;            // 데이터를 받는데 필요한 클래스
import java.io.OutputStream;           // 데이터를 전달하는데 필요한 클래스
import java.net.InetAddress;           // 데이터를 주고받기 위한 주소관련 클래스
import java.net.InetSocketAddress;     // 데이터를 주고 받기 위한 소켓 관련 클래스
import java.net.ServerSocket;          // 서버 소켓 관련 클래스
import java.net.Socket;                // 소켓 관련 클래스
 try {   ... 
            }catch(){ ...    }

가장 먼저 보이는 것은 try/ catch문으로 감쌌다는 것이다.

소켓 통신 역시, try, catch문으로 감싸지 않으면 사용할 수 없다.

        ServerSocket serverSocket = null;
        try {
            InetAddress localhost = InetAddress.getLocalHost();
            serverSocket = new ServerSocket();

이어서 자신의 IP주소값을 불러와 localhost에 저장하고, ServerSocket의 객체를 선언했다.

            serverSocket.bind(
                    new InetSocketAddress(localhost, 5001) );

InetSocketAddress클래스에 localhost,5001 값을 넣어 얻은 값을, 다시 객체 serverSocket에 넣어 소켓에 묶었다. 여기서 bind() 함수는 주소, 포트를 소켓에 넣는 함수다. 즉, localhost에 저장된 ip값과, 5001번 포트라는 정보를 소켓에 넣은 것이다.

 

 while (true){ .... }

이어서 반복문이 나온다. 서버는 정보가 들어오전까지는 계속 열려져 있는 상태여야지 클라이언트가 접촉할 수 있다.

System.out.println("서버가 연결을 기다리는 중.....");
                Socket socket = serverSocket.accept(); // Blocking... 대기중..
                InetSocketAddress isa =
                        (InetSocketAddress)socket.getRemoteSocketAddress();
                System.out.println("서버가 연결을 수락함!" + isa.getHostName() );

이어서 Socket의 객체를 만들고, 그 객체에 serverSocket.accept() 값을 넣었다. 이때 프로그램은 동작을 멈추고 클라이언트 포트가 연결 될때까지 무한 대기한다.

socket.getRemoteSocketAddress()는 연결된 시스템에 대한 주소를 반환한다. 따라서 isa는 연결된 곳의 주소를 얻어와 InetSocketAddress 형식으로 캐시팅해서 저장된 값이다. 연결이 되면 getHostName을 통해 연결하려는 주소를 출력한다.

                byte[] bytes = null;
                String message = null;
                InputStream is = socket.getInputStream();
                bytes = new byte[1024]; // 1024바이트만큼 버퍼 메모리 생성

클라이언트 데이터를 수신하기 위한 사전준비다. socket.getInputStream() 함수를 통해 만들어진 정보를 얻어오는 통로 그 자체를 is라는 변수로 만든다. 그리고 1024바이트만큼의 버퍼메모리를 만들어, 들어오는 메세지의 충분히 받을수 있는 넉넉한 공간을 만들어둔다. 만약 1024가 아닌 1바이트같이 작은 메모리를 할당하면, 정보가 잘려서 들어올수도 있다.

int readByteCount = is.read(bytes);
message = new String(bytes, 0, readByteCount, "UTF-8");
System.out.println("서버가 데이터 받기 성공함.");
System.out.println("서버가 받은 데이터 : " + message);

입력 스트림으로 읽어들인 바이트들을 미리 준비된 bytes배열에 저장하고, 읽은 바이트 갯수를 리턴한다. 통신을 통해 들어온 데이터가 bytes에 저장된 동시에 들어온 데이터의 길이를 리턴해 readByteCount에 저장한 것이다.

message = new String(bytes, 0, readByteCount, "UTF-8"); 는 bytes에 저장된 내용을을, 0부터 readByteCount까지 읽어 새로운 문자열형을 만들어 message변수에 저장한다는 의미다. 이때 인코딩 타입은 UTF-8이 된다.

 

이제 반대로 클라이언트에서 서버로 데이터를 보내는 코드를 분석해보자

        try {
            socket.connect(
                    new InetSocketAddress("127.0.0.1",5001) );
            System.out.println( "서버 접속 성공!");

먼저 try문으로 감싸고, 이후에 접속하고자 하는 ip주소값을 넣고, 서버와 약속된 포트를 입력하면 된다. 여기서 127.0.0.1은 자기 자신 ip주소값과 같은 의미를 가진다.

            byte[] bytes = null;
            String message = null;

            OutputStream os = socket.getOutputStream();
            message = "Hi! This is Client~";

이어서 보내고자 하는 데이터를 바이트 형식으로 받을 바이트 배열을 준비하고, OutputStream객체를 만들어 통로를 연다. 

그리고 보내고자 하는 데이터를 만든다. 여기서는 문자열형 형식으로 만들었다.

            bytes = message.getBytes("UTF-8");
            os.write(bytes);
            os.flush(); // 버퍼에 있는 데이터를 다 내보낸다.
            System.out.println("데이터 보내기 성공!");

이제 getBytes() 함수를 통해 바이트 형식으로 변환한다. 이때 인코딩 형식을 지정해줄수 있다.

이후, OutputStream객체 os의 메서드 write를 이용해 정보를 서버로 전송한다.

그리고 이후 버퍼 메모리에 남아있을수도 있는 잔여 데이터를 flush()함수로 마저 작업을 처리하게끔 한다. 이로서 메세지가 전송이 된다. 서버의 콘솔을 보면 확인할 수 있다.

보다시피 원하는 데이터가 server 콘솔에서 실행이 되었다.

서버에서 데이터를 보내고 클라에서 수신하는 것 역시 똑같이 하면 된다.

 

이는 매우 기초적인 수준의 소켓 통신이지만, 이도 상당히 복잡한 구조를 지닌다. 변수가 많고 방대한데다 복잡한 네트워크는 굉장히 어려운 분야다.

'공부 > java' 카테고리의 다른 글

19일차  (0) 2022.12.08
18일차 복습  (0) 2022.12.08
16일차 복습  (0) 2022.12.06
15일차 복습  (0) 2022.12.04
14일차 복습  (0) 2022.12.01