Java

1: N 소켓 양방향 통신

H_u 2024. 5. 31. 11:59
728x90
반응형
SMALL
1:N 소켓 양방향 통신에서는 하나의 서버가 여러 클라이언트와 동시에 양방향 통신을 할 수 있습니다. 이는 채팅 애플리케이션, 멀티플레이어 게임 서버 등에서 흔히 사용됩니다.

필요 개념

  1. 서버와 클라이언트 소켓:
    • 서버는 하나의 ServerSocket을 통해 여러 클라이언트의 연결 요청을 기다립니다.
    • 클라이언트는 각각의 Socket을 통해 서버에 연결을 요청하고, 연결된 후 서버와 통신합니다.
  2. 멀티스레딩:
    • 서버는 각 클라이언트와의 통신을 별도의 스레드에서 처리합니다. 이를 통해 여러 클라이언트와 동시에 통신할 수 있습니다.
    • 각 클라이언트는 서버와의 통신을 처리하는 자체 스레드를 가집니다.
  3. 동기화 및 자원 관리:
    • 여러 스레드가 동시에 데이터를 읽고 쓸 수 있으므로, 데이터의 일관성을 유지하기 위한 동기화가 필요합니다.
    • 서버는 연결된 클라이언트 소켓을 관리하고, 클라이언트가 연결을 끊을 때 자원을 적절히 해제해야 합니다.
  4. 데이터 송수신:
    • 서버와 클라이언트는 서로 데이터를 주고받을 수 있어야 합니다. 이를 위해 입력 스트림과 출력 스트림을 사용합니다.

1:N 소켓 양방향 통신의 개념을 시각적으로 표현

+---------------------+ +---------------------+

| 서버 | | 클라이언트 1 |

| +---------------+ | | +---------------+ |

| | ServerSocket | | | | Socket | |

| +-------+-------+ | | +-------+-------+ |

| | | | | |

| v | | v |

| +-------+-------+ | | +-------+-------+ |

| | Socket[1] | |<--------->| | Network | |

| +-------+-------+ | | +---------------+ |

| | Socket[2] | | +---------------------+

| +-------+-------+ |

| | Socket[3] | | +---------------------+

| +-------+-------+ | | 클라이언트 2 |

| ... | | +---------------+ |

| +-------+-------+ | | | Socket | |

| | Socket[N] | |<--------->| +-------+-------+ |

| +---------------+ | | | |

+--------------------+ | v |

| +-------+-------+ |

| | Network | |

| +---------------+ |

+---------------------+

 

1단계 - MultiClient 처리 - 단 브로드캐싱은 안되는 상황 ← 서버측 코드 입니다.

package ch06;

 

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.ServerSocket;

import java.net.Socket;

import java.util.Vector;

 

public class MultiClientServer {

 

private static final int PORT = 5000;

// 하나의 변수에 자원을 통으로 관리하기 기법 --> 자료구조

// 자료구조 ---> 코드 단일, 멀티 ---> 멀티 스레드 --> 자료 구조 ??

// 객체 배열 <-- Vector<> : 멀티 스레드에 안정적이다.

private static Vector<PrintWriter> clientWriters = new Vector<>();

//private static Set<PrintWriter> clientWriters = ConcurrentHashMap.newKeySet(); // 스레드 안전한 클라이언트 출력 스트림 집합

 

public static void main(String[] args) {

System.out.println("Server started....");

 

try (ServerSocket serverSocket = new ServerSocket(PORT)){

while(true) {

// 1. serverSocket.accept() 호출하면 블록킹 상태가 된다. 멈춰있음

// 2. 클라이언트가 연결 요청하면 새로운 소켓 객체 생성이 된다.

// 3. 새로운 스레드를 만들어 처리 ... (클라이언트가 데이터를 주고 받기 위한 스레드)

// 4. 새로운 클라이언트가 접속 하기 까지 다시 대기 유지(반복)

Socket socket = serverSocket.accept();

// 새로운 클라이언트가 연결 되면 새로운 스레가 생성된다.

new ClientHandler(socket).start();

}

} catch (Exception e) {

 

}

 

} // end of main

 

// 정적 내부 클래스 설계

private static class ClientHandler extends Thread {

 

private Socket socket;

private PrintWriter out;

private BufferedReader in;

 

public ClientHandler(Socket socket) {

this.socket = socket;

}

 

// 스레드 start() 호출시 동작 되는 메서드 - 약속

@Override

public void run() {

 

try {

in = new BufferedReader(new InputStreamReader(socket.getInputStream()));

out = new PrintWriter(socket.getOutputStream(), true);

 

// 여기서 중요 ! - 서버가 관리하는 자료구조에 자원 저장(클,연결된 소켓->outStream)

clientWriters.add(out);

String message;

while( (message = in.readLine() ) != null ) {

System.out.println("Received : " + message);

broadcastMessage(message);

}

} catch (Exception e) {

//e.printStackTrace();

} finally {

try {

socket.close();

System.out.println("...... 클라이언트 연결 해제 ....... ");

} catch (IOException e) {

//e.printStackTrace();

}

}

}

} // end of ClientHandler

 

// 모든 클라이언트에게 메시지 보내기- 브로드캐스트

private static void broadcastMessage(String message) {

for(PrintWriter writer : clientWriters) {

writer.println(message);

}

}

}

 

