기타/JAVA

[JAVA Network] 채팅 서버/클라이언트

푸쿠이 2018. 12. 3. 10:47

책 '자바 네트워크 프로그래밍' 보고 정리했습니다.


 

 

- 서버

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class ChatServer {
	public ChatServer() {
		System.out.println("Chat Server started");
		try {
			ServerSocketChannel serverSocketChannel = ServerSocketChannel.open(); // 서버 소켓 채널 열기
			ServerSocket socket = serverSocketChannel.socket(); // 내부 소켓 얻기
			socket.bind(new InetSocketAddress(5000)); // 포트번호로 바인딩
			
			boolean running = true;
			while (running) {
				System.out.println("Waiting for request ..."); // 요청 기다리기
				SocketChannel socketChannel = serverSocketChannel.accept(); // 블로킹, 소켓에 대한 접속을 받아들임 
				System.out.println("Connected to Client"); // 연결 완료
				String message;
				Scanner scanner = new Scanner(System.in);
				while (true) {
					System.out.println("> ");
					message = scanner.nextLine(); // 메세지 입력받기
					if (message.equalsIgnoreCase("quit")) { // 대소문자 구분없이 quit 입력 시 종료하기
						HelperMethods.sendMessage(socketChannel, "Server terminating"); // 종료
						running = false;
						break;
					} else {
						HelperMethods.sendMessage(socketChannel, message); // 메세지 보내기
						
						System.out.println("Waiting for message from client ...");
                                                // 메세지 받을 때까지 기다리기, 받으면 출력
						System.out.println("Message: " + HelperMethods.receiveMessage(socketChannel));
					}
				}
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		new ChatServer();
	}
}

- 클라이언트

import java.io.IOException;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.channels.SocketChannel;
import java.util.Scanner;

public class ChatClient {
	public ChatClient(){
		SocketAddress address = new InetSocketAddress("127.0.0.1", 5000); // 소켓 생성
		try (SocketChannel socketChannel = SocketChannel.open(address)){ // 접속하기
			System.out.println("Connected to Chat Server"); // 연결이 됨
			String message;
			Scanner scanner = new Scanner(System.in);
			while(true){
				System.out.println("Waiting for message from the server ..."); // 하나를 받을 때까지 기다리기
				System.out.println("Message: " + HelperMethods.receiveMessage(socketChannel)); // 받을 때까지 블로킹, 받으면 출력
				System.out.print("> ");
				message = scanner.nextLine(); // 입력하기
				if(message.equalsIgnoreCase("quit")){ // 대소문자 구분 없이 quit 입력 시 종료
					HelperMethods.sendMessage(socketChannel, message);
					break;
				}
				HelperMethods.sendMessage(socketChannel, message); // 보내기
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		new ChatClient();
	}
}

- HelperMethods

서버와 클라이언트 간의 메세지를 주고 받을 때, 고정 길이 or 가변 길이 메소드를 실행하는 HelperMethods 입니다.

 

고정 길이 메소드를 사용하면, message 가 정해놓은 고정 길이를 초과할 때, 오류가 발생합니다.

가변 길이 메소드를 사용하면, 이러한 문제를 해결할 수 있습니다. 

import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SocketChannel;

public class HelperMethods {
	
	// buffer.flip() 은 position(현재위치)를 limit(어디까지 사용할 지)로 설정하고, position을 0으로 바꾼다.
	// 그래서 처음부터 읽으려고 할 때, 사용한다.
	
	// 고정 길이 메소드
	public static void sendFixedLengthMessage(SocketChannel socketChannel, String message){
		try {
			ByteBuffer buffer = ByteBuffer.allocate(64); // 버퍼 크기를 64로 지정한다.
			buffer.put(message.getBytes()); 			 // 메세지 길이만큼 저장한다.
			buffer.flip(); 								 // 버퍼를 읽을 준비를 한다.
			while(buffer.hasRemaining()){ 	 // 읽을 것이 있으면,
				socketChannel.write(buffer); // 쓴다.
			}
			System.out.println("Sent: " + message);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static String receiveFixedLengthMessage(SocketChannel socketChannel){
		String message = "";
		try {
			ByteBuffer byteBuffer = ByteBuffer.allocate(64); // 버퍼 크기를 64로 지정한다.
			socketChannel.read(byteBuffer); 				 // 읽는다.
			byteBuffer.flip(); 								 // 버퍼를 읽을 준비를 한다.
			while(byteBuffer.hasRemaining()){ 	 	// 읽을 것이 있으면,
				message += (char) byteBuffer.get(); // 하나씩 추가해준다.
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
		return message; // 하나하나씩 모아진 메세지를 return 해준다.
	}
	
	// 가변 길이 메시지 처리
	public static void sendMessage(SocketChannel socketChannel, String message){
		try {
			ByteBuffer buffer = ByteBuffer.allocate(message.length() + 1); // 종료 문자를 넣기 위해 +1 을 한다.
			buffer.put(message.getBytes()); 							   // 메세지를 넣는다.
			buffer.put((byte)0x00); 									   // 메세지 끝에 종료 문자를 넣는다.
			buffer.flip(); 												   // 버퍼를 읽을 준비를 한다.
			while(buffer.hasRemaining()){ 	 // 읽을 것이 있으면
				socketChannel.write(buffer); // 쓴다.
			}
			System.out.println("Sent: " + message);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	public static String receiveMessage(SocketChannel socketChannel){
		try {
			ByteBuffer byteBuffer = ByteBuffer.allocate(16); // 버퍼 크기를 16 으로 지정한다.
			String message = "";
			while(socketChannel.read(byteBuffer) > 0){ // 더 읽을 것이 있다면, (16씩 계속 읽는 중)
				char byteRead = 0x00; // 종료 문자를 설정한다.
				byteBuffer.flip(); 	  // 버퍼를 읽을 준비를 한다.
				
				while(byteBuffer.hasRemaining()){ 	   // 읽어온 16 중에서 읽을 것이 있다면,
					byteRead = (char)byteBuffer.get(); // 하나씩 받는다.
					if(byteRead == 0x00){ // 종료 문자라면,  while을 탈출한다.
						break;
					}
					message += byteRead;  // 종료 문자가 아니면, message 에 추가한다.
				}
				
				if(byteRead == 0x00){ // 종료 문자라면 while을 탈출한다.
					break;
				}
				byteBuffer.clear(); // 버퍼 clear.
			}
			return message; // 하나하나씩 모아진 message를 return 한다.
		} catch (IOException e) {
			e.printStackTrace();
		}
		return "";
	}
}