- 소켓 프로그래밍은 소켓을 이용한 통신 프로그래밍을 말한다.
- 소켓이란 프로세스간의 통신에 사용되는 양쪽의 endpoint를 의미하며, 소켓 통신에 사용되는 프로토콜이 TCP냐 UDP냐에 따라 다른 종류의 소켓을 이용한다.
TCP와 UDP
- TCP/IP 프로토콜은 이기종 시스템간의 통신을 위한 표준 프로토콜로, 프로토콜의 집합이다.
- TCP와 UDP 모두 TCP/IP 프로토콜에 포함되어 있으며, OSI 7계층의 전송계층에 해당한다.
TCP | UDP | |
연결방식 | 연결기반 - 연결 후 통신 - 1:1 통신 |
비연결기반 - 연결없이 통신 - 1:1, 1:N, N:N 통신 |
특징 | 데이터의 경계를 구분하지 않는다. (byte-stream) UDP보다 전송속도가 느리다. 신뢰성 있는 데이터 전송 - 데이터의 전송순서 보장 - 데이터의 수신여부 확인 (손실되면 재전송) - 패킷을 관리할 필요가 없다. |
데이터의 경계를 구분한다. (datagram) TCP보다 전송속도가 빠르다. 신뢰성 없는 데이터 전송 - 데이터의 전송순서가 바뀔 수 있다. - 데이터의 수신여부 확인 안한다. - 패킷을 관리해야 한다. |
관련 클래스 | Socket, ServerSocket | DatagramSocket, DatagramPacket, MulticastSocket |
- TCP는 데이터를 전송하기 전에 먼저 상대편과 연결을 한 후에 데이터를 전송하며, 잘 전송되었는지 확인하고 전송에 실패했다면 해당 데이터를 재전송하기 때문에 신뢰있는 데이터 전송이 요구되는 통신에 적합 (파일 전송)
- UDP는 상대편과 연결하지 않고 데이터를 전송하며 데이터가 바르게 수신되었는지 확인하지 않기 때문에 데이터가 전송되었는지 확인할 수 없고, 데이터를 보낸 순서대로 수신한다는 보장이 없다.
- 대신 이러한 확인과정이 필요하지 않기 때문에 TCP에 비해 빠른 전송이 가능하므로, 데이터가 중간에 손실되더라도 빠른 전송이 필요한 경우에 적합 (게임, 동영상)
※ 참고: 파이썬으로 구현한 소켓 프로그래밍
https://github.com/hahyuning/SocketProgramming
TCP 소켓 프로그래밍
기본 개념
- 서버소켓은 포트와 결합되어 포트를 통해 원격 사용자의 연결요청을 기다리다가 연결요청이 올때마다 새로운 소켓을 생성하여 상대편 소켓과 통신할 수 있도록 연결하고, 실제적인 데이터 통신은 서버소켓과는 관계없이 소켓과 소켓 간에 이루어진다.
- 포트는 호스트가 외부와 통신하기 위한 통로로, 0 ~ 65535 사이의 번호로 구별된다.
- 보통 1023번 이하의 포트는 FTP나 Telnet과 같은 기존의 다른 통신 프로그램들에 의해서 사용되는 경우가 많기 때문에 1023번 이상의 번호 중에서 사용하지 않는 포트를 골라서 사용해야 한다.
- 소켓들이 데이터를 주고받는 연결통로는 입출력 스트림으로, 소켓은 입력스트림과 출력스트림, 2개의 스트림을 가지고 있다.
- 한 소켓의 입력스트림은 상대편 소켓의 출력스트림과 연결되고, 출력스트림은 입력스트림과 연결된다.
통신과정
- 서버 프로그램에서는 서버소켓을 사용해서 서버 컴퓨터의 특정 포트에서 클라이언트의 연결 요청을 처리할 준비를 한다.
- 클라이언트 프로그램은 접속할 서버의 IP 주소와 포트 정보를 가지고 소켓을 생성해서 서버에 연결을 요청한다.
- 서버소켓은 클라이언트의 연결요청을 받으면 서버에 새로운 소켓을 생성해서 클라이언트의 소켓과 연결되도록 한다.
- 클라이언트의 소켓과 새로 생성된 서버의 소켓은 서버소켓과 관계없이 일대일 통신을 한다.
관련 클래스
- 자바에서는 TCP를 이용한 소켓 프로그래밍을 위해 Socket과 ServerSocket 클래스를 제공한다.
클래스 | 설명 |
Socket | - 프로스세 간 통신을 담당하며, InputStream과 OutputStream을 가지고 있다. - 이 두 스트림을 통해 프로세스 간의 통신이 이루어진다. |
ServerSocket | - 포트와 연결되어 외부의 연결 요청을 기다리다가 연결 요청이 들어오면, Socket을 생성해서 소켓 간의 통신이 이루어지도록 한다. - 한 포트에 하나의 ServerSocket만 연결할 수 있다. |
TCP 서버
- 여러 개의 쓰레드를 생성해서 클라이언트의 요청을 동시에 처리하도록 구현
- 서버에 접속하는 클라이언트의 수가 많을 때는 쓰레드를 이용해서 클라이언트의 요청을 병렬적으로 처리하는 것이 좋다.
public class TcpIpServer implements Runnable {
ServerSocket serverSocket;
Thread[] threads;
public static void main(String[] args) {
// 5개의 쓰레드를 생성하는 서버를 생성한다.
TcpIpServer server = new TcpIpServer(5);
server.start();
}
public TcpIpServer(int num) {
try {
// 서버소켓을 생성하여 7777번 포트와 결합시킨다.
serverSocket = new ServerSocket(7777);
System.out.println(getTime() + " 서버가 준비되었습니다.");
threads = new Thread[num];
} catch (IOException e) {
e.printStackTrace();
}
}
public void start() {
for (int i = 0; i < threads.length; i++) {
threads[i] = new Thread(this);
threads[i].start();
}
}
public void run() {
while (true) {
try {
System.out.println(getTime() + " 연결 요청을 기다립니다.");
// 요청 대기시간을 5초로 설정한다.
// 5초동안 접속 요청이 없으면 SocketTimeoutException이 발생한다.
serverSocket.setSoTimeout(5 * 1000);
// 서버소켓은 클라이언트의 연결 요청이 올때까지 실행을 멈추고 계속 기다린다.
// 클라이언트의 연결 요청이 오면 클라이언트 소켓과 통신할 새로운 소켓을 생성한다.
Socket socket = serverSocket.accept();
System.out.println(getTime() + socket.getInetAddress() + " 로부터 연결 요청이 들어왔습니다.");
System.out.println("getPort() : " + socket.getPort());
System.out.println("getLocalPort() : " + socket.getLocalPort());
// 소켓의 출력 스트림을 얻는다.
OutputStream out = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(out);
// 원격 소켓에 데이터를 보낸다.
dos.writeUTF("[Notice] Test Message1 from Server.");
System.out.println(getTime() + " 데이터를 전송했습니다.");
// 스트림과 소켓을 닫아준다.
dos.close();
socket.close();
} catch (SocketTimeoutException e) {
System.out.println("지정된 시간동안 접속 요청이 없어 서버를 종료합니다.");
System.exit(0);
} catch (IOException e) {
e.printStackTrace();
}
}
}
static String getTime() {
SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss]");
return f.format(new Date());
}
}
- Socket 클래스에 정의된 getPort()와 getLocalPort()를 사용해서 TCP/IP 통신에서 소켓이 사용하고 있는 포트를 알아낼 수 있다.
- getPort()가 반환하는 값은 상대편 소켓이 사용하는 포트이고, getLocalPort()가 반환하는 값은 소켓 자신이 사용하는 포트이다.
- 클라이언트 프로그램의 소켓이 사용한느 포트는 사용가능한 임의의 포트가 선택된다.
TCP 클라이언트
- 연결하고자 하는 서버의 IP와 포트번호를 가지고 소켓을 생성하면 자동적으로 서버에 연결 요청을 하게 된다.
- 서버 프로그램이 실행되고 있지 않거나 서버의 전원이 꺼져 있어서 서버와 연결이 실패하면 ConnectionException이 발생한다.
public class TcpIpClient {
public static void main(String[] args) {
try {
String serverIp = "127.0.0.1";
System.out.println("서버에 연결중입니다. 서버 IP: " + serverIp);
// 소켓을 생성하여 연결 요청을 한다.
Socket socket = new Socket(serverIp, 7777);
// 소켓의 입력 스트림을 얻는다.
InputStream in = socket.getInputStream();
DataInputStream dis = new DataInputStream(in);
// 소켓으로부터 받은 데이터를 출력한다.
System.out.println("서버로부터 받은 메시지 : " + dis.readUTF());
System.out.println("연결을 종료합니다.");
// 스트림과 소켓을 닫는다.
dis.close();
socket.close();
} catch (ConnectException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
}
}
UDP 소켓 프로그래밍
기본 개념
- UDP는 연결지향적인 프로토콜이 아니기 때문에 SeverSocket이 필요하지 않다.
- UDP 통신에서 사용하는 소켓은 DatagramSocket이며, 데이터를 DatagramPacket에 담아서 전송한다.
- DatagramPacket은 헤더와 데이터로 구성되어 있으며, 헤더에는 DatagramPacket을 수신할 호스트의 주소와 포트가 저장되어 있다.
UDP 서버
public class UdpServer {
public void start() throws IOException {
// 포트 7777번을 사용하는 소켓을 생성한다.
DatagramSocket socket = new DatagramSocket(7777);
DatagramPacket inPacket, outPacket;
byte[] inMsg = new byte[10];
byte[] outMsg;
while (true) {
// 데이터를 수신하기 위한 패킷을 생성한다.
inPacket = new DatagramPacket(inMsg, inMsg.length);
// 패킷을 통해 데이터를 수신한다.
socket.receive(inPacket);
// 수신한 패킷으로부터 클라이언트의 IP 주소와 포트를 얻는다.
InetAddress address = inPacket.getAddress();
int port = inPacket.getPort();
// 서버의 현재 시간을 반환한다.
SimpleDateFormat sdf = new SimpleDateFormat("[hh:mm:ss]");
String time = sdf.format(new Date());
outMsg = time.getBytes();
// 패킷을 생성해서 클라이언트에게 전송한다.
outPacket = new DatagramPacket(outMsg, outMsg.length, address, port);
socket.send(outPacket);
}
}
public static void main(String[] args) {
try {
// UDP 서버를 실행시킨다.
new UdpServer().start();
} catch (IOException e) {
e.printStackTrace();
}
}
}
UDP 클라이언트
public class UdpClient {
public void start() throws IOException, UnknownHostException {
DatagramSocket datagramSocket = new DatagramSocket();
InetAddress serverAddress = InetAddress.getByName("127.0.0.1");
// 데이터가 저장될 공간
byte[] msg = new byte[100];
DatagramPacket outPacket = new DatagramPacket(msg, 1, serverAddress, 7777);
DatagramPacket inPacket = new DatagramPacket(msg, msg.length);
datagramSocket.send(outPacket); // DatagramPacket 전송
datagramSocket.receive(inPacket); // DatagramPacket 수신
System.out.println("current server time : " + new String(inPacket.getData()));
datagramSocket.close();
}
public static void main(String[] args) {
try {
new UdpClient().start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
'Computer Science > Network' 카테고리의 다른 글
네트워킹 기본지식 (0) | 2022.02.13 |
---|