본문 바로가기
교육, 학습/멀티캠퍼스_풀 스택

JAVA 문법 - 자바 TCP, UDP구현 :: 소켓 프로그래밍

by 개발하는 경제학도 2022. 1. 19.

강의 소개

현재 수강하고 있는 멀티캠퍼스 k-digital 지능형 웹서비스 풀 스택 과정을 수강하며 적은 내용입니다.

교재로는 자바의 정석을 사용하고 있습니다.


소켓 프로그래밍

소켓 프로그래밍은 소켓을 이용한 통신 프로그래밍을 뜻한다. 소켓(socket)은 프로세스 간 통신을 위한 양쪽 끝단을 뜻한다. 마치 다른 곳에 있는 사람 둘이 통화하기 위해서는 전화기가 필요한 것과 같다.

자바에서는 java.net패키지로 이 소켓 프로그래밍을 지원한다. 아래서는 TCP, UDP를 활용한 소켓 프로그래밍에 대해서 다룬다.

 

TCP/IP 프로토콜

TCP/IP 프로토콜은 이기종 시스템간의 통신을 위한 표준 프로토콜이다.

TCP와 UDP 모두 이 TCP/IP 프로토콜에 포함되어 있다. 또한, OSI 7 계층의 전송계층에 해당하는 프로토콜이다.

 

TCP, UDP

TCP와 UDP는 전송 방식이 다르다.

간단히 말하면 TCP는 1:1 통신방식이며, 전화에 비유될 수 있다.

UDP는 1:n 통신방식이며, 우편에 비유된다.

 


 

1. TCP

 

- 특징

TCP 통신은 전화에 비유된다.

1대 1 통신방식이다.

연결 기반이다.

상대방이 데이터를 전달 받았는지 관심 있다. 즉, 신뢰할 수 있는 방식이다. 만약 상대방이 데이터를 받지 못했다면 재전송한다.

스트림 방식으로 데이터를 주고 받는다.

자바 클래스로는 Socket, SeverSocket을 사용한다.

 

 

- 통신할 때 순서(요약)

연결하려는 컴퓨터는 클라이언트, 연결을 허락해서 서비스를 제공하는 컴퓨터는 서버이다.

 

준비단계) 서버는 클라이언트의 연결을 허락할 수 있도록 준비한다.

서버는 서버 소켓을 사용해서 서버의 특정 포트에서 클라이언트의 연결 요청을 처리할 준비를 한다.

1) 클라이언트가 서버로 연결 요청을 보낸다.

이때, 클라이언트는 접속할 서버의 IP주소와 포트 정보로 소켓을 생성하여 요청을 보낸다.

2) 서버가 연결 허락한다. (accept)

서버에 새로운 소켓을 생성해서 클라이언트의 소켓과 연결시킨다.

3) 클라이언트가 서버로 요청 내용을 보낸다. 즉, 클라이언트가 요청을 출력하면 서버로 입력된다.

4) 입력받은 요청을 분석해 적당한 처리 결과를 클라이언트로 응답한다. 즉, 서버는 출력하고 클라이언트는 입력받는다.

더 이상 3, 4를 반복할 수 없으면 연결을 해제한다.

 

 

- TCP방식의 서버와 클라이언트 통신

(서버 부분과 클라이언트의 순서가 번갈아가며 진행되니 순서에 맞게 번갈아가며 봐야 한다.)

[TCP방식 서버] [TCP방식 클라이언트]
TCP방식에는 서버를 먼저 실행한다.

1) 서버를 시작한다.
ServerSocket 객체를 생성한다.

ServerSocket ss = new ServerSocket(포트번호지정);

터미널에서 ipconfig, InetAddress.getLocalhost() 로 본인의 ip주소를 확인할 수 있다.


3) 클라이언트의 접속 허락한다.

Socket s = ss.accept();

Socket은 서버, 클라이언트를 연결하는 양 끝 점이다.

5) 클라이언트의 요청 내용을 받는다.
s.getInputStream(); // 2byte전송불가(한글같은)
Scanner sc = new Scanner(is);
sc.next();
sc.nextLine();

6) 서버가 클라이언트로 요청 처리 결과를 준다. (서버가 출력한다.)

OutputStream o = s.getOutputStream();
PrintSteam p = new PrintStream(o);
p.println(출력내용);

OutputStream클래스는 1byte라 영문자만 있거나, 키보드에 있는 특수문자만 가능하다.

따라서 PrintStream이 한글같은 2byte문자를 출력하기 위해 보조로 도와준다.


9) 클라이언트가 연결 해제해서 자동으로 연결 해제된다.

// s.close();


그렇다고 서버가 종료되는 것은 아니다. 서버는 여러 클라이언트의 요청을 받으므로 서버는 계속 살아있어야 한다.




