본문 바로가기
Java/study

IO기반 네트워킹

by avvin 2019. 4. 22.

네트워크 기초


네트워크 : 여러 대의 컴퓨터를 통신 회선으로 연결한 것


인터넷 : 지역 네트워크를 통신 회선으로 연결한 것


참고 : 생활코딩 작심삼일 프로젝트 :  서버란 무엇인가? (https://www.opentutorials.org/module/638/5201)


서버와 클라이언트


클라이언트 : 서비스를 받는 프로그램 / 인터넷에서 서비스를 받기 위해 연결을 요청 (request)


서버 : 서비스를 제공하는 프로그램 / 인터넷에서 서비스를 제공하려 연결을 수락, 응답(respond)

IP : 컴퓨터 고유 주소, 네트워크 어댑터(Lan 카드)마다 할당된다.
xxx.xxx.xxx.xxx 
x : 부호 없는 0~255 사이의 정수
cmd.exe에서 "C:\>ipconfig /all" 로 확인할 수 있다,

프로그램은 DNS(Domain Name System)를 이용하여 연결할 컴퓨터의 IP주소를 찾는다.

포트(port) 번호 
: 한 대의 컴퓨터에는 다양한 서버 프로그램들이 실행될 수 있는데 IP는 컴퓨터의 네트워크 어댑터까지만 안내하는 정보.
포트는 컴퓨터 내에서 실행되는 서버를 택1 하기 위한 추가적인 정보

포트 바인딩(port binding) : 서버가 시작할 때 고정적인 포트 번호를 가지고 실행하는 것.

동적 포트 번호 : 클라이언트도 서버에서 보낸 정보를 받기 위해 포트번호가 필요한데, 서버와 같이 고정적인 포트번호가 아니라 
운영체제가 자동으로 부여하는 동적 포트 번호를 사용한다. 
동적 포트번호는 클라이언트가 서버로 연결 요청을 할 때 전송되어 서버가 클라이언트로 데이터를 보낼 때 사용된다.



Java 에서 IP주소 얻기


자바는 IP주소를 java.net.InetAddress 객체로 표현

로컬컴퓨터의 IP주소뿐만 아니라 도메인 이름을 DNS에서 검색한 후 IP주소를 가져오는 기능 제공


InetAddress ia = InetAddress.getLocalHost(); //로컴컴퓨터 아이피 리턴


InetAddress ia = InetAddress.getByName(String host); //  도메인 이름을 통해 아이피 얻기


연결 클라이언트가 많은 회사의 경우 서버의 과부하를 막기 위해 하나의 도메인 이름에 여러 개의 컴퓨터 IP를 등록해서 운영하기도 한다. 이 경우 DNS에 등록된 모든 IP주소를 얻고 싶다면 getAllByName() 메서드 사용. 리턴 타입은 InetAddress[ ]


InetAddress[ ] iaArr = InetAddress.getAllByName(String host); // 도메인 예시) www.naver.com


InetAddress 객체에서 IP주소를 얻을 때 : getHostAddress() 메서드가 문자열로 된 IP주소 리턴


String ip = InetAddress.getHostAddress();


※UnknownHostException 이 발생할 수 있으므로 예외처리한다.



TCP 네트워킹 (Transmission Control Protocol)


*프로토콜(protocol) : 컴퓨터간에 정보를 주고받을 때의 통신방법에 대한 규칙과 약속


TCP는 연결 지향적 프로토콜.

연결 지향적 프로토콜

클라이언트와 서버가 연결된 상태에서 데이터를 주고받는 프로토콜. 클라이언트가 연결 요청을 하고, 서버가 연결을 수락하면통시누선로가 고정되고, 모든 데이터는 고정된 통신 선로를 통해서 순차적으로 전달된다. 

때문에 TCP 는 데이터를 정확하고 안정적으로 전달한다.


TCP의 단점

데이터를 보내기 전에 반드시 연결이 형성되어야 하고(가장 시간이 많이 걸리는 작업이다.)

고정된 통신 선로가 최단선(네트워크 길이 측면)이 아닐 경우 상대적으로 UDP(User Datagram Protocol)보다 데이터 전송 속도가 느릴 수 있다.


java는 TCP네트워킹을 위해 java.net.SeverSocket / java.net.Socket 클래스 제공


ServerSocket과 Socket의 용도


TCP 서버의 역할

1. 클라이언트가 연결 요청을 해오면 연결을 수락

2. 연결된 클라이언트와 통신


자바에서는 이 두 역할별로 별로의 클래스 제공


ServerSocket : 1. 연결 요청 기다리면서 연결 수락 담당


Socket : 2. 연결된 클라이언트와는 통신 담당


클라이언트가 연결 요청을 해오면 ServerSocket은 연결을 수락하고 통신용 Socket을 만든다.


통신을 위해 ServerSocket 생성할 때 포트를 하나 지정 (서버 바인딩 포트)

클라이언트는 Socket을 생성해서 서버의 IP주소와 바인딩 포트번호로 연결요청

accept() 메서드로 연결 수락하고 통신용 Socket 생성

서버와 클라이언트는 각각의 Socket을 이용하여 데이터를 주고 받는다.


1. 서버 소켓 생성과 연결 수락


2. (클라이언트) 소켓 생성과 연결 요청


3. 소켓 데이터 통신(데이터 보내고 받기)




1. 서버 소켓 생성과 연결 수락


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
32
33
34
35
36
37
38
39
40
41
42
43
package networking;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
 
public class ServerExample {
 
    public static void main(String[] args) {
 
        ServerSocket serverSocket = null;
 
        try {
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress("localhost"5001));
            while (true) {
                System.out.println("[연결 기다림]");
 
                Socket socket = serverSocket.accept(); // 연결 수락하여 소켓 생성
                InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                // getRemoteSocketAddress()의 리턴타입은 SocketAddress
                // : InetSocketAddress의 상위 클래스
 
                System.out.println("[연결 수락함]");
            }
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        if (!serverSocket.isClosed()) {
            try {
                serverSocket.close(); //서버 소켓 닫기
            } catch (IOException e2) {
            }
 
        }
 
    }
 
}
 
cs




2. (클라이언트) 소켓 생성과 연결 요청


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
package networking2;
 
import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.Socket;
 
public class ClientExample {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            socket = new Socket(); // (클라이언트) 소켓 생성
            System.out.println("[연결 요청]");
            socket.connect(new InetSocketAddress("localhost"5001));
            // 주어진 소켓주소로 연결 요청
            // "localhost", 5001 : 현재 PC의 5001포트
            System.out.println("[연결 성공]");
 
        } catch (Exception e) {
        }
 
        if (!socket.isClosed()) {
            try {
                socket.close();
            } catch (IOException e1) {
            }
        }
 
    }
}
 
