소켓 통신 I/O Stream
I/O Stream
Stream : 연속적으로 단방향으로 흘러가는 것을 의미하며, 데이터는 출발지에서 나와 도착지로 흘러간다는 개념이다.
스트림 통신은 단방향 통신을 하기에 하나의 스트림으로 입출력 동시에 할 수가없다...
그렇기에 데이터를 보낼때에는 OutputStream 을 사용한다.
그렇기에 데이터를 받을때에는 InputStream 을 사용한다.
java.io 패키지
파일 시스템의 정보를 얻기 위한 File클래스와 데이터 입출력을 하기위한 I/O Stream 클래스가 제공된다.
바이트 단위 입출력 스트림 : 그림, 멀티미디어, 문자등 모든 종류의 데이터들을 주고 받을 수 있다.
문자 단위 입출력 스트림 : 오로지 문자만 주고받을 수 있게 특화 되어있다.
Java.io 패키지의 주요 클래스 | 설명 |
File | 파일 시스템의 파일 정보를 얻기 위한 클래스 |
Console | 콘솔로부터 문자를 입출력하기 위한 클래스 |
InputStream / OutputStream | 바이트 단위 입출력을 위한 최상위 입출력 스트림 클래스 |
FileInputStream / FileOutputStream | 바이트 단위 입출력을 위한 하위 스트림 클래스 |
DataInputStream / DataOutputStream | |
ObjectInputStream / ObjectOutputStream | |
PrintStream | |
BufferedInputStream / BufferedOutputStream | |
Reader / Writer | 문자 단위 입출력을 위한 최상위 입출력 스트림 클래스 |
FileReader / FileWriter | 문자 단위 입출력을 위한 하위 스트림 클래스 |
InputStreamReader / OutputStreamWriter | |
PrinterWriter | |
BufferedReader / BufferedWriter |
InputStream
바이트 기반 입력 스트림의 최상위 클래스이다. ( 추상클래스 )
모든 바이트 기반 입력 스트림은 이 클래스를 상속받아서 만들어진다.
InputStream 클래스에는 바이트 기반 입력 스트림이 기본적으로 가져야 할 메소드
메소드 | 설명 |
int available() | 현재 읽을 수 있는 바이트 수를 반환한다. |
void close() | 현재 열려있는 InputStream을 닫는다 |
void mark(int readlimit) | InputStream에서 현재의 위치를 표시해준다 |
boolean markSupported() | 해당 InputStream에서 mark()로 지정된 지점이 있는지에 대한 여부를 확인한다 |
abstract int read() | InputStream에서 한 바이트를 읽어서 int값으로 반환한다 |
int read(byte[] b) | byte[] b 만큼의 데이터를 읽어서 b에 저장하고 읽은 바이트 수를 반환한다 |
int read(byte[] b, int off, int len) | len만큼 읽어서 byte[] b의 off위치에 저장하고 읽은 바이트 수를 반환한다 |
void reset() | mark()를 마지막으로 호출한 위치로 이동 |
long skip(long n) | InputStream에서 n바이트만큼 데이터를 스킵하고 바이트 수를 반환한다 |
OutputStream
바이트 기반 출력 스트림의 최상위 클래스이다. ( 추상클래스 )
모든 바이트 기반 출력 스트림 클래스는 이 클래스를 상속받아서 만들어져있다.
flush() 는 출력 스트림과 버퍼된 출력 바이트를 강제로 쓰게하여 데드락 현상을 방지시켜준다.
OutputStream 클래스에서 모든 바이트 기반 출력 스트림이 기본적으로 가져야할 메소드
메소드 | 설명 |
void close() | OutputStream을 닫는다 |
void flush() | 버퍼에 남아있는 출력 스트림을 출력한다 |
void write(byte[] b) | 버퍼의 내용을 출력한다 |
void write(byte[] b, int off, int len) | b배열 안에 있는 시작 off부터 len만큼 출력한다 |
abstract void write(int b) | 정수 b의 하위 1바이트를 출력한다 |
짱좋은 참고 블로그 : https://develop-im.tistory.com/54
1. Server
server코드 같은경우는 평범한 Thread 환경으로 Client가 접속하길 기다리며 접속시 byte 배열을 받는 과정
public class TCPServerSocket {
public static void main(String[] args) {
try {
ServerSocket serverSocket = new ServerSocket(8000);
while(true) {
System.out.println("waiting for connect...");
Socket socket = serverSocket.accept();
System.out.println("client info " + socket.getInetAddress() + ", port: " + socket.getPort());
new ServerThread(socket).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
static class ServerThread extends Thread {
private Socket socket;
public ServerThread(Socket socket) {
this.socket = socket;
}
@Override
public void run() {
try {
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[4];
inputStream.read(buffer);
ByteBuffer data = ByteBuffer.wrap(buffer);
data.order(ByteOrder.LITTLE_ENDIAN);
int size = data.getInt();
byte[] bytes = new byte[size];
inputStream.read(bytes);
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2. Client
Client 코드에서는 동작을 시키면 서버로 연결하여 byte 배열을 보내는과정
public class ClientSocket {
public static void main(String[] args) {
String message ;
InetAddress inetAddress;
try {
InetSocketAddress socketAddress = new InetSocketAddress("127.0.0.1", 8000);
Socket socket = new Socket();
socket.connect(socketAddress);
OutputStream outputStream = socket.getOutputStream();
InputStream inputStream = socket.getInputStream();
byte[] test = new byte[4];
byte[] abc = new byte[52];
test[3] = (byte)(52 >> 24);
test[2] = (byte)(52 >> 16);
test[1] = (byte)(52 >> 8);
test[0] = (byte)(52);
outputStream.write(test);
outputStream.write(abc);
outputStream.flush();
} catch (IOException e) {
e.printStackTrace();
}
}
}
Client 코드에서 정보를 전송을 할때 데이터 정보의 크기의 정보를 BIG_ENDIAN으로 바이트 오더링 후 전송한다
-> 네트워크 통신은 데이터를 BIG_ENDIAN으로 오더링 후 전송한다.
Server 코드에서 정보를 수신할때 데이터 정보의 크기를 LITTLE_ENDIAN으로 오더링 후 읽는다.
-> CPU의 따라 오더링 방식이 다르다.
ENDIAN 특징 및 정리
바이트 오더는 데이터가 바이트 단위로 메모리에 저장되는 순서를 의미하며, 각 CPU 벤더 의존적은 특징을 지니고 있다.
크게 BIG_ENDIAN을 사용하는 AMD CPU, LITTLE_ENDIAN을 사용하는 Intel CPU 가 존재한다. 이기종 간 톡신을 하는 네트워크 프로그래밍에는 두종간 올바른 통신을 하기 위해서는 통일된 방식이 필요한데 네트워크 바이트 오더링 표준은 BIG_ENDIAN 방식이다. 그렇기에 데이터를 전송할때에는 원칙으로는 BIG_ENDIAN으로 전송하고 각자 CPU에 맞는 ENDIAN으로 변환 후 읽어들이면 된다.
* 바이트 오더는 2바이트 이상의 프리미티브 타입에 적용되는 내용이다. 1바이트 단위의 문자열 데이터는 바이트 오더의 변경이 불필요함
* 프리미티브타입 ( 기본형 ) : 총 8가지 기본형 타입을 미리 정의하여 제공되며 기본값이 있어 Null이 존재하지 않는다. 만약 기본 타입에 Null 을 넣고 싶으면 래퍼 클래스를 활용하면 된다. 추가로 실제 값을 저장하는 공간으로 스택 메모리에 저장된다.
프리미티브 타입 참고 블로그 : https://gbsb.tistory.com/6
ENDIAN 참고 블로그 : https://hiddenviewer.tistory.com/78
BIG_ENDIAN <--> LITTLE_ENDIAN 변환 관련 예제 : https://zion830.tistory.com/39