네트워크 통신(1)
날짜: 2021년 6월 15일
학습목표
자바를 통한 네트워크 통신에 대해서 알아보자. 자바의 신 교재를 통해서, 간략하게 네트워크 프로그래밍에 대해 알아보고, 코드로 실습해보자.
- 네트워크 프로그래밍이란?
- Socket프로그래밍
- UDP 프로그래밍
네트워크 프로그래밍 이란?
사용자들이 바로 옆의 장비와 데이터를 주고 받는 작업을 네트워킹(networking) 이라 한다.
네트워크 계층
이런 네트워크는 총 7가지 계층으로 이루어져 있으나, 이 포스팅에서는 간단하게 알아보고, 추후에 7가지 계층에 대해서 자세히 알아보자.
위 그림의 애플리케이션 레이어 중 대표적인 HTTP, ftp, telnet들은 모두 TCP(Transmission Control Protocol) 통신을 한다 만약 자바로 TCP 통신을 한다면, 자바에서 제공하는 API를 사용하면 된다. 즉, 애플리케이션 레이어에서 프로그래밍만 하면 그 이하의 처리는 자바에서 모두 처리해준다는 뜻이다.
TCP와 UDP
이런 네트워킹에는 TCP와 UDP 방식이 존재하는데, TCP방식은 연결기반 프로토콜 이라 불린다.
전송한 데이터를 상대가 받았는지 확인할수 있다. 즉 데이터 전송을 보장한다.
반면 UDP(User Datagram Protocol) 는 데이터 전송이 제대로 됬는지 확인하지 않는다. 즉 데이터 전송에 대한 보장이 없다는 뜻이다.
그럼 왜 TCP만 쓰지 UDP를 쓸까??
→ 모든 데이터 전송이 반드시 꼭 전달되야만 하는 데이터는 아니다. 그리고 물론 기능이 더 보장되므로 TCP의 비용이 UDP보다 비싸고, 속도도 느리다. 둘의 tradeoff를 알고, 상황에 맞는 방식을 사용하자.
포트
포트에 대해 알아보자. 일반적인 웹 애플리케이션은 80을 쓴다. 이건 그냥 정해진거라 외우면 된다. 만약, 99를 쓰고 싶다면, 웹 서버에 포트번호를 99로 지정한 후에, 웹 서버 주소 뒤에 :(콜론)을 붙이고 99를 쓰면 된다. (80은 쓰지않아도 기본값으로 붙는다) 보안이 좀더 강화된 SSL을 이용한다면 443을쓴다. 이런식으로 0 ~1023까지는 용도가 정해진것들이 많으므로, 해당 범위에선 사용자 지정포트를 지양하도록하자. 하지만 포트는 16비트로 구성되어 65,535까지 사용이 가능하다!!
소켓 프로그래밍
위에서 알아본 TCP통신을 자바에서 수행하려면 소켓(Socket) 클래스를 사용하면 된다.
java.net 패키지에 선언되어 있으며, API를 열어 어떤 클래스들이 있는지 한번 확인해보자!
A socket is an endpoint for communication between two machines.
자바 API 공식문서의 Socket클래스에 나온 소켓에 대한 설명이다. 두 기계(서버)간의 통신의 도착지점 이라고 생각하면 되겠다. (즉, 서로간의 통신이 끝나는 지점?!) 추가적으로 실질적인 소켓의 수행은 소켓 클래스가 아니라, SokectImpl 클래스를 통해 이루어진다고 한다.
이 소켓 클래스는 데이터를 보내는 쪽 (주로 클라이언트)에서 객체를 생성해서 사용한다. 데이터를 받는 쪽 (주로 서버)에서 요청을 받으면, 요청에 대한 소켓 객체를 생성, 데이터를 처리한다. 즉 이 소켓 클래스는 서버,클라이언트 구분없이 원격에 있는 장비와의 연결 상태를 보관하고 있다고 생각하면 된다.
그러면 서버에서는 어떻게 데이터를 받을까??
서버에서는 서버소켓(ServerSocket) 클래스를 사용한다. 위 설명에서 서버는 요청에 대한 소켓 객체를 만든다고 했는데, 이 객체는 new 연산이 필요없고, 서버소켓 클래스에서 제공하는 메소드에서 클라이언트 요청이 들어오면 자동으로 소켓 객체를 생성, 전달해준다.
A server socket waits for requests to come in over the network
위의 소켓 클래스와 마찬가지로 실질적인 수행은 SocketImpl 클래스에서 수행된다고 한다!
서버소켓은 많은 메소드를 제공하지만, 지금 여기서는 생성자와, 두 가지 메소드만 알아도 충분히 네트워크 프로그래밍이 가능하다. 해당 사항을 알아보자!
여기서 backlog라는 값이 있는데, 쉽게 생각하면 큐의 개수라고 보면 된다. 서버소켓 객체가 바빠서 연결 요청을 처리 못하고 대기 시킬때가 있는데, 그 때의 최대 대기 개수라고 보면 된다.
The backlog argument is the requested maximum number of pending connections on the socket.
backlog - requested maximum length of the queue of incoming connections.
자바 API문서에서는 backlog에 대한 설명을 위와 같이 하고있다. 운영체제에서 배웠던, 메모리큐?... 와 비슷한것 같다. 아마 여기서도 비슷한 스케줄링?... 알고리즘이 사용되지 않을까 싶다.
백로그는 지정하지 않을 시 기본값 50으로 설정된다. 만약 우리가 만든 애플리케이션의 접속이 원할하지 않다면, 이 개수를 적절하게 증가시키는 것이 좋다.
한가지 유의할 점은 매개변수가 없는 첫번째 생성자를 제외한 생성자는 생성되자마자 연결을 대기할 수 있는 상태이다. 즉, 첫번째 생성자의 경우 별도의 연결작업을 수행해줘야 한다. 이렇게 연결을 맺고, 끊는 메소드 두가지를 알아보자.
여기서 close()메소드를 보면 감이 오듯이, 사용이 끝난 소켓은 close() 호출하지 않으면, 해당 포트는 동작하는 서버나 PC에서 다른 프로그램이 사용할 수없다.
이번에는 데이터를 보내기위한 소켓 클래스에 대해서 알아보자.
서버소켓은 요청이 들어오면 자동적으로 생성이 됬지만, 클라이언트 쪽에서는 직접 생성해야한다.
host와port를 매개변수로 받는 생성자가 가장 편리하고, 대부분의 다른 생성자들은 별도의 용도가 있는 소켓 객체를 생성한다고 보면 된다. 위 의 3가지 생성자를 제외하면 모두 생성과 함께 지정된 서버에 자동으로 접속한다. 서버소켓과 마찬가지로 close() 메소드를 통해 소켓을 닫을 수 있다.
간단한 소켓 통신을 해보자!
앞서 배웠던 내용들을 토대로, 코드로 실습해보자!
package network;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketServerSample {
public static void main(String[] args) {
SocketServerSample sample = new SocketServerSample();
sample.startServer();
}
public void startServer() {
try(ServerSocket server = new ServerSocket(9999)){
while(true) {
System.out.println("Server:Waiting for request.");
try(Socket client = server.accept()){
System.out.println("Server:Accepted");
try(InputStream stream = client.getInputStream()){
try(BufferedReader in = new BufferedReader(
new InputStreamReader(stream))){
String data = null;
StringBuilder receivedData = new StringBuilder();
while((data=in.readLine()) != null) {
receivedData.append(data);
}
System.out.println("Received data : " + receivedData);
if(receivedData != null && "EXIT".equals(receivedData.toString())) {
System.out.println("Stop SocketServer");
break;
}
System.out.println("---------------------------");
}
}
}
}
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
천천히 코드의 주요부분을 보며 이해해보자.
ServerSocket server = new ServerSocket(9999)
포트번호 9999를 이용해 서버소켓 객체를 생성했다. 그러므로, 다른 프로그램에서 이 프로그램이 띄운 서버로 접속하려면 포트번호 9999를 사용하면 된다.
Socket client = server.accept()
서버소켓 클래스에 선언된 accept() 메소드를 호출, 다른 원격 호출을 대기하는 상태가 된다. (연결 대기 상태로 만드는것) 만약 연결이 완료되면 client 변수에 서버소켓 객체를 할당한다.
InputStream stream = client.getInputStream()
데이터를 받기 위해서는 소켓클래스에 선언된 getInputStream() 메소드를 호출, InputStream객체를 받는다. 만약 접속한 상대방에게 데이터를 전송하려면 getOutputStrema() 메소드로 OutputStream객체를 받은 후 이 stream에 전달하면 된다.
그럼 이제 client부분을 구현해보자!
package network;
import java.io.BufferedOutputStream;
import java.io.OutputStream;
import java.net.Socket;
import java.util.Date;
public class SocketClientSample {
public static void main(String[] args) {
SocketClientSample sample = new SocketClientSample();
sample.sendSocketSample();
}
public void sendSocketSample() {
for(int loop =0; loop<3; loop++) {
sendSocketData("I liked java at " + new Date());
}
sendSocketData("EXIT");
}
public void sendSocketData(String data) {
System.out.println("Client:Connecting...");
try(Socket socket = new Socket("127.0.0.1",9999)){
System.out.println("Client: Connect status = " + socket.isConnected());
Thread.sleep(1000);
try(OutputStream stream = socket.getOutputStream()){
try(BufferedOutputStream out = new BufferedOutputStream(stream)){
byte[] bytes = data.getBytes();
out.write(bytes);
System.out.println("Client : Sent data");
}
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
클라이언트 단의 주요 코드들을 이해해보자.
Socket socket = new Socket("127.0.0.1",9999)
127.0.0.1 이라는 IP는 같은장비라는 것을 의미한다. 그리고 포트번호는 서버쪽에서 지정한 포트번호와 동일해야 한다. 이 두개의 매개변수를 같는 생성자를 통해 객체를 생성, 접속을 했다.
Thread.sleep(1000);
멀티쓰레드의 동기화로 인한 경쟁상태를 막고자 sleep으로 중간중간 텀을 주었다.
OutputStream stream = socket.getOutputStream()
데이터를 서버에 전달하기 위해 getOutputStream() 메소드로 OutputStream 객체를 얻어왔다.
out.write(bytes);
wirte 코드를 통해서 데이터를 서버로 전달한다!!
결과
서버 콘솔
Server:Waiting for request. Server:Accepted Received data : I liked java at Tue Jun 15 11:39:39 KST 2021 --------------------------- Server:Waiting for request. Server:Accepted Received data : I liked java at Tue Jun 15 11:39:40 KST 2021 --------------------------- Server:Waiting for request. Server:Accepted Received data : I liked java at Tue Jun 15 11:39:41 KST 2021 --------------------------- Server:Waiting for request. Server:Accepted Received data : EXIT Stop SocketServer
클라이언트 콘솔
Client:Connecting... Client: Connect status = true Client : Sent data Client:Connecting... Client: Connect status = true Client : Sent data Client:Connecting... Client: Connect status = true Client : Sent data Client:Connecting... Client: Connect status = true Client : Sent data
추가로 이 예제를 실행하며 발생할 수있는 예외들이 있다. 당황하지 말고 잘 읽어보자.
- java.net.BindException : Address already in use
- 서버를 띄워 놓고 또 띄웠을 때 발생한다. 이미 지정된 port번호를 사용하고 있기 때문에 동일 port 번호를 사용할 수 없기 때문이다.
- java.net.ConncectException : Connection refused
- 서버를 띄워 놓지 않고 클라이언트 프로그램만 수행했을 때 발생한다. 왜냐하면 받을 서버가 없기 때문에, 던질 곳도 없기 때문이다.
위의 예제코드의 실행 흐름을 좀더 보기 쉽게 그림으로 나타내 보았다.
참고로 여기선 클라이언트에서 서버로 데이터를 전송했지만, 그 반대도 상관없다.
Feedback
왜일까?? 이번엔 왠지 쉽게 쉽게 공부가 된다. 그만큼 예제와 개념이 쉬워서 일까?.. 어쩌면 API 공식문서를 같이 본게 도움이 된것 같기도 하다. 실습 코드때문에 포스팅이 길어져 나눠서 UDP 프로그래밍을 실습 해보자!
알게된 점
- 네트워크 프로그래밍이란?
- 네트워킹이란 바로 옆의 장비와 데이터를 서로 주고받는 것을 말한다.
- 네트워크 프로그래밍이란 이런 네트워킹을 구현하는 프로그래밍이다.
- 네트워킄 OSI 7 계층으로 이루어져 있다.
- 자바 API의 지원으로 개발자들은 애플리케이션 레이어에서만 프로그래밍을 하면 된다.
- 통신 방식에는 TCP와 UDP방식이 존재한다.
- TCP(Transmission Control Protocol) : 연결 기반 통신으로, 데이터가 전송 되었음을 보장한다.
- UDP(User Datagram Protocol) : TCP와 달리 데이터가 전송되었음을 보장하지 않는다.
- TCP가 더 좋아 보이는데 왜 UDP가 있을까?
- TCP는 당연하게도 비용과 속도가 느리다.
- 모든 데이터가 반드시 전달되야 하는것은 아니다.
- 상황에 맞게 두 방식의 tradeoff를 알고 구현해야한다.
- port
- 웹 애플리케이션은 기본적으로 80 포트를 사용한다. 외우면 된다.
- 99포트를 사용하고 싶다면 웹 서버 설정을 바꾼후, 웹 서버 주소 뒤에 :99를 붙여주면 된다. 즉 80은 쓰지않아도 자동적으로 붙는다.
- 보안이 좀더 강화된 SSL이용시 443포트를 사용한다.
- 0~1023은 이미 정해진 용도가 있기 때문에, 사용자 지정을 지양하자. 다만 포트는 16비트로 65,535개의 포트가 존재한다!
- Socket프로그래밍
- 자바에서 TCP 통신을 구현하려면 socket 클래스를 사용하면된다.
- backlog : 처리량 이 너무많아 연결 큐에 대기시, 대기 할수 있는 최대 연결의 수
- ServerSocket 객체는 new 연산자로 생성할 필요없이, 연결 요청이 들어오면, 그에 맞는 socket 객체를 생성해 준다.
- Socket(Client단) 은 new 연산자로 직접 생성해 줘야한다.
알아야할 점
- OSI 7 계층
- 소켓연결에 문제가 있을 경우 Timeout관련 메소드는 꼭 확인해보자! → 실제 운영 시스템에서는 매우 중요하기 때문이다.
- UDP 프로그래밍
References
자바의신 VOL2
'프로그래밍 > Java' 카테고리의 다른 글
Non-Blocking,Blocking VS Async,Sync (0) | 2021.06.19 |
---|---|
네트워크 통신(2) (0) | 2021.06.15 |
Buffer (0) | 2021.06.14 |
NIO(3) (0) | 2021.06.14 |
NIO(2) (0) | 2021.06.14 |