TCP/IP 소개
두 시스템 간에 데이터가 손상없이 안전하게 전송되도록 하는 통신 프로토콜입니다. TCP에서 동작하는 응용프로그램의 사례로는
email, FTP, 웹(HTTP)등이 있습니다.
이는 연결형 통신이라는 특징이 있습니다. 한 번 연결 후 계속 데이터가 전송가능합니다. 마치 친구와 전화를 걸고 받을 때처럼 말이죠. 그리고 보낸 순서대로 받아 응용프로그램에게 전달합니다.
소켓 (socket)
TCP/IP 네트워크를 이용하여 쉽게 통신 프로그램을 작성하도록 지원하는 기술입니다. 소켓ㅇ리란 두 응용프로그램 간의 양방향 통신 링크의 한쪽 끝 단입니다. 소켓끼리 데이터를 주고받을 수 있으며, 소켓은 특정 IP 포트 번호와 결합합니다. 소켓에는 서버 소켓과 클라이언트 소켓이 존재하게 됩니다.
서버 소켓과 클라이언트 소켓
서버 소켓은 클라이언트의 접속을 기다리는 목적으로만 사용됩니다. 클라이언트는 소켓을 이용하여 서버에 접속하고요, 서버 소켓은 클라이언트가 접속해 오면 클라이언트 소켓을 별도로 만들어 상대 클라이언트와 통신합니다. 정리하면 서버는 접속을 기다리는 소켓이고 클라이언트는 통신을 실시하는 소켓입니다
예를 들어 서버는 80번 서버 소켓을 만들고 접속을 기다립니다. 서버의 서버 소켓은 클라이언트의 소켓이 접속해 올 떄마다 클라이언트 소켓을 별도로 만들어 통신하는 원리입니다.
- 서버에서는 ServerSocket()을 만들어서 기다립니다.
- 클라이언트에서 Socket()을 생성했을 때(연결 요청) accept()가 연결을 수락하여 별도의 클라이언트 소켓을 만들게 됩니다.
- 이렇게 만든 소켓끼리 서로 데이터를 주고 받게 됩니다.
기본적인 소켓을 이용한 서버 클라이언트 통신 프로그램의 전형적인 구조입니다.
- 서버는 ServerSocket클래스를 이용하여 서버 소켓을 생성하고 accept()메소드를 호출하여 클라이언트 접속을을 기다리게 됩니다. 클라이언트는 소켓을 이용하여 서버에 접속합니다.
- 클라이언트는 Socket클래스를 이용하여 클라이언트 소켓을 생성하여 서버에 접속을 지시합니다.
- 서버의 accept메소드가 접속 요청을 받으면 이를 위한 전용 클라이언트 소켓을 따로 생성합니다.
- 서버와 클라이언트 모두 입출력 스트림을 얻어 내고 통신할 준비를 합니다.
- 입출력 스트림을 이용하여 데이터를 주고 받게 됩니다.
- 통신이 끝나면 소켓을 닫습니다.
코드 예시
/ServerEx2.java
package com.company.socket;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerEx2 {
public static void main(String[] args) {
ServerSocket listener = null;
Socket socket = null;
try {
listener = new ServerSocket(9999);
System.out.println("연결 기다림...");
socket = listener.accept();
System.out.println("연결 되었음");
} catch(IOException e) {}
try {
listener.close();
socket.close();
} catch(Exception e) {}
}
}
/ClientEx2.java
package com.company.socket;
import java.io.IOException;
import java.net.Socket;
public class ClientEx2 {
public static void main(String[] args) {
Socket socket = null;
try {
socket = new Socket("localhost", 9999);
} catch(IOException e) {}
try {
socket.close();
} catch(IOException e) {}
}
}
서버에서 데이터를 받는 과정
네트워크 입출력 스트림 생성
- Socket객체의 getInputStream()과 getOutputStream()메소드를 이용하여 입출력 데이터 스트림 생성을 하게 됩니다.
BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
BufferedWrite out = new BufferedWrite(new OutputStreamWriter(socket.getOutputStream()));
또한 이를 클라이언트로부터 데이터를 수신받을 때는 다음과 같은 코드를 쳐주시면 됩니다.
int x = in.read(); // 클라이언트로부터 한 개의 문자를 수신
String inputMessage = in.readLine(); // 한 행의 문자열 수신
readLine() 메소드는 \n문자가 올 때까지 계속 읽고 \n이 도착하면 문자열을 리턴합니다. 리턴되는 문자열은 \n이 삽입되지 않습니다.
서버에서 라인 단위로 일기 때문에 클라이언트에서 문자열 끝에 \n을 붙여서 보내야 합니다.
클라이언트에서 데이터 전송 과정
BufferedReader in = new BufferedReader(new InputStreamReader(clintSocket.getInputStream()));
BufferedWriter out = new BufferedWriter(new OutputStreamWriter(clientSocket.getOutputStream()));
또한 서버로의 데이터 전송하는 방법은 다음과 같은 코드를 쳐 주시면 됩니다.
out.write("hello" + "\n");
out.flush()
Hello에 \n을 붙여서 보내는 이유는 서버 쪽에서 라인 단위로 수신하도록 작성해 놓았기 때문입니다.
버퍼가 차기 전까지 데이터를 보내지 않기 때문에 강제로 flush()를 호출하여 즉각 전송하게 됩니다.
/ServerEx3.java
package com.company.socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class ServerEx2 {
public static void main(String[] args) {
ServerSocket listener = null;
Socket socket = null;
BufferedReader in = null;
BufferedWriter out = null;
try {
listener = new ServerSocket(9999);
System.out.println("연결 기다림...");
socket = listener.accept();
System.out.println("연결 되었음");
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
while(true) {
// 즐 단위로 수신하게 된다. ( 클라이언트로 부터 온 메시지를 )
String inputMessage = in.readLine();
System.out.println("데이터 받기 성공");
if(inputMessage.equalsIgnoreCase("bye")) {
System.out.println("클라이언트에서 연결을 종료했음");
break;
}
System.out.println("클랴이언트: " + inputMessage);
}
} catch(IOException e) {}
try {
listener.close();
socket.close();
} catch(Exception e) {}
}
}
/ClientEx3.java
package com.company.socket;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStreamWriter;
import java.net.Socket;
import java.util.Scanner;
public class ClientEx2 {
public static void main(String[] args) {
Socket socket = null;
BufferedWriter out = null;
Scanner scanner = new Scanner(System.in);
try {
socket = new Socket("localhost", 9999);
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true) {
System.out.println("보내기>>");
String outputMessage = scanner.nextLine();
if(outputMessage.equalsIgnoreCase("bye")) {
out.write(outputMessage + "\n");
out.flush(); break;
}
out.write(outputMessage + "\n");
out.flush();
}
} catch(IOException e) {}
try {
socket.close();
} catch(IOException e) {}
}
}
- 먼저 ServerEx3.java는 BufferedReader로 클라이언트로 부터 온 데이터를 받은 코드입니다. while문으로 계속해서 수신받고 있으며, 라인단위로 받고 있습니다. 그리고 "bye"를 입력하게 되면 클라이언트와의 연결을 종료하는 코드를 작성해 주었습니다.
- 그 다음에 ClientEx3.java는 BufferedWriter로 클라이언트가 버퍼에 문자를 담아서 서버로 보내는 코드입니다. 이도 똑같이 while문을 돌려서 계속 받다가 bye하면 버퍼에 있던 내용을 다 보내서 서버도 이를 보고 소켓을 끊을 것입니다. 뭔가 슬프네요..
이 상황에서 클라이언트에서 "Bye"를 치게 되면 둘다 연결을 끊게 되는 것입니다.
그리고 이제 반대의 상황 즉, 서버에서 클라이언트로 메세지를 보내고, 클라이언트에서 서버에서 메세지를 보내는 코드를 추가로 작성해 보도록 하겠습니다.
/ServerEx3.java
package com.company.socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class ServerEx2 {
public static void main(String[] args) {
ServerSocket listener = null;
Socket socket = null;
BufferedReader in = null;
BufferedWriter out = null;
Scanner scanner = new Scanner(System.in);
try {
listener = new ServerSocket(9999);
System.out.println("연결 기다림...");
socket = listener.accept();
System.out.println("연결 되었음");
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true) {
// 즐 단위로 수신하게 된다. ( 클라이언트로 부터 온 메시지를 )
String inputMessage = in.readLine();
System.out.println("데이터 받기 성공");
if(inputMessage.equalsIgnoreCase("bye")) {
System.out.println("클라이언트에서 연결을 종료했음");
break;
}
System.out.println("클랴이언트: " + inputMessage);
//----------------------------------------
System.out.println("보내기 >>");
String outputMessage = scanner.nextLine();
out.write(outputMessage + "\n");
out.flush();
}
} catch(IOException e) {}
try {
listener.close();
socket.close();
} catch(Exception e) {}
}
}
/ClientEx3.java
package com.company.socket;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class ClientEx2 {
public static void main(String[] args) {
Socket socket = null;
BufferedWriter out = null;
Scanner scanner = new Scanner(System.in);
BufferedReader in = null;
try {
socket = new Socket("localhost", 9999);
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
in = new BufferedReader(new InputStreamReader((socket.getInputStream())));
while(true) {
System.out.println("보내기>>");
String outputMessage = scanner.nextLine();
if(outputMessage.equalsIgnoreCase("bye")) {
out.write(outputMessage + "\n");
out.flush(); break;
}
out.write(outputMessage + "\n");
out.flush();
//----------------------------------------
String inputMessage = in.readLine();
System.out.println("서버: " + inputMessage);
}
} catch(IOException e) {}
try {
socket.close();
} catch(IOException e) {}
}
}
그리고 대화를 나눈 결과는 아래와 같습니다.
서버-클라이언트 채팅 프로그램 만들기
- 서버와 클라이언트가 1:1로 채팅합니다.
- 클라이언트와 서버가 서로 한번씩 번갈아 가면서 문자열을 전송합니다.
- 문자열 끝에 \n을 덧붙여 보내고 라인 단위로 수신합니다.
- 클라이언트가 bye를 보내면 프로그램을 종료합니다.
- 예제는 서버와 클라이언트가 한번씩 번갈아 가면서 상대에게 채팅 문자를 전송해야 합니다.
- 언제든지 보내고 받으려면, 보내는 스레드와 별도로 받는 스레드를 가지고 있어야 합니다.
- 사용자 입력 도중 상대로부터 메시지를 전송 받을 때 입력 창 외 다른 출력창이 필요합니다.
- 언제든지 채팅 메시지를 주고 받을 수 있는 것은 멀티 스레드를 사용해야 합니다.
수식 계산 서버-클라이언트 만들기
/CalcServerEx.java
package com.company.socket;
import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
import java.util.StringTokenizer;
public class CalcServerEx {
public static String calc(String exp) {
StringTokenizer st = new StringTokenizer(exp, " ");
if(st.countTokens() != 3) return "error";
String res = "";
int op1 = Integer.parseInt(st.nextToken());
String opcode = st.nextToken();
int op2 = Integer.parseInt(st.nextToken());
switch(opcode) {
case "+":
res = Integer.toString(op1 + op2);
break;
case "-":
res = Integer.toString(op1 - op2);
break;
case "*":
res = Integer.toString(op1 * op2);
break;
default:
res = "error";
}
return res;
}
public static void main(String[] args) {
BufferedReader in = null;
BufferedWriter out = null;
ServerSocket listener = null;
Socket socket = null;
try {
listener = new ServerSocket(9999);
System.out.println("연결을 기다리고 있었습니다.....");
socket = listener.accept(); // 클라이언트로부터 연결 요청 대기
System.out.println("연결되었습니다.");
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true) {
String inputMessage = in.readLine();
if(inputMessage.equalsIgnoreCase("bye")) {
System.out.println("클라이언트에서 연결을 종료하였음");
break;
}
System.out.println(inputMessage);
String res = calc(inputMessage);
out.write(res + "\n");
out.flush();
}
} catch(IOException e) {
System.out.println(e.getMessage());
} finally {
try {
if(socket != null) socket.close(); // 통신용 소켓 닫기
if(listener != null) listener.close(); // 서버 소켓 닫기
} catch(IOException e) {
System.out.println("클라이언트와 채팅 중 오류가 발생했습니다.");
}
}
}
}
/CalcClientEx.java
package com.company.socket;
import java.io.*;
import java.net.Socket;
import java.util.Scanner;
public class CalcClientEx {
public static void main(String[] args) {
BufferedReader in = null;
BufferedWriter out = null;
Socket socket = null;
Scanner scanner = new Scanner(System.in);
try {
socket = new Socket("localhost", 9999);
in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
out = new BufferedWriter(new OutputStreamWriter(socket.getOutputStream()));
while(true) {
System.out.println("계산식(빈칸으로 띄어 입력, 예: 24 + 42) >>");
String outputMessage = scanner.nextLine();
if(outputMessage.equalsIgnoreCase("Bye")) {
out.write(outputMessage + "\n");
out.flush();
break;
}
out.write(outputMessage + "\n");
out.flush();
String inputMessage = in.readLine();
System.out.println("계산 결과 " + inputMessage);
}
} catch(IOException e) {
System.out.println(e.getMessage());
} finally {
try {
scanner.close();
if(socket != null) socket.close();
} catch(IOException e) {
System.out.println("서버와 채팅 중 오류가 발생했습니다.");
}
}
}
}
'School > Java Programming' 카테고리의 다른 글
[ Java Programming ] IOStream && FileStream (0) | 2022.05.26 |
---|---|
Java Programming - Thread notify-wait() (0) | 2022.05.19 |
Java Programming - Thread basic (0) | 2022.05.19 |
Java Programming - Collection Framework (0) | 2022.04.07 |
Java Programming - Basic API Class (0) | 2022.03.24 |