티스토리 뷰

Java/Class

13. Networking : UDP,TCP

알 수 없는 사용자 2018. 11. 16. 10:26


이번 포스팅에서 다룰 내용은

Networking에 관한 것입니다.

요즘은 네트워킹 없는 프로그램을 찾기 힘들 정도로

네트워킹을 기본으로 사용되기 때문에 

동작 방식과 원리를 이해하고

넘어가야 한다고 생각합니다.


=====================================================================


Networking : UDP, TCP





1. Networking : What?


 요즘 세대는 컴퓨터뿐만 아니라 스마트폰을 기본으로 대부분 사용하기 때문에 네트워킹이라는 단어의 정확한 의미는 몰라도 어느 정도 감은 잡으리라 생각합니다. 

 네트워킹의 사전적 의미는 '사람들이 이루는 여러 종류의 일을 횡적으로 연결하여 네트워크를 구성하는 것'을 말합니다.

초기에는 몇 대의 컴퓨터끼리의 구성으로 이루어져 있었지만, 지금은 마치 지구가 하나의 거대한 네트워크망인 것처럼 인터넷을 통해 다양하고 방대한 양의 데이터를 공유하는 것이 가능해졌습니다.


[SVG - 드라이브 미디어 접촉 기계적 인조인간]



 이와 같이 자바에서도 java.net 패키지를 통해 네트워킹이 가능하도록 쉽게 설계할 수 있습니다.

이제까지 만들어본 프로그램의 형태와 다르게, 기계적으로 연결되어 있지 않은 서로 다른 머신간에 데이터를 주고받는 프로그램을 설계할 수 있는 것이죠.






2. Networking : Server / Client?


 네트워킹을 공부할 때는 서버와 클라이언트의 개념을 파악하고 있어야 합니다.


클라이언트와 서버는 컴퓨터의 역할에 따라 구분됩니다. 클라이언트는 서비스를 사용하는 컴퓨터, 서버는 서비스를 제공하는 컴퓨터로 정의할 수 있습니다.


예를 들어, 게임이나 SNS를 하시는 분들은 서버 점검이나 서버 문제로 인한 접속오류 이러한 문구를 보셨을 것입니다.

SNS에 게시물을 등록하기 위해 사용자가 작성한 게시물은 다른 사용자들이 게시물을 공유할 수 있도록 모아두는 곳을 서버라고 생각하면 좋습니다.

때문에 서버 점검이나 서버 문제로 인한 접속오류같이 서버가 다운된다면 클라이언트들도 프로그램사용에 제한이 걸리게 되는 것이죠.


이처럼 프로그램을 사용하는 소비자(SNS 유저)를 클라이언트, SNS을 관리하는 회사(페이스북, 인스타..)를 서버라고 생각하면 좋습니다.


위와 같이 전용 서버를 두고 서비스를 제공하는 것을 서버기 반 모델이라 하는데 별도의 전용 서버 없이 각 클라이언트가 서버 역할을 동시에 수행하는 것을 P2P 모델이라 합니다.





3. Networking : UDP / TCP?


 네트워킹을 통해 데이터를 전송하는 방식은 TCP와 UDP로 구분됩니다.

TCP와 UDP는 각자의 장단점이 있어 차이점을 알고 필요에 따라 설계할 필요가 있습니다.


보통 TCP는 전화, UDP는 문자메시지로 비유합니다.


전화의 경우 상대방에게 연결을 위한 신호를 보낸 후에 상대방이 전화를 받았을 때 서로의 용건을 전달할 수 있는 구조입니다. 이와 같이 TCP는 데이터를 보내기 전에 무조건 상대방과 연결되어 있어야 하는 구조입니다.

반면, 문자의 경우 상대방의 전화기가 켜저 있든 꺼져있든 상대방의 번호만 안다면 문자메세지를 보낼 수 있습니다. 이처럼 UDP는 상대방과의 연결상태는 중요하지 않고 데이터를 무작정 보낼 수 있습니다.


 

 TCP

