package _client_socket.ch05;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 서버와 양방향 통신하는 클라이언트 측 코드
*/
public class MulitNClient {
private final String name; // Socket생성시 사용자 이름 넣기 위한 값이다.
// final상수인데도 오류 없이 사용가능한 이유는 아래에서 확인하자.
private Socket socket; // 소켓을 생성했을 시 변수명을 설정해주었다.
private PrintWriter writerStream; // 서버로 메시지를 전송하는 스트림
private BufferedReader readerStream; // 서버로 메시지를 읽어오는 스트림
private BufferedReader keyboardReader; // 키보드값을 읽기위한 스트림
public MulitNClient(String name){
this.name = name; // 생성자 생성시 이것 하나만으로 생성되고 name값을
// 입력해야 하기에 위 final상수에 값을 넣지 않아도 오류가 나지 않았다.
}
// 서버에 5000번 코트로 연결 처리
private void connectToServer() throw IOException{ // new Socket생성시 나는 오류를 throw IOException 던져 주었다.
socket = new Socket("localhost",5000); // Socket에 연결할 컴퓨터의 ip주소와 포트번호를 입력해주어야 연결가능하다.
System.out.println("connected to the server"); // 서버에 연결되었다고 보여주는 출력문이다.
}
// 입출력 스트림을 설정하는 처리
private void setupStreams() throws IOException{
writerStream = new PrintWriter(socket.getOutputStream(),true); // 소켓에 메세지를 보내는 스트림이다
readerStream = new BufferedReader(new InputStreamReader(socket.getInputStream())); // 소켓에서 메세지를 읽어주는 스트림이다.
keyboardReader = new BufferedReader(new InputStreamReader(System.in)); // 키보드 값을 받아 오는 메세지다.
}
// 서버로 부터 온 메세지를 실제로 읽는 행위
public void createdReadThread() {
return new Thread(() -> { // 람다식을 활용하였다.
try {
String serverMessage; // 값을 잠시 담아주기 위한 변수명 설정
while( ( serverMessage = readerStream.readLine() ) != null ){
// while문을 돌려 readerStream의 값이 들어올때마다 serverMessage에 넣어준다.
System.out.println("Server Message : " + serverMessage);
// sout은 서버로 부터 받아온 값을 내 콘솔에 보여주기 위한 출력이다.
}
} catch (Exception e) {
e.printStackTrace(); // 정확히 무엇을 캐치하는지 알려줘
}
});
}
// 키보드에서 입력을 받아 서버로 메세지를 전송하는 행위
private Thread createWriteThread() {
return new Thread(new Runnable() {
@override
public void run(){
try {
String keyboardMessage; // 값을 담아주기위한 변수
while( (keyboardMessage = keyboardReader.readLine()) != null ){
// 키보드에서 쓴값을 변수에 담아주고
WriterStream.println("[" + name + "]" + keyboardMessage);
// 키보드에서 쓴값을 출력한다.
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
// 자원을 정리하고 소켓을 닫는 행위
private void cleanup(){
try {
if (socket != null) { // 소켓에 연결된것이 없을때
socket.close(); 소켓을 닫아준다.
}
} catch (Exception e) {
e.printStackTrace();
}
}
// 읽기 /쓰기 스레드를 생성 및 스타트 처리
private void startCommuication () throws InterruptedException{
Thread readThread = createdReadThread();
Thread writerStream = createWriteThread();
readThread.start(); 실행하는 메서드
writerStream.start();실행하는 메서드
// 내가 종료 될때 까지 메인 쓰레드 종료 하지말고 기다려
readThread.join();
writeThread.join();
}
public void chartRun(){
try {
connectToServer(); // 서버에 5000번 코트로 연결 처리
setupStreams();// 입출력 스트림을 설정하는 처리 도구를 갖춤
startCommuication();// 읽기 /쓰기 스레드를 생성 및 스타트 처리 실질적 행동의 주체
} catch (IOException | InterruptedException e) { // 이 코드에 대해서 해석해줘 | 이거는 어떤 의미야?
} finally {
cleanup(); // 자원을 정리하고 소켓을 닫는 행위
}
}
// 메인 함수
public static void main(String[] args) {
System.out.println("클라이언트 프로그램 시작");
MulitNClient nClient = new MulitNClient("장승원"); // Socket을 생성
nClient.chartRun(); // 모든행위를 하게 해준다.
}
}
chap gpt 의 대답
좋아요! 당신이 제공한 MulitNClient 코드를 기반으로, 오타와 구조 문제를 교정하면서, 주석이 없는 부분의 설명, 의문을 가질 수 있는 부분에 대한 해설, 그리고 마지막에 전체 실행 흐름 요약까지 해드릴게요.
✅ 오타 및 주요 수정 사항
원래 코드문제수정
MulitNClient | 클래스 이름 오타 | MultiNClient |
throw IOException | 메서드 선언 오류 | throws IOException |
@override | 오타 | @Override |
WriterStream.println(...) | 변수명 대소문자 오타 | writerStream.println(...) |
writerStream 이름 충돌 | 변수명이 Thread 이름으로 재사용됨 | writeThread 등 다른 이름 사용 권장 |
readThread.start(); 실행하는 메서드 | 주석이 코드 라인에 섞임 | 분리 |
socket.close(); 소켓을 닫아준다. | 코드와 주석이 붙음 | 분리 필요 |
✅ 정리된 전체 코드 + 설명이 빠졌던 부분 주석 추가
package _client_socket.ch05;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.PrintWriter;
import java.net.Socket;
/**
* 서버와 양방향 통신하는 클라이언트 측 코드
*/
public class MultiNClient {
private final String name; // 사용자 이름 (final: 생성자에서 초기화하면 OK)
private Socket socket; // 서버와 연결되는 소켓
private PrintWriter writerStream; // 서버로 보내는 출력 스트림
private BufferedReader readerStream; // 서버로부터 메시지를 받는 입력 스트림
private BufferedReader keyboardReader; // 키보드 입력을 받는 입력 스트림
public MultiNClient(String name) {
this.name = name;
}
// 서버에 5000번 포트로 연결 시도
private void connectToServer() throws IOException {
socket = new Socket("localhost", 5000);
System.out.println("Connected to the server");
}
// 입출력 스트림 초기화
private void setupStreams() throws IOException {
writerStream = new PrintWriter(socket.getOutputStream(), true); // autoFlush = true
readerStream = new BufferedReader(new InputStreamReader(socket.getInputStream()));
keyboardReader = new BufferedReader(new InputStreamReader(System.in));
}
// 서버로부터 받은 메시지를 출력하는 읽기 쓰레드 생성
public Thread createdReadThread() {
return new Thread(() -> {
try {
String serverMessage;
while ((serverMessage = readerStream.readLine()) != null) {
System.out.println("Server Message: " + serverMessage);
}
} catch (Exception e) {
e.printStackTrace();
// 예: 서버 연결이 끊긴 경우 IOException, null 포인터 등
}
});
}
// 키보드 입력을 서버로 전송하는 쓰기 쓰레드 생성
private Thread createWriteThread() {
return new Thread(new Runnable() {
@Override
public void run() {
try {
String keyboardMessage;
while ((keyboardMessage = keyboardReader.readLine()) != null) {
writerStream.println("[" + name + "] " + keyboardMessage);
}
} catch (Exception e) {
e.printStackTrace();
}
}
});
}
// 소켓 및 스트림 종료
private void cleanup() {
try {
if (socket != null) {
socket.close(); // 소켓 닫기
}
} catch (Exception e) {
e.printStackTrace(); // 소켓이 이미 닫혀있을 경우 예외 발생 가능
}
}
// 읽기/쓰기 쓰레드 시작 및 동기화
private void startCommunication() throws InterruptedException {
Thread readThread = createdReadThread(); // 서버로부터 메시지를 읽는 쓰레드
Thread writeThread = createWriteThread(); // 사용자 입력을 서버로 보내는 쓰레드
readThread.start(); // 각각 비동기 쓰레드로 실행
writeThread.start();
// 메인 쓰레드는 둘이 끝날 때까지 대기 (클라이언트가 종료되지 않게)
readThread.join();
writeThread.join();
}
// 클라이언트 실행 전체 흐름을 관리
public void chatRun() {
try {
connectToServer(); // 서버에 연결
setupStreams(); // 스트림 설정
startCommunication(); // 읽기/쓰기 쓰레드 시작
} catch (IOException | InterruptedException e) {
e.printStackTrace();
// IOException: 서버 연결 또는 스트림 오류
// InterruptedException: join() 대기 중 인터럽트
} finally {
cleanup(); // 예외 발생 여부 관계없이 자원 해제
}
}
// 실행 시작 지점
public static void main(String[] args) {
System.out.println("클라이언트 프로그램 시작");
MultiNClient client = new MultiNClient("장승원"); // 이름 지정
client.chatRun(); // 클라이언트 전체 실행 흐름
}
}
❓ 질문하신 부분 설명
catch (Exception e)
❓ 정확히 무엇을 캐치하는지 알려줘
- Exception은 모든 예외의 최상위 타입입니다.
- 여기서 주로 발생 가능한 예외는:
- IOException: 스트림 읽기/쓰기 중 발생
- NullPointerException: 스트림이 null일 경우
- SocketException: 연결이 비정상적으로 종료되었을 때
- EOFException: 연결된 소켓이 강제로 끊길 때
catch (IOException | InterruptedException e)
❓ | 이건 어떤 의미야?
- Java 7부터 도입된 멀티 캐치 구문입니다.
- IOException 또는 InterruptedException이 발생하면 하나의 블록에서 처리합니다.
- 이 구문은 코드 중복을 줄여줍니다.
🔄 실행 흐름 요약
main()
└─> chatRun()
├─> connectToServer() ← 서버 연결 시도 (localhost:5000)
├─> setupStreams() ← 스트림 준비
├─> startCommunication()
│ ├─> createReadThread().start() ← 서버로부터 메시지 수신
│ ├─> createWriteThread().start() ← 키보드 입력을 서버로 전송
│ └─> join() ← 두 쓰레드 종료까지 대기
└─> cleanup() ← 소켓 정리
📌 추가 팁
- Scanner 대신 BufferedReader를 쓴 이유: 한 줄 단위로 빠르게 읽기 위함 (네트워크 기반에서 더 일반적).
- 키보드 입력을 쓰레드로 처리하는 이유: UI 입력 차단 없이 수신을 병렬로 처리하기 위함.
- readThread.join()은 현재 메인 쓰레드가 읽기 쓰레드 종료까지 기다리게 함 → 클라이언트가 강제 종료되지 않도록 유지.
728x90