티스토리 뷰

Network

<TCP 통신 구현> Socket

춘햄 2021. 4. 1. 19:53

1. Socket: 네트워크에 연결된 컴퓨터 간에 데이터를 주고 받을 때 사용하는 도구.

 

하드웨어적인 의미로 소켓이라는 용어를 사용하기도 하지만 여기서는 소프트웨어적인 개발 도구를 뜻한다.

자바에서는 TCP 소켓 통신을 위해 Socket Class와 ServerSocket Class를, UDP 통신을 위해서는 DatagramPacket Class와 DatagramSocket Class를 제공한다.

 

 1) Socket의 연결과 전송 과정:

 

 2) Socket Constructual:

Socket(InetAddress address, int port) 주어진 IP address와 포트를 가지고 소켓 생성
Socket(String host, int port) 주어진 host와 포트를 가지고 소켓 생성

 - Socket 객체 생성 시 두 가지 예외 발생 가능성이 있다

  1. Host를 찾을 수 없거나 Port가 열려 있지 않은 경우 UnknownHostException 예외 발생

  2. Network 실패나 방화벽 때문에 서버에 접근할 수 없을 때 IOException 예외 발생

 

 3) Socket의 주요 메서드:

InetAddress getInetAddress() InetAddress로 IP주소 반환
InputStream getInputStream() 입력 스트림 반환
InetAddress getLocalAddress() 로컬 주소 반환
int getPort() 포트 반환
boolean isClosed() 소켓이 닫혀있으면 true, 열려있으면 false 반환
boolean isConnected() 소켓이 연결되어 있으면 true, 비 연결이면 false 반환
void setSoTimeout(int itmeout) 소켓 객체의 timeout시간을 설정
void close() 객체 닫기

2. ServerSocket Class: ServerSocket 클래스는 TCP 서버 소켓을 의미한다

 

클라이언트의 TCP연결을 받기 위해서 ServerSocket 클래스의 객체를 생성해야 한다. 객체가 생성되면 대기상태가 되어 클라이언트의 TCP 연결 요청이 있을 때까지 기다리게 된다.

클라이언트의 연결 요청이 들어오면 TCP 소켓을 반환하고 이를 통해 클라이언트와 통신할 수 있게 된다.

 

 1) ServerSocket Structual:

ServerSocket(int port) 주어진 포트로 서버소켓 생성
ServerSocket(int port, int backlog) 주어진 포트로 서버소켓을 생성하고 연결 대기 큐의 크기를 지정

 

 2) ServerSocket 주요 메서드:

Socket accept() 클라이언트의 연결 요청이 들어올 때까지 블로킹된 채로 기다리며 연결 수립이 되면 클라이언트와 통신 가능한 Socket 객체를 반환
void close() ServerSocket 닫기
int getLocalPort() 청취하고 있는 Port 번호 반환
int getSoTimeout() accept() 메소드가 유효할 수 있는 시간을 반환. 0이면 무한대
boolean isClosed() 객체가 닫혀있는지 여부 반환
void setSoTimeout(int timeout) accept() 메소드가 유효할 수 있는 시간을 설정

간단한 예제로 Socket을 생성하고 연결을 어떤 식으로 진행하는 지 알아보자.

우선, 아무 기능 없이 그저 연결만 하는 "클라이언트 - 서버"를 만들어 보겠다.

 


《간단한 Socket 연결 예제》

 

○ServerSide.java

package com.choonham;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;

public class ServerSide {

	public static void main(String args[]) {
		ServerSocket serverSocket = null;

		try {
			serverSocket = new ServerSocket(7777); // Windows가 안쓸만한 포트 번호로 Socket 생성
			SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss]"); // 시간 포맷 선언
			Date d = new Date();
			String time = f.format(d); // Date에 선언한 포맷 적용

			System.out.println(time + "서버가 준비되었습니다.");
		} catch (IOException e) {
			System.out.println(e.getMessage());
		}

		// 서버는 빨간불에도 멈추지 않아 Boy~
		while (true) {
			try {
				System.out.println("연결 요청을 기다리는 중...");
				Socket socket = serverSocket.accept(); // 클라이언트의 연결 요청을 받아 Socket 객체 return. 즉, socket 에 요청이 들어와 있는 지
														// 지속적으로 확인함

				SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss]"); // 시간 포맷 선언
				Date d = new Date();
				String time = f.format(d); // Date에 선언한 포맷 적용

				System.out.println(time + "클라이언트(" + socket.getInetAddress() + ") 접속");

				/* Output Stream을 확인해보자 (Server쪽은 가지고 있는 데이터를 내보내므로 Out!!) */
				OutputStream out = socket.getOutputStream();
				DataOutputStream dos = new DataOutputStream(out);
				dos.writeUTF("서버로부터 메시지가 전송되었습니다. ");

				d = new Date();
				time = f.format(d);

				System.out.println(time + "메시지가 전송되었습니다.");

				// 문은 항상 닫는 습관을 들이자. last open first close
				dos.close();
				out.close();
				socket.close();

			} catch (IOException e) {
				System.out.println(e.getMessage());
			}
		}

	} // main END

} // server Class END

○ClientSide.java

package com.choonham;

import java.io.DataInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.Socket;

public class ClientSide {