cs



3. Socket 데이터 통신


클라이언트가 연결 요첨 ( connect() )하고 서버가 연결 수락 ( accept() )했다면

양쪽의 Socket 객체로부터 각각 입력 스트림(InputStream)과 출력 스트림(OutputStream)을 얻을 수 있다.


1) 클라이언트가 먼저 "Hello Server"를 서버로 보낸다

2) 서버가 이 데이터를 받고

3) "Hello Client"를 클리이언트로 보내면

4) 클라이언트가 이 데이터를 받는다. 


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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
package networking2;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.Socket;
 
public class ClientExample {
    public static void main(String[] args) {
        Socket socket = null;
        try {
            // 2. (클라이언트)소켓 생성과 연결요청
            socket = new Socket(); // (클라이언트) 소켓 생성
            System.out.println("[연결 요청]");
            socket.connect(new InetSocketAddress("localhost"5001));
            // 주어진 소켓주소로 연결 요청
            // "localhost", 5001 : 현재 PC의 5001포트
            System.out.println("[연결 성공]");
 
            // 3.Socket 데이터 통신
            // 3-1) 클라이언트가 먼저 message : "Hello Server"를 서버로 보낸다.
            byte[] bytes = null// 출력할 정보 담을 바이트배열
            String message = null;
 
            OutputStream os = socket.getOutputStream(); // 소켓을 통해 출력스트림 생성
 
            message = "Hello Server";
            bytes = message.getBytes("UTF-8");
            // 메시지 문자열을 바이트 배열에 UTF-8 문자셋으로 저장
 
            os.write(bytes);// 소켓 출력 스트림의 write(출력할 정보)로 데이터 보내기
            // 이미 연결된 상태이기 때문에 주소(InetSocketAddress)를 따로 줄 필요가 없다
            os.flush();
            System.out.println("[데이터 보내기 성공]");
 
            // 3-4)서버에게서 message : "Hello Client"를 받는다.
 
            InputStream is = socket.getInputStream();
 
            bytes = new byte[100]; // 읽어오는 데이터를 저장할 바이트 배열 생성
            int readByteCount = is.read(bytes);
            // 서버소켓의 입력스트림의 read(byte[])메서드로 읽은 데이터를 바이트배열에 저장하고
            // 항목 수를 리턴한다.
            message = new String(bytes, 0, readByteCount, "UTF-8");
            // 바이트 배열을 문자열 타입에 복사해서 문자열 참조변수인 메시지에 담는다.
 
            System.out.println("[데이터 받기 성공]" + message);
 
        } catch (Exception e) {
        }
 
        if (!socket.isClosed()) {
            try {
                socket.close();
            } catch (IOException e1) {
            }
        }
 
    }
}
 
cs





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
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
package networking;
 
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.net.Socket;
 
public class ServerExample {
 