10) 또 다른 클라이언트의 요청이 들어온다&처리한다.
서버 종료시까지 3~9번을 반복한다. 
2) 서버에 접속을 시도한다.

Socket s = new Socket("ip", port번호);

서버 ip와 포트 번호를 입력해서 Socket을 생성한다.


4) 클라이언트가 요청한다. (클라이언트가 출력한다.)

OutputStream o = s.getOutputStream(); 
PrintSteam p = new PrintStream(o); 
p.println(출력내용);


7) 클라이언트가 서버의 응답 내용을 받는다.

s.getInputStream();
Scanner sc = new Scanner(is);
sc.next();
sc.nextLine();

getInputStream()은 1byte이다. 따라서 한글 등의 2byte를 받기 위해 Scanner의 도움을 받는다.


8) 클라이언트가 서버 접속 해제한다.

s.close();

4~7번의 요청, 응답을 반복하다가 해제한다.
(서버는 아무것도 안해도 접속해제된다.)

 

 

- 전체 구현 예시

[서버]

TCP방식에는 서버를 먼저 실행한다.

// 서버 특징: 클라이언트보다 먼저, 항상 실행중(무한대 실행: demon)

public class TCPServer {  

	public static void main(String[] args) {
		try {
			// 서버 시작(= ServerSocket 생성)
			ServerSocket ss = new ServerSocket(50000);  //포트번호 지정
			System.out.println("[===서버: 50000포트에서 시작===]");
			
			int cnt = 0;
			
			while(true) { 
				Socket s = ss.accept();
				System.out.println("[===서버: 클라이언트와 연결===]");
				
				// 읽어올 때 코드 
				InputStream is = s.getInputStream();
				Scanner sc = new Scanner(is); // Scanner: 2byte처리.
				System.out.println(
                "[===서버: 클라이언트로부터 받은 인사말===" + sc.nextLine() +"]");
				
				// 서버에서 클라이언트로 응답을 전송한다.
				OutputStream o = s.getOutputStream(); . 
				PrintStream p = new PrintStream(o); 
				p.print("클라이언트님도 안녕하신가요?\n"); // 보낼 메세지 
				System.out.println("[===서버: 클라이언트로 인사말 출력===]");
				p.flush(); // 버퍼의 데이터를 한 번에 쏟아낸다. 
				
                // 이 경우 서버 <> 클라이언트 간 연결만 해제. 서버는 아직 살아있다.
				s.close();
				System.out.println("[===서버: " 
                + s.getInetAddress().getHostAddress() 
                + "클라이언트와 연결 해제===]"); 
                
				// 서버는 무한 실행되어야 하지만, 구현이므로 5번 요청처리시 종료한다.
				if(cnt > 5) { 
					ss.close();  // 서버를 죽인다.
					System.out.println("[===서버: 서버 종료===]");
					
				}
				cnt += 1;
			}
		}
		catch (IOException error) {
			error.printStackTrace();  // 에러 원인 확인
		}
		
	}

}

 

[클라이언트]

//클라이언트를 먼저 실행하면 ConnectException.(접속하려는 ip, port번호를 가진 서버주소가 없다) 
public class TCPClient {

	public static void main(String[] args) throws Exception{
		//서버접속
		Socket s = new Socket("localhost", 50000/*서버 포트 번호*/);
		System.out.println("[===클라이언트: 서버와 접속===]");
		
		// 보낼 때 코드
		OutputStream o = s.getOutputStream();
		PrintStream p = new PrintStream(o);
		p.print("안녕 hello 서버님\n");
		System.out.println("[===클라이언트: 서버로 인사말 출력===]");
		p.flush();
		
		// 읽어올 때 코드 
		InputStream is = s.getInputStream();
		Scanner sc = new Scanner(is); // Scanner도 2byte처리를 위한 보조.
		System.out.println("[===클라이언트: 서버로부터 받은 인사말===" + sc.nextLine() +"]");

		
		s.close();  // 서버와 연결끊기
		System.out.println("[===클라이언트: 서버와 접속 해제===]");
	}
}

 

 


 

2. UDP

 

- 특징

UDP 통신은 우편, 소포에 비유된다.(내용만 보내는 것이 아니라 보내는 사람, 받는 사람을 명시한다.)

비연결 방식이다.

데이터의 전송에 신뢰성이 없다. 제대로 도착했는지 확인하지 않기 때문이다.

데이터를 순서대로 보낸다는 보장이 없다.

도착에 대해 확인 과정이 없어 TCP보다 전송이 빠르다.

DatagramPacket, DatagramSocket클래스를 사용한다.

 

 

- 통신할 때 순서(요약)

 