UDP 

전화 방식 (1:1) 

문자 방식(1:1, 1:n, n:n) 

 - 상대방과 연결되어 있다는 전제하에 데이터를 전송하기 때문에 신뢰성있음.

- 데이터의 전송속도 보장, 수신여부 확인

- 패킷 관리 x

- 전송속도 느림

 - 상대방과의 연결상태는 중요하지 않기 때문에 신뢰성 없음.

- 전송순서 보장 x, 수신여부 확인 x

- 패킷 관리 필요

- 전송속도 빠름

- 구현이 쉬움.







4. Networking : How do we use UDP?


 UDP를 사용하기 위해 필요한 클래스는 DatagramSocket, DatagramPacket, SocketAddress만 사용하면 되기 때문에 쉽습니다.


소켓과 패킷이라는 단어가 생소할 수 있지만, 택배에 비유하여 이해하면 쉽습니다.

소켓이란, 멀리 떨어진 사람 간의 전화를 위해 전화기가 필요한 것처럼 전화기 역할을 하는 것이 소켓입니다.

또한 패킷은 전화를 통해 서로 주고받는 음성이 패킷이라 할 수 있습니다.


택배에 비유하자면 소켓은 택배원, 패킷은 택배 물품이라고 생각할 수 있습니다.

패킷(택배 박스)에 내가 보내고자하는 ip주소(집주소)를 담은 SocketAddress와 데이터(보낼 물품)를 담아 소켓(택배원)에게 보내달라하는 셈이죠.




이제 코드상으로 어떻게 구현해야 하는지 살펴보겠습니다.


먼저 데이터를 받는 과정입니다.

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketAddress;
import java.net.SocketException;

public class Source01_Network {
public static void main(String[] args) {
System.out.println("[시스템] 스타트");

DatagramSocket socket = null;
try {
socket = new DatagramSocket(45454);
int port = socket.getLocalPort();
System.out.println("[시스템] 소켓 확보됨! " + port);

// 받는 작업
DatagramPacket packet = new DatagramPacket(new byte[1024], 1024);
System.out.print("[시스템] 수신대기!..");
socket.receive(packet);
System.out.println("완료");
SocketAddress addr = packet.getSocketAddress();
System.out.println("[시스템] 수신된 패킷 " + packet.toString());

byte[] b = packet.getData();
int len = packet.getLength();

String s = new String(b,0,len);
System.out.println(s);
System.out.println("누가 보냈어 : " + addr);

}catch(SocketException e) {
System.out.println("[시스템] 소켓 오류!" + e.toString());
}catch(IOException e) {
System.out.println("[시스템] 소켓 오류!" + e.toString());
}finally {
if(socket != null)
socket.close();
}

}
}


네트워킹이라 해서 어려운 코드와 복잡하게 설계될 것 같지만 실제로 동작은 이 정도로도 가능합니다.


- DatagramSocket : 

 DatagramSocket은 생성자의 인자값으로 port 번호를 결정해줍니다. 만약 인자값을 넘겨주지 않는다면 기본 생성자로 사용하지 않는 port 번호를 자동으로 생성할 수 있습니다. 하지만 포트 번호를 고정해야 계속해서 전달받을 수 있기 때문에 편의상 고정해두는 것이 좋습니다.


- DatagramPacket : 

 넘어올 패킷을 받기 위해 생성하는 DatagramPacket은 기본적으로 byte 배열과 길이 값을 설정해주어야 합니다. 하지만 넘어올 데이터의 정확한 길이는 받기 전에는 알 수 없는 것이기 때문에 여유롭게 길이를 생성해주어야 합니다.


- receive() :

DatagramSocket에 정의된 receive() 메소드는 패킷을 받기 위한 동작을 합니다. 이 메소드는 데이터를 받을 때까지 무한정 대기하기 때문에 만약 프로그래밍 과정에서 이러한 과정을 세밀하게 설계하지 않는다면 프로그램이 멈추거나 비정상적으로 작동할 수 있습니다.