Vector 클래스는 자바의 java.util 패키지에 포함된 동기화된 리스트 구현체입니다. Vector는 동기화된 메서드를 제공하여 멀티스레드 환경에서 안전하게 사용할 수 있습니다. 그러나 이러한 동기화 메서드는 성능에 영향을 미칠 수 있습니다.

ConcurrentHashMap vs HashMap vs Hashtable

  1. HashMap
    • 비동기화된 맵 구현으로, 단일 스레드 환경에서 사용됩니다.
    • 스레드 안전하지 않기 때문에 멀티스레드 환경에서는 사용하면 안 됩니다.
  2. Hashtable
    • 동기화된 맵 구현으로, 모든 메서드가 동기화되어 있습니다.
    • 동기화 메서드 사용으로 인해 성능 저하가 발생할 수 있습니다.
  3. ConcurrentHashMap
    • 동시성 제어가 추가된 고성능 맵 구현입니다.
    • 내부적으로 세분화된 잠금을 사용하여 높은 동시성을 제공합니다.
    • 멀티스레드 환경에서 가장 적합한 선택입니다.

사용법 확인

import java.util.concurrent.ConcurrentHashMap;

 

public class ConcurrentHashMapExample {

public static void main(String[] args) {

// ConcurrentHashMap 생성

ConcurrentHashMap<String, Integer> map = new ConcurrentHashMap<>();

 

// 키-값 쌍 추가

map.put("one", 1);

map.put("two", 2);

map.put("three", 3);

 

// 값 가져오기

System.out.println("Value for 'one': " + map.get("one"));

System.out.println("Value for 'two': " + map.get("two"));

 

// 키 확인

System.out.println("Map contains key 'three': " + map.containsKey("three"));

 

// 값 제거

map.remove("two");

System.out.println("Map contains key 'two' after removal: " + map.containsKey("two"));

 

// 모든 키 출력

for (String key : map.keySet()) {

System.out.println("Key: " + key + ", Value: " + map.get(key));

}

}

}

 

  • ConcurrentHashMap.newKeySet()은 내부적으로 ConcurrentHashMap을 사용하여 스레드 안전한 Set 을 생성합니다.
  • Set 은 여러 스레드가 동시에 접근하거나 수정할 수 있는 환경에서 안전하게 사용할 수 있습니다.
  • Set 의 모든 수정 연산은 내부적으로 ConcurrentHashMap의 동시성 제어 메커니즘을 사용하여 높은 성능과 스레드 안전성을 제공합니다.

Set<PrintWriter> clientWriters = ConcurrentHashMap.newKeySet();

 

일반적인 Set 자료 구조를 사용하는 것이 아니라, ConcurrentHashMap 의 특성을 지닌 스레드 안전한 Set 을 만드는 개념입니다. 이를 통해 ConcurrentHashMap의 내부 구조와 동시성 제어 메커니즘을 활용하여 높은 성능과 스레드 안전성을 갖춘 Set을 생성할 수 있습니다.

 

 

클라이언트 만들기

package ch06;

 

import java.io.BufferedReader;

import java.io.IOException;

import java.io.InputStreamReader;

import java.io.PrintWriter;

import java.net.Socket;

 

public abstract class AbstractClient {

 

private String name;

private Socket socket;

private PrintWriter socketWriter;

private BufferedReader socketReader;

private BufferedReader keyboardReader;

 

public AbstractClient(String name) {

this.name = name;

}

 

public final void fun() {

try {

connectToServer();

setupStreams();

startService(); // join() 걸어둔 상태

} catch (IOException e) {

System.out.println(">>>>> 접속 종료 <<<<< ");

} finally {

cleanup();

}

 

}

 

protected abstract void connectToServer() throws IOException;

 

private void setupStreams() throws IOException {

socketReader = new BufferedReader(new InputStreamReader(socket.getInputStream()));

socketWriter = new PrintWriter(socket.getOutputStream(), true);

keyboardReader = new BufferedReader(new InputStreamReader(System.in));

}

 

private void startService() throws IOException {

 

Thread readThread = createReadThread();

Thread writeThread = createWriteThread();

 

// 스레드 시작

readThread.start();

writeThread.start();

 

// 메인 스레드 대기 처리

try {

readThread.join();

writeThread.join();

} catch (InterruptedException e) {}

 

}

 

private Thread createWriteThread() {

return new Thread(() -> {

 

try {

String msg;

while( (msg = keyboardReader.readLine()) != null) {

socketWriter.println("[ "+ name +" ] : " + msg);

}

} catch (IOException e) {

e.printStackTrace();

}

});

}

 

private Thread createReadThread() {

return new Thread(() -> {

try {

String msg;

while(( msg = socketReader.readLine()) != null ) {

System.out.println("방송 옴 : " + msg);

}

} catch (IOException e) {

e.printStackTrace();

}

});

}

 

 

private void cleanup() {

if(socket != null) {

try {

socket.close();

} catch (IOException e) {

e.printStackTrace();

}

}

}

}

728x90
반응형
SMALL

'Java' 카테고리의 다른 글

제네릭(Generic)  (0) 2024.06.07
네트워크 프로토콜이란?  (0) 2024.05.31
1:1 양방향 통신(채팅 기본 기능 구현)  (0) 2024.05.31
1:1 양방향 통신  (0) 2024.05.31
1:1 단방향 통신 (클라이언트측)  (0) 2024.05.31