	public static void main(String args[]) {
		try {
			String serverIP = "127.0.0.1"; // 너무나 유명한 LocalHost
			Socket socket = new Socket(serverIP, 7777); // 현재 존재하는 7777 port socket에 요청을 보낸다.

			System.out.println("서버의 연결 중.... (연결 중인 서버: " + serverIP + ")");

			InputStream in = socket.getInputStream();
			DataInputStream dis = new DataInputStream(in);

			System.out.println("SERVER MSG: " + dis.readUTF());
			System.out.println("연결 종료");

			dis.close();
			in.close();
			socket.close();

		} catch (IOException e) {
			System.out.println(e.getMessage());
		}
	}

}

위와 같이 서버와 클라이언트를 각각 간단하게 구성하여 실행시키면, 

ServerSide
Client Side

이렇게 Client에서 요청이 들어왔을 때까지 대기하다 요청이 들어오면 연결을 수행하고 종료가 되는 것을 볼 수 있다. 

명심할 점은, 데이터를 내보내는 쪽(socket에 데이터를 보내는 쪽)은 OutputStream을 선언한 뒤, write해야한다는 것이고, 데이터를 받는 쪽(socket내에 데이터를 가져오는 쪽)은 반대로 InputStream을 선언한 뒤, 이를 read해야한다는 점이다. (이 개념이 잘 이해가 안되는 바람에 조금 고생했다...)

 

그럼 조금 더 들어가서, 간단한 서버 - 클라이언트 간 채팅 또한 구현할 수 있다.


《Server - Client 간 Socket 채팅 예제

 

○Sender.java

package com.choonham;

import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Scanner;

public class Sender extends Thread {

	Socket socket;
	DataOutputStream out; // Sender는 데이터를 내보내야하기 때문에 outputStream
	String name;

	public Sender() {
		// TODO Auto-generated constructor stub
	}

	public Sender(Socket socket) { // 데이터를 보낼 socket을 인자로 받아서...
		this.socket = socket;
		try {
			out = new DataOutputStream(socket.getOutputStream()); // Socket의 Output Stream 내나!

			name = "=> [" + socket.getInetAddress() + "]";
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void run() { // 콘솔 입력 창을 지속적으로 스캔하기 위한 Thread
		Scanner scanner = new Scanner(System.in);
		while (out != null) {
			try {
				SimpleDateFormat f = new SimpleDateFormat("[hh:mm:ss]"); // 시간 포맷 선언
				Date d = new Date();
				String time = f.format(d); // Date에 선언한 포맷 적용

				out.writeUTF(time + name + scanner.nextLine());
				// Socket에 데이터를 보내기 위해선 OutputStream을 선언해놓고 위와 같이 write 해줘야 한다.
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

○Receiver.java

package com.choonham;

import java.io.DataInputStream;
import java.io.IOException;
import java.net.Socket;

public class Receiver extends Thread {

	Socket socket;
	DataInputStream in;

	public Receiver() {
		// TODO Auto-generated constructor stub
	}

	public Receiver(Socket socket) {
		this.socket = socket;
		try {
			in = new DataInputStream(socket.getInputStream()); // 데이터를 받는 쪽은 항상 InputStream을 사용한다.

		} catch (IOException e) {
			e.printStackTrace();
		}

	}

	public void run() {
		while (in != null) {
			try {
				System.out.println(in.readUTF());
				// socket에 저장된 데이터를 읽기 위해서는 read를 사용해야한다.
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

}

dsad우선 위와 같이 데이터를 Socket에 쓸 Sender.java와 Socket에 있는 데이터를 읽어들일 Receiver.java를 만들어주고, 각각의 클래스는 Thread를 사용하여

1. Sender.run() -> 콘솔창의 입력을 받아 Socket에 write()

2. Receiver.run() -> Socket이 가지고 있는 데이터를 read()

이렇게 두 가지 기능을 수행한다.

 

이후 Sender와 Receive를 이용할 ServerSide, ClientSide를 생성해주면 끝이다.

 

○UDPSender.java

package com.choonham;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;

/* 서버 역할 담당 */
public class UDPSender {

	public static void main(String args[]) {
		ServerSocket serverSocket = null;
		Socket socket = null;

		try {
			serverSocket = new ServerSocket(5555); // Windows가 안쓸만한 포트 번호로 Socket 생성
			System.out.println("서버가 준비되었습니다.  <==== 여기는 서버입니다.");
			socket = serverSocket.accept(); // 클라이언트의 연결 요청을 받아 Socket 객체 return. 즉, socket 에 요청이 들어와 있는 지 지속적으로 확인함

			// socket 받아라~
			Sender sender = new Sender(socket);
			Receiver receiver = new Receiver(socket);

			sender.start();
			receiver.start();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

 

○UDPReceiver.java

package com.choonham;

import java.io.IOException;
import java.net.Socket;

/*클라이언트 역할 담당*/
public class UDPReceiver {

	public static void main(String args[]) {
		try {
			String serverIP = "127.0.0.1";

			Socket socket = new Socket(serverIP, 5555);
			System.out.println("클라이언트가 서버에 연결되었습니다. <=== 여기는 클라이언트 입니다.");

			Sender sender = new Sender(socket);
			Receiver receiver = new Receiver(socket);

			sender.start();
			receiver.start();

		} catch (IOException e) {
			e.printStackTrace();
		}
	}

}

이 2개의 코드는 Sender와 Receiver를 사용하는 부분을 제외하고는 이전 예제와 동일하므로 따로 설명하지는 않겠다.

4개의 클래스를 모두 구현하여 Server와 Client를 실행시켜보면...

 

Socket 연결이 잘 된걸 확인할 수 있다.

 

Socket 연결은 네트워크를 다루기 이전에 반드시 숙지를 해야하므로, 두번, 세번 같은 예제를 반복해보며 개념을 이해하는 것이 중요하다.

 

 

끝!!

Comments