- getSocketAddress() :

패킷을 택배 박스로 비교했는데 택배 박스에는 보낼 주소뿐만 아니라 보내는 사람의 주소도 포함되어있습니다. 마찬가지로 UDP에서도 패킷에는 목적지의 주소뿐만 아니라 보내는 사람의 주소까지 포함되어 있어 getSocketAddress() 메소드를 통해 보내는 사람의 ip주소를 확인할 수 있습니다.


- getData() :

소켓을 통해 얻은 패킷은 getData() 메소드를 통해 얻을 수 있습니다. 이때 반환 값은 Byte[] 타입이기 때문에 이를 String 화하여 데이터를 얻을 수 있습니다.



이제 데이터를 전송하는 과정에 대해서 보겠습니다.

import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
import java.net.SocketAddress;

public class Source02_Network_Send {
public static void main(String[] args) {
try(DatagramSocket soc = new DatagramSocket();){
int port = soc.getLocalPort(); // 자기가 보낼 port 개방
System.out.println("[시스템] 네트워크 가능! " + port);

String data = "가나다라";
byte[] b = data.getBytes();

// InetAddress address = InetAddress.getByName("192.168.10.192");
SocketAddress address = new InetSocketAddress("보낼 곳의 ip", 62667);
DatagramPacket packet = new DatagramPacket(b,b.length,address);
soc.send(packet);
System.out.println("[시스템] 패킷 전송 완료");

}catch(IOException e){
System.out.println("[시스템] 네트워크 오류! " + e.toString());
}
}
}


- DatagramSocket :

데이터를 받는 과정에서의 DatagramSocket은 자신이 보내고자 하는 port를 설정하는 것입니다.


- InetAddress, SocketAddress :

기본적으로 데이터를 전송하기 위해서는 목적지의 ip주소와 port를 알아야 합니다. 때문에 자바에서는 ip주소를 InetAddress 객체를 통해 관리하고, port와 합쳐 SocketAddress로 관리하게 됩니다.


- DatagramPacket :

보내고자 하는 데이터를 byte 배열로 변환하여 길이와 함께 인자로 전달하여 패킷을 생성합니다. 이때 목적지를 담은 SocketAddress를 설정하여 생성해야 합니다.


- send() :

받는 동작에서 receive() 메소드가 있다면 보내는 동작에는 send() 메소드가 있습니다. 보내고자 하는 패킷을 소켓을 통해 전송합니다.




수신 측의 프로그램을 실행시켜 둔 후 발신 프로그램을 실행시키면 다음과 같은 결과를 얻을 수 있습니다.


[수신 측 메세지]


[발신 측 메세지]






5. Networking : How do we use TCP?


 UDP보다 TCP가 구현 방법이 좀 더 복잡한 것은 맞지만 그렇다고 완전 어려운 것도 아닙니다.

데이터 교환을 위한 상대방과의 연결확인 작업을 추가해주면 됩니다.



 TCP에서는 연결확인 작업을 위해 ServerSocket과 Socket을 사용합니다.

저는 이해하기 쉽게 연결을 통해 socket이 만들어지면 그 안에 데이터를 전송할 수 있는 Stream 통로가 생성되어 위의 사진과 같이 데이터를 주고 받는 형식이 TCP라고 설명드리고 싶습니다.


이번에는 서버와 클라이언트 관점에서 서버의 구성부터 보겠습니다.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

public class Source04_Network_Server {
public static void main(String[] args) {
try {
System.out.println("[server] starting");

ServerSocket server = new ServerSocket(56789);
System.out.println("[server] bind at " + server.getLocalPort());

Socket socket = server.accept();

System.out.println("[server] connected by opposite ");
System.out.println("[server] local : " + socket.getLocalSocketAddress());
System.out.println("[server] remote : " + socket.getRemoteSocketAddress());

DataInputStream dis = new DataInputStream(socket.getInputStream());
DataOutputStream dos = new DataOutputStream(socket.getOutputStream());

String req = dis.readUTF();
System.out.println("[server] received : " + req);
String resp = "success";
dos.writeUTF(resp);
System.out.println("[server] send : " + resp);

}catch(IOException e) {
System.out.println("[server] main error " + e.toString());
}
}
}


