IP
IPv4 프로토콜은 IP주소를 4byte로 표현한다. 따라서 약 46억개의 아이피 주소만을 사용할 수 있다. PC가 보급되기 이전까지는 충분했을 지 모르나 컴퓨터가 보급되면서 이 주소만으로 식별하기는 힘들어졌다. 따라서 공인 IP주소를 라우터에 부여하고, 라우터는 연결된 기기에 사설 IP 주소를 할당하는 방식으로 IPv4의 문제점을 해결하였다.
공인 IP (Public IP) | 사설 IP (Private IP) | |
---|---|---|
할당 주체 | ISP(인터넷 서비스 공급자) | 라우터(공유기) |
할당 대상 | 개인 또는 회사의 서버(라우터) | 개인 또는 회사의 기기 |
고유성 | 인터넷 상에서 유일한 주소 | 하나의 네트워크 안에서 유일 |
공개 여부 | 내/외부 접근 가능 | 외부 접근 불가능 |
사설 IP 주소만으로는 인터넷에 직접 연결할 수 없다. 라우터를 통해 1개의 공인(Public) IP만 할당하고, 라우터에 연결된 개인 PC는 사설(Private) IP를 각각 할당받아 인터넷에 접속할 수 있게 된다.
사설 IP를 할당 받은 스마트폰 혹은 개인 PC가 데이터 패킷을 인터넷으로 전송하면, 라우터(공유기)가 해당 사설 IP를 공인 IP로 바꿔서 전송한다. 인터넷에서 오는 데이터 패킷의 목적지도 해당하는 사설 IP로 변경한 후 개인 스마트폰 혹은 PC에 전송한다.
사설 IP 주소대역은
- Class A : 10.0.0.0 ~ 10.255.255.255
- Class B : 172.16.0.0 ~ 172.31.255.255
- Class C : 192.168.0.0 ~ 192.168.255.255
로 고정된다.
랜카드
전 세계 모든 랜카드는 고유번호가 있다. 이 번호는 다른 랜카드 업체와 충돌하면 안 되기 때문에 국제조직으로부터 받아서 써야 한다. 이 부여된 번호를 제조할 때 붙이는 것이다. Lan Card 고유번호는 NIC Address, MAC(Media Access Control) Address라고도 부른다.
port fowarding
랜카드가 공유기에 접속하는 순간 공유기는 사설아이피를 보내줘서 “이 번호를 써!” 라고 말해준다. 공유기는 랜카드가 사용할 아이피주소를 보내주면서 내부적으로에 기록해서, 외부로부터 데이터가 들어오면 제대로 보낼 수 있도록 한다. 이 때 공유기 아래 공유기가 있다면 사설 아이피가 달라진다.
친구에게 데이터를 보내달라고 부탁할 때는 공인 아이피주소를 알려줘야 한다. 친구가 공인아이피로 데이터를 보낼 건데, 문제는 공유기(공인아이피)를 다른 사람들과 공유하고 있다는 것이다. 공유기로 “21번 포트로 이 데이터를 보내줘!”라고 요청이 오면, 공유기는 다수의 기기와 연결되어 있기 때문에 어느 기기로 연결을 해줘야 할 지 모를 것이다. 이러한 상황에서 공유기에 이정표를 달아주는 것을 포트 포워딩(port fowarding)이라 한다. 포트 번호와 사설 아이피 주소를 연결시켜주는 작업을 말한다. 서버쪽 개발자는 자기 공유기에 접속해서 자기 아이피를 알아내고 그 공유기에서 포트 포워딩 설정을 해야 한다. 공유기에 포트 포워딩 설정을 하지 않으면 친구는 개발자에게 데이터를 보낼 수 없다. 한편, 컴퓨터를 껐다가 키면 주소가 달라지는데, 이러면 다시 포트 포워딩을 설정해야 한다. 따라서 집에서 서버를 구축하는 것은 바람직하지 않다.
패킷
데이터를 전송할 때, 데이터는 통째로 보내지는 것이 아니다. 데이터를 쪼개서 하나의 덩어리로 보낸다.패킷이라는 작은 데이터 단위로 쪼개서 보낸다. 패킷에는 보내는 이의 주소(아이피 주소 + 포트번호), 받는 이의 주소(아이피 주소 + 포트번호) 정보도 함께 들어있다. 이 패킷에 각각 순서대로 번호를 붙인 후 네트워크로 보내는데, 도착하는 순서는 다를 수 있다. 라우터는 패킷을 데이터가 덜 몰리는 곳으로 보내버린다.
TCP/IP 프로토콜
받을 때는 운영체제에 내장되어 있는 소프트웨어인 TCP/IP 프로토콜이 패킷 번호대로 패킷을 순서대로 조정하고 그제서야 목적지로 보내준다. 만약 패킷이 오지 않았다면, (오고 있는 중이라 하더라도 너무 늦으면) 프로토콜이 “야, 5번 패킷이 안왔어! 다시 보내줘!” 라고 말해준다. 이 때 먼저 온 5번 패킷을 일단 받고, 5번 패킷이 뒤늦게 또 오면 그건 버려버린다.
Socket
데이터를 주고받을 때 Socket이라는 클래스를 사용한다.
도메인 이름과 DNS, ISP
도메인 이름은 아이피 주소를 기억하긴 인간이 너무 어려우니까 이름으로 별명으로 붙여놓은 것에 불과하다. 브라우저 주소창에 도메인 이름을 치면, 인터넷 서비스를 제공해주는 사업자(Internet Serve Provider,ISP)(SKT, KT, LGU+)가 관리하는 DNS(Domain Name System) 서버에 도메인 명에 맞는 아이피 주소를 달라고 요청을 하게 된다. 그러면 DNS 서버는 아이피 주소를 리턴한다.
이 아이피 주소는 도메인 명에 해당하는 아이피 주소일 수도 있고, 아닐 수도 있다. 아주 유명한 경우는 ISP가 관리하는 DNS 서버가 해당하는 아이피주소를 바로 리턴하지만, 대부분 그 도메인 주소를 관리하는 서버(또다른 DNS 서버)의 아이피 주소를 리턴한다. 예를 들어,www.naver.com
에 해당하는 아이피 주소를 ISP의 DNS가 모른다면 네이버가 관리하는 DNS 서버의 아이피를 리턴해주고( naver.com 주소를 관리하는 컴퓨터를 알고 있어. 그 컴퓨터 주소를 알려줄게..!
), 그러면 다시 그 아이피로 접근하여 www.naver.com
의 아이피 주소를 요청한다. 이때 네이버가 관리하는 DNS 서버가 해당하는 아이피 주소를 리턴하고 그제서야 접속이 가능하게 된다. 이렇게 두 번 거칠 수 있지만 유명하면 DNS 서버에 있기 때문에 두 단계를 거칠 수도 있고 한 단계로 바로 리턴받을 수 있다.
컴퓨터는 ISP로부터 받은 DNS 서버 주소를 알고 있다. ifconfig -all
을 주소창에 치면 DNS 서버의 주소가 두 개 나온다. 하나가 다운되면 도메인 주소에 해당하는 아이피주소를 찾을 수 없기 때문에 예비 서버를 하나 두는 것이다. 둘 다 다운되면 도메인 이름을 쳐봐야 아이피 주소를 알 수 없다. 따라서 접속할 수 없다.
아이피 주소를 모르면 접속할 수 없다. 도메인 이름은 인간에게 친숙한 이름을 아이피에 붙인 것일 뿐이다.
실습
대기열
- 클라이언트가 접속을 요청하면 대기열에 클라이언트 정보를 저장한다.
- 저장은 큐 방식으로 한다.
- 대기열의 크기가 연결을 허락하는 최대 개수이다.
- 대기열을 초과하여 클라이언트 요청이 들어왔을 때는 서버는 응답하지 않는다.
- 클라이언트는 내부에 설정된 시간(timeout) 동안 기다리다 응답을 받지 못하면 예외를 던지고 연결 요청을 취소한다.
- 서버가 실행중이면 객체가 생성되어 리턴되고, 일정 시간이 지나고도 응답이 없다면(서버가 실행중이지 않다는 뜻일 수 있다.) 예외를 던진다.
new ServerSocket(포트번호, 대기열크기)
- 대기열의 개수를 지정하지 않으면 기본 50개이다.
- 서버가 실행중일 때 포트주소가 이미 사용중일 때는
BindException
이 발생한다.
기본 대기열 크기 확인
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Server0210 {
public static void main(String[] args) throws Exception {
Scanner keyboard = new Scanner(System.in);
System.out.println("서버 실행");
ServerSocket ss = new ServerSocket(8888);
System.out.println("서버 소켓 생성 완료!");
System.out.println("클라이언트 연결을 기다리는 중...");
keyboard.nextLine();
ss.close();
System.out.println("서버 종료!");
keyboard.close();
}
}
이 서버에 반복문을 돌아서 계속 연결하면 50번째에 끊긴다. 서버 connnection refused
예외가 발생한다. 즉 대기열의 개수를 지정하지 않으면 기본 50개 클라이언트까지 수용할 수 있다.
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
public class Client0211 {
public static void main(String[] args) throws Exception {
// 실행을 잠시 중단시키기 위해 사용
Scanner keyScan = new Scanner(System.in);
System.out.println("클라이언트 실행!");
int count = 0;
while (true) {
try {
Socket socket = new Socket("localhost", 8888);
System.out.printf("서버에 연결됨! - %d\n", ++count);
// 서버의 대기열에 접속 순서대로 대기한다.
// 서버에서 연결이 승인되면, 비로서 입출력을 할 수 있다.
// 일단 멈춤!
keyScan.nextLine();
// 대기열의 크기에 따라 연결되는 클라이언트 수의 제한을 확인하기 위해
// 반복해서 서버와 연결한다.
} catch (Exception e) {
e.printStackTrace();
break;
}
}
System.out.println("서버와의 연결을 끊었음.");
keyScan.close();
}
}
대기열 크기 조정
다음과 같이 대기열에 있을 클라이언트의 개수를 지정해줄 수 있다.
1
ServerSocket = new ServerSocket(8888, 2);
그러면 다음과 같이 대기열에 2개의 클라이언트만 수용할 수 있다.
1
2
3
4
5
6
클라이언트 실행!
서버에 연결됨!1
서버에 연결됨!2
java.net.ConnectException: Connection refused: connect
타임아웃 시간 설정
JVM은 운영체제에 종속되어 있다. 따라서 운영체제에 다라 대기시간이 달라질 수 있다. 그러나 클라이언트의 최대 대기시간을 직접 설정할 수도 있다.
- 타임아웃으로 지정된 시간 안에 서버와 연결되지 않을 경우 즉시 예외가 발생한다.
- Windows의 경우,
- 로컬에 접속할 때 타임아웃 설정이 정상적으로 동작되지 않는다.
- 원격 윈도우 PC에 서버를 실행하여 접속한다면 정상적으로 동작한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Client0310 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
System.out.println("클라이언트 실행!");
// 1) 소켓을 생성한다.
Socket socket = new Socket();
System.out.println("소켓 생성됨.");
// 2) 연결할 서버의 주소를 준비한다.
SocketAddress socketAddress = new InetSocketAddress("localhost", 8888);
// 3) 서버와의 연결을 시도한다.
System.out.println("서버와 연결 중..");
socket.connect(socketAddress, 10000);
System.out.println("서버와 연결되었음!");
keyScan.nextLine();
socket.close();
System.out.println("서버와 연결을 끊었음.");
}
}
Sock 생성 부분이 이해가 잘 안된다면 File 입출력 할 때 코드를 떠올리면 된다.
1
2
3
4
5
File file = new File("test.txt");
FileInputStream in = new FileInputStream(file);
// 이렇게도 할 수 있고
FileInputStream in = new FileInputStream("test.txt");
// 이렇게 할 수도 있다.
1
2
3
4
// 마찬가지로 이렇게 할 수도 있고 바로 넘겨줄 수도 았다.
Socket socekt = new Socket();
SocketAddress socketAddress = new Inet~;
socket.connect(socketAddress, 5000);
상황에 따른 테스트
Socket socket = ss.accept()
- 큐에 대기 중인 클라이언트 중 첫 번째 클라이언트를 꺼내서 연결 승인
- 클라이언트가 서버에 연결을 요청하면 서버는 대기열에 추가한다.
- 서버소켓에서 연결을 승인하면 클라이언트와 통신할 수 있는 소켓을 리턴한다.
- 대기열에 기다리고 있는 클라이언트가 없으면, 접속할 때까지 기다린다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Server0410 {
public static void main(String[] args) throws Exception {
Scanner keyboard = new Scanner(System.in);
ServerSocket ss = new ServerSocket(8888, 2);
System.out.println("서버 소켓 생성 완료!");
System.out.println("클라이언트 연결을 기다리는 중...");
Socket socket = ss.accept();
System.out.println("대기 중인 클라이언트 중 한 개의 클라이언트에 대해 연결 승인!");
keyboard.nextLine();
socket.close();
System.out.println("클라이언트와의 연결 해제!");
ss.close();
System.out.println("서버 종료!");
keyboard.close();
}
}
대기열에 클라이언트가 없을 때
accept()
는 블로킹 상태에 놓인다. 즉 리턴하지 않는다.
여러 클라이언트가 대기열에 있을 때
accept()
를 호출하는 순간 즉시 대기열에서 맨 앞의 클라이언트 정보를 꺼내 연결한다. Queue 방식으로 대기열을 관리하기 때문에 맨 뒤에 빈자리가 생긴다.
byte stream
바이트 스트림을 사용할 때는 바로 출력하기 때문에 출력할 때 flush()를 호출하지 않아도 된다.
바이트값 주고받기
서버
- Client와 Server의 통신 규칙에 따라 순서대로 입출력해야 한다.
- 입출력은 blocking 모드로 동작하기 때문이다. 즉
read()
라는 메서드는 클라이언트가 보낸 데이터를 읽기 전까지 리턴하지 않는다. - 클라이언트와 서버 간의 데이터를 주고 받는 통신규칙을 프로토콜이라 한다.
- 클라이언트에서 한 줄의 문자열을 보내면 서버는 한 줄의 문자열을 읽은 후에 응답해야 한다.
- 서로 읽겠다고 하거나 서로 쓰겠다고 하면 안 된다. 합을 잘 맞춰야 한다.
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
socket = serverSocket.accept();
System.out.println("대기열에서 클라이언트 정보를 꺼내 소켓을 생성하였음.");
// 클라이언트와 데이터를 주고 받을 입출력 스트림 객체를 준비한다.
out = socket.getOutputStream();
in = socket.getInputStream();
System.out.println("클라이언트와 통신할 입출력 스트림이 준비되었음.");
System.out.print("데이터를 읽기 전에 잠깐!");
keyboard.nextLine();
System.out.println("클라이언트가 보낸 1바이트를 기다리고 있음!");
// => 클라이언트가 1바이트를 보낼 때까지 리턴하지 않는다.
int request = in.read(); // blocking 모드로 작동한다.
System.out.println(request);
// 서버가 데이터를 보내지 않으면 클라이언트의 read()는 리턴하지 않는다.
// 이를 확인하기 위해 잠시 실행을 멈춘다.
System.out.print("데이터를 보내기 전에 잠깐!");
keyboard.nextLine();
// => 클라이언트에게 받은 문자열을 그대로 보낸다.
// 물론 클라이언트가 보낸 데이터를 다 읽을 때까지 리턴하지 않는다.
out.write(request);
// out.flush();
// byte stream 을 사용할 때는 바로 출력한다.
// 따라서 flush()를 호출하지 않아도 된다.
System.out.println("클라인트에게 데이터를 보냈음.");
- 한번 데이터를 전송하면 상대방이 데이터를 받을 때가지는 다시 전송을 못 한다.
- 입출력 프로그램은 리턴할 때까지 blocking 모드로 동작한다.
클라이언트
- 서버에게 데이터 보내기
- 보통 클라이언트 쪽에서 먼저 서버로 데이터를 보낸다.
- 실제
write()
소켓의 내부 버퍼로 출력한다. 따라서 write() 호출 후 즉시 리턴될 것이다. 즉 상대편에서 읽을 때까지 기다리는 것이 아니다. 보낼 데이터를 랜카드의 메모리에 올려 놓으면 바로 리턴한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
keyScan = new Scanner(System.in);
socket = new Socket("localhost", 8888);
System.out.println("서버와 연결되었음!");
out = socket.getOutputStream();
in = socket.getInputStream();
System.out.println("소켓을 통해 입출력 스트림을 준비하였음!");
System.out.print("데이터를 보내기 전에 잠깐!>");
keyScan.nextLine();
// 서버에게 데이터 보내기
out.write(100);
System.out.println("서버에 데이터를 보냈음!");
int response = in.read();
// 서버의 응답을 받는다.
// 서버가 응답할 때까지 리턴하지 않는다. 즉 blocking 모드로
System.out.println(response);
바이트 배열 주고받기
서버
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
public class Server0120 {
public static void main(String[] args) {
try (Scanner keyboard = new Scanner(System.in);
ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("클라이언트의 연결을 기다리고 있음.");
try (Socket socket = serverSocket.accept();
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream()) {
System.out.println("대기열에서 클라이언트 정보를 꺼내 소켓을 생성하였음.");
System.out.println("클라이언트와 통신할 입출력 스트림이 준비되었음.");
System.out.println("클라이언트가 보낸 100바이트를 기다리고 있음!");
// => 클라이언트가 100바이트를 보낼 때까지 리턴하지 않는다.
byte[] buf = new byte[100];
int size = in.read(buf);
System.out.printf("읽은 바이트 수: %d\n", size);
for (int i = 0; i < size; i++) {
if (i > 0 && (i % 20) == 0) {
System.out.println(); // 20바이트 출력한 후 줄 바꾼다.
}
System.out.println("%x", buf[i]);
}
System.out.print("데이터를 보내기 전에 잠깐!");
keyboard.nextLine();
// 클라이언트에서 받은 바이트 개수 만큼 배열을 출력한다.
out.write(buf, 0, size);
System.out.println("클라인트에게 데이터를 보냈음.");
}
System.out.println("클라이언트와의 연결을 끊었음.");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("서버 종료!");
}
}
클라이언트
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
public class Client0120 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
try (Socket socket = new Socket("localhost", 8888);
OutputStream out = socket.getOutputStream();
InputStream in = socket.getInputStream()) {
System.out.println("서버와 연결되었음!");
System.out.print(">");
keyScan.nextLine();
// 서버에 보낼 바이트 배열을 준비한다.
// => 0 ~ 99의 값을 가진 배열이다.
byte[] bytes = new byte[100];
for (int i = 0; i < i < 100; i++) {
byte[i] = (byte) i;
}
// 서버에 바이트 배열을 전송한다.
out.write(bytes);
System.out.println("서버에 데이터를 보냈음!");
byte[] buf = new byte[100];
int size = in.read(buf);
System.out.printf("바이트 배열의 크기: %d\n", size);
for (int i = 0; i < size; i++) {
if (i > 0 && (i % 20) == 0) {
System.out.println(); // 20바이트 출력한 후에 줄 바꾼다.
}
System.out.printf("%x ", buf[i]);
}
} catch (Exception e) {
e.printStackTrace();
}
keyScan.close();
}
}
데이터 주고받기: DataIOStream
DataIOStream은 IOStream을 상속한 데코레이터다. 그러나 IOStream을 DataIOStream 객체를 받는 레퍼런스로 선언하면 안 된다. IOStream에는 사용할 DataIOStream의 readUTF, readByte 등의 메서드가 없기 때문이다.
서버
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
public class Server0120 {
public static void main(String[] args) {
try (Scanner keyboard = new Scanner(System.in);
ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("클라이언트의 연결을 기다리고 있음.");
try (Socket socket = serverSocket.accept();
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
DataInputStream in = new DataInputStream(socket.getInputStream())) {
System.out.println("클라이언트와 연결되었고, 입출력 스트림도 준비되었음!");
System.out.println("클라이언트가 보낸 Data를 기다리고 있음!");
int value = in.readInt();
byte value2 = in.readByte();
float value3 = in.readFloat();
String value4 = in.readUTF();
System.out.printf("%d, %d, %f, %s\n", value, value2, value3, value4);
System.out.println("데이터를 보내기 전에 잠깐!");
keyboard.nextLine();
// 클라이언트에서 받은 Data를 그대로 리턴한다.
out.writeInt(value);
out.writeByte(value2);
out.writeFloat(value3);
out.writeUTF(value4);
System.out.println("클라인트에게 데이터를 보냈음.");
}
System.out.println("클라이언트와의 연결을 끊었음.");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("서버 종료!");
}
}
클라이언트
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
public class Client0120 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
try (Socket socket = new Socket("localhost", 8888);
DataOutputStream out = new DataOutputStream(socket.getOutputStream());
DataInputStream in = new DataInputStream(socket.getInputStream())) {
System.out.println("서버와 연결되었음!");
System.out.print(">");
keyScan.nextLine();
// 서버에 데이터를 전송한다.
out.writeInt(1567891234);
out.writeByte(100);
out.writeFloat(3.14f);
out.writeUTF("ABC가각간");
System.out.println("서버에 데이터를 보냈음!");
// 서버에서 보낸 int값을 읽는다.
int value = in.readInt();
int value2 = in.readByte();
float value3 = in.readFloat();
String value4 = in.readUTF();
System.out.printf("%d, %d, %f, %s\n", value, value2, value3, value4);
} catch (Exception e) {
e.printStackTrace();
}
keyScan.close();
}
}
문자열 주고받기: PrintStream과 Scanner
Scanner는 데코레이터 역할을 하고 있긴 하지만(기능확장) 데코레이터는 아니다. 상속을 받지 않았기 때문이다.
서버
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
public class Server0120 {
public static void main(String[] args) {
try (Scanner keyboard = new Scanner(System.in);
ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("클라이언트의 연결을 기다리고 있음.");
try (Socket socket = serverSocket.accept();
PrintStream out = new PrintStream(socket.getOutputStream());
Scanner in = new Scanner(socket.getInputStream())) {
System.out.println("클라이언트가 보낸 한 줄의 문자열을 기다리고 있음!");
String str = in.nextLine();
System.out.println(str);
System.out.print("데이터를 보내기 전에 잠깐!");
keyboard.nextLine();
out.println(str);
System.out.println("클라인트에게 데이터를 보냈음.");
}
System.out.println("클라이언트와의 연결을 끊었음.");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("서버 종료!");
}
}
클라이언트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Client0120 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
try (Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(socket.getOutputStream());
Scanner in = new Scanner(socket.getInputStream())) {
System.out.println("서버와 연결되었음!");
System.out.print(">");
keyScan.nextLine();
out.println("AB가각간");
System.out.println("서버에 데이터를 보냈음!");
String str = in.nextLine();
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
keyScan.close();
}
}
byte stream + buffer
서버
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
public class Server0120 {
public static void main(String[] args) {
try (Scanner keyboard = new Scanner(System.in);
ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("클라이언트의 연결을 기다리고 있음.");
try (Socket socket = serverSocket.accept();
PrintStream out = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
Scanner in = new Scanner(new BufferedInputStream(socket.getInputStream()))) {
System.out.println("클라이언트가 보낸 한 줄의 문자열을 기다리고 있음!");
String str = in.nextLine();
System.out.println(str);
System.out.print("데이터를 보내기 전에 잠깐!");
keyboard.nextLine();
out.println(str);
System.out.println("클라인트에게 데이터를 보냈음.");
}
System.out.println("클라이언트와의 연결을 끊었음.");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("서버 종료!");
}
}
클라이언트
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public class Client0120 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
try (Socket socket = new Socket("localhost", 8888);
PrintStream out = new PrintStream(new BufferedOutputStream(socket.getOutputStream()));
Scanner in = new Scanner(new BufferedInputStream(socket.getInputStream()))) {
System.out.println("서버와 연결되었음!");
System.out.print(">");
keyScan.nextLine();
out.println("AB가각간");
System.out.println("서버에 데이터를 보냈음!");
String str = in.nextLine();
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
keyScan.close();
}
}
Character Stream
character stream 클래스의 경우 출력 데이터를 내부 버퍼에 보관하고 있다가 flush
가 호출되면 비로소 출력을 수행한다.
서버
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
public class Server0120 {
public static void main(String[] args) {
try (Scanner keyboard = new Scanner(System.in);
ServerSocket serverSocket = new ServerSocket(8888)) {
System.out.println("클라이언트의 연결을 기다리고 있음.");
try (Socket socket = serverSocket.accept();
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
PrintWriter out = new PrintWrtier(socket.getOutputStream())) {
System.out.println("클라이언트가 보낸 한 줄의 문자열을 기다리고 있음!");
String str = in.readLine();
System.out.println(str);
System.out.print("데이터를 보내기 전에 잠깐!");
keyboard.nextLine();
out.println(str);
out.flush();
System.out.println("클라인트에게 데이터를 보냈음.");
}
System.out.println("클라이언트와의 연결을 끊었음.");
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("서버 종료!");
}
}
클라이언트
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
public class Client0120 {
public static void main(String[] args) {
Scanner keyScan = new Scanner(System.in);
try (Socket socket = new Socket("localhost", 8888);
BufferedReader in = new BufferedReader(new InputStream(socket.getInputStream()));
PrintWriter out = new PrintWriter(socket.getOutputStream())) {
System.out.println("서버와 연결되었음!");
System.out.print(">");
keyScan.nextLine(); // blocking: 키보드에서 한 줄을 읽을 때까지
out.println("AB가각간");
out.flush();
System.out.println("서버에 데이터를 보냈음!");
String str = in.nextLine();
System.out.println(str);
} catch (Exception e) {
e.printStackTrace();
}
keyScan.close();
}
}