본문 바로가기

프로그래밍/Java

Java_소켓(Socket) 통신

소켓(Socket) 통신 

: 소켓통신이란 양 끝단에 포트 번호를 달아서 byte stream으로 통신을 통해서 데이터를 주고 받는 것을 뜻한다. (달아서 데이터를 주고 받음.)

소켓 통신을 하기 위해선 서버와 클라이언트가 필요하다.

→ 포트(port) 란 ?

: 항구라는 뜻으로 한 컴퓨터에 여러가지 통신을 하고 싶다면 포트 번호가 필요하다.

→ 서버와 클라이언트

서버 : 서비스 제공자 / 클라이언트 : 고객

 

  • 서버측 : ServerSocket → 연결만 받는다. / Socket → 실제로 데이터를 주고 받는다.
  • 클라이언트측 : Socket → 어느 주소에 어느 포트로 연결해야 할지 먼저 알아야 한다. / ip 주소 : 포트번호가 필요하다.

 

System 클래스의 표준 입출력 멤버

  • System.out : 표준 출력(모니터) 스트림 → System.out.println("출력 메세지");
  • System.in : 표준 입력(키보드) 스트림 → System.in.read(); // 한 바이트 읽기
  • System.err : 표준 에러 출력(모니터) 스트림 → System.err.println("에러 메세지");

 

 

BufferedReader 예제

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
package ch01;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
 
public class BufferedReaderExample {
 
