소켓 프로그래밍

dev_hnbm·2023년 11월 23일
0

대덕인재개발원

목록 보기
18/30

📡 소켓 프로그래밍이란?

소켓 프로그래밍이란 네트워크 상에서 데이터 통신을 위한 프로그래밍 기술이다. 소켓은 프로세스 간 통신을 가능하게 하는 엔드 포인트를 사용하여 데이터를 주고받는 방법을 제공하며 가장 대표적인 프로토콜은 TCPUDP이다.

TCP (전송 제어 프로토콜)

  1. 연결 지향: 통신 시작 전 연결을 설정하고, 통신이 완료된 후 명시적으로 연결을 해제함으로 신뢰성이 향상되며 전송 중 발생할 수 있는 에러나 패킷 손실을 감지하고 복구할 수 있다.
  2. 신뢰성 높음: 데이터 전송시 각 패킷에 일련 번호를 부여하고, 수신측에서는 이 일련 번호를 사용하여 패킷의 순서를 확인하고 손실된 패킷을 다시 요청한다.
  3. 흐름 제어: 수신자의 처리 속도에 따라 데이터를 조절하여 과다한 데이터의 축적을 방지한다. 수신자가 처리할 수 있는 속도를 초과하는 양의 데이터를 전송하지 않도록 한다.
  4. 혼잡 제어: 네트워크 혼잡을 방지하기 위해 데이터 전송 속도를 동적으로 조절한다. 혼잡이 감지되면 송신하는 측이 전송 속도를 조절하여 네트워크 혼잡을 완화한다.

TCP 소켓 프로그래밍

서버가 먼저 실행되어 클라이언트의 연결 요청을 기다리고 있어야 한다.

  1. 소켓 생성
    클라이언트와 서버 모두 소켓 생성
    소켓이란? 통신을 위한 엔드 포인트 → 데이터를 주고받을 수 있게 해준다.
  2. 바인딩 (소켓에 주소 정보(IP, 포트번호) 할당)
    1024번 이하는 이미 정해진 포트 번호이므로 사용하지 않기
    server: 소켓을 특정 IP 주소와 포트에 바인딩
    client: 서버에 연결하기 위해 서버의 IP 주소와 포트 정보를 알아야 함
  3. 연결 대기
    server: listen() 메소드로 클라이언트의 연결 대기
    연결은 큐에 저장되며, 클라이언트의 요청에 따라 연결 수락
  4. 연결 수락
    server: accept() 메소드로 클라이언트의 연결 요청이 올 때까지 기다리다가 요청시 연결을 수락하고, 새로운 소켓을 생성하고, 클라이언트의 소켓과 연결
    생성된 새로운 소켓은 실제 데이터 통신에 사용된다.
  5. 데이터 송수신
    server, client: send(), recv() 메소드로 바이트 형태의 데이터를 주고 받음
  6. 연결 종료
    close() 메소드로 통신 완료시 소켓을 닫아 연결 종료

💻 현재 내 컴퓨터를 나타내는 방법

  1. IP 주소: cmdipconfig 쳤을 때 나오는 그거.. 192.168. 어쩌구
  2. 지정 IP 주소: 127.0.0.1은 무조건 내 컴퓨터를 의미
  3. 컴퓨터 이름: ex) DESKTOP-C9FOMVC
  4. 지정 컴퓨터 이름: localhost는 무조건 내 컴퓨터를 의미

예제1) 서버에서 클라이언트로 메시지 전송

  • ServerSocketSocket
    • ServerSocket은 서버 측에서 사용되며, 클라이언트의 연결을 수락하는 역할을 한다. 특정 포트에서 클라이언트의 연결을 수락하고 이 연결에 대한 새로운 Socket을 생성한다. 즉, ServerSocket은 클라이언트의 연결을 받아들이고 해당 클라이언트와 통신할 수 있는 Socket을 생성한다.
    • Socket은 클라이언트와 서버 간 실제 통신을 담당한다. 클라이언트는 Socket을 생성하여 서버에 연결하고, 서버는 ServerSocket을 사용하여 클라이언트의 연결을 수락한 후 해당 클라이언트와 통신하기 위해 Socket을 생성한다. Socket은 데이터를 읽고 쓰는 데 사용되며, 서버 및 클라이언트 간 양방향 통신을 지원한다.


      즉, ServerSocket은 클라이언트의 연결을 수락하고 해당 클라이언트와 통신할 수 있는 Socket을 생성
      Socket은 클라이언트와 실제 데이터 교환 담당
      두 클래스가 함께 사용되어야 서버와 클라이언트 간 양방향 통신이 이루어진다.