1) 수신자가 발신자로 요청한다. 

이때 수신자가 데이터가 담긴 DatagramPacket에 [발신자 주소, 발신자 포트, 요청 내용, 수신자 주소, 수신자 포트]를 전송한다. 연결을 미리 하는 개념이 아니기 때문이다.

2) 발신자 주소가 일치하는 컴퓨터(발신자)에 전달한다.

DatagramPacket에서 지정한 발신자 주소의 DatagramSocket에 도착한다.

**DatagramPacket은 헤더(호스트의 주소, 포트)와 데이터로 전달되어있다. 마치 소포에 주소가 적혀있고 그 안에 내용물이 담긴 것과 같다.

3) 발신자는 요청받은 일을 처리해서 결과를 응답한다.

이때 발신자가 [발신자 주소, 발신자 포트, 요청 내용, 수신자 주소, 수신자 포트]로 응답한다. 내용만 들어가는 게 아니라, 매번 본인이 누구고 누구한테 발송할 것인지 항상 함께 보내야 한다.

 

 

- 전체 구현 예시(출처: 자바의 정석)

[발신자]

public class UdpServer {
	public void start() throws IOException {
		// 포트 9999번을 사용하는 소켓을 생성한다.
		DatagramSocket socket = new DatagramSocket(9999);
		DatagramPacket inPacket, outPacket;

		byte[] inMessage = new byte[10];
		byte[] outMessage;

		while(true) {
			// 데이터를 수신하기 위한 패킷을 생성한다.
			inPacket = new DatagramPacket(inMessage, inMessage.length);
			socket.receive(inPacket); // 패킷을 통해 데이터를 받는다.

			// 수신한 패킷에서 클라이언트의 IP주소와 Port를 얻는다.
			InetAddress address = inPacket.getAddress();
			int port = inPacket.getPort();

			// 서버의 현재 시간을 시분초 형태로 반환한다.			
			SimpleDateFormat simple = new SimpleDateFormat("[hh:mm:ss]");
			String time = simple.format(new Date());
			outMessage = time.getBytes(); // byte배열로 변환한다.

			// 패킷을 생성해서 클라이언트에게 send한다.
			outPacket 
            = new DatagramPacket(outMessage, outMessage.length, address, port);
			socket.send(outPacket);
		}
	}

	public static void main(String args[]) {
		try {  
			new UdpServer().start();  // UDP서버를 실행시킨다.
		} catch (IOException error) {
			error.printStackTrace();
		}
	}
}

 

[수신자]

public class UdpClient {
	public void start() throws IOException, UnknownHostException {
		DatagramSocket datagramSocket = new DatagramSocket();
		InetAddress serverAddress = InetAddress.getByName("127.0.0.1");

		// 데이터가 저장될 공간으로 임의로 지정한 크기의 byte배열을 생성한다. 한글은 1문자당 3byte로 취급된다.
		byte[] message = new byte[200];

		DatagramPacket outPacket = new DatagramPacket(message, 1, serverAddress, 9999);
		DatagramPacket inPacket = new DatagramPacket(message, message.length);

		datagramSocket.send(outPacket);    // send메서드: DatagramPacket을 전송한다.
		datagramSocket.receive(inPacket);  // receive메서드: DatagramPacket을 수신한다.

		System.out.println("서버의 현재 시간 :" + new String(inPacket.getData()));

		datagramSocket.close();
	} 

	public static void main(String args[]) {
		try {
			new UdpClient().start();
		} catch(Exception error) {
			error.printStackTrace();
		}
	} 
}

 

 

DatagramPacket

UDP 방식 통신을 하는 컴퓨터들이 주고받을 데이터 저장 객체이다. UDP는 데이터 내용을 보낼 때 모든 컴퓨터 환경을 고려하여 바이트 배열로만 보내야 한다. (byte [])

 

getBytes() 메서드

인공지능 스피커 등은 바이트만 받는다. 따라서 바이트로 바꿔주기 위해 사용한다.

 

- String에서 byte []로 변경

String s = "java";
byte[] b = s.getBytes(StandardCharsets.UTF_8);

한글은 바이트 형태로 가면 1문 자당 3바이트로 변경된다.

ex. "자바" -> 3 + 3 =6 바이트로 변경된다.

 

- byte []에서 String으로 변경

byte b[] = {65, 66 ,67};
String s1 = new String(b, StandardCharsets.UTF_8); // "ABC"

 

 


출처: 자바의 정석(남궁 성 저), 멀티캠퍼스

 

Java의 정석:최신 Java 8.0 포함, 도우출판자바의 정석 구매하기 이것이 자바다:신용권의 Java 프로그래밍 정복, 한빛미디어이것이 자바다 구매하기

이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

댓글