    // main 함수
    public static void main(String[] args) {
        // 스트림 사용 -> 기반 + 보조
        // System.in : 키보드에서 값 입력 받기
        BufferedReader reader = new BufferedReader(new InputStreamReader(System.in));
 
        try {
            String input = reader.readLine();
            System.out.println("input : " + input);
 
            // 메인 쓰레드가 일하고 있다.
            while (input != null) {
                System.out.print("입력 : ");
                input = reader.readLine();
                System.out.println("입력한 값 : " + input);
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
 
    } // end of main
 
// end of class
cs

→ 기반 스트림(InputStreamReader) + 보조 스트림(BufferedReader)을 사용하여 키보드로 입력받은 값을 출력하는 예제이다.

→ .readLine() 메서드는 스트림에서 한 줄의 문자를 읽고 데이터를 문자열로 반환한다.

→ while문 안에 input이 비어있지 않으면 입력한 값을 출력하도록 무한 반복한다. 

 

<결과 화면>

 

 

BufferedWriter 예제

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
package ch01;
 
import java.io.BufferedWriter;
import java.io.FileWriter;
import java.io.IOException;
 
public class BufferedWriterExample {
 
    // main 함수
    public static void main(String[] args) {
        // 글자를 만들어두고 파일에다가 출력하기
        // 출력 스트림 필요하다. 
        // 기반 + 보조
        BufferedWriter writer = null;
        
        try {
            writer = new BufferedWriter(new FileWriter("output1.txt"));
            writer.write("Hello world");
            writer.newLine(); // 한줄 띄우는 명령어
            writer.write("안녕하세요");
            // 물을 내려주는 게 좋다.
            writer.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
 
    } // end of main
 
// end of class
cs

→ 기반 스트림(FileWriter)와 보조스트림(BufferedWriter)를 사용하여 output1.txt파일에 출력하는 예제이다.

→ .newLine(); 메서드는 한 줄을 띄우는 명령어이다. 

→ write를 사용하면 flush()메서드를 사용하여 다 썼다고 물을 내려줘야 한다.

 

<결과 화면>

→ 파일을 실행하고 해당 Java Project 파일을 새로고침(F5)하면 output1.txt 파일이 생성이 되고 위와 같이 출력된다.

 

 

 

Socket 예제1 - 서버(Server) - 단방향 통신

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
package ch02;
 
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
 
public class ServerTest {
 
    // 클라이언트로 연결 받는 소켓
    ServerSocket serverSocket;
 
    // 실제 통신을 담당할 소켓 필요
    Socket socket;
 
    public ServerTest() {
        initData();
    }
 
    private void initData() {
        try {
            // 서버 소켓 생성
            serverSocket = new ServerSocket(10000);
 
            // 클라이언트 접속 대기
            socket = serverSocket.accept(); // 대기하고 있음 -> 클라이언트가 들어오면 -> 소켓 반환
            System.out.println("클라이언트 연결 완료");
 
            // 스트림 연결 (입력 스트림)
            // 입력 스트림 연결 -> 대상 : 소켓
            BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 
            // 데이터를 읽는 명령
            System.out.println(reader.readLine() + "\n");
 
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    
    
    public static void main(String[] args) {
        new ServerTest();
    } //end of main
    
//end of class
cs

 → 위에서 설명한 바와 같이 Server클래스에는 ServerSocket과 Socket이 필요하다.

ServerSocket : 클라이언트로 연결 받는 소켓 / Socket : 실제 통신을 담당할 소켓

 

→ 서버 소켓을 생성하여 포트번호를 서버 소켓 생성자 안에 넣어준다.

→ Socket에 클라이언트와의 접속을 이어주기 위해 .accept() 메서드를 사용하여 저장한다. (클라이언트가 들어오면 소켓을 반환하고 클라이언트와 연결이 완료된다.)

 

→ 그 다음 기반 스트림 (InputStreamReader)과 입력 스트림 (BufferedReader)으로 연결하여 .getInputStream()을 사용하여 Socket으로 연결한다.

 

→ 그리고 클라이언트의 데이터를 읽어오는 명령어로 내용을 읽어온다. (문장의 끝을 알려주기 위해 .readLine()과 +"\n"을 사용함.)

 

 

Socket 예제1 - 클라이언트(Client) - 단방향 통신

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 ch02;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
 
public class ClientTest {
 
    // 통신을 하기 위해 소켓이 필요 + 주소(ip) + 포트번호
    Socket socket;
//    final String IP = "192.168.0.137";
    final String IP = "localhost";
    final int PORT = 10000;
 
    BufferedReader reader;
    BufferedWriter writer;
 
    public ClientTest() {
        initData();
    }
 
    private void initData() {
        System.out.println("클라이언트에서 서버 접속 요청");
 
        try {
            socket = new Socket(IP, PORT);
 
            // 클라이언트와 서버 연결 완료
 
            // 키보드에서 데이터를 입력받기
            reader = new BufferedReader(new InputStreamReader(System.in));
            String input = reader.readLine(); // 키보드에서 데이터 입력 받음
 
            // 소켓 통신을 통해서 데이터를 출력
            // 출력 스트림 연결 : 대상 소켓
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
 
            writer.write(input);
            writer.newLine(); // 문장의 끝을 알려주어야 한다.
            writer.flush();
 
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
                writer.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
 
    public static void main(String[] args) {
        new ClientTest();
    } // end of main
 
// end of class
cs

→ 위에서 설명한 바와 같이 Client 클래스에는 Socket과 ip주소 즉, 포트번호가 필요하다.

Socket을 생성하고 자신의 컴퓨터 ip주소를 입력하거나 localhost(ip와 같음)를 사용하여 선언한다. 그리고 포트 번호도 생성한다. (포트 번호는 Server 클래스와 포트 번호가 같아야 한다.)

 

→ 소켓의 생성하여 생성자 안에 ip 주소와 포트 번호를 저장한다.

이까지 했으면 서버와 클라이언트의 연결이 완료된다.

 

→ 그 다음 기반 스트림(InputStreamReader)와 보조 스트림(BufferedReader)를 사용하여 키보드에서 데이터를 입력받기 위해 System.in을 해준다. 입력받은 값을 input에 저장한다.

 

→ 키보드에서 입력받은 데이터를 Server로 출력하기 위해 OutputStreamWriter와 BufferedWriter를 사용하여 소켓으로 서버와 연결한다. 

 

→ 출력 스트림으로 .write(input)을 해주어 키보드에서 입력한 input 내용을 출력하도록 하고 문장의 끝을 알리기 위해 newLine()을 사용하고 flush()를 해주어 출력 버퍼를 비워준다. 

 

 

 

Socket 예제2 - 서버(Server) - 양방향 통신

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
package ch05;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.ServerSocket;
import java.net.Socket;
 
public class ServerTest {
 
    // 클라이언트로 연결 받는 소켓
    ServerSocket serverSocket;
 
    // 실제 통신을 담당할 소켓 필요
    Socket socket;
 
    // 입력 스트림 - 소켓을 담당
    BufferedReader reader;
    
    // 출력 스트림
    BufferedWriter bufferedWriter;
    
    // 서버측에서 키보드 데이터를 받기 위해 입력 스트림이 필요하다.
    // 키보드에 대한 입력 데이터를 담당
    BufferedReader keyboardReader;
 
    public ServerTest() {
        initData();
    }
 
    private void initData() {
        try {
            // 서버 소켓 생성
            serverSocket = new ServerSocket(10000);
 
            // 클라이언트 접속 대기
            socket = serverSocket.accept(); // 대기하고 있음 -> 클라이언트가 들어오면 -> 소켓 반환
            System.out.println("클라이언트 연결 완료");
 
            // 스트림 연결 (입력 스트림)
            // 입력 스트림 연결 -> 대상 : 소켓
            reader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 
            // 키보드에 연결 스트림 준비
            keyboardReader = new BufferedReader(new InputStreamReader(System.in));
 
            // 클라이언트측으로 데이터를 보내기 위해 출력 스트림 연결 - 대상 소켓
            bufferedWriter = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
 
            // 새로운 작업자 생성해서 일 해라고 명령하기
            WriterThread writerThread = new WriterThread();
            // Runnable 타입을 시작 시키는 방법
            Thread thread = new Thread(writerThread);
            thread.start(); // run() 메서드 안에 코드가 동작
 
            // 데이터를 읽는 명령 - main 쓰레드
            while (true) {
                String msg = "클라이언트 >>> " + reader.readLine() + "\n";
                System.out.println(msg);
            }
 
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                reader.close();
                bufferedWriter.close();
                keyboardReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    } // end of initData
 
    // 내부 클래스 생성
    class WriterThread implements Runnable {
 
        // 새로운 작업자가 해야 할 일을 정의한다.
        @Override
        public void run() {
            // 키보드에서 데이터를 입력 받아
            // 소켓 출력 스트림을 통해서 데이터를 보내주어야 한다.
            while (true) {
                try {
                    String serverMsg = "서버 : " + keyboardReader.readLine(); // 키보드의 데이터를 받을 수 있다.
                    System.out.println("서버가 작성한 문구 확인 : " + serverMsg);
                    // 출력 스트림을 통해서 데이터 보내기 <-- 소켓
                    bufferedWriter.write(serverMsg + "\n");
                    bufferedWriter.flush();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
    }
 
    public static void main(String[] args) {
        new ServerTest();
    } // end of main
 
// end of class
cs

(finally문의 close()를 사용하기 위해 멤버변수에서 스트림을 null로 초기화하여 try-catch문 안에서 생성하도록 하였다.)

 

→ 단방향 통신에서는 Server에서 Client의 입력 내용을 출력하도록 했다면, 양방향 통신에서는 Server에서도 입력한 내용을 출력하기 위해 키보드에 연결할 스트림(keyboardReader)과 Client로 보내기 위한 데이터를 출력 스트림(bufferedReader)을 생성해야 한다.

 

→ keyboardReader는 키보드와 연결할 뿐 실제 데이터의 통신은 bufferedWriter가 담당한다.

 

→ 내부 클래스로 ThreadWriter 클래스를 생성하여 쓰레드를 이용하여 Server에서 보낼 메세지를 bufferedWriter를 통해 Client에서 출력하도록 하였다.

 

→ 그 다음 while문을 사용하여 Client에서 보내는 메세지를 무한반복하여 출력하도록 했다.

 

 

 

Socket 예제2 - 클라이언트(Client) - 양방향 통신

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
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package ch05;
 
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.net.UnknownHostException;
 
public class ClientTest {
 
    // 통신을 하기 위해 소켓이 필요 + 주소(ip) + 포트번호
    Socket socket;
//    final String IP = "192.168.0.137";
    // 내 IP
    final String IP = "localhost";
    // 친구 IP
    final String IP2 = "192.168.0.183";
    final int PORT = 10000;
 
    BufferedReader keyboardReader;
    BufferedWriter writer;
 
    // 소켓통신으로 통해 들어온 데이터를 입력받아야 함
    // 입력 스트림 필요
    BufferedReader socketReader;
 
    public ClientTest() {
        initData();
    }
 
    private void initData() {
        System.out.println("클라이언트에서 서버 접속 요청");
 
        try {
            socket = new Socket(IP, PORT);
 
            // 클라이언트와 서버 연결 완료
 
            // 파이프로 연결만 해주는 역할
            // 키보드에서 데이터를 입력받기
            keyboardReader = new BufferedReader(new InputStreamReader(System.in));
 
            // 소켓 통신을 통해서 데이터를 출력
            // 출력 스트림 연결 : 대상 소켓
            writer = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
 
            // 입력받은 데이터를 소켓과 연결
            // 소켓과 입력 스트림 연결하기 (초기화)
            // 입력 스트림 연결 완료
            socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));
 
            // 서버측으로 온 데이터 확인 (입력 스트림)
            new Thread(new ReadThread()).start();
 
            // 위에 선언한 것들 여기서 사용 !!
            while (true) {
                System.out.println("키보드 입력 대기");
                String input = keyboardReader.readLine(); // 키보드에서 데이터 입력 받음
                writer.write(input);
                writer.newLine(); // 문장의 끝을 알려주어야 한다.
                writer.flush();
            }
 
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            try {
                keyboardReader.close();
                writer.close();
                socketReader.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    } // end of initData
 
    // 내부 클래스 생성
    class ReadThread implements Runnable {
 
        @Override
        public void run() {
            // 소켓 통신으로 들어온 데이터를 읽어야 함
            while (true) {
                try {
                    // readLine() : 글을 읽는 기능
                    String serverMsg = socketReader.readLine();
                    System.out.println("서버 >>> " + serverMsg);
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
 
    }
 
    public static void main(String[] args) {
        new ClientTest();
    } // end of main
 
// end of class
cs

(상대방의 IP주소를 안다면 멤버변수에서 상대방의 IP주소를 선언하고 생성자에서 그 IP주소로 변수명을 바꾸어주고 상대방 또한 똑같이 내 IP주소로 연결하면 서로 채팅이 가능하다.)

 

→ Server에서 출력한 데이터를 입력받기 위해 BufferedReader(socketReader)를 생성해준다.

 

→ 내부 클래스로 Server에서 입력한 내용을 받기 위해 쓰레드를 생성하여 사용한다. (Server 클래스에서 사용한 것처럼 쓰레드를 생성할 수 있지만, 위와 같이 생성할 수도 있다.)

 

→ while문을 이용하여 무한 반복으로 데이터를 입력받을 수 있도록 해주었다. 

 

<결과 화면>

 

 

명령 프롬프트에서 ipconfig 명령어를 사용해 자신의 ip주소를 확인할 수 있다.

'프로그래밍 > Java' 카테고리의 다른 글

Java_템플릿 메서드 패턴, 싱글톤 패턴, 빌더 패턴  (1) 2023.03.23
Java_람다식(Lambda Expression)  (1) 2023.03.22
Java_Input/Output 스트림(Stream)  (0) 2023.03.02
Java_컬렉션 프레임워크  (0) 2023.02.21
Java_Thread  (0) 2023.02.17