Published on

[ Socket ] 소켓 프로그래밍 개요

Authors
  • avatar
    Name
    유사공대생
    Twitter

소켓 프로그래밍

소켓 프로그래밍은 소켓을 이용한 통신 프로그래밍이다. 소켓(Socket)이란 프로세스간의 통신에 사용되는 양쪽 끝단(endpoint)를 의미한다. 서로 멀리 떨어진 두 사람이 통신하기 위해서 전화기가 필요한 것 처럼, 프로세스간의 통신을 위해서는 그 무언가가 필요하고, 그것이 바로 소켓이다.

자바에서는 java.net 패키지를 통해 소켓 프로그래밍을 지원하는데, 소켓 통신에 사용되는 프로토콜에 따라 다른 종류의 소켓을 구현하여 제공한다.

TCP와 UDP

TCP/IP 프로토콜은 이기종 시스템간의 통신을 위한 표준 프로토콜로 프로토콜의 집합이다. TCP와 UDP 모두 TCP/IP 프로토콜(TCP/IP protocol suites)에 포함되어 있으며, OSI 7계층의 전송계층(transport layer)에 해당하는 프로토콜이다.

TCP와 UDP는 전송 방식이 다르며, 각 방식에 장단점이 있다. 어플리케이션의 특징에 따라 적절한 프로토콜을 선택하여 사용하도록 하자.

항목TCPUDP
연결 방식연결방식(connection-oriented)
- 연결 후 통신(전화기)
- 1:1 통신방식
비연결기반(connectionless-oriented)
- 연결없이 통신(소포)
- 1:1, n:n 통신 방식
특징데이터의 경계를 구분안함(byte-stream)
신뢰성 있는 데이터 전송
- 데이터 전송순서가 보장됨
- 데이터 수신여부를 확인함(데이터가 손실되면 재전송됨)
- 패킷을 관리할 필요가 없음
UDP보다 전송속도가 느림
데이터의 경계를 구분함.(datagram)
신뢰성 없는 데이터 전송
- 데이터의 전송순서가 바뀔 수 있음
- 데이터의 수신여부를 확인 안함(데이터가 손실되어도 알 수 없음)
- 패킷을 관리해주어야 함
TCP보다 전송속도가 빠름
관련 클래스Socket
ServerSocket
DatagramSocket
DatagramPacket
MulticastSocket

TCP 소켓 프로그래밍

TCP 소켓 프로그래밍은 클라이언트와 서버간의 일대일 통신이다. 먼저 서버 프로그램 실행되어 클라이언트 프로그램의 연결요청을 기다리고 있어야 한다. 서버 프로그램과 클라이언트 프로그램간의 통신과정을 단계별로 보면 다음과 같다.

image

  1. 서버 프로그램에서는 서버소켓을 이용해서 서버 컴퓨터의 특정 포트에서 클라이언트의 연결요청을 처리할 준비를 한다.
  2. 클라이언트 프로그램은 접속할 서버의 IP주소와 포트 정보를 가지고 소켓을 생성해서 서버에 연결을 요청히다.
  3. 서버소켓은 클라이언트의 연결요청을 받으면 서버에 새로운 소켓을 생성해서 클라이언트의 소켓과 연결되도록 한다.
  4. 이제 클라이언트의 소켓과 새로 생성된 서버의 소켓은 서버소켓과 관계없이 일대일 통신을 한다.

서버소켓(ServerSocket)은 포트와 결합(bind)되어 포트를 통해 원격 사용자의 연결요청을 기다리다가 연결요청이 올 때마다 새로운 소켓을 생성하여 상대편 소켓과 통신할 수 있도록 연결한다. 여기까지가 서버소켓의 역할이고, 실제적인 데이터 통신은 서버소켓과 관계없이 소켓과 소켓 간에 이루어진다.

여러 개의 소켓이 하나의 포트를 공유해서 사용할 수 있지만, 서버소켓은 다르다. 서버소켓은 포트를 독점한다. 만일 한 포트를 둘 이상의 서버소켓과 연결하는 것이 가능하다면 클라이언트 프로그램이 어떤 서버소켓과 연결되어야하는지 알 수 없을 것이다.

