네트워크 프로그램은 byte를 한 host에서 다른 host로 보내는 것을 말합니다.
여기서 byte의 관점에서만 살펴볼 수 있습니다. 그렇다면, client에서 data를 보내는 것은 어떤 file에 data를 쓰는 것과 동일하게 볼 수 있고, server가 보낸 data를 읽는 것은 file의 data를 읽는 것과 동일하게 볼 수 있습니다. byte입장에서는 대상만 달라질 뿐 같은 동작입니다.
따라서 Java에서는 Stream을 잘 이해해야 network programming을 잘할 수 있을 것입니다.
Java에서 제공하는 IO operation입니다.
Java는 stream을 통해서 IO가 발생합니다. Stream은 data들의 sequence를 의미랍니다.
Java는 InputStream과 OutputStream을 지원하고, 읽어들일 source에 따라서 다른 종류의 Stream을 사용하게 됩니다. 모두 같은 Stream을 확장하는 것이라 쓰는 것과 읽는 것은 같은 메서드를 사용하게 됩니다.
java의 stream은 일반적으로 synchronous(동기화)됩니다. 다른 말로 blocking 모드라고도 합니다. 즉, 데이터를 읽거나 쓸 때, 해당 동작을 모두 완료하기까지 아래 코드가 block되어 기다리게 되는 것입니다.
반대로 기다리지 않고, 다른 line을 실행할 수 있는 모드는 Nonblocking I/O라고도 부릅니다. 해당 모드는 추후에 알아보도록 하겠습니다.
✏️ write(int b)
✏️ write(byte[] data)
✏️ flush()
✏️ close()
다형성이란 하나의 object가 여러 형태를 띄는 것입니다. outputStream에서 다형성을 활용하는 것을 살펴봅시다.
public class GenerateCharactersSingleByte {
public static void main(String[] args) {
try {
generateCharacters(System.out);
} catch (IOException e) {
e.printStackTrace();
}
}
public static void generateCharacters(OutputStream out) throws IOException {
int firstPrintableCharacter = 33;
int numberOfPrintableCharacters = 94;
int numberOfCharactersPerLine = 72;
int start = firstPrintableCharacter;
int count = 0;
while(count < 1000) {
for (int i = start; i < start + numberOfCharactersPerLine; i++){
out.write((byte)((i - firstPrintableCharacter) % numberOfPrintableCharacters + firstPrintableCharacter));
}
out.write((byte)'\r');
out.write((byte)'\n');
start = ((start + 1) - firstPrintableCharacter) % numberOfPrintableCharacters + firstPrintableCharacter;
count++;
}
}
}
✏️ read()
✏️ read(byte[] input)
✏️ available()
✏️ skip(long n)
InputStream과 OutputStream은 원시적은 클래스입니다. Filter Class는 여러 클래스들을 chaining해서 사용합니다. Chaining을 통해서 개발에 필요한 기능을 간단하게 제공합니다.
예를 들어서, 네트워크에서 생각해봅니다. 네트워크는 프로토콜마다 다른 data를 사용합니다. InputStream과 OutputStream은 byte만 있으니까 아스키코드나 Zip을 처리하려면 개발자가 직접 처리를 해야합니다. 이런 상황을 위해서 Java가 Filter Class를 제공해서 도와줍니다.
Filter class는 크게 byte stream과 한글과 같은 하나의 byte로 표현이 안되는 문자를 위한 character stream이 있습니다. character stream은 Reader과 Writer로 나누어집니다.
public static void main(String[] args) {
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream("filename1");
fos = new FileOutputStream("filename2");
int readCount = 0;
byte[] buffer = new byte[512];
while((readCount=fis.read(buffer)) != -1){
//System.out.write(buffer, 0, readCount);
fos.write(buffer, 0, readCount);
}
} catch (Exception ex){
System.out.println(ex);
} finally {
try {
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
어떠한 목적에 의해서 Stream을 묶어서 사용하는 chaining이 적용된 Stream입니다.
BufferedOutStream와 BufferedInputStream 클래스가 있고, 읽고 쓰기 전에 버퍼를 사용하게 됩니다.
public BufferedInputStream(InputStream in)
public BufferedInputStream(InputStream in, int bufferSize)
public BufferedOutputStream(OutputStream out)
public BufferedOutputStream(OutputStream out, int bufferSize)
위와 같은 생성자를 가지고 있습니다. 파라미터로 inputStream이나 outputStream인자를 받습니다. 이를 보면 chaining이 적용된 것을 알 수 있고, 다형성도 나타난다는 것을 알 수 있습니다.
FileInputStream fis = null;
FileOutputStream fos = null;
try {
fis = new FileInputStream(args[0]);
fos = new FileOutputStream(args[1]);
BufferedInputStream bis = new BufferedInputStream(fis);
BufferedOutputStream bos = new BufferedOutputStream(fos);
int readCount = 0;
byte[] buffer = new byte[512];
while((readCount=bis.read(buffer)) !=-1){
bos.write(buffer, 0, readCount);
}
bis.close();
bos.close();
}
public DataInputStream(InputStream in)
public DataOutputStream(OutputStream out)
기존의 inputStream과 outputStream은 byte 단위로 읽고 쓰게 됩니다. 그러면 서로 다른 os는 다른 encoding방식을 사용할 수도 있기 때문에 문제가 생길 수 있습니다. 한 os에서 쓴 것을 다른 os에서 읽는 것이 불가능할 수 있습니다.
따라서 character stream을 제공하는 것입니다. java는 내부적으로 Character을 Unicode로 저장합니다. Reader와 Writer는 자동으로 local character set으로 번역을 해줍니다.
파일로부터 읽어와서 화면에 출력하는 예제입니다.
FileReader와 FileWriter 클래스도 존재하지만 항상 디폴트 인코딩을 사용하므로 아래 예제와 같이 하면 같은 동작을 수행할 수 있습니다.
public static void main(String[] args) {
FileInputStream fis = null;
InputStreamReader isr = null;
OutputStreamWriter osw = null;
try {
fis = new FileInputStream("filename.txt");
isr = new InputStreamReader(fis);
osw = new OutputStreamWriter(System.out);
char[] buffer = new char[512];
int readCount = 0;
while((readCount = isr.read(buffer)) != -1) {
osw.write(buffer, 0, readCount);
}
fis.close();
isr.close();
osw.close();
} catch (IOException e) {
e.printStackTrace();
}
}
BufferedReader and BufferedWriter
BufferedInputStream과 하는 일이 동일한데, character기반입니다.
char 배열 사용
프로그램이 읽어들일 때는 버퍼를 이용하고, 버퍼가 비면 source로부터 최대한 읽어 옵니다.
쓸 때는 데이터가 버퍼에 들어가고, 버퍼가 꽉차면 내보내게 됩니다.
InputStreamReader만 쓰는 것보다, 버퍼를 이용하기 때문에 속도를 향상시킬 수 있습니다.
method