필기노트

JAVA 소켓 통신(Socket, Server, Client) 본문

JAVA

JAVA 소켓 통신(Socket, Server, Client)

우퐁코기 2023. 1. 19. 14:05
반응형

Java 소켓 통신(Socket)을 사용하는 이유

먼저 자바에서 소켓 통신은 C 또는 C++ 언어로 구현된 프로젝트와의 통신에 많이 사용됩니다. 

이유는 Java와 C의 데이터 개념이 다르기 때문인데요. C에서는 구조체를 사용하는데 반해서 Java에는 구조체가 없습니다.

이처럼 Java의 Object 구조를 C에서 이해하지 못하고 C의 구조체를 자바에서 이해하지 못하기 때문에 서로 통신을 위해서는 byte 단위로 정보를 주고받아야 합니다.

(Socket을 사용한 전문 통신)

 

 

Http 통신과 Socket 통신의 차이점

- 단방향 통신인 Http 통신

Http 통신은 Client의 요청(Request)이 있을 때만 서버가 응답(Response)하여 해당 정보를 전송하고 곧바로 연결을 종료하는 방식입니다. Client가 요청을 보내는 경우에만 Server가 응답하는 단방향 통신으로 반대로 Server가 Client에게 요청을 보낼 수는 없습니다.

 

- 양방향 통신인 Socket 통신

Server와 Client가 특정 Port를 통해 실시간으로 양방향 통신을 하는 방식입니다.

Http 통신과는 다르게 Server와 Client가 특정 Port를 통해 연결되어 있어서 실시간으로 양뱡향 통신을 할 수 있습니다.

Streaming 중계나 실시간 채팅, 게임 등과 같이 즉각적으로 정보를 주고받는 경우에 사용됩니다.

 

 

Socket 통신 흐름 살펴보기

소켓은 응용프로그램에서 TCP/IP를 이용하는 창구 역할을 하며,

두 프로그램이 네트워크를 통해 서로 통신을 수행할 수 있도록 양쪽에서 생성되는 링크의 단자입니다.

두 소켓이 연결되면 서로 다른 프로그램이 서로 데이터를 전달할 수 있게 됩니다.

 

이러한 Socket 통신은 일련의 규칙이 정해져 있는데요.

1. 먼저 기다리는 측을 Server라고 하며, Server에서는 Port를 열고 Client의 접속을 기다립니다.

2. 그리고 접속하는 측을 Client라고 하며, Server의 IP와 Port에 접속하여 통신이 연결됩니다.

3. Server와 Client 간의 통신은 Send, Receive의 형태로 주고받습니다.

4. 그리고 통신이 끝나면 close()로 접속을 끊습니다.

 

 

Server

public class TcpServer {
    public final static int tcpServerPort = 9999;

    public static void main(String[] args) {
        new TcpServer(tcpServerPort);
    }

    public TcpServer(int tcpServerPort) {
        try {
            ServerSocket serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress(tcpServerPort));
            System.out.println("starting tcp Server: " + tcpServerPort);
            while (true) {
                Socket socket = serverSocket.accept();
                System.out.println("Connected " + socket.getLocalPort() + ", From " + socket.getRemoteSocketAddress().toString());
                Server tcpServer = new Server(socket);  // Thread
                tcpServer.start();
            }
        } catch (IOException e) {
            e.getStackTrace();
        }
    }

    public class Server extends Thread {
        private Socket socket;

        public Server(Socket socket) {
            this.socket = socket;
        }

        public void run() {
            try {
                while (true) {
                    // Socket에서 가져온 출력스트림
                    OutputStream os = this.socket.getOutputStream();
                    DataOutputStream dos = new DataOutputStream(os);

                    // Socketㅔ엇 가져온 입력스트림
                    InputStream is = this.socket.getInputStream();
                    DataInputStream dis = new DataInputStream(is);

                    // read int
                    int recieveLength = dis.readInt();

                    // receive bytes
                    byte receiveByte[] = new byte[recieveLength];
                    dis.readFully(receiveByte, 0, recieveLength);
                    String receiveMessage = new String(receiveByte);
                    System.out.println("receiveMessage : "+receiveMessage);

                    // send bytes
                    String sendMessage = "서버에서 보내는 데이터";
                    byte[] sendBytes = sendMessage.getBytes("UTF-8");
                    int sendLength = sendBytes.length;
                    dos.writeInt(sendLength);
                    dos.write(sendBytes, 0, sendLength);
                    dos.flush();
                }
            } catch (EOFException e) {
                // readInt()를 호출했을 때 더 이상 읽을 내용이 없을 때
            } catch (IOException e) {
                e.printStackTrace();
            } finally {
                try {
                    if (this.socket != null) {
                        System.out.println("[Socket closed]");
                        System.out.println("Disconnected : " + this.socket.getInetAddress().getHostAddress() + ":" + this.socket.getPort());
                        this.socket.close();
                    }
                } catch (Exception e) {

                }
            }
        }
    }
}
ServerSocket serverSocket = new ServerSocket();

ServerSocket 클래스는 TCP 서버의 역할을 한다. 클라이언트의 연결 요청을 기다리며 요청이 오면 요청을 수락합니다.

 