포트(port)는 호스트(컴퓨터)가 외부와 통신을 하기 위한 통로로 하나의 호스트가 65536개의 포트를 가지고 있으며 포트는 번호로 구별된다. 포트의 번호는 0~65535의 범위에 속하는 값인데 보통 1023번 이하의 포트는 FTP나 Telnet과 같은 기존의 다른 통신 프로그램들에 의해서 사용되는 경우가 많기 때문에 1023번 이상의 번호 중에서 사용하지 않는 포트를 골라서 사용해야 한다.

두 서버소켓이 서로 다른 프로토콜을 사용하는 경우에는 같은 포트를 사용할 수 있다. 포트는 같아도 클라이언 트 프로그램이 사용하는 프로토콜로 어떤 서버소켓과 연결되어야 하는지 구별할 수 있기 때문이다. 그래도 가능하면 하나의 포트는 하나의 서버소켓만 사용하도록 하는 것이 바람직하다.

정리하면, **서버소켓은 소켓간의 연결만 처리하고 실제 데이터는 소켓들끼리 서로 주고받는다. **소켓들이 데이터를 주고받는 연결통로는 바로 입출력스트림이다.

입출력스트림

image

소켓은 입력스트림, 출력스트림 두개를 가지고 있다. 이 스트림들은 연결된 상대편 소켓의 스트림들과 교차연결된다.

한 소켓의 입력스트림은 상대편 소켓의 출력스트림과 연결되고, 출력스트림은 입력스트림으로 연결된다. 그래서 한 소켓에서 출력스트림으로 데이터를 보내면 상대편 소켓에서는 입력스트림으로 받게 된다.

자바에서 TCP 소켓프로그래밍에서는 Socket, ServerSocket 클래스를 제공한다.

  • Socket: 프로세스간의 통신을 담당하며, InputSteam과 OutputStream을 가지고 있다. 이 두 스트림을 통해 프로세스간의 통신(입출력)이 이루어진다.
  • ServerSocket: 포트와 연결(bind)되어 외부의 연결요청을 기다리다 연결 요청이 들어오면, Socket을 생성해서 소켓과 소켓간의 통신이 이루어지도록 한다. 한 포트에 하나의 ServerSocket만 연결할 수 있다.(프로토콜이 다르면 같은 포트를 공유할 수 있다.)

서버 예시 코드

import java.net.ServerSocket;
import java.io.*;
import java.net.Socket;
import java.util.Date;
import java.text.SimpleDateFormat;

public class TcpIpServer {
    public static void main(String args[]) {
        ServerSocket serverSocket = null;
        try {
            serverSocket = new ServerSocket(7777); // 서버소켓을 생성, 7777 포트와 바인드(bind)
            System.out.println(getTime() + "서버가 준비되었습니다.");
        } catch (IOException e) {
            e.printStackTrace();
        }
        while (true) {
            try {
                System.out.println(getTime() + "연결요청을 기다립니다.");
                // 서버소켓은 클라이언트의 연결요청이 올 때까지 실행을 멈추고 계속 기다림
                // 클라이언트의 연결요청이 오면 클라이언트 소켓과 통신할 새로운 소켓을 생성
                Socket socket = serverSocket.accept();
                System.out.println(getTime() + socket.getInetAddress() +
                "로부터 연결 요청이 들어왔습니다.");

				// 소켓의 출력스트림 얻기
                OutputStream out = socket.getOutputStream();
                DataOutputStream dos = new DataOutputStream(out);

				// 원격 소켓(remote socket)에 데이터를 보냄
                dos.writeUTF("[Notice] Test Message1 from Server.");
                System.out.println(getTime() + "데이터를 전송했습니다.");

				// 스트림과 소켓을 닫음
                dos.close();
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    static String getTime() {
        SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss]");
        return f.format(new Date());
    }
}