이번 차수에서는 소켓의 스트림을 통해 데이터를 전송하는 것에 대해서 다루었다.
데이터 전송
눈여겨 보아야 할 부분은 ServerSocket이 클라이언트 측과 연결되면 생성되는 소켓(Socket Class)에서 제공되는 OutputStream을 통해 데이터를 보낸다는 것이다. 이를 클라이언트 측에서는 소켓(서버 측에서 생성되는 소켓과 동일한 Class)에서 제공되는 InputStream을 통해 데이터를 받을 수 있다.
※ Java의 Socket을 이용한 네트워크 통신에 대해 알고 싶어 이 부분에 대해서 공부하기 시작하면서 추가적으로 Stream에 대한 공부가 이어져야 할 것 같다... Stream에 대한 이해도가 낮은 것을 많이 느낀다.
※ 서버 측에서 DataOutputStream을 Filter Stream으로 삼았기 때문에, 클라이언트 측에서도 DataInputStream을 사용해야 한다는 점을 봤을 때, 데이터 송수신 시 사용되는 Stream은 일치해야 하는 것 같다.
※ Stram은 Node Stream, Filter Stream 등으로 구분되는 것 같다. 예로 들어 서버 측 클래스의 코드를 보면, OutputStream이 Node Stream이고, DataOutputStream이 Filter Stream이다.
※ DataOutputStream, DataInputStream : 자바의 기본 데이터 타입별로 출력하는 별도의 메소드들을 지니는 스트림이다.
서버 측
public class DemoServer {
public static void main(String[] args) throws IOException {
int port = 5050;
int number = Integer.parseInt(args[0]); // Text 형태로 들어오기 때문에 변환
String str = new String(args[1]);
ServerSocket ssk = new ServerSocket(port);
System.out.println("접속 대기중");
System.out.println("클라이언트 IP : " + socket.getInetAddress().getHostAddress());
while (true) {
Socket socket = ssk.accept();
// Client와의 연결을 위한(Data를 보내기 위해) Stream 생성
OutputStream os = socket.getOutputStream();
DataOutputStream dos = new DataOutputStream(os);
// DataOutputStream : 들어오는 데이터의 자료형에 따라 받아올 수 있는 Filter Stream.
// Node Stream인 OutputStream과 연결해야 함
dos.writeInt(number);
dos.writeUTF(str);
dos.flush();
dos.close();
socket.close();
}
}
}
클라이언트 측
public class DemoClient {
public static void main(String[] args) throws IOException {
Socket sk = new Socket("127.0.0.1", 5050);
System.out.println("서버와 접속이 되었습니다.");
InputStream is = sk.getInputStream();
DataInputStream dis = new DataInputStream(is);
// Server에서 Filter Stream으로 DataOutputStream을 사용했기 때문에 Client측에서도 DataInputStream을 사용해야 함
int number = dis.readInt();
System.out.println("서버에서 전송된 값 : " + number);
String str = dis.readUTF();
System.out.println("서버에서 전송된 값2 : " + str);
dis.close();
is.close();
sk.close();
}
}
테스트 결과
Echo Server 구현
※ 에코 서버란?
클라이언트가 요청을 보내게 되면 그에 맞는 적당한 (간단한) 응답을 하는 서버를 의미. 요청 메세지를 메아리로 다시 보내준다는 의미이다.
서버 측
public class EchoServerTest {
public static void main(String[] args) {
int port = 7070;
ServerSocket ssk = null;
Socket socket = null;
try {
ssk = new ServerSocket(port);
System.out.println("접속 대기중");
while(true) {
socket = ssk.accept();
System.out.println(socket.getInetAddress() + ":" + socket.getLocalPort() + "에서 접속되었습니다.");
/* 원격 서버로 연결할 때는 getPort()를 사용해도 되나, 로컬 서버에 연결할 때는 루프백 주소를 사용하기 때문에
getPort() 사용 시 계속해서 포트 번호가 바뀌어서 나옴 */
// 클라이언트와 통신하기 위한 스트림 생성
OutputStream os = socket.getOutputStream(); // OutputStream은 브릿지 역할을 하는 스트림
PrintWriter pw = new PrintWriter(os, true); // 두 번째 매개변수로 true를 전달하면 자동으로 flush 기능을 제공
pw.println("hi!");
// Read & Reply Client Message
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
String clientMsg = "";
while ((clientMsg = br.readLine()) != null) {
if (clientMsg.startsWith("hi") || clientMsg.startsWith("hello")) {
pw.println("nice to meet you, " + socket.getInetAddress() + "..^^");
} else if (clientMsg.startsWith("what date is it today")) {
LocalDateTime date = LocalDateTime.now();
pw.println(date.getYear() + "/" + date.getMonthValue() + "/" + date.getDayOfMonth());
} else {
pw.println("bye, " + socket.getInetAddress());
}
}
os.close();
pw.close();
is.close();
isr.close();
br.close();
socket.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
클라이언트 측
public class EchoClientTest {
public static void main(String[] args) {
String ip = "127.0.0.1";
int port = 7070;
Socket socket = null;
try {
socket = new Socket(ip, port);
System.out.println("서버에 접속했습니다.");
String serverMsg = "", sendMsg = "";
// Read & Print Server Message
InputStream is = socket.getInputStream();
InputStreamReader isr = new InputStreamReader(is);
BufferedReader br = new BufferedReader(isr);
serverMsg = br.readLine();
System.out.println("Server Message > " + serverMsg);
// Input & Send Message
OutputStream os = socket.getOutputStream();
PrintWriter pw = new PrintWriter(os, true); // Send Message (Write to OutputStream)
InputStreamReader isr2 = new InputStreamReader(System.in); // Input with Keyboard
BufferedReader br2 = new BufferedReader(isr2);
while ((sendMsg = br2.readLine()) != null) {
pw.println(sendMsg); // Send
serverMsg = br.readLine(); // Read reply message that comes from server
System.out.println("Server Message > " + serverMsg);
}
is.close();
isr.close();
isr2.close();
br.close();
br2.close();
socket.close();
} catch (UnknownHostException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
}
}
※ 서버 소켓을 같은 IP와 포트로 여러개 만드려고 하거나, 클라이언트 소켓을 닫아주지 않으면서 해당 클래스의 인스턴스를 여러 개 생성하려 하자 Address already in use: NET_Bind 오류가 발생했다. 이미 사용 중인 포트라서 문제가 된 것이다.. 다급히 소켓을 닫아주는 코드를 추가했다.
'Backend > Java' 카테고리의 다른 글
Java Networking) Multi Thread환경을 Synchronized를 통해 동기화시키기 (0) | 2022.04.29 |
---|---|
Java Networking) 채팅 서버 만들어보기 (0) | 2022.04.25 |
Java Networking) ServerSocket 생성해 Socket 연결해보기 (0) | 2022.04.18 |
Java Networking) URL, URLConnection, openStream(), openConnection(), getInputStream() (0) | 2022.04.12 |
Java) CLI를 통한 Java 컴파일 시 발생하는 인코딩 오류 (0) | 2022.04.11 |