package ddit.practice;

import java.io.DataOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;

public class TCPServer {

	public static void main(String[] args) throws IOException {
		ServerSocket server = new ServerSocket(7777);
		Socket socket = server.accept();
		
		Scanner sc = new Scanner(System.in);
		System.out.print("메시지 >> ");
		String msg = sc.nextLine();
		
		OutputStream out = socket.getOutputStream();
		DataOutputStream dataOut = new DataOutputStream(out);
		
		dataOut.writeUTF(msg);
		
		dataOut.close(); socket.close(); server.close();
	}

}
package ddit.practice;

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

public class TCPClient {

	public static void main(String[] args) throws UnknownHostException, IOException {
		Socket socket = new Socket("localhost", 7777);
		
		InputStream in = socket.getInputStream();
		DataInputStream dataIn = new DataInputStream(in);
		
		System.out.println(dataIn.readUTF());
		
		dataIn.close(); socket.close();
	}

}



예제2) 1:1 채팅 구현하기

Server: 클라이언트의 연결을 기다리고 연결 수립시 해당 클라이언트와의 통신 관리
Client: 서버에 연결하고 서버와의 통신 관리
Sender: 메시지를 입력받아 상대에게 전송하며 서버와 클라이언트 양쪽에서 동일한 코드를 사용할 수 있음
Receiver: 상대로부터 메시지를 수신하여 출력하며 서버와 클라이언트 양쪽에서 동일한 코드를 사용할 수 있음


SenderReceiver를 스레드 클래스로 구현하는 이유
네트워크 통신이 블로킹 연산이기 때문
블로킹 연산은 작업이 완료될 때까지 해당 스레드가 대기하며, 다른 작업을 수행할 수 없는 상태이다.

package ddit.practice;

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

public class TCPChatServer {

	public static void main(String[] args) {
		try {
			ServerSocket server = new ServerSocket(7777);
			Socket socket = server.accept();
			
			// 클라이언트 접속시 클라이언트와 연결된 socket 객체를 스레드에 주입
			TCPChatSender sender = new TCPChatSender(socket);
			TCPChatReceiver receiver = new TCPChatReceiver(socket);
			
			sender.start(); receiver.start();
		} catch (IOException e) {}
		
	}

}
package ddit.practice;

import java.net.Socket;

public class TCPChatClient {

	public static void main(String[] args) {
		try {
			Socket socket = new Socket("localhost", 7777);
			
			TCPChatSender sender = new TCPChatSender(socket);
			TCPChatReceiver receiver = new TCPChatReceiver(socket);
			
			sender.start(); receiver.start();
		} catch (Exception e) {} 

	}

}
package ddit.practice;

import java.io.DataOutputStream;
import java.net.Socket;
import java.util.Scanner;

public class TCPChatSender extends Thread {
	private Socket socket;
	private DataOutputStream dataOut;
	
	private String name;
	private Scanner scan;
	
	public TCPChatSender(Socket socket) {
		this.socket = socket;
		scan = new Scanner(System.in);
		
		System.out.print("이름 >> ");
		name = scan.nextLine();
		
		try {
			dataOut = new DataOutputStream(this.socket.getOutputStream());
		} catch (Exception e) {}
	}
	
	@Override
	public void run() {
		while(dataOut != null) {
			try {
				dataOut.writeUTF(name + ": " + scan.nextLine());
			} catch (Exception e) {}
		}
	}

}
package ddit.practice;

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

public class TCPChatReceiver extends Thread {
	private Socket socket;
	private DataInputStream dataIn;
	
	public TCPChatReceiver(Socket socket) {
		this.socket = socket;
		
		try {
			dataIn = new DataInputStream(this.socket.getInputStream());
		} catch (Exception e) {}
	}
	
	@Override
	public void run() {
		while(dataIn != null) {
			try {
				System.out.println(dataIn.readUTF());
			} catch (Exception e) {}
		}
	}

}

0개의 댓글