    public static void main(String[] args) {
 
        ServerSocket serverSocket = null;
 
        try {
            // 1. 서버소켓 생성과 연결 수락
            serverSocket = new ServerSocket();
            serverSocket.bind(new InetSocketAddress("localhost"5001));
            while (true) {
                System.out.println("[연결 기다림]");
 
                Socket socket = serverSocket.accept(); // 연결 수락하여 소켓 생성
                InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
                // getRemoteSocketAddress()의 리턴타입은 SocketAddress
                // : InetSocketAddress의 상위 클래스
 
                System.out.println("[연결 수락함]");
 
                // 3.Socket 데이터 통신
                // 3-2) 서버가 "Hello Server"메시지(데이터)를 받는다
                byte[] bytes = null// 출력할 정보 담을 바이트배열
                String message = null;
 
                InputStream is = socket.getInputStream();
 
                bytes = new byte[100]; // 읽어오는 데이터를 저장할 바이트 배열 생성
                int readByteCount = is.read(bytes);
                // 서버소켓의 입력스트림의 read(byte[])메서드로 읽은 데이터를 바이트배열에 저장하고
                // 항목 수를 리턴한다.
                message = new String(bytes, 0, readByteCount, "UTF-8");
                // 바이트 배열을 문자열 타입에 복사해서 문자열 참조변수인 메시지에 담는다.
 
                System.out.println("[데이터 받기 성공]" + message);
 
                // 3-3)message : "Hello Client"를 클라이언트로 보낸다
 
                OutputStream os = socket.getOutputStream(); // 소켓을 통해 출력스트림 생성
 
                message = "Hello Client";
                bytes = message.getBytes("UTF-8");
                // 메시지 문자열을 바이트 배열에 UTF-8 문자셋으로 저장
 
                os.write(bytes);// 소켓 출력 스트림의 write(출력할 정보)로 데이터 보내기
                os.flush();
                System.out.println("[데이터 보내기 성공]");
 
            }
 
        } catch (IOException e) {
            e.printStackTrace();
        }
 
        if (!serverSocket.isClosed()) {
            try {
                serverSocket.close(); // 서버 소켓 닫기
            } catch (IOException e2) {
            }
 
        }
 
    }
 
}
 
cs



스레드 병렬처리

accept() / connect() / read() / write() 메서드를 실행할 경우에 해당 작업이 완료되기 전까지 블로킹 된다.
= ServerSocket과 Socket은 동기(블로킹) 방식으로 구동된다.

main 스레드가 직접 입출력 작업을 담당하게 되면 지속적으로 연결 수락 기능을 수행하지 못하게 되기때문에 
위 작업들은 별도의 작업 스레드를 생성해서 병렬적으로 처리하는 것이 좋다.
하지만 수천 개의 클라이언트가 동시에 연결될 경우 수천 개의 스레드가 생성되고 서버 성능이 급격히 저하되어 다운되는 현상이 발생할 수 있다.
클라이언트의 폭증으로 인해 서버의 과도한 스레드 생성을 방지하려면 스레드풀을 사용하는 것이 바람직하다.

스레드풀은 스레드 수를 제한해서 사용하기 때문에 (작업 큐의 작업량만 증가시킬 뿐)서버성능은 완만히 저하된다.



UDP 네트워킹 (User Datagram Protocol) : 비연결 지향적 프로토콜

비연결 지향적 프로토콜 : 데이터를 주고받을 때 연결 절차를 거치지 않고, 발신자가 일방적으로 데이터를 발신하는 방식

연결과정이 없기 때문에 TCP보다 빠른 전송이 가능하지만 데이터 전달의 신뢰성은 떨어진다.

순차적으로 보낸다고 순차적으로 도착하진 않는다.


속도가 중요한 프로그램에서는 UDP를 사용하고

신뢰성이 중요한 프로그램에서는 TCP를 사용한다.


자바는 UDP 네트워킹을 위해 java.net.DatagramSocket과 java.net.DatagramPacket 클래스 제공


DatagramPacket 클래스 


발신자 패킷 객체(데이터, 수신자 정보가 담김)를 생성

수신자 패킷 객체(읽은 데이터를 저장항 바이트 배열을 매개값으로) 생성


DatagramSocket 클래스


발신자 소켓 객체를 생성(매개값으로 포트넘버 안줘도 된다 = 포트바인딩 필요없다)하고 

send(Packet객체) 메서드로 정보를 전송

수신자 소켓 객체(포트 바인딩 필요)를 생성하고 recieve(packet)메서드로 받은 데이터를 읽어온다




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

NIO기반 입출력  (0) 2019.04.24
스레드풀(ThreadPool)  (0) 2019.04.24
IO기반 입출력  (0) 2019.04.22
병렬 처리 (스트림과 병렬처리 뒷부분)  (0) 2019.04.21
스트림과 병렬처리  (0) 2019.04.20