1:N 소켓 양방향 통신에서는 하나의 서버가 여러 클라이언트와 동시에 양방향 통신을 할 수 있습니다. 이는 채팅 애플리케이션, 멀티플레이어 게임 서버 등에서 흔히 사용된다.
필요 개념
- 서버와 클라이언트 소켓:
- 서버는 하나의 ServerSocket을 통해 여러 클라이언트의 연결 요청을 기다립니다.
- 클라이언트는 각각의 Socket을 통해 서버에 연결을 요청하고, 연결된 후 서버와 통신합니다.
- 멀티스레딩:
- 서버는 각 클라이언트와의 통신을 별도의 스레드에서 처리합니다. 이를 통해 여러 클라이언트와 동시에 통신할 수 있습니다.
- 각 클라이언트는 서버와의 통신을 처리하는 자체 스레드를 가집니다.
- 동기화 및 자원 관리:
- 여러 스레드가 동시에 데이터를 읽고 쓸 수 있으므로, 데이터의 일관성을 유지하기 위한 동기화가 필요합니다.
- 서버는 연결된 클라이언트 소켓을 관리하고, 클라이언트가 연결을 끊을 때 자원을 적절히 해제해야 합니다.
- 데이터 송수신:
- 서버와 클라이언트는 서로 데이터를 주고받을 수 있어야 합니다. 이를 위해 입력 스트림과 출력 스트림을 사용합니다.
1:N 소켓 양방향 통신의 개념을 시각적으로 표현
1단계 - MultiClient 처리 - 단 브로드캐싱은 안되는 상황 ← 서버측 코드.
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 MulitClientServer {
private static final int PORT = 5000;
// 하나의 변수에 자원을 통으로 관리하는 기법 --> 자료구조
// 자료구조 --> 코드 단일 , 멀티 --> 멀티 스레드 --> 자료구조
// 객체 배열 <-- Vector<> : 멀티 스레드에 안정적이다
private static Vector<PrintWriter> clienWriters = new Vector<>();
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 { // 각각의 기능 확장 -> 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)
clienWriters.add(out);
String message;
while ((message = in.readLine()) != null) {
System.out.println("Received :" + message);
broadcastMessage(message);
// 약속 -> 클, 서
// : 기분으로 처리, / 기분 <-
String[] parts = message.split(":", 2);
System.out.println("parts 인덱스 갯수 : " + parts.length);
// 명령 부분을 분리
String command = parts[0];
// 데이터 부분을 분리
String data = parts.length > 1 ? parts[1] : "";
if(command.equals("msg")){
System.out.println("연결된 전체 사용자에게 MSG 발송");
broadcastMessage(message);
} else if(command.equals("BYE")) {
System.out.println("Client disconnected...");
break;
}
}// end of while
// ... finally 구문으로 빠진다
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
socket.close();
// 도전과제 !!
// 서버측에서 관리하고 있는 P.W 제거 해야 한다
// 인덱스 번호가 필요하다
// clienWriters.add() 할 떄 지정된 나의 인덱스 번호가 필
//clienWriters.remove();
System.out.println(".... 클라이언트 연결 해제 ....");
} catch (IOException e) {
e.printStackTrace();
}
}
}
} // end of ClientHanler
// 모든 클라이언트에게 메시지 보내기 - 브로드캐스트
private static void broadcastMessage(String message) {
for(PrintWriter writer : clienWriters) {
writer.println(message);
}
}
}
Vector 클래스는 자바의 java.util 패키지에 포함된 동기화된 리스트 구현체이다. Vector는 동기화된 메서드를 제공하여 멀티스레드 환경에서 안전하게 사용할 수 있습니다. 그러나 이러한 동기화 메서드는 성능에 영향을 미칠 수 있다.
ConcurrentHashMap vs HashMap vs Hashtable
- HashMap
- 비동기화된 맵 구현으로, 단일 스레드 환경에서 사용됩니다.
- 스레드 안전하지 않기 때문에 멀티스레드 환경에서는 사용하면 안 됩니다.
- Hashtable
- 동기화된 맵 구현으로, 모든 메서드가 동기화되어 있습니다.
- 동기화 메서드 사용으로 인해 성능 저하가 발생할 수 있습니다.
- 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 자료 구조를 사용하는 것이 아니라, 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();
}
}
}
}
'JAVA' 카테고리의 다른 글
(공부) 연결 (0) | 2024.06.05 |
---|---|
[JAVA] 네트워크 프로토콜 (0) | 2024.05.24 |
[JAVA] 1:1 양방향 통신(채팅 기본 기능 구현) (0) | 2024.05.24 |
[JAVA] 1:1 양방향 통신 (0) | 2024.05.24 |
[JAVA] 1:1 단방향 통신 (클라이언트측) (0) | 2024.05.24 |