본문 바로가기
→ My Meta+IT/JAVA Source

Multi-threaded chatting Application in Java

by DigitalJobs 2022. 12. 6.

자바 네트워크 프로그램을 구현할 때 멀티 스레드는 필수라고 생각합니다. Java 1.8 이후 멀티스레드를 구현하기 위한 방법이 스레드 풀을 형성하여 관리하는 방법도 있지만, 스레드에 입문하시는 분들께는 채팅 프로그램으로 간단히 서버와 클라이언트 코드를 이용해 소켓 프로그래밍에 익숙해지시길 바랍니다.

 

우선 서버 클래스 소스 코드입니다.

import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;

public class TcpIpMulChatServer {
	static int i = 0;
	HashMap clients;
	String[] users = new String[100];

	TcpIpMulChatServer() {
		clients = new HashMap();
		Collections.synchronizedMap(clients);
	}

	public void start() {
		ServerSocket serverSocket = null;
		Socket socket = null;

		try {
			serverSocket = new ServerSocket(7777);
			System.out.println("서버가 시작되었습니다.");

			while (true) {
				socket = serverSocket.accept();
				System.out.println("[" + socket.getInetAddress() + ":" + socket.getPort() + "]" + "에서 접속하였습니다.");
				ServerReceiver thread = new ServerReceiver(socket);
				thread.start();
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	void sendToAll(String msg) {
		Iterator it = clients.keySet().iterator();

		while (it.hasNext()) {
			try {
				DataOutputStream out = (DataOutputStream) clients.get(it.next());
				out.writeUTF(msg);
			} catch (IOException e) {

			}
		}
	}

	public static void main(String[] args) {
		new TcpIpMulChatServer().start();
	}

	class ServerReceiver extends Thread {

		Socket socket;
		DataInputStream in;
		DataOutputStream out;

		ServerReceiver(Socket socket) {
			this.socket = socket;
			try {
				in = new DataInputStream(socket.getInputStream());
				out = new DataOutputStream(socket.getOutputStream());
			} catch (IOException e) {

			}
		}

		public void run() {
			String name = "";
			try {
				name = in.readUTF();
				users[i++] = name;
				sendToAll(name + "님이 들어오셨습니다.");
				clients.put(name, out);
				System.out.println("현재 서버접속자 수는 " + clients.size() + "입니다.");
				for (int i1 = 0; i1 < users.length; i1++) {
					if (users[i1] != null)
						sendToAll(users[i1]);
				}
				sendToAll("접속 인원 : " + String.valueOf(clients.size() + "명"));
				while (in != null) {
					sendToAll(in.readUTF());

				}
			} catch (IOException e) {

			} finally {
				sendToAll(name + "님이 나가셨습니다.");
				for (int i = 0; i < users.length; i++) {
					if (users[i] != null) {
						if (users[i].equals(name)) {
							for (int j = i; j < users.length; j++) {
								if (users[j + 1] == null) {
									users[j] = null;
									break;
								} else {
									users[j] = users[j + 1];
								}
							}
						}
					}
				}
				System.out.println("접속자 닉네임");
				for (int i1 = 0; i1 < users.length; i1++) {
					if (users[i1] != null)
						sendToAll(users[i1]);
				}
				clients.remove(name);
				sendToAll("접속 인원 : " + String.valueOf(clients.size() + "명"));
				System.out.println("[" + socket.getInetAddress() + ":" + socket.getPort() + "]" + "에서 접속을 종료하였습니다.");
				System.out.println("현재 채팅방 인원은 " + clients.size() + "명 입니다.");
			}
		}
	}
}

멀티쓰레드를 공부하다 보니 서버 클래스에서는 생성자에서 HashMap()을 사용할 것이며, 이것은 synchronizedMap()을 사용하여 동기화를 시켜주는 것이 중요합니다. 이것은 멀티스레드를 사용하실대 각 스레드와의 무결성을 보장해 줄 수 있습니다.

TcpIpMulChatServer() {

          clients = new HashMap();

          Collections.synchronizedMap(clients);

}

 

서버 소스코드의 main()이 포함된 클래스 TcpIpMulChatServer부터 소스를 따라가 보시길 바랍니다.

클라이언트 클래스 소스코드는 아래와 같습니다.

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import java.net.*;
import java.text.SimpleDateFormat;
import java.util.*;

import javax.swing.JOptionPane;

public class TcpIpMulChatClient extends Frame {
	static Scanner scanner = new Scanner(System.in);
	static boolean sendName = true;
	Panel connectP = new Panel();
	Panel chatP = new Panel();

	static TextArea chatBoard = new TextArea();
	static TextArea conPList = new TextArea();
	static TextField inChat = new TextField();
	static Button enterBtn = new Button("send");

	TcpIpMulChatClient(String title) {
		super(title);
		addWindowListener(new WindowAdapter() {
			public void windowClosing(WindowEvent we) {
				System.exit(0);
			}
		});
		chatP.setSize(400, 30);
		chatP.setBackground(Color.black);
		chatP.setLayout(null);
		inChat.setFont(new Font("Dialog", Font.PLAIN, 18));
		inChat.setSize(385, 30);
		enterBtn.setBounds(385, 0, 100, 30);
		enterBtn.setBackground(Color.yellow);
		chatP.add(inChat);
		chatP.add(enterBtn);
		conPList.setSize(100, 500);
		connectP.setSize(100, 500);
		connectP.add(conPList);
		connectP.setLayout(null);
		chatBoard.setFont(new Font("Dialog", Font.PLAIN, 15));
		add(chatBoard, "Center");
		add(chatP, "South");
		add(connectP, "East");
		setSize(500, 500);
		setVisible(true);
	}

	public static void main(String[] args) {
		String name = JOptionPane.showInputDialog("이름을 입력하세요.");
		chatBoard.append(name + "님으로 채팅에 참여되었습니다.\n");
		try {
			String serverIp = "127.0.0.1";
			// 소켓을 생성하여 연결요청
			Socket socket = new Socket(serverIp, 7777);
			new TcpIpMulChatClient(name);
			Thread sender = new Thread(new ClientSender(socket, name));
			Thread receiver = new Thread(new ClientReceiver(socket, name));
			inChat.addActionListener(new BtnEvent(enterBtn, sender, socket, name));
			enterBtn.addActionListener(new BtnEvent(enterBtn, sender, socket, name));
			sender.start();
			receiver.start();
		} catch (ConnectException ce) {
			ce.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	static class BtnEvent implements ActionListener {
		TextField tf;
		Button b;
		Socket socket;
		String name, sendText = "";
		Thread sender;

		BtnEvent(TextField tf, Thread sender, Socket socket, String name) {
			this.name = name;
			this.socket = socket;
			this.tf = tf;
			this.sender = sender;
		}

		BtnEvent(Button b, Thread sender, Socket socket, String name) {
			this.name = name;
			this.socket = socket;
			this.b = b;
			this.sender = sender;
		}

		public void actionPerformed(ActionEvent e) {
			Thread sender = new Thread(new ClientSender(socket, name));
			sender.start();
			inChat.setText("");
		}

	}

	static class ClientSender extends Thread {
		Socket socket;
		DataOutputStream out;
		String name, sendText = inChat.getText();

		ClientSender(Socket socket, String name) {
			this.socket = socket;
			try {
				out = new DataOutputStream(socket.getOutputStream());
				this.name = name;
			} catch (Exception e) {

			}
		}

		public void run() {
			try {
				if (sendName) {
					out.writeUTF(name);
					sendName = false;
				} else {
					if (out != null) {
						out.writeUTF(getTime() + " [" + name + "] " + sendText);
					}
				}
			} catch (IOException ie) {
			}
		}
	}

	static class ClientReceiver extends Thread {
		Socket socket;
		DataInputStream in;
		String name, reText, users="접속자\n";

		ClientReceiver(Socket socket, String name) {
			this.socket = socket;
			this.name = name;
			try {
				in = new DataInputStream(socket.getInputStream());
			} catch (IOException e) {

			}
		}

		public void run() {
			while (in != null) {
				try {
					reText = in.readUTF();
					if (!reText.contains(".")) {
						if(reText!=null) {
							users += reText + "\n";
							conPList.setText(users);
						}
					} else {
						users = "접속자\n";
						chatBoard.append(reText + "\n");
					}
				} catch (IOException e) {

				}
			}
		}
	}

	static String getTime() {
		String name = Thread.currentThread().getName();
		SimpleDateFormat f = new SimpleDateFormat("hh:mm:ss.");
		return f.format(new Date());
	}
}

클라이언트 클래스도 main()에서 시작해서 다른 메소드들을 호출하여 사용하는 방식입니다.

 

              Thread sender = new Thread(new ClientSender(socket, name));
              Thread receiver = new Thread(new ClientReceiver(socket, name));

다만 멀티 쓰레드 채팅을 구현하려다 보니 클래스에서도 메시지를 보내고 받기 위한 소켓을 스레드로 구현하였고, getTime() 메서드 안에서도 시간의 흐름도 스레드로 구현되어있는 것을 확인할 수 있습니다.

 

 

댓글