일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
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 |
- CSS
- 중소기업면접
- 프로그래밍
- HTML
- 필기
- 알고리즘
- 코딩
- Linux
- 웹
- 농은면접
- 부스트코스
- BOJ
- 정수내림차순으로배치하기
- algorithm
- 웹개발
- 연결요소의개수
- 후기
- 프로그래밍언어
- 프로그래머스
- 건보필기
- 한국재정정보원
- 필기후기
- 웹프로그래밍
- 공부
- 백준
- 인강
- 수박수박수박수박수?
- 확인문제
- java
- 이클립스
- Today
- Total
공부하는 히욤이
[이것이 자바다] Chapter 18 IO 기반 입출력 및 네트워킹 본문
18.1 IO 패키지 소개
- 자바에서 데이터는 스트림(Stream)을 통해 입출력 됨
- 스트림은 단일 방향으로 연속적으로 흘러가는 것 => 데이터는 출발지에서 나와 도착지로 들어간다는 개념
18.2 입력 스트림과 출력 스트림
- 입력 스트림(InputStream) : 프로그램이 데이터를 입력 받을 때
- 출력 스트림(OutputStream) : 프로그램이 데이터를 보낼 때
- 기본적인 데이터 입출력(IO : Input/Output) API는 java.io 패키지에서 제공
java.io 패키지의 주요 클래스 | 설명 |
File | 파일 시스템의 파일 정보를 얻기 위한 클래스 |
Console | 콘솔로부터 문자를 입출력하기 위한 클래스 |
InputStream / OutputStream | 바이트 단위 입출력을 위한 최상위 입출력 스트림 클래스 |
FileInputStream / FileOutputStream DataInputStream / DataOutputStream ObjectInputStream / ObjectOutputStream PrintStream BufferedInputStream / BufferedOutputStream |
바이트 단위 입출력을 위한 하위 스트림 클래스 |
Reader / Writer | 문자 단위 입출력을 위한 최상위 입출력 스트림 클래스 |
FileReader / FileWriter InputStreamReader / OutputStreamWriter PrintWriter BufferedReader / BufferedWriter |
문자 단위 입출력을 위한 하위 스트림 클래스 |
- 바이트 기반 스트림은 그림, 멀티미디어, 문자 등 모든 종류의 데이터를 받고 보낼 수 있다.
- 문자 기반 스트림은 오직 문자만 받고 보낼 수 있다.
구분 | 바이트 기반 스트림 | 문자 기반 스트림 | ||
입력 스트림 | 출력 스트림 | 입력 스트림 | 출력 스트림 | |
최상위 클래스 | InputSream | OutputStream | Reader | Writer |
하위 클래스(예) | XXXInputStream(FileInputStream) | XXXOutputStream(FileOutputStream) | XXXReader(FileReader) | XXXWriter(FileWriter) |
18.2.1 InputStream
- 바이트 기반 입력 스트림의 최상위 클래스로 추상 클래스
- FileInputStream, BufferedInputStream, DataInputStream 클래스 모두 InputStream 클래스를 상속
* InputStream 클래스의 주요 메소드
리턴 타입 | 메소드 | 설명 |
int | read( ) | 입력 스트림으로부터 1바이트를 읽고 읽은 바이트를 리턴 |
int | read(byte[] b) | 입력 스트림으로부터 읽은 바이트들을 매개값으로 주어진 바이트 배열 b에 저장하고 실제로 읽은 바이트 수를 리턴 |
int | read(byte[] b, int off, int len) | 입력 스트림으로부터 len개의 바이트만큼 읽고 매개값으로 주어진 바이트 배열 b[off]부터 len까지 저장한다. 실제로 읽은 바이트 수인 len개를 리턴한다. 만약 len개를 모두 읽지 못하면 실제로 읽은 바이트 수를 리턴 |
void | close( ) | 사용한 시스템 자원을 반납하고 입력 스트림을 닫음 |
read()메소드
- 입력 스트림으로부터 1바이트를 읽고 4바이트 int 타입으로 리턴 => 리턴된 4바이트 중 끝의 1바이트에만 데이터가 있음
- 입력 스트림에서 5개의 바이트가 들어온다면 read()메소드로 1바이트씩 5번 읽을 수 있음
- 더 이상 입력 스트림으로부터 바이트를 읽을 수 없으면 read() 메소드는 -1을 리턴
InputStream is = new FileInputStream("C:/test.jpg");
int readByte;
while ((readByte = is.read()) != -1) {
}
read(byte[] b)메소드
- 입력 스트림으로부터 매개값으로 주어진 바이트 배열의 길이만큼 바이트를 읽고 배열에 저장해 읽은 바이트 수를 리턴
- 실제로 읽은 바이트 수가 배열의 길이보다 작을 경우 읽은 수만큼 리턴
- 입력 스트림으로부터 바이트를 읽을 수 없으면 -1을 리턴
InputStream is = new FileInputStream("C:/test.jpg");
int readByteNo;
byte[ ] readBytes = new byte[100];
while ((readByteNo = is.read(readBytes)) != -1) {
}
read(byte[] b, int off, int len)메소드
- len개의 바이트만큼 읽고 매개 값으로 주어진 바이트 배열 b[off]부터 len개까지 저장한다.
- 읽은 바이트 수인 len개까지 리턴한다. 실제로 읽은 바이트 수가 len개 보다 작을 경우 실제로 읽은 수만큼만 리턴
- 바이트를 더 이상 읽을 수 없다면 -1을 리턴
- read(byte[] b)와 차이점은 한번에 읽어들이는 바이트 수를 len 매개값으로 조절 할 수 있고, 배열에서 저장이 시작되는 인덱스를 지정할 수 있다.
close()메소드
- InputStream을 더 이상 사용하지 않을 경우 close( ) 메소드를 호출해서 InputStream에서 사용했던 시스템 자원을 풀어줌
18.2.2 OutputStream
- 바이트 기반 출력 스트림의 최상위 클래스로 추상 클래스
- FileOutputStream, BufferedOutputStream, DataOutputStream 클래스 모두 OutputStream 클래스를 상속
* OutputStream 클래스의 주요 메소드
리턴 타입 | 메소드 | 설명 |
void | write(int b) | 출력 스트림으로 1바이트를 보낸다(b의 끝 1바이트) |
void | write(byte[] b) | 출력 스트림으로 주어진 바이트 배열 b의 모든 바이트를 보낸다. |
void | write(byte[] b, int off, int len) | 출력 스트림으로 주어진 바이트 배열b[off]부터 len개까지의 바이트를 보낸다. |
void | flush( ) | 버퍼에 잔류하는 모든 바이트를 출력한다. |
void | close( ) | 사용한 시스템 자원을 반납하고 출력 스트림을 닫는다. |
write(int b)메소드
- 매개 변수로 주어진 int 값에서 끝에 있는 1바이트만 출력 스트림으로 보냄 (int 타입이라고 4바이트 모두 보내는 것 아님)
OutputStream os = new FileOutputStream("C:/test.txt");
byte[ ] data = "ABC".getBytes();
for(int i = 0; i < data.length; i++) {
os.write(data[i]); //"A", "B", "C" 하나씩 출력
}
write(byte[] b)메소드
- 매개값으로 주어진 바이트 배열의 모든 바이트를 출력 스트림으로 보냄
OutputStream os = new FileOuputStream("C:/test.txt");
byte[ ] data = "ABC".getBytes;
os.write(data); //"ABC" 모두 출력
write(byte[] b, int off, int len)메소드
- b[off]부터 len개의 바이트를 출력 스트림으로 보냄
OutputStream os = new FileOuputStream("C:/test.txt");
byte[ ] data = "ABC".getBytes;
os.write(data, 1, 2); //"BC" 모두 출력
flush( )와 close( )메소드
- 출력 스트림은 내부에 작은 버퍼(buffer)가 있어 데이터 출력 전 버퍼에 쌓여있다가 순서대로 출력됨
- flush( ) 메소드는 버퍼에 잔류하고 있는 데이터를 모두 출력시키고 버퍼를 비우는 역할
- OutputStream을 더 이상 사용하지 않을 경우에는 close( ) 메소드를 호출해서 OutputStream에서 사용했던 시스템 자원을 풀어줌
OutputStream os = new FileOuputStream("C:/test.txt");
byte[ ] data = "ABC".getBytes;
os.write(data);
os.flush();
os.close();
18.2.3 Reader
- 문자 기반 입력 스트림의 최상위 클래스로 추상 클래스
- FileReader, BufferReader, InputStreamReader 클래스는 모두 Reader 클래스를 상속
* Reader 클래스의 주요 메소드
리턴 타입 | 메소드 | 설명 |
int | read( ) | 입력 스트림으로부터 한 개의 문자를 읽고 리턴한다. |
int | read(char[ ] cbuf) | 입력 스트림으로부터 읽은 문자들을 매개값으로 주어진 문자 배열 cbuf에 저장하고 실제로 읽은 문자 수를 리턴 |
int | read(char[] cbuf, int off, int len) | 입력 스트림으로부터 len개의 문자를 읽고 매개값으로 주어진 문자 배열 cbuf[off]부터 len개까지 저장한다. 실제로 읽은 문자 수인 len개를 리턴 |
void | close( ) | 사용한 시스템 자원을 반납하고 입력 스트림을 닫음 |
read( )메소드
- 입력 스트림으로부터 한 개의 문자(2바이트)를 읽고 4바이트 int 타입으로 리턴 => 4바이트 중 끝 2바이트에만 문자 데이터가 있음
- read( )메소드가 리턴한 int 값을 char 타입으로 변환하면 읽은 문자를 얻을 수 있다.
- 더 이상 입력 스트림으로부터 문자를 읽을 수 없으면 -1을 리턴
Reader reader = new FileReader("C:/test.txt");
int readData;
while ((readData = reader.read()) != -1){
char charData = (char) readData;
}
read(char[] cbuf, int off, int len)메소드
- len개의 문자만큼 읽고 매개값으로 주어진 문자 배열 cbuf[off]부터 len개까지 저장
- 읽은 문자 수인 len개를 리턴
- 문자를 더 이상 읽을 수 없다면 -1을 리턴
- read(char[] cbuf)와ㅏ 차이점은 한번에 읽어 들이는 문자 수를 len 매개값으로 조절 할 수 있고 배열에서 저장이 시작되는 인덱스를 지정 가능하다.
close( )메소드
- Reader를 더 이상 사용하지 않을 경우 close( ) 메소드를 호출해서 Reader에서 사용했던 시스템 자원을 풀어줌
18.2.4 Writer
- 문자 기반 출력 스트림의 최상위 클래스로 추상 클래스
- FileWriter, BufferedWriter, PrintWriter, OutputStreamWriter 클래스는 모두 Writer 클래스를 상속
* Writer 클래스의 주요 메소드
리턴 타입 | 메소드 | 설명 |
void | write(int c) | 출력 스트림으로 주어진 한 문자를 보낸다(c의 끝 2바이트) |
void | write(char[ ] cbuf) | 출력 스트림으로 주어진 문자 배열 cnuf의 모든 문자를 보낸다. |
void | write(char[] cbuf, int off, int len) | 출력 스트림으로 주어진 문자 배열b[off]부터 len개까지의 문자를 보낸다. |
void | write(String str) | 출력 스트림으로 주어진 문자열을 전부 보낸다. |
void | write(String str, int off, int len) | 출력 스트림으로 주어진 문자열 off 순번부터 len개까지의 문자를 보낸다. |
void | flush( ) | 버퍼에 잔류하는 모든 문자열을 출력한다. |
void | close( ) | 사용한 시스템 자원을 반납하고 출력 스트림을 닫는다. |
write(int c)메소드
- 매개 변수로 주어진 int 값에서 끝에 있는 2바이트(한 개의 문자)만 출력 스트림으로 보냄 (int 타입이라고 4바이트 모두 보내는 것 아님)
Writer writer = new FileWriter("C:/test.txt");
char[] data = "홍길동."toCharArray();
for(int i = 0; i < data.length; i++) {
writer.write(data[i]); //"홍", "길", "동"을 하나씩 출력
}
write(char[ ] cbuf)메소드
- 매개값으로 주어진 char[ ]배열의 모든 문자를 출력 스트림으로 보냄
Writer writer = new FileWriter("C:/test.txt");
char[] data = "홍길동."toCharArray();
writer.write(data); //"홍길동"을 모두 출력
write(byte[] b, int off, int len)메소드
- b[off]부터 len개의 문자를 출력 스트림으로 보냄
Writer writer = new FileWriter("C:/test.txt");
char[] data = "홍길동."toCharArray();
writer.write(data, 1, 2); //"길동"만 출력
write(String str)와 write(String str, int off, int len) 메소드
- write(String str)은 문자열 전체를 출력 스트림으로 보냄
- wrtie(String str, int off, int len)은 주어진 문자열 off 순번부터 len개 까지의 문자를 보냄
- flush( ) 메소드는 버퍼에 잔류하고 있는 데이터를 모두 출력시키고 버퍼를 비우는 역할
- OutputStream을 더 이상 사용하지 않을 경우에는 close( ) 메소드를 호출해서 OutputStream에서 사용했던 시스템 자원을 풀어줌
Writer writer = new FileWriter("C:/test.txt");
String data = "안녕 자바 프로그램";
writer.write(data);
writer.flush();
wrtier.close();
18.3 콘솔 입출력
- 콘솔(console) : 시스템을 사용하기 위해 키보드로 입력 받고 화면으로 출력하는 소프트웨어
- 유닉스나 리눅스 운영체제는 터미널(terminal), windows 운영체제는 명령 프롬포트
18.3.1 System.in 필드
- InputStream 타입의 필드이므로 InputStream 변수로 참조가 가능
InputStream is = System.in;
- 어떤 키가 입력 되었는지 확인하려면 InputStream의 read( ) 메소드로 한 바이트씩 읽으면 된다.
int asciiCode = is.read();
- 리턴된 int 값에는 10진수 아스키 코드(ASCII CODE)가 들어있다.
- 아스키 코드 : 미국표준협회가 컴퓨터에서 문자를 숫자로 매칭하는 방법을 표준화 시킨 것
- 1byte로 표현되는 256가지의 숫자에 영어 알파벳, 아라비아 숫자, 특수 기호를 매칭
- 숫자로 된 아스키 코드 대신 키보드에서 입력한 문자를 직접 얻고 싶다면 read()메소드를 읽은 아스키 코드를 char 타입으로 변환하면 된다.
char inputChar = (char) is.read();
//콘솔에서 입력한 번호 알아내기
import java.io.IOException;
import java.io.InputStream;
public class SystemInExample1 {
public static void main(String[] args) throws IOException {
System.out.println("==메뉴==");
System.out.println("1. 예금 조회");
System.out.println("2. 예금 출금");
System.out.println("3. 예금 입금");
System.out.println("4. 종료 하기");
System.out.print("메뉴를 선택하세요:");
InputStream is = System.in; //키보드 입력 스트림 얻기
char inputChar = (char)is.read(); //아스키 코드를 읽고 문자로 리턴
switch (inputChar) {
case '1': {
System.out.println("예금 조회를 선택하셨습니다.");
break;
}
case '2': {
System.out.println("예금 출금을 선택하셨습니다.");
break;
}
case '3': {
System.out.println("예금 입금을 선택하셨습니다.");
break;
}
case '4': {
System.out.println("종료 하기을 선택하셨습니다.");
break;
}
}
}
}
- InputStream의 read() 메소드는 1바이트만 읽기 때문에 1바이트의 아스키 코드로 표현되는 숫자, 영어, 특수문자는 읽을 수 있지만 한글과 같이 2바이틀를 필요로 하는 유니코드는 read()메소드로 읽을 수 없음
- 한글을 얻기 위해서는 read(byte[] b)나 read(byte[] b, int off, int len) 메소드로 전체 입력된 내용을 바이트 배열로 받고, 이 배열을 이용해서 String 객체를 생성하면 된다.
byte[] byteData = new byte[15];
int readByteNo = System.in.read(byteData);
// readByteNo : 읽은 바이트 수 byteData : 실제로 읽은 바이트 수 저장
- 변환할 문자열은 바이트 배열의 0번 인덱스에서 시작해서 ㅇ릭은 바이트수 -2만큼이다 => Enter키에 해당하는 마지막 두 바이트를 제외하기 위해서
String strData = new String(byteData, 0, readByteNo-2);
// byteData : 바이트 배열, 0: 시작 인덱스, readByteNo-2 : 읽은 바이트수 -2
// 콘솔에서 입력한 한글 알아내기
import java.io.IOException;
import java.io.InputStream;
public class SystemInExample2 {
public static void main(String[] args) throws IOException {
InputStream is = System.in;
byte[] datas = new byte[100];
System.out.print("이름:");
int nameBytes = is.read(datas);
String name = new String(datas, 0, nameBytes-2);
System.out.print("하고 싶은 말:");
int commentBytes = is.read(datas);
String comment = new String(datas, 0, commentBytes-2);
System.out.println("입력한 이름 : " + name);
System.out.println("입력한 하고 싶은 말 : " + comment);
}
}
18.3.2 System.out 필드
- 콘솔로 데이터를 출력하기 위해서 사용
- out은 PrintStream 타입의 필드
OutputStream os = System.out;
- 1개의 바이트를 출력하려면 OutputStream의 writer(int b) 메소드를 이용 => 바이트 값은 아스키 코드
- OutputStream의 write(int b)의 메소드는 1바이트만 보낼 수 있기 때문에 1바이트로 표현 가능한 숫자, 영어, 특수문자는 출력이 가능하지만, 2바이트로 표현되는 한글은 출력 할 수 없음
- 한글을 출력하기 위해서는 한글을 바이트 배열로 얻은 다음, write(byte[ ] b)나 write(byte[] b, int off, int len) 메소드로 콘솔로 출력
String name = "홍길동";
byte[] nameBytes = name.getBytes();
os.write(nameBytes);
os.flush();
// 연속된 숫자, 영어, 한글 출력
import java.io.IOException;
import java.io.OutputStream;
public class SystemOutExample {
public static void main(String[] args) throws IOException {
OutputStream os = System.out;
for (byte b = 48; b < 58; b++) {
os.write(b); //아스키 코드 48에서 57까지의 문자 출력
}
os.write(10); //라인 피드 10을 입력하면 다음 행으로 넘어감
for (byte b = 97; b < 123; b++) {
os.write(b); //아스키 코드 97에서 122까지의 문자 출력
}
os.write(10);
String hangul = "가나다라마바사아자차카파타하";
byte[] hangulBytes = hangul.getBytes();
os.write(hangulBytes);
os.flush();
}
}
- out은 원래 PrintStream 타입의 필드이므로 print() 또는 println()메소드를 사용하면 더 쉽게 콘솔에 출력 가능
18.3.3 Console 클래스
- Console 객체를 얻기 위해서 System의 정적 메소드인 console()을 호출 하면 된다.
Console console = System.console();
- 이클립스에서 실행하면 System.console()메소드는 null을 리턴하기 때문에 반드시 cmd에서 실행해야 한다.
리턴 타입 | 메소드 | 설명 |
String | readLine() | Enter키를 입력하기 전의 모든 문자열을 읽음 |
char[] | readPassword() | 키보드 입력 문자를 콘솔에 보여주지 않고 문자열을 읽음 |
18.3.4 Scanner 클래스
- java.util 패키지의 Scanner 클래스를 이용하면 콘솔로부터 기본 타입의 값을 바로 읽을 수 있다.
Scanner scanner = new Scanner(System.in);
- Scaaner가 콘솔에서만 사용되는 것은 아니고 생성자 매개값에 File, InputStream, Path 등과 같이 다양한 입력 소스를 지정할 수 있음
리턴 타입 | 메소드 | 설명 |
boolean | nextBoolean() | boolean(true/false) 값을 읽음 |
byte | nextByte() | byte 값을 읽음 |
short | nextShort() | short 값을 읽음 |
int | nextInt() | int 값을 읽음 |
long | nextLong() | long 값을 읽음 |
float | nextFloat() | float 값을 읽음 |
double | nextDouble() | double 값을 읽음 |
String | nextLine() | String 값을 읽음 |
18.4 파일 입출력
18.4.1 File 클래스
- IO 패키지(java.io)에서 제공하는 File 클래스는 파일 크기, 파일 속성, 파일 이름 등의 정보를 얻어내는 기능과 파일 생성 및 삭제 가능을 제공
- 디렉토리를 생성하고 디렉토리에 존재하는 파일 리스트를 얻어내는 기능도 있다.
- 파일의 데이터를 읽고 쓰는 기능은 지원하지 않음 => 파일의 입출력은 스트림을 사용해야 함
* C:\temp 디렉토리의 file.txt 파일을 File 객체로 생성하는 코드
File file = new File("C:\\temp\\file.txt");
File file = new ile("C:/temp/file.txt");
- File 객체를 생성했다고 해서 파일이나 디렉토리가 생성되는 것은 아님
- 생성자 매개값으로 주어진 경로가 유효하지 않더라도 컴파일 에러나 예외가 발생하지 않음
- File 객체를 통해 해당 경로에 실제로 파일이나 디렉토리가 있는지 확인하려면 exists( ) 메소드를 호출 할 수 있음
- 디렉토리나 파일이 파일 시스템에 존재한다면 true, 존재하지 않는다면 false를 리턴
boolean isExists = file.exists();
- exitsts() 메소드의 리턴 값이 false 라면 createNewFile( ), mkdir( ), mkdirs( ) 메소드로 파일 또는 디렉토리를 생성 할 수 있다.
리턴 타입 | 메소드 | 설명 |
boolean | createNewFile( ) | 새로운 파일을 생성 |
boolean | mkdir( ) | 새로운 디렉토리를 생성 |
boolean | mkdirs( ) | 경로상에 없는 모든 디렉토리를 생성 |
boolean | delete( ) | 파일 또는 디렉토리 삭제 |
- 파일 또는 디렉토리가 존재할 경우 다음 메소드를 통해 정보를 얻을 수 있다.
리턴 타입 | 메소드 | 설명 |
boolean | canExecute( ) | 실행 할 수 있는 파일인지 여부 |
boolean | canRead( ) | 읽을 수 있는 파일인지 여부 |
boolean | canWrite( ) | 수정 및 저장할 수 있는 파일인지 여부 |
String | getName( ) | 파일의 이름을 리턴 |
String | getParent( ) | 부모 디렉토리를 리턴 |
File | getParentFile( ) | 부모 디렉토리를 File 객체로 생성한 후 리턴 |
String | getPath( ) | 전체 경로를 리턴 |
boolean | isDirectory( ) | 디렉토리인지 여부 |
boolean | isFile( ) | 파일인지 여부 |
boolean | isHidden( ) | 숨김 파일인지 여부 |
long | lastModified( ) | 마지막 수정 날짜 및 시간을 리턴 |
long | length( ) | 파일의 크기를 리턴 |
String[ ] | list( ) | 디렉토리에 포함된 파일 및 서브디렉토리 목록 전부를 String 배열로 리턴 |
String[ ] | list(FilenameFilter filter) | 디렉토리에 포함된 파일 및 서브디렉토리 목록 중에 FilenameFilter에 맞는 것만 String 배열로 리턴 |
File[ ] | listFiles( ) | 디렉토리에 포함된 파일 및 서브 디렉토리 목록 전부를 File 배열로 리턴 |
File[ ] | listFiles(FilenameFile filter) | 디렉토리에 포함된 파일 및 서브 디렉토리 목록 중에 FilenameFilter에 맞는 것만 File 배열로 리턴 |
// File 클래스를 이용한 파일 및 디렉토리 정보 출력
import java.io.File;
import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.text.SimpleDateFormat;
public class FileExample {
public static void main(String[] args) throws URISyntaxException, IOException {
File dir = new File("C:/Temp/Dir");
File file1 = new File("C:/Temp/file1.txt");
File file2 = new File("C:/Temp/file2.txt");
File file3 = new File(new URI("file:///C:/Temp/file3.txt")); //파일 경로를 URI 객체로 생성해서 매개값으로 제공해도 됨
if (dir.exists() == false) {
dir.mkdirs();
}
if (file1.exists() == false) {
file1.createNewFile();
}
if (file2.exists() == false) {
file2.createNewFile();
}
if (file3.exists() == false) {
file3.createNewFile();
}
File temp = new File("C:/Temp");
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd a HH:mm");
File[] contents = temp.listFiles();
System.out.println("날짜 시간 형태 크기 이름");
System.out.println("---------------------------------------------------------------");
for (File file : contents) {
System.out.print(sdf.format(file.lastModified()));
if (file.isDirectory()) {
System.out.print("\t<DIR>\t\t\t"+file.getName());
} else {
System.out.print("\t<DIR>\t\t\t"+file.length() + "\t" + file.getName());
}
System.out.println();
}
}
}
18.4.2 FileInputStream
- 파일로부터 바이트 단위로 읽어들일 때 사용하는 바이트 기반 입력 스트림
- 바이트 단위로 읽기 때문에 그림, 오디오, 비디오, 텍스트 파일 등 모든 종류의 파일을 읽을 수 있음
// 첫번째 방법
FileInputStream fis = new FileInputStream("C:/Temp/image.gif");
// 두번째 방법
File file = new File("C:/Temp/image.gif");
FileInputStream fis = new FileInputSream(file);
18.4.3 FileOutputStream
- 바이트 단위로 데이터를 파일에 저장할 때 사용하는 바이트 기반 출력 스트림
- 바이트 단위로 저장하기 때문에 그림, 오디오, 비디오, 텍스트 등 모든 종류의 데이터를 파일로 저장할 수 있다.
// 첫번째 방법
FileOutputStream fos = new FileOutputStream("C:/Temp/image.gif");
// 두번째 방법
File file = new File("C:/Temp/image.gif");
FileOutputStream fos = new FileOutputStream(file);
- 파일이 이미 존재 할 경우, 데이터를 출력하면 파일을 덮어쓰게 되므로 기존의 파일 내용은 사라짐
- 기존의 파일 내용 끝에 데이터를 추가할 경우에는 FileOutputStream 생성자의 두번째 매개값을 true로 주면 됨
FileOutputStream fos = new FileOutputStream("C:/Temp/data.txt", true);
FileOutputStream fos = new FileOutputStream(file, true);
18.4.4 FileReader
- 텍스트 파일을 프로그램으로 읽어들일 때 사용하는 문자 기반 스트림
- 문자 단위로 읽기 때문에 텍스트가 아닌 그림, 오디오, 비디오 등의 파일은 읽을 수 없음
// 방법1
FileReader fr = new FileReader("C:/Temp/file.txt");
// 방법2
File file = new File("C:/Temp/file.txt");
FileReader fr = new FileReader(file);
- FileReader 객체가 생성될 때 파일과 직접 연결 되는데 만약 파일이 존재하지 않으면 FileNotFoundException이 발생
- 한 문자를 읽기 위해서 read() 메소드를 사용하거나 읽은 문자를 char 배열에 저장하기 위해서 read(char[] cbuf) 또는 read(char[] cbuf, int off, int len) 메소드를 사용
18.4.5 FileWriter
- 텍스트 데이터를 파일에 저장할 때 사용하는 문자 기반 스트림
- 문자 단위로 저장하기 때문에 텍스트가 아닌 그림, 오디오, 비디오 등의 파일은 저장할 수 없음
// 방법1
FileWriter fw = new FileWriter("C:/Temp/file.txt");
// 방법2
File file = new File("C:/Temp/file.txt");
FileWriter fw = new FileWriter(file);
- FileWriter를 생성하면 지정된 파일이 이미 존재할 경우 그 파일을 덮어쓰게 되므로 기존의 파일 내용은 사라짐
- 기존의 파일 내용 끝에 데이터를 추가 할 경우에는 FileWriter 생성자에 두번째 매개값으로 true를 주면 됨
FileWriter fw = new FileWriter("C:/Temp/data.txt", true);
FileWriter fw = new FileWriter(file, true);
- 한 문자를 저장하기 위해서는 write() 메소드를 사용하고 문자열을 저장하기 위해서는 write(String str) 메소드를 사용
18.5 보조 스트림
- 보조스트림이란 다른 스트림과 연결되어 여러 가지 편리한 기능을 제공해주는 스트림임
- 보조스트림은 자체적으로 입출력을 수행할 수 없기 때문에 입력소스와 바로 연결되는 InputStream, FileInputStream, Reader, FileReader, 출력소스와 바로 연결되는 OutputStream, FileOutputStream, Writer, FileWriter 등에 연결해서 입출력 수행
- 문자 변환, 입출력 성능 향상, 기본 데이터 타입 입출력, 객체 입출력 등의 기능을 제공
보조스트림 변수 = new 보조스트림(연결스트림)
// 콘솔 입력 스트림을 문자 변환 보조 스트림인 InputStreamReader에 연결하는 코드
InputStream is = System.in;
InputStreamReader reader = new InputStreamReader(is);
// 보조 스트림은 또 다른 보조 스트림에도 연결되어 스트림 체인을 구성할 수 있음
// 문자 변환 보조 스트림인 InputStreamReader를 다시 성능 향상 보조 스트림인 BufferedReader에 연결하는 코드
InputStream is = System.in;
InputStreamReader reader = new InputStreamReader(is);
BufferedReader br = new BufferedReader(reader);
18.5.1 문자 변환 보조 스트림
- 소스 스트림이 바이트 기반 스트림이면서 입출력 데이터가 문자라면 Reader와 Writer로 변환해서 사용하는 것을 고려해야 한다 => Reader와 Writer는 문자 단위로 입출력하기 때문에 바이트 기반 스트림보다는 편리하고 문자셋의 종류를 지정할 수 있기 때문에 다양한 문자를 입출력 할 수 있음
InputStreamReader
- 바이트 입력 스트림에 연결되어 문자 입력 스트림인 Reader로 변환시키는 보조 스트림
// 콘솔 입력을 위한 InputStream을 Reader 타입으로 변환
InputStream is = System.in;
Reader reader = new InputStreamReader(is);
// 파일 입력을 위한 FileInputStream을 Reader 타입으로 변환
FileInputStream fis = new FileInputStream("C:/Temp/file.txt");
Reader reader = new InputStreamReader(fis);
// 콘솔에서 한글 입력받기
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
public class InputStreamReaderExample {
public static void main(String[] args) throws IOException {
InputStream is = System.in; //InputStream 바이트 기반
Reader reader = new InputStreamReader(is); //Reader 문자 기반
int readCharNo;
char[] cbuf = new char[100];
while ((readCharNo = reader.read(cbuf)) != -1) {
String data = new String(cbuf, 0, readCharNo);
System.out.println(data);
}
}
}
OutputStreamReader
- 바이트 출력 스트림에 연결되어 문자 출력 스트림인 Writer로 변환시키는 보조 스트림
// 파일 출력을 위한 FileOutputStream을 Writer 타입으로 변환
FileOutputStream fos = new FileOutputStream("C:/Temp/file.txt");
Writer writer = new OutputStreamWriter(fos);
// 파일로 출력하기
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.io.Writer;
public class OutputStreamWriterExample {
public static void main(String[] args) throws IOException {
FileOutputStream fos = new FileOutputStream("C:/Temp/file.txt");
Writer writer = new OutputStreamWriter(fos);
String data = "바이트 출력 스트림을 문자 출력 스트림으로 변환";
writer.write(data);
writer.flush();
writer.close();
System.out.println("파일 저장이 끝났습니다.");
}
}
18.5.2 성능 향상 보조 스트림
- 바이트 기반 스트림에는 BufferedInputStream, BufferedOutputStream이 있고 문자 기반 스트림에는 BuffereReader와 BufferedWriter가 있다.
BufferedInputStream과 BufferedReader
- BufferedInputStream은 바이트 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림
- BufferedReader는 문자 입력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림
- BufferedInputStream과 BufferedReader는 입력 소스로부터 자신의 내부 버퍼 크기만큼 데이터를 미리 읽고 버퍼에 저장해 둔다.
BufferedInputSream bis = new BufferedInputStream(바이트 입력 스트림);
BufferedReader br = new BufferedReader(문자 입력 스트림);
BufferedOutputStream과 BufferedWriter
- BufferedOutputStream은 바이트 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림
- BufferedWriter는 문자 출력 스트림에 연결되어 버퍼를 제공해주는 보조 스트림
- BufferedOutputStream과 BufferedWriter는 프로그램에서 전송한 데이터를 내부 버퍼에 쌓아 두었다가 버퍼가 꽉차면 버퍼의 모든 데이터를 한꺼번에 보낸다.
BufferedOutputSream bos = new BufferedOutputStream(바이트 출력 스트림);
BufferedWriter bw = new BufferedWriter(문자 출력 스트림);
18.5.3 기본 타입 입출력 보조 스트림
- 바이트 스트림은 바이트 단위로 입출력하기 때문에 자바의 기본 데이터 타입인 boolean, char, short, int, long, float, double 단위로 입출력 할 수 없다.
- DataInputStream과 DataOutputStream 보조 스트림을 연결하면 기본 데이터 타입으로 입출력 가능
DataInputStream dis = new DataInputStream(바이트 입력 스트림);
DataOutputStream dos = new DataOutputStream(바이트 출력 스트림);
DataInputStream | DataOutputStream | ||
boolean | readBoolean() | void | writeBoolean(boolean v) |
byte | readByte() | void | writeByte(int v) |
char | readChar() | void | writeChar(int v) |
double | readDouble() | void | writeDouble(double v) |
float | readFloat() | void | writeFloat(float v) |
int | readInt() | void | writeInt(int v) |
long | readLong() | void | writeLong(long v) |
short | readShort() | void | wrtieShort(int v) |
String | readUTF() | void | wrtieUTF(String str) |
- 데이터 타입의 크기가 모두 다름으로 DataOutputStream으로 출력한 데이터를 다시 DataInputStream으로 읽어 올 때 출력한 순서와 동일한 순서로 읽어야 한다.
18.5.4 프린터 보조 스트림
- PrintStream과 PrintWriter는 프린터와 유사하게 출력하는 print(), println() 메소드를 가지고 있는 보조 스트림임
- PrintStream은 바이트 출력 스트림과, PrintWriter는 문자 출력 스트림과 연결 된다.
PrintStream ps = new PrintStream(바이트출력스트림);
PrintWriter pw = new PrintWriter(문자 출력 스트림);
- PrintStream과 PrintWriter는 println()과 print() 이외에 printf()도 제공한다.
- printf()메소드는 형식화된 문자열을 출력할 수 있도록 하기 위해 제공되는 메소드
%[argument_index$] [flags] [width] [.precision] conversion
매개값의 순번 -, 0 전체자릿수 소수자릿수 변환 문자
- %와 conversion(변환문자)는 필수적으로 작성하고 그 이외의 항목은 생략 가능
형식화된 문자 | 설명 | 출력형태 | |
정수 | %d | 정수 | 123 |
%6d | 6자리 정수, 왼쪽 빈자리 공백 | ___123 | |
%-6d | 6자리 정수, 오른쪽 빈자리 공백 | 123___ | |
%06d | 6자리 정수, 왼쪽 빈자리 0 채움 | 000123 | |
실수 | %10.2f | 소수점 이상 7자리, 소수점 이하 2자리, 왼쪽 빈자리 공백 | ____123.45 |
%-10.2f | 소수점 이상 7자리, 소수점 이하 2자리, 오른쪽 빈자리 공백 | 123.45____ | |
%010.2f | 소수점 이상 7자리, 소수점 이하 2자리, 왼쪽 빈자리 0 채움 | 0000123.45 | |
문자열 | %s | 문자열 | abc |
%6s | 6자리 문자열, 왼쪽 빈자리 공백 | ___abc | |
%-6s | 6자리 문자열, 오른쪽 빈자리 공백 | abc___ | |
날짜 | %tF | %tY-%tM-%td | 2010-01-06 |
%tY | 4자리 년 | 2010 | |
%ty | 2자리 년 | 10 | |
%tm | 2자리 월 | 01 | |
%td | 2자리 일 | 06 | |
%tH | 2자리 시(0~23) | 08 | |
%tl | 시(0~12) | 8 | |
%tM | 2자리 분 | 06 | |
%tS | 2자리 초 | 24 | |
특수 문자 |
/t | 탭(tab) | |
/n | 줄바꿈 | ||
%% | % | % |
18.5.5 객체 입출력 보조 스트림
- 객체는 문자가 아니기 때문에 바이트 기반 스트림으로 출력해야 함
- 객체를 출력하기 위해서는 객체의 데이터(필드값)를 일렬로 늘어선 연속적인 바이트로 변경해야 하는데, 이것을 객체 직렬화(serialization)라고 함
- 반대로 파일에 저장되어 있거나 네트워크에서 전송된 객체를 읽을수도 있는데, 입력 스트림으로 부터 읽어들인 연속적인 바이트를 객체로 복원하는 것을 역직렬화(deserializtion)
ObjectInputStream, ObjectOutputStream
- 객체를 입출력할 수 있는 두개의 보조 스트림인 ObjectInputStream과 ObjectOutputStream을 제공
- ObjectOutputStream은 바이트 출력 스트림과 연결되어 객체를 직렬화
- ObjectInputStream은 바이트 입력 스트림과 연결되어 객체를 역직렬화
ObjectInputStream ois = new ObjectInputStream(바이트 입력 스트림);
ObjectOutputStream oos = new ObjectOutputStream(바이트 출력 스트림);
- ObjectOutputStream으로 객체를 직렬화하기 위해서 wrtieObject() 메소드를 사용
- ObejctInputStream의 readObject() 메소드는 입력 스트림에서 읽은 바이트를 역직렬화해서 객체로 생성한다.
직렬화가 가능한 클래스(Serializable)
- 자바는 Serializable 인터페이스를 구현한 클래스만 직렬화 할 수 있도록 제한
- Serializable 인터페이스는 필드나 메소드가 없는 빈 인터페이스지만, 객체를 직렬화 할 때 private 필드를 포함ㅎ나 모든 필드를 바이트로 변환해도 좋다는 표시 역할을 한다.
public class XXX implements Serializable { }
- 객체를 직렬화하면 바이트로 변환되는 것은 필드들이고, 생성자 및 메소드는 직렬화에 포함되지 않음
- 역직렬화 할 때에는 필드의 값만 복원 됨
- 필드선언에 static 또는 transient가 붙어 잇을 경우에는 직렬화가 되지 않음
serialVersionUID 필드
- 직렬화된 객체를 역직렬화 할 때는 직렬화 했을때와 같은 클래스를 사용해야 함
- 클래스의 이름이 같더라도 클래스의 내용이 변경되면, 역직렬화는 실패하며 예외가 발생 함
- serialVersionUID는 같은 클래스임을 알려주는 식별자 역할을 함
- Serializable 인터페이스를 구현한 클래스를 컴파일하면 자동적으로 serialVersionUID 정적 필드가 추가 됨
- 클래스를 재컴파일하면 serialVersionUID의 값이 달라짐
writeObject()와 readObject() 메소드
- writeObject() 메소드는 직렬화될 때 자동으로 호출되고, readObject()메소드는 역직렬화 될 때 자동으로 호출
- 접근 제한자가 private가 아니면 자동 호출되지 않기 때문에 반드시 private를 붙여줘야 함
18.6 네트워크 기초
- 네트워크는 여러 대의 컴퓨터를 통신 회선으로 연결한 것
18.6.1 서버와 클라이언트
- 서버(server) : 서비스를 제공하는 프로그램
- 클라이언트(client) : 서비스를 받는 프로그램
- 인터넷에서 두 프로그램이 통신하기 위해서는 연결을 요청하는 역할과 연결을 수락하는 역할이 필요함
- 클라이언트는 서비스를 받기 위해 연결을 요청하고 서버는 연결을 수락하여 서비스를 제공한다.
- 서버는 클라이언트가 요청(request)하는 내용을 처리해주고 응답(response)를 클라이언트로 보낸다.
- 클라이언트/서버 모델은 한개의 서버와 다수의 클라이언트로 구성되는 것이 보통이나 두개의 프로그램이 서버인 동시에 클라이언트 역할을 하는 p2p(peer to peer) 모데돌 있다.
- p2p 모델에서는 먼저 접속을 시도한 컴퓨터가 클라이언트가 됨
18.6.2 IP주소와 포트(Port)
- IP(Internet Protocol) : 컴퓨터의 고유한 주소
- IP주소는 xxx.xxx.xxx.xxx와 같은 형식으로 표현
- xxx는 0~255 사이의 정수
- 프로그램은 DNS(Domain Name System)을 이용해서 연결한 컴퓨터의 IP주소를 찾는다.
- 포트(port) 번호 ; 컴퓨터 내에서 실행하는 서버를 선택하기 위해서는 추가적인 정보가 필요함
- 포트 바인딩(binding) : 서버는 시작 할 때 고정적인 포트번호를 가지고 실행
- 클라이언트도 서버에서 보낸 정보를 받기 위해 포트 번호가 필요한데, 서버와 같이 고정적인 포트 번호가 아닌 운영체제가 자동으로 부여하는 동적 포트 번호를 사용
- 통적 포트 번호는 클라이언트가 서버로 연결 요청을 할 때 전송되어 서버가 클라이언트로 데이터를 보낼 때 사용 됨
구분명 | 범위 | 설명 |
Well Know Prot Numbers | 0~1023 | 국제인터넷주소관리기구(ICANN)가 특정 애플리케이션용으로 미리 예약한 포트 |
Registered Port Numbers | 1024~49151 | 회사에서 등록해서 사용할 수 있는 포트 |
Dynamic Or Private Port Numbers | 49152~65535 | 운영체제가 부여하는 동적 포트 또는 개인적인 목적으로 사용할 수 있는 포트 |
18.6.3 InetAddress로 IP 주소 얻기
- Java는 IP주소를 java.net.InetAddress 객체로 표현 함
- InetAddress는 로컬 컴퓨터의 IP주소 뿐만 아니라 도메인 이름을 DNS에서 검색한 후 IP 주소를 가져오는 기능을 제공
- 로컬 컴퓨터의 InetAddress를 얻고 싶다면 InetAddress.getLocalHost()메소드를 호출 하면 됨
InetAddress is = InetAddress.getLocalHost();
- 외부 컴퓨터의 도메인 이름을 알고 있다면 두개의 메소드를 이용해 InetAddress 객체를 얻으면 됨
InetAddress is = InetAddress.getByName(String host); //단 하나의 IP주소를 얻어와 InetAddress를 생성하고 리턴
InetAddress[] iaArr = InetAddress.getAllByName(String host); // 연결 클라이언트가 많은 회사의 경우 하나의 도메인 이름에 여러 개의 컴퓨터 IP를 등록해서 운영
// IP 주소 얻기
import java.net.InetAddress;
import java.net.UnknownHostException;
public class InetAddressExample {
public static void main(String[] args) {
try {
InetAddress ia = InetAddress.getLocalHost();
System.out.println("내 컴퓨터 IP 주소 : " + ia.getHostAddress());
InetAddress[] iaArr = InetAddress.getAllByName("www.naver.com");
for (InetAddress inetAddress : iaArr) {
System.out.println("www.naver.com IP 주소 : " + inetAddress.getHostAddress());
}
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
}
18.7 TCP 네트워킹
- TCP(Transmission Control Protocol) : 연결 지향적 프로토콜
- 연결 지향적 프로토콜 : 클라이언트와 서버가 연결된 상태에서 데이터를 주고받는 프로토콜
- 클라이언트가 연결 요청을 하고, 서버가 연결을 수락하면 통신 선로가 고정되고, 모든 데이터는 고정된 통신 선로를 통해서 순차적으로 전달 됨
- 데이터를 정확하고 안정적으로 전달 함
- 데이터를 보내기 전 반드시 연결이 형성되어야 하고 고정된 통신 선로가 최단선이 아닐 경우 상대적으로 UDP보다 데이터 전송 속도가 느릴 수 있음
- TCP 네트워킹을 위해 java.net.ServerSocket과 java.net.Socket 클래스를 제공
18.7.1 ServerSocket과 Socket용도
- TCP 서버의 역할
- 클라이언트가 연결 요청을 해오면 연결을 수락하는 것 => java.net.ServerScoet 클래스가 담당
- 연결된 클라이언트와 통신하는 것 => java.net.Socket 클래스가 담당
- 클라이언트가 연결을 요청해오면 ServerSocket은 연결을 수락하고 통신용 Socket을 생성 함
- 서버는 클라이언트가 접속할 포트를 가지고 있어야 하는데 이 포트가 바인딩 포트임
- 서버는 고정된 포트 번호에 바인딩해서 실행하므로, ServerSocket을 생성할 때 포트 번호 하나를 지정해야 한다.
- 서버가 실행되면 클라이언트는 서버의 IP주소와 바인딩 포트 번호로 Socket을 생성해 연결 요청을 할 수 있다.
- ServerSocket은 클라이언트가 연결 요청을 해오면 accpet() 메소드로 연결 수락을 하고 통신용 Socket을 생성함
- 클라이언트와 서버는 각각의 Socket을 이용해서 데이터를 주고 받음
18.7.2 ServerSocket 생성과 연결 수락
- ServerSocket을 얻는 방법
- 생성자에 바인딩 포트를 대입하고 객체를 생성
ServerSocket serverSocket = new ServerSocket(5001);
2. 디폴트 생성자로 객체를 생성하고 포트 바인딩을 위해 bind() 메소드를 호출
bind() 메소드의 매개값은 포트 정보를 가진 InetSocketAddress이다.
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetAddress(5001));
- ServerSocket을 생성할 때 해당 포트가 이미 사용중이라면 BindException 발생
- accpet() 메소드는 클라이언트가 연결 요청하기 전까지 블로킹(스레드가 대기 상태) 됨
- 연결된 클라이언트의 IP와 포트 정보를 알고 싶으면 Socket의 getRemoteSocketAddress() 메소드를 호출해서 SocketAddress를 얻으면 된다. => 실제 리턴되는 것은 InetSocketAddress 객체이므로 타입 변환 해야 함
InetSocketAddress socketAddress = (InetSocketAddress) socekt.getRemoteSocketAddress();
* InetSocketAddress 리턴타입과 메소드
리턴 타입 | 메소드명(매개변수) | 설명 |
String | getHostName() | 클라이언트 IP 리턴 |
int | getPort() | 클라이언트 포트 번호 리턴 |
String | toString() | "IP:포트번호" 형태의 문자열 리턴 |
- 클라이언트 연결 수락이 필요없으면 ServerSocket의 close()메소드를 호출해서 포트를 언바인딩 시켜야 함
serverSocket.close();
// 연결 수락
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 생성
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("localhost", 5001));
while (true) {
System.out.println("[연결 기다림]");
//클라이언트 연결 수락
Socket socket = serverSocket.accept();
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("[연결 수락함] " + isa.getHostName());
}
} catch (IOException e) {
e.printStackTrace();
}
// serverSocket이 닫혀있지 않을 경우
try {
if (!serverSocket.isClosed()) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
18.7.3 Socket 생성과 연결 요청
- 클라이언트가 서버에 연결을 요청하려면 java.net.Socket을 이용해야 함
- Socket 객체를 생성함과 동시에 연결 요청을 하려면 생성자의 매개값으로 서버의 IP 주소와 바인딩 포트 번호를 제공하면 됨
// 로컬 PC의 5001 포트에 연결 요청하는 코드
try{
Socket socket = new Socket("localhost", 5001); //방법 1
Socket socket = new Socket(new InetSocketAddress("localhost", 5001)); //방법 2
} catch (UnknownHostException e) {
// IP 표기 방법이 잘못되었을 경우
} catch (IOException e) {
// 해당 포트의 서버에 연결할 수 없는 경우
}
- Socket 생성과 동시에 연결 요청을 하지 않고 기본 생성자로 Socket을 생성 한 후, connect() 메소드로 연결 요청 가능
socket = new Socket();
socket.connect( new InetAddress("localhost", 5001) );
- UnknownHostException은 잘못 표기된 IP주소를 입력했을 경우 예외 발생
- IOException은 주어진 포트로 접속할 수 없을 때 예외 발생
- Socket 생성자 및 connect() 메소드는 서버와 연결이 될 때 까지 블로킹 됨
// 연결 요청
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 생성
socket = new Socket();
System.out.println("[연결 요청]");
// 연결 요청
socket.connect(new InetSocketAddress("localhost", 5001));
System.out.println("[연결 성공]");
} catch (IOException e) {
e.printStackTrace();
}
// 연결이 되어있을 경우
if (!socket.isClosed()) {
try {
socket.close();
} catch (Exception e) {
}
}
}
}
18.7.4 Socket 데이터 통신
- 클라이언트가 연결 요청(connect()) 하고 서버가 연결 수락(accpet()) 했다면, 양쪽의 Socket 객체로부터 각각 입력 스트림(InputStream)과 출력 스트림(OutputStream)을 얻을 수 있음
// 입력 스트림 얻기
InputStream is = socket.getInputStream();
// 출력 스트림 얻기
OutputStrem os = socket.getOutputStream();
* 상대방에게 데이터를 보내기 위해서는 보낼 데이터를 byte[] 배열로 생성하고 이것을 매개값으로해서 OutputStream의 write() 메소드를 호출
String data = "보낼 데이터";
byte[] byteArr = data.getBytes("UTF-8");
OutputStream outputStream = socket.getOutputStream();
outputStream.write(byteArr);
outputStream.flush();
* 상대방이 보낸 데이터를 받기 위해서는 받은 데이터를 저장할 byte[] 배열을 하나 생성하고, 이것을 매개값으로 해서 InputStream의 read()메소드를 호출
byte[] byteArr = new byte[100];
InputStream inputStream = socket.getInputStream();
int readByteCount = inputStream.read(byteArr);
String data = new String(byteArr, 0, readByteCount, "UTF-8");
* 데이터 받고 보내기
// 데이터 받고 보내기 Client
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 {
// socket 생성
socket = new Socket();
System.out.println("[연결 요청]");
// 연결 요청
socket.connect(new InetSocketAddress("localhost", 5001));
System.out.println("[연결 성공]");
byte[] bytes = null;
String message = null;
// 데이터 보내는 코드
OutputStream os = socket.getOutputStream();
message = "Hello Server";
bytes = message.getBytes("UTF-8");
os.write(bytes);
os.flush();
System.out.println("데이터 보내기 성공");
// 상대방이 보낸 데이터를 받기 위한 코드
InputStream is = socket.getInputStream();
bytes = new byte[100];
int readByteCount = is.read(bytes);
message = new String(bytes, 0, readByteCount, "UTF-8");
System.out.println("[데이터 받기 성공] : " + message);
os.close();
is.close();
} catch (IOException e) {
e.printStackTrace();
}
// 연결이 되어있을 경우
if (!socket.isClosed()) {
try {
socket.close();
} catch (Exception e) {
}
}
}
}
// 데이터 받고 보내기 Server
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 {
// serverSocket 생성
serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("localhost", 5001));
while (true) {
System.out.println("[연결 기다림]");
//클라이언트 연결 수락
Socket socket = serverSocket.accept();
InetSocketAddress isa = (InetSocketAddress) socket.getRemoteSocketAddress();
System.out.println("[연결 수락함] " + isa.getHostName());
byte[] bytes = null;
String message = null;
//데이터를 받기 위한 코드
InputStream is = socket.getInputStream();
bytes = new byte[100];
int readByteCount = is.read(bytes);
message = new String(bytes, 0, readByteCount, "UTF-8");
System.out.println("[데이터 받기 성공] : " + message);
// 데이터 보내기 위한 코드
OutputStream os = socket.getOutputStream();
message = "Hello Client";
bytes = message.getBytes("UTf-8");
os.write(bytes);
os.flush();
System.out.println("[데이터 보내기 성공]");
is.close();
os.close();
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
// serverSocket이 닫혀있지 않을 경우
try {
if (!serverSocket.isClosed()) {
serverSocket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
데이터를 받기위해 InputStream의 read()메소드를 호출하면 상대방이 데이터를 보내기 전까지는 블로킹(blocking) 되는데 read() 메소드가 블로킹 해제되고 리턴 되는 경우는 세가지 임
블로킹이 해제되는 경우 | 리턴값 |
상대방이 데이터를 보냄 | 읽은 바이트 수 |
상대방이 정상적으로 Socket의 close()를 호출 | -1 |
상대방이 비정상적으로 호출 | IOException 발생 |
18.7.5 스레드 병렬 처리
- 서버 어플리케이션은 지속적으로 클라이언트와 연결 수락 기능을 해야 하는데, 입출력이 블로킹되면 이 작업을 할 수 없게 됨 => accept(), connect(), read(), write()는 별도의 작업 스레드를 생성해서 병렬적으로 처리하는 것이 좋음
- 클라이언트의 폭증으로 인해 서버의 과도한 스레드 생성을 방지하려면 스레드풀을 사용하는 것이 바람직함
18.7.6 채팅 서버 구현
18.8 UDP 네트워킹
- UDP(User Datagram Protocol)은 비연결 지향적 프로토콜
- 비연결 지향적 프로토콜 : 데이터를 주고 받을 때 연결 절차를 거치지 않고, 발신자가 일방적으로 데이터를 발신하는 방식
- 연결 과정이 없기 때문에 TCP 보다는 빠른 전송을 할 수 있지만 데이터 전달의 신뢰성은 떨어짐
- 발신자가 데이터 패킷을 순차적으로 보내더라도 이 패킷들은 서로 다른 통신 선로를 통해 전달 될 수 있음
- 먼저 보낸 패킷이 느린 선로를 통해 전송될 경우 나중에 보낸 패킷보다 늦게 도착할수도 있음
- 일부 패킷은 잘못된 선로로 전송되어 유실 가능
- 데이터 전달의 속도가 중요한 프로그램은 UDP 사용
- 데이터 전달의 신뢰성이 중요한 프로그램은 TCP 사용
- UDP 프로그래밍을 위해 java.net.DatagramSocket과 java.net.DatagramPacket 클래스를 제공
- DatagramSocket은 발신점과 수신점에 해당하는 클래스
- DatagramPacket은 주고받는 패킷 클래스
18.8.1 발신자 구현
- DatagramSocket 객체 생성과 보내고자 하는 데이터를 byte[] 배열로 생성
DatagramSocket datagramSocket = new DatagramSocket();
byte[] byteArr = data.getBytes("UTF-8");
- 데이터와 수신자 정보를 담고 있는 DatagramPacket 생성
- DatagramPacket의 첫번째 매개값은 보낼 데이터인 byte[] 배열, 두번째 매개값은 byte[] 배열에서 보내고자 하는 항목 수, 전체 항목을 보내려면 length 값을 대입하면 됨, 세번째 매개값은 수신자 IP와 포트 정보를 가지고 있는 SocketAddress임
- SocketAddress는 추상 클래스이므로 하위 클래스인 InetSocketAddress객체를 생성해서 대입하면 됨
byte[] byteArr = data.getBytes("UTF-8");
DatagramPacket packet = new DatagramPacket(byteArr, byteArr.length, new InetSocketAddress("localhost",5001));
DatagramSocket의 send() 메소드를 호출하면 수신자에게 데이터가 전달 됨
datagramSocket.send(packet);
* 발신자 프로그램 코드
// 발신자
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetSocketAddress;
public class UdpSendExample {
public static void main(String[] args) throws Exception {
//DatagramSocket 생성
DatagramSocket datagramSocket = new DatagramSocket();
System.out.println("[발신 시작]");
for (int i = 1; i < 3; i++) {
String data = "메시지" + i;
byte[] byteArr;
byteArr = data.getBytes("UTF-8");
//DatagramPacket 생성
DatagramPacket packet = new DatagramPacket(byteArr, byteArr.length, new InetSocketAddress("localhost", 5001));
//DatagramPacket 전송
datagramSocket.send(packet);
System.out.println("[보낸 바이트 수]:" + byteArr.length +"bytes");
}
System.out.println("[발신 종료]");
datagramSocket.close();
}
}
18.8.2 수신자 구현
- 수신자로 사용할 DatagramSocket 객체는 바인딩할 포트 번호를 매개값으로 지정하고 생성해야 함
DatagramSocket datagramSocket = new DatagramSocket(5001);
- DatagramSocket이 생성되면 receive() 메소드를 호출해서 패킷을 읽을 준비 한다.=> receive() 메소드는 패킷을 받을 때까지 블로킹 되고, 패킷이 도착하면 매개값으로 주어진 DatagramPack에 패킷 내용을 저장함
datagramSocket.receive(datagramPacket);
- 패킷 내용을 저장할 DatagramPacket 객체는 다음과 같이 생성첫번째 매개값은 읽은 패킷 데이터를 저장할 바이트 배열, 두번째 매개값은 읽을 수 있는 최대 바이트 수로 첫번째 바이트 배열의 크기와 같거나 작아야 함
DatagramPacket datagramPacket = new DatagramPacket(new byte[100], 100);
- receive() 메소드로 패킷을 읽었다면 DatagramPacket의 getData()메소드로 데이터가 저장된 바이트 배열을 얻어낼 수 있다.
- getLength()를 호출해서 읽은 바이트 수를 얻을 수 있다.
String Data = new String(packet.getData(), 0, packet.getLength(), "UTf-8");
- 수신자가 패킷을 받고 나서 발신자에게 응답 패킷을 보내고 싶다면 발신자의 IP와 포트를 알아야 함
- => DatagramPacket의 getSocketAddress()를 호출하면 발신자의 SocketAddress 객체를 얻어 낼 수 있어, 발신자에게 응답 패킷을 보낼 때 send() 메소드에서 이용 가능
SocketAddress socketAddress = packet.getSocketAddress();
- 수신자는 항상 데이터를 받을 준비를 해야 하므로 작업 스레드를 생성해 receive()메소드를 반복적으로 호출해야 함
* 수신자 프로그램 코드
import java.net.DatagramPacket;
import java.net.DatagramSocket;
public class UDPReceiveExample extends Thread{
public static void main(String[] args) throws Exception {
//5001번 포트에서 수신하는 DatagramSocket 생성
DatagramSocket datagramSocket = new DatagramSocket(5001);
Thread thread = new Thread() {
@Override
public void run() {
System.out.println("[수신 시작]");
try {
while (true) {
//DatagramPacket 수신
DatagramPacket datagramPacket = new DatagramPacket(new byte[100], 100);
datagramSocket.receive(datagramPacket);
String data = new String(datagramPacket.getData(), 0, datagramPacket.getLength(), "UTf-8");
System.out.println("[받는 내용: " + datagramPacket.getSocketAddress()+"]" + data);
}
} catch (Exception e) {
System.out.println("[수신 종료]");
}
}
};
thread.start();
Thread.sleep(10000);
datagramSocket.close();
}
}
<해당 포스트는 교육 목적을 위해 '이것이 자바다 신용권의 Java 프로그래밍 정복' 의 도서와 한빛미디어 유튜브를 참고하여 개인적으로 정리한 것으로 모든 내용의 출처와 저작권은 신용권과 한빛미디어에 있습니다.>
'Programming > Java' 카테고리의 다른 글
Eclipse Github 연동해서 프로젝트 내려받기 (0) | 2021.05.09 |
---|---|
[이것이 자바다] Chapter 08 인터페이스 (0) | 2021.05.06 |
[이것이 자바다] Chapter 07 확인문제 (0) | 2021.05.06 |
[이것이 자바다] Chapter 06 확인문제 (0) | 2021.05.06 |
[이것이 자바다] Chapter 01 확인문제 (0) | 2021.04.25 |