-  ServerSocket :

ServerSocket은 클라이언트에서 발생한 socket을 연결하는 작업입니다. 서버와 클라이언트 둘 중 하나만 ServerSocket을 사용하여 클라이언트와 연결할 준비를 하는 것입니다. .ServerSocket 객체를 new 연산자를 통해 생성하는 것만으로 연결이 되는 것은 아닙니다.


- accept() :

실질적으로 클라이언트와 서버가 연결되는 부분입니다. 해당 port로 들어오는 socket을 기다리다가 클라이언트의 socket이 들어온다면 socket 객체를 만들어냅니다.

이때 socket 객체가 반환된다면 연결에 성공하는 것으로 UDP에서 receive() 메소드를 통해 데이터가 올 때까지 기다리는 작업과 비슷합니다.


- DataInputStream / DataOutputStream :

UDP와 달리 TCP는 InputStream / OutputStream을 통해 데이터 전송이 진행됩니다. 연결에 성공하여 socket이 생성되면 InputStream과 OutputStream을 얻을 수 있습니다. 이때 사용되는 Stream의 종류는 Data뿐만 아니라 DataIn,Out | ObjectIn, Out | BufferedReader, BufferedWritter, PrintWritter, Scanner로 변형시켜 데이터를 교환할 수 있는데 주의할 점은 보낼 때 사용한 Stream과 받을 때 사용하는 Stream이 같아야 한다는 것입니다.



이제 클라이언트의 동작을 봅시다.

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

public class Source04_Network_Client {
public static void main(String[] args) {
try {
Socket socket = new Socket("서버의 ip주소", 56789);

System.out.println("[client] connet to opposite in " + socket.getLocalPort());

InputStream is = socket.getInputStream();
OutputStream os = socket.getOutputStream();

DataOutputStream dos = new DataOutputStream(os);
DataInputStream dis = new DataInputStream(is);

String req = "join#saan#1q2w3e4r#가나다";
dos.writeUTF(req);
System.out.println("[client] send : " + req);

String resp = dis.readUTF();
System.out.println("[client] received : " + resp);

System.out.println("[client] done");
}catch(IOException e) {
System.out.println("[client] socket error .." + e.toString());
}
}
}


클라이언트에서 서버와 다른 점은 ServerSocket을 사용하지 않는다는 점입니다. 앞서 설명한 것과 같이 ServerSocket은 한쪽에서만 사용하여 연결을 기다려야 하기 때문입니다. 또한 서버와 클라이언트의 Stream 형태를 맞추기 위해 Data 형태의 Stream을 사용했습니다.


오히려 TCP는 UDP에 비해 서버나 클라이언트 한쪽만 설계하면 나머지 한쪽은 설계가 쉽다고 느껴지기도 합니다.


이제 UDP와 마찬가지로 서버를 실행한 후에 클라이언트를 실행하여 연결을 시도하고 데이터가 전송되는지 봅시다.


[서버에서 받은 데이터]


[클라이언트에서 전송하는 데이터]


이렇게 자바에서도 네트워킹을 쉽게 구현할 수 있습니다. 하지만 프로그램의 스케일이 커질수록 데이터 전송이 꼬일 수 있기 때문에 주의하여 사용해야 합니다.

'Java > Class' 카테고리의 다른 글

12. Thread : 쓰레드  (16) 2018.11.14
11. Collections Framework : 객체 비교  (0) 2018.11.04
10. Collections Framework : Map  (0) 2018.10.31
9. Collections Framework : Queue, Deque  (0) 2018.10.30
8. Collections Framework : List  (0) 2018.10.29
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
1 2 3 4
5 6 7 8 9 10 11
12 13 14 15 16 17 18
19 20 21 22 23 24 25
26 27 28 29 30 31
글 보관함