serverSocket.bind(new InetSocketAddress(tcpServerPort));

디폴트 생성자로 객체 생성 후 포트 바인딩을 위해 bind() 메서드 호출

InetSocketAddress 클래스는 IP 소켓 주소(IP 주소 + 포트 넘버)를 구현한다.

 

tcpServer.start();

Thread 클래스는 start() 메서드 실행 시 run() 메서드가 수행되도록 내부적으로 동작합니다. 

 

Socket socket = serverSocket.accept();

Socket 클래스는 연결된 클래스와 통신하는 역할을 한다.

accept()는 클라이언트가 연결 요청하기 전까지 블로킹됩니다.

여기서 블로킹이란 스레드가 대기 상태가 된다는 뜻입니다.

연결 수락 : 클라이언트가 연결 요청을 하면 accept()는 클라이언트와 통신한 Socket을 만들고 리턴합니다.

연결된 클라이언트의 IP와 포트 정보를 얻고 싶다면 Socket의 getRemoteSocketAddress() 를 호출

 

OutputStream os = this.socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);

기본 자료형 단위로 데이터를 읽고 쓰게 하는 필터 스트림.

 

dis.readFully(receiveByte, 0, recieveLength);

입력 스트림에서 len 바이트를 읽어서 b[]의 off 위치에 저장한다.

 

 

Client

public class TcpClient {
    public static void main(String[] args) {

        Socket socket = null;

        try {
            socket = new Socket();   // Server와 통신하기 위한 Socket
            socket.connect(new InetSocketAddress("localhost", 9999));
            System.out.println("[Server 접속 Success]");

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

            // Socket에서 가져온 출력스트림
            OutputStream os = socket.getOutputStream();
            DataOutputStream dos = new DataOutputStream(os);

            // send bytes
            message = "클라이언트에서 보내는 데이터";
            bytes = message.getBytes("UTF-8");

            dos.writeInt(bytes.length);
            dos.write(bytes, 0, bytes.length);
            dos.flush();

            System.out.println("[Data Send Success] " + message);

            // Socket에서 가져온 입력스트림
            InputStream is = socket.getInputStream();
            DataInputStream dis = new DataInputStream(is);

            // read int
            int receiveLengh = dis.readInt();

            // receive bytes
            if (receiveLengh > 0) {
                byte receiveByte[] = new byte[receiveLengh];
                dis.readFully(receiveByte, 0, receiveLengh);

                message = new String(receiveByte);
                System.out.println("[Data Receive Success] " + message);
            }

            // OutputStream, InputStream close
            os.close();
            is.close();

            // Socket 종료
            socket.close();
            System.out.println("[Socket closed]");

        } catch (Exception e) {
            e.printStackTrace();
        }

        if(!socket.isClosed()) {
            try {
                socket.close();
                System.out.println("[Socket closed]");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}
Socket socket = new Socket("localhost", 포트번호);
Socket socket = new Socket(new InetSocketAddress("localhost", 포트번호));

Client에서 Socket을 사용하기 위한 두 가지 방법입니다.
연결하려는 외부 서버의 IP주소 대신 도메인 이름을 알고 있을 때 InetSocketAddress class를 사용할 수 있습니다.

 

int receiveLengh = dis.readInt();

데이터를 받기 위해 InputStream의 read() 메서드를 호출하면 상대방이 데이터를 보내기 전까지 블로킹이 됩니다.

read() 메서드가 블로킹 해제되고 리턴되는 경우는 다음과 같습니다.

(1) 상대방이 데이터를 보냈을 때 (리턴 값 => 읽은 바이트 수)

(2) 상대방이 정상적으로 Socket의 close() 메서드를 호출했을 때 (-1)

(3) 상대방이 비정상적으로 종료했을 (IOException 발생)

 

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

socket.close();

사실 Client 코드에서 is, os를 close()하는 부분이 없더라도 동작하는데 아무런 문제가 없습니다.

소켓을 닫으면 소켓의 InputStream OutputStream 닫힙니다.

반대로 InputStream, OutputStream이 close() 되면 Socket도 close() 되기 때문에 이 점은 주의해야 합니다.

 

 

결과

 

 

Reference

 

자바 소켓 통신(Socket)을 사용하는 이유와 동작 원리 및 코드

Java 소켓 통신(Socket)을 사용하는 이유 먼저 자바에서 소켓 통신은 C 또는 C++ 언어로 구현된 프로젝트와의 통신에 많이 사용됩니다. 이유는 Java와 C의 데이터 개념이 다르기 때문인데요. C에서는

wildeveloperetrain.tistory.com

 

자바 TCP 네트워킹, ServerSocket과 Socket / ServerSocet의 생성 및 연결 수락

안녕하세요 알통몬입니다.공감 및 댓글은 포스팅 하는데 아주아주 큰 힘이 됩니다!!포스팅 내용이 찾아주신 분들께 도움이 되길 바라며더 깔끔하고 좋은 포스팅을 만들어 나가겠습니다^^ TCP 네

altongmon.tistory.com

반응형
Comments