프리 정보 컨텐츠

자바 GUI 네트워크 채팅프로그램 멀티쓰레드 구현코드 본문

JAVA

자바 GUI 네트워크 채팅프로그램 멀티쓰레드 구현코드

쏜스 2021. 1. 29. 11:54

자바 Socket 통신을 기반으로 멀티쓰레드와, Swing GUI를 활용해 네트워크 채팅 프로그램을 구현해보았다.

기본적인 Socket 통신이 어떻게 이루어지는지 개념정리는 아래 링크를 참고하자.

 

자바 Socket 클라이언트/서버 개념 및 통신구조

 

위의 페이지에서 설명하는 것에서 추가된 것은 쓰레드이다.

서버

서버는 클라이언트 상대용 Socket을 자바 ArrayList에 저장하고 현재 상대하고 있는 하나의 클라이언트에만

국한되는 것이 아니라 ArrayList에 보관 중인 모든 Socket을 꺼내서 글을 쓴다.

클라이언트

수시로 날아오는 메시지 처리를 위해 글을 읽는 부분을 쓰레드로 빼서 처리한다.

쓰레드를 활용하지 않으면 글을 쓰고 있을 때 상대방이 주는 메시지를 리얼타임으로 받지 못한다.

총 코드 

 

 

다섯 가지 클래스로 서버, 클라이언트, 쓰레드를 나누어서 작성한 코드를 예로 든다.

 

ChatServer 클래스와 Thread를 상속받은 ServerSocketThread를 살펴보자.

package chatting;

import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.ArrayList;
import java.util.List;

public class ChatServer {
	ServerSocket serverSocket;
	Socket socket;
	List<Thread> list;		// ServerSocketThread 객체 저장
	
	public ChatServer() {
		list = new ArrayList<Thread>();
		System.out.println("서버가 시작되었습니다.");
	}
	public void giveAndTake() {
		try {
			serverSocket = new ServerSocket(5420);		// 소켓 접속 대기
			serverSocket.setReuseAddress(true); 		// ServerSocket이 port를 바로 다시 사용한다 설정(port를 잡고있음)
			
			while(true) {
				socket = serverSocket.accept();			// accept -> 1. 소켓 접속 대기 2. 소켓 접속 허락
				ServerSocketThread thread = new ServerSocketThread(this, socket);	// this -> ChatServer 자신
				addClient(thread);		// 리스트에 쓰레드 객체 저장
				thread.start();
			}
		} catch (IOException e) {
			
			e.printStackTrace();
		}
	}
	// synchronized : 쓰레드들이 공유데이터를 함께 사용하지 못하도록 하는 것
	// 클라이언트가 입장 시 호출되며, 리스트에 클라이언트 담당 쓰레드 저장
	private synchronized void addClient(ServerSocketThread thread) {
		// 리스트에 ServerSocketThread 객체 저장
		list.add(thread);
		System.out.println("Client 1명 입장. 총 " + list.size() + "명");
	}		
	// 클라이언트가 퇴장 시 호출되며, 리스트에 클라이언트 담당 쓰레드 제거
	public synchronized void removeClient(Thread thread) {
		list.remove(thread);
		System.out.println("Client 1명 퇴장. 총 " + list.size() + "명");
	}
	// 모든 클라이언트에게 채팅 내용 전달
	public synchronized void broadCasting(String str) {
		for(int i = 0; i < list.size(); i++) {
			ServerSocketThread thread = (ServerSocketThread)list.get(i);
			thread.sendMessage(str);
		}
	}
}

ChatServer 생성자에 제네릭 타입은 Thread로써 list배열을 선언해준다.

arrayList에 접속한 thread객체를 저장하며

thread를 실행한다.

 

thread가 실행하는 run 함수는 아래 클래스인 ServerSocketThread에서 실행한다.

아래 코드를 살펴보자.

package chatting;

import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;


public class ServerSocketThread extends Thread {
	Socket socket;
	ChatServer server;
	BufferedReader in;		// 입력 담당 클래스
	PrintWriter out;		// 출력 담당 클래스
	String name;
	String threadName;
	
	public ServerSocketThread(ChatServer server, Socket socket) {
		this.server = server;
		this.socket = socket;
		threadName = super.getName();	// Thread 이름을 얻어옴
		System.out.println(socket.getInetAddress() + "님이 입장하였습니다.");	// IP주소 얻어옴
		System.out.println("Thread Name : " + threadName);
	}
	// 클라이언트로 메시지 출력
	public void sendMessage(String str) {
		out.println(str);
	}
	// 쓰레드
	@Override
	public void run() {
		try {
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			// true : autoFlush 설정
			out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
			
			sendMessage("대화자 이름을 넣으세요");
			name = in.readLine();
			server.broadCasting("[" + name + "]님이 입장하셨습니다.");
			
			while(true) {
				String str_in = in.readLine();
				server.broadCasting("[" + name + "] " + str_in);
			}
		} catch (IOException e) {
			System.out.println(threadName + " 퇴장했습니다.");
			server.removeClient(this);
			//e.printStackTrace();
		} finally {
			try {
				socket.close();
			} catch (IOException e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
	}
}

Client와 데이터를 주고받기 위해서 InputStream, OutputStream 생성

boradCasting 함수를 사용함으로써 모든 클라이언트에게 채팅 내용을 전달

무한 반복문에서 채팅 내용을 반복

클라이언트가 퇴장하는 경우 removeClient 함수 호출

package chatting;

import java.awt.BorderLayout;
import java.awt.Container;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.Socket;
import java.net.UnknownHostException;

import javax.swing.JFrame;
import javax.swing.JScrollPane;
import javax.swing.JTextArea;
import javax.swing.JTextField;


public class ClientGui extends JFrame implements ActionListener, Runnable{
	// 클라이언트 화면용
	Container container = getContentPane();
	JTextArea textArea = new JTextArea();
	JScrollPane scrollPane = new JScrollPane(textArea);
	JTextField textField = new JTextField();
	// 통신용
	Socket socket;
	PrintWriter out;
	BufferedReader in;
	String str; 		// 채팅 문자열 저장
	
	public ClientGui(String ip, int port) {
		// frame 기본 설정
		setTitle("챗팅");
		setSize(550, 400);
		setLocation(400, 400);
		init();
		start();
		setVisible(true);
		// 통신 초기화
		initNet(ip, port);
		System.out.println("ip = " + ip);
	}	
	// 통신 초기화
	private void initNet(String ip, int port) {
		try {
			// 서버에 접속 시도
			socket = new Socket(ip, port);
			// 통신용 input, output 클래스 설정
			in = new BufferedReader(new InputStreamReader(socket.getInputStream()));
			// ture : auto flush 설정
			out = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
		} catch (UnknownHostException e) {
			System.out.println("IP 주소가 다릅니다.");
			//e.printStackTrace();
		} catch (IOException e) {
			System.out.println("접속 실패");
			//e.printStackTrace();
		}
		// 쓰레드 구동
		Thread thread = new Thread(this); // run 함수 -> this
		thread.start();
	}
	private void init() {
		container.setLayout(new BorderLayout());
		container.add("Center", scrollPane);
		container.add("South", textField);
	}
	private void start() {
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		textField.addActionListener(this);
	}
	// 응답 대기
	// -> 서버로부터 응답으로 전달된 문자열을 읽어서, textArea에 출력하기
	@Override
	public void run() {
		while(true) {
			try {
				str = in.readLine();
				textArea.append(str + "\n");
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}
	@Override
	public void actionPerformed(ActionEvent e) {
		// textField의 문자열을 읽어와서 서버로 전송함
		str = textField.getText();
		out.println(str);
		// textField 초기화
		textField.setText("");
	}
}

클라이언트 화면은 구성하고 initNet 함수를 통해

socket이 port를 통해서 서버에 접속을 시도함

글을 쓰기 위한 InputStream, 받기 위한 OutputStream 설정

쓰레드를 구동시킴으로써 채팅 기능을 활성화.

 

서버 메인, 클라이언트 메인에서 설정해놓은 클래스를 호출

package chatting;

import java.net.InetAddress;
import java.net.UnknownHostException;

public class ClientGuiMain {
	public static void main(String[] args) {
		try {
			InetAddress ia = InetAddress.getLocalHost();
			String ip_str = ia.toString();
			String ip = ip_str.substring(ip_str.indexOf("/") + 1);
			 new ClientGui(ip, 5420);
		} catch (UnknownHostException e) {
			e.printStackTrace();
		}
	}
}

package chatting;

public class ChatServerMain {
	public static void main(String[] args) {
		ChatServer server = new ChatServer();
		server.giveAndTake();
	}
}

네트워크 환경을 쓰레드로써 구동시키는 방법의 흐름은 이해되나,

직접 코드를 작성하며 작동이 안 되는 어려움이 있었다.

 

java network Gui 채팅프로그램

Comments