자바로 PC방 프로그램만들기

잠시수련. 자바채팅 1:1GUI -> 다중채팅

대망의 다중채팅 시작...입니다.
이번편에는 영상이 세개로 구성되어있습니다.

첫번째 영상에서는 다중채팅의 거의 모든 구현을 마칩니다. 

두번째 영상에서는 잔버그를 잡습니다
( 날코딩이다보니.. 귀찮아서 녹화를 그냥 했습니다만 과감한 패스를 요합니다. ㅋㅋㅋㅋ) 

세번째 영상에서는 다중채팅을 기반으로, 향후 Pc방 프로그램이 어떤 원리로 돌아가는 지 프로그램을 하나 간단히 만들어보면서 설명합니다 ( 결론은 채팅과 같습니다;;;) 

 

짧은 녹화였지만 프로그래밍의 순서를 생각해보았는데, 

1. 무엇을 하고 싶은 지 (요구사항) 를 제대로 분석.

2. 분석한 것을 토대로 해당 언어의 해당 기술들 파악과 구현

3. 나머지 놓친 것들. 버그 수정과 유지보수 이라는 생각이 들었습니다.

프로그래밍을 하기에 앞서서, 하고 싶은 일을 최대한 쉽고 논리적으로 풀기만 하면 나머지는 언어가 조금만 숙련되면 얼마든지 풀 수가 있습니다. 앞으로도 어떤 프로그래밍을 하던 이 순서를 잘 생각하며 프로그래밍 해야겠다는 생각이 녹화하면서 들었습니다. 

 

자 그러면 채팅방 동영상과 파워포인트 시작합니다.

동영상 1탄..거의 모든 것의 구현..

5번째 슬라이드가 핵심입니다. 그림을 보시면서 요구사항을 이해하시면 좋을 것입니다.
(소스의 가독성을 위해 예외처리를 모두 Throws 처리했습니다 ) 

크게 세가지로 구동됩니다.  ServerBackground만 살펴보면 될 것같습니다. 

1. 역할의 분리로 서버는  while 문안에서 리시버를 계속 생성.

여기서 쓰레드의 특징. 같은 이름으로 계속 new 로 생성해서 각기 따로 작동을 유념하시면 됩니다.

//SerberBackground.java 세팅 부분. 
while (true) {
    /** XXX 01. 첫번째. 서버가 할일 분담. 계속 접속받는것. */
	System.out.println("서버 대기중...");
	socket = serverSocket.accept(); // 먼저 서버가 할일은 계속 반복해서 사용자를 받는다.
	System.out.println(socket.getInetAddress() + "에서 접속했습니다.");
	// 여기서 새로운 사용자 쓰레드 클래스 생성해서 소켓정보를 넣어줘야겠죠?!
	Receiver receiver = new Receiver(socket);
	receiver.start();
}

2. 리시버의 행동

쓰레드리시버가 계속 InputStream을 리스닝 하면서 메시지가 들어오면 서버에게 메시지 전파를 부탁합니다.

제일 처음 가동될 시 닉네임을 받아서 서버에게 저장을 요청합니다.

/** XXX 2. 리시버가 한일은 자기 혼자서 네트워크 처리 계속..듣기.. 처리해주는 것. */
public Receiver(Socket socket) throws IOException {
    out = new DataOutputStream(socket.getOutputStream());
	in = new DataInputStream(socket.getInputStream());
	nick = in.readUTF();
	addClient(nick, out);
}

public void run() {
	try {// 계속 듣기만!!
		while (in != null) {
			msg = in.readUTF();
			sendMessage(msg);
			gui.appendMsg(msg);
		}
	} catch (IOException e) {
		// 사용접속종료시 여기서 에러 발생. 그럼나간거에요.. 여기서 리무브 클라이언트 처리 해줍니다.
		removeClient(nick);
	}
}

리시버가 이렇게 동작하면..

맵에 이렇게 넣어서 처리해줍니다. 

Collections.synchronizedMap(clientsMap); // 이걸 교통정리 해줍니다^^

 

3. 서버에서 클라이언트 저장맵을 봐주세요

클라이언트맵에는 클라이언트가 접속할 시마다, 클라이언트 정보를 (닉네임, DataoutStream)을 기억합니다. 

꺼낼 때는 반복자 iterator 로 꺼내옵니다. 공부겸 테스트코드를 만들어보았습니다..

while을 치고 이클립스에서 자동완성 두번째 칸으로 들어가시면 iterator 를 이용한 반복문을 만들어줍니다. 

List<String> list = new ArrayList<String>();
    	list.add("hi");
		list.add("hello");
		
		Iterator<String> iterator = list.iterator();
		while (iterator.hasNext()) {
			String string = (String) iterator.next();
			System.out.println(string);
		}

 

동영상 2탄. 디버깅...;; 끄적끄적입니다;

별거가 없네요;; 클라이언트 시작될 때 scanner로 닉네임 세팅하는 정도..들어갔습니다. 

만든 소스 정리합니다. 서버back, 서버 Gui, 클라이언트 Back, 클라이언트 gui 로 구성됩니다.

파일 주소 링크 : http://adunhansa.tistory.com/186

서버 back

package chat.server;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

public class ServerBackground {

    // 지금까지 한일. GUi연동시키면서 서버Gui에 메시지띄움.
	// 다음 이슈. Gui 상에서 일단 1:1 채팅을 하고 싶다.
	private ServerSocket serverSocket;
	private Socket socket;
	private ServerGui gui;
	private String msg;

	/** XXX 03. 세번째 중요한것. 사용자들의 정보를 저장하는 맵입니다. */
	private Map<String, DataOutputStream> clientsMap = new HashMap<String, DataOutputStream>();

	public final void setGui(ServerGui gui) {
		this.gui = gui;
	}

	public void setting() throws IOException {
			Collections.synchronizedMap(clientsMap); // 이걸 교통정리 해줍니다^^
			serverSocket = new ServerSocket(7777);
			while (true) {
				/** XXX 01. 첫번째. 서버가 할일 분담. 계속 접속받는것. */
				System.out.println("서버 대기중...");
				socket = serverSocket.accept(); // 먼저 서버가 할일은 계속 반복해서 사용자를 받는다.
				System.out.println(socket.getInetAddress() + "에서 접속했습니다.");
				// 여기서 새로운 사용자 쓰레드 클래스 생성해서 소켓정보를 넣어줘야겠죠?!
				Receiver receiver = new Receiver(socket);
				receiver.start();
			}
	}

	public static void main(String[] args) throws IOException {
		ServerBackground serverBackground = new ServerBackground();
		serverBackground.setting();
	}

	// 맵의내용(클라이언트) 저장과 삭제
	public void addClient(String nick, DataOutputStream out) throws IOException {
		sendMessage(nick + "님이 접속하셨습니다.");
		clientsMap.put(nick, out);
	}

	public void removeClient(String nick) {
		sendMessage(nick + "님이 나가셨습니다.");
		clientsMap.remove(nick);
	}

	// 메시지 내용 전파
	public void sendMessage(String msg) {
		Iterator<String> it = clientsMap.keySet().iterator();
		String key = "";
		while (it.hasNext()) {
			key = it.next();
			try {
				clientsMap.get(key).writeUTF(msg);
			} catch (IOException e) {
				e.printStackTrace();
			}
		}
	}

	// -----------------------------------------------------------------------------
	class Receiver extends Thread {
		private DataInputStream in;
		private DataOutputStream out;
		private String nick;

		/** XXX 2. 리시버가 한일은 자기 혼자서 네트워크 처리 계속..듣기.. 처리해주는 것. */
		public Receiver(Socket socket) throws IOException {
			out = new DataOutputStream(socket.getOutputStream());
			in = new DataInputStream(socket.getInputStream());
			nick = in.readUTF();
			addClient(nick, out);
		}

		public void run() {
			try {// 계속 듣기만!!
				while (in != null) {
					msg = in.readUTF();
					sendMessage(msg);
					gui.appendMsg(msg);
				}
			} catch (IOException e) {
				// 사용접속종료시 여기서 에러 발생. 그럼나간거에요.. 여기서 리무브 클라이언트 처리 해줍니다.
				removeClient(nick);
			}
		}
	}
}

 서버 gui

package chat.server;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.io.IOException;

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

public class ServerGui extends JFrame implements ActionListener {

    private static final long serialVersionUID = 1L;
	private JTextArea jta = new JTextArea(40, 25);
	private JTextField jtf = new JTextField(25);
	private ServerBackground server = new ServerBackground();

	public ServerGui() throws IOException {

		add(jta, BorderLayout.CENTER);
		add(jtf, BorderLayout.SOUTH);
		jtf.addActionListener(this);

		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
		setBounds(200, 100, 400, 600);
		setTitle("서버부분");

		server.setGui(this);
		server.setting();
	}

	public static void main(String[] args) throws IOException {
		new ServerGui();
	}

	@Override
	public void actionPerformed(ActionEvent e) {
		String msg = "서버 : "+ jtf.getText() + "\n";
		System.out.print(msg);
		server.sendMessage(msg);
		jtf.setText("");
	}

	public void appendMsg(String msg) {
		jta.append(msg);
	}

}

클라이언트 백그라운드

package chat.client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.Socket;

public class ClientBackground {

    private Socket socket;
	private DataInputStream in;
	private DataOutputStream out;
	private ClientGui gui;
	private String msg;
	private String nickName;

	public final void setGui(ClientGui gui) {
		this.gui = gui;
	}

	public void connet() {
		try {
			socket = new Socket("127.0.0.1", 7777);
			System.out.println("서버 연결됨.");
			
			out = new DataOutputStream(socket.getOutputStream());
			in = new DataInputStream(socket.getInputStream());
			
			//접속하자마자 닉네임 전송하면. 서버가 이걸 닉네임으로 인식을 하고서 맵에 집어넣겠지요?
			out.writeUTF(nickName); 
			System.out.println("클라이언트 : 메시지 전송완료");
			while(in!=null){
				msg=in.readUTF();
				gui.appendMsg(msg);				
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public static void main(String[] args) {
		ClientBackground clientBackground = new ClientBackground();
		clientBackground.connet();
	}

	public void sendMessage(String msg2) {
		try {
			out.writeUTF(msg2);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void setNickname(String nickName) {
		this.nickName = nickName;
	}

}

 

클라이언트 Gui

package chat.client;

import java.awt.BorderLayout;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.util.ArrayList;
import java.util.Scanner;

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

public class ClientGui  extends JFrame implements ActionListener{

    private static final long serialVersionUID = 1L;
	private JTextArea jta = new JTextArea(40, 25);
	private JTextField jtf = new JTextField(25);
	private ClientBackground client = new ClientBackground();
	private static String nickName;
	
	public ClientGui() {
		
		add(jta, BorderLayout.CENTER);
		add(jtf, BorderLayout.SOUTH);
		jtf.addActionListener(this);
		
		setDefaultCloseOperation(EXIT_ON_CLOSE);
		setVisible(true);
		setBounds(800, 100, 400, 600);
		setTitle("클라이언트");
		
		client.setGui(this);
		client.setNickname(nickName);
		client.connet();
	}
	
	public static void main(String[] args) {
		Scanner scanner = new Scanner(System.in);
		System.out.print("당신의 닉네임부터 설정하세요 : ");
		nickName = scanner.nextLine();
		scanner.close();		
		
		new ClientGui();
		
		
	}

	@Override
	//말치면 보내는 부분
	public void actionPerformed(ActionEvent e) {
		String msg = nickName+ ":" + jtf.getText()+"\n";
		client.sendMessage(msg);
		jtf.setText("");
	}

	public void appendMsg(String msg) {
		jta.append(msg);
	}
	

}

 

동영상 3탄. 채팅에 기반하여서, Pc방 프로그램 제작 개요

요구사항이 채팅과 비슷한 부분이 있습니다. 요구사항이 틀려도 본질적인 기능은 채팅과 비슷하기 때문에 어떻게 동작하는지만 그냥 빠르게 제작하였습니다. 

어차피 다음편부터 다시 다룰 내용이므로 자세한 내용은 생략합니다.

 

 

댓글

댓글 본문
  1. 홍은빈
    와 진짜 너무 감사합니다
  2. 성현찡
    와 공부하다가 아라한사 님의 글을보고 감탄하면서 글을적는데요
    일단은 너무 간결하고 깔끔한 코딩이라 ㄲ마짝놀랏구여

    저런식으로 코딩되면 결국엔 클라이언트에서 글을쓰면 다른클라이언크도다볼수잇지않나요?/

    한클라에ㅐ서 메세지보내면
    클라->서버->모든클라
    이런식으로요
  3. 지나가는 행인1
    아라한사님, 영상 너무 잘보고 있고 감사의 말씀 올려요.
    근데 제가 궁금한게 있는데요. 1:1채팅이라고 해도 서버<->클라 잖아요?
    그런데 실제로 만약에 서비스하는 앱을 만들게 되면 2명의 클라를 서버에서 받아야되는건데(1:1채팅이라고 하면요)
    그렇다면 결국 그것이 클라<->서버<->클라 인데, 이러면 다중채팅이 아닌가요?
  4. 궁금해요요
    생성자 만들어서 클래스 달라도 띄운거 아닌가요??
    메인부에서 생성자 호출로??
  5. 궁금해요요
    생성자 만들어서 클래스 달라도 띄운거 아닌가요??
    메인부에서 생성자 호출로??
  6. @궁금해요
    서로 다른 패키지라서 따로 가능할 껄요?
  7. 궁금해요
    궁금한게 있는데요, 서버하고 클라이언트를 어떻게 따로 띄우신거에요?
    이클립스 내에서 클래스 별로 실행이 안되던데, 알려주실수 있나요?
  8. 아라한사
    으아 감사합니다. ㅎㅎ
    대화보기
    • 두둥
      영상 잘보고 갑니다.^^
    버전 관리
    아라한사
    현재 버전
    선택 버전
    graphittie 자세히 보기