JAVA – 1:N 채팅 (Socket)

1:N 채팅

Socket을 이용해서 1:N 채팅을 할 수 있는 데스크톱 어플리케이션이다.

Socket: 서버와 클라이언트의 양방향 연결이 이루어지는 양방향 통신으로, 단방향 통신인 http 통신과 달리 서버에서 클라이언트로 메시지 전송이 가능하다. (http 통신도 가능하기는 하나 클라이언트가 항상 request를 먼저 보내야 response가 가능함) 보통 채팅 시스템에 많이 사용되는 통신 기법이다.

클라이언트단과 서버단의 코드를 제공한다.
설명은 FlowChart와 주석을 참고하면 된다.


Flow Chart


Client Code

package client;

import java.net.*;
import java.io.*;
import java.util.Scanner;

//송신 스레드 
//서버에게 메시지를 보내는 역할 
class ClientSender extends Thread{
	 Socket socket;
	 DataOutputStream out;
	 String name;
	 
	  // (서버와 송신하는) 스레드 객체의 멤버 초기화 
	  ClientSender(Socket socket, String name) {
		  this.socket = socket;
		  try {
		   this.out = new DataOutputStream(socket.getOutputStream()); // 데이터 송신을 편하게 하도록 도와주는 객체 (클라이언트 -> 서버) 
		   this.name = name;
		  } 
		  catch(Exception e) {}
	  }
	  
	  @Override
	  public void run() {
		  Scanner scanner = new Scanner(System.in);
		  try {
			  	// 처음에는 name 전송 
			    if(out!=null) {
			    	out.writeUTF(name);
			    }
			    // 이후에는 다음을 반복 (사용자의 입력 대기하다가 입력 받으면 저장 -> 입력 메시지를 서버로 전송)
			    while(out!=null) { 
			    	String msg = scanner.nextLine();
			    	out.writeUTF("["+name+"]"+msg); 
			    }
		  }
		  catch(IOException e) {}
	  } 
}


//수신 스레드
//서버의 메시지를 받는 역할
class ClientReceiver extends Thread{
	  Socket socket;
	  DataInputStream in;
	  
	  // (서버와 수신하는) 스레드 객체의 멤버 초기화 
	  ClientReceiver(Socket socket) {
		  this.socket = socket;
		  try {
		    in = new DataInputStream(socket.getInputStream());  // 데이터 수신을 편하게 하도록 도와주는 객체 (서버 -> 클라이언트)
		  } 
		  catch(IOException e) {}
	  }
	  
	  @Override
	  public void run() {
		  // 반복 (수신 대기하다가 메시지 받으면 저장 -> 출력) 
		  while(in!=null) {
			  try {
			     String msg = in.readUTF();
			     System.out.println(msg);
			   }
			  catch(IOException e) {}
		  }
	  } 
}


public class client {

	 public static void main(String args[]) {	
		  try {
			  // 접속할 서버 IP 
			  String serverIp = "127.0.0.1"; 
			
			  // 소켓을 생성하여 연결을 요청한다.
			  Socket socket = new Socket(serverIp, 7777); 
			  System.out.println("서버에 연결되었습니다.");
			  
			  // 클라이언트 이름 입력하기
			  Scanner scanner = new Scanner(System.in);
			  System.out.println("회원 이름을 입력해주세요:");
			  String name = scanner.nextLine();
			  
			  // 서버와 수신/송신하는 스레드 생성
			  Thread sender = new Thread(new ClientSender(socket, name));
			  Thread receiver = new Thread(new ClientReceiver(socket));
			  
			  // 스레드 동작 
			  sender.start();
			  receiver.start();
		  }
		  catch(ConnectException ce) {
			  // 서버에 접속 실패 
			  System.out.println("서버와 연결을 실패했습니다.");
			  ce.printStackTrace();
		  }
		  catch(Exception e) {}
	 }
}


Server Code

package server;

import java.net.*;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

//수신 스레드
//클라이언트의 메시지를 받는 역할
class ServerReceiver extends Thread {
	  server s;
	  Socket socket;
	  DataInputStream in;
	  DataOutputStream out;
	  
	 // (접속한 클라이언트와 수신하는) 스레드 객체의 멤버 초기화 
	  ServerReceiver(server s, Socket socket) {
		  this.s=s;
		  this.socket = socket;
		  try {
			  in = new DataInputStream(socket.getInputStream()); // 데이터 수신을 편하게 하도록 도와주는 객체 (접속한 클라이언트 -> 서버)
			  out = new DataOutputStream(socket.getOutputStream()); // 데이터 송신을 편하게 하도록 도와주는 객체 (서버 -> 접속한 클라이언트) 
		  } 
		  catch(IOException e) {}
	  }
	  
	  @Override
	  public void run() {
		  String name = "";
		  String clients_key = "";
		  try {
			  // 처음 클라이언트가 접속한 상황 -> 대기하다가 수신된 클라이언트 이름을 받아서 clients에 저장, 현재 클라이언트 수 출력 
			  name = in.readUTF(); 
			  clients_key=name+"("+socket.getInetAddress()+")";
			  s.clients.put(clients_key, out); // 클라이언트 이름, 데이터 송신 관련 객체를 저장 
			  System.out.println("#"+name+"님이 들어오셨습니다.");
			  s.sendToAll("#"+name+"님이 들어오셨습니다.");
			  System.out.println("현재 서버접속자 수는 " + s.clients.size() + "입니다.");
			  
			  // 이후에는 다음을 반복 (수신 대기하다 메시지 받으면 저장 -> 모든 클라이언트에게 메시지 송신)
			  while(in!=null) {
				  String msg = in.readUTF();
				  s.sendToAll(msg);
			  }
		  }
		  catch(IOException e) {}
		  // try문이 종료되면 다음을 실행 ex) 클라이언트가 나가서 in객체가 제대로 동작을 안 함
		  finally {
			  s.sendToAll("#"+name+"님이 나가셨습니다.");
			  s.clients.remove(clients_key);
			  System.out.println("["+socket.getInetAddress()+":"+socket.getPort()+"]"+"에서 접속을 종료하였습니다.");
			  System.out.println("현재 서버접속자 수는 "+ s.clients.size()+"입니다.");
		  } 
	  } 
}

public class server {
	// 접속한 클라이언트 목록  
	ConcurrentHashMap clients;
	// key:클라이언트 이름+아이피, value:데이터 송신 관련 객체 (서버 -> 클라이언트)  
	 server() {
	  clients = new ConcurrentHashMap();
	 }
	
	// 서버 오픈
	 public void start() {
		 ServerSocket serverSocket = null;
		 Socket socket = null;
	
		 try {
			 // 서버 컴퓨터의 7777 포트에서 채팅 프로그램 동작 
			 serverSocket = new ServerSocket(7777);
			 System.out.println("서버가 시작되었습니다.");
			 
			 // 반복 (접속할 클라이언트 대기 -> 접속한 클라이언트에 대한 수신 스레드 생성) 
			 while(true) {
				 // 대기하다가 연결된 클라이언트가 있으면 해당 클라이언트에 대한 소켓을 생성함
				 socket = serverSocket.accept(); 
				 System.out.println("["+socket.getInetAddress() + ":"+socket.getPort()+"]"+"에서 접속하였습니다.");
				 // 해당 클라이언트의 메시지를 수신하는 스레드 생성 
				 ServerReceiver thread = new ServerReceiver(this,socket);
				 // 스레드 동작 
				 thread.start();
			 }
		 }
		 catch(Exception e) {
			 e.printStackTrace();
		 }
	 } 
	 
	 // 모든 클라이언트에게 송신 
	 void sendToAll(String msg) {
		 Iterator it = clients.keySet().iterator();
		 
		 // clients에 저장된 key값이 있으면 반복 
		 while(it.hasNext()) {
			 try {
				 // 현재 클라이언트와 송신하기 위해서 out 객체 가져오기
				 DataOutputStream out = (DataOutputStream)clients.get(it.next());
				 // 현재 클라이언트에게 송신 
				 out.writeUTF(msg);
			 }
			 catch(IOException e){}
		 } 
	 } 

	 public static void main(String args[]) {
		 // 서버 객체 생성
		 server s = new server();
		 // 서버 오픈
		 s.start();
	 }  
} 

Leave a Reply

Your email address will not be published. Required fields are marked *