- Outsider's Dev Story
- 흔한 개발자의 개발 노트
- 내 아이큐 80, 너도 80, 둘이 합쳐 160 - 내…
- 조대협의 블로그
- 자바캔(Java Can Do IT) / 최범균 님
- Sangon, Han – 개발자; 읽고 생각하고 쓰고 …
- The Evolution of the Web
- NAVER D2
- Dashboard - SLiPP
- ITWorld Korea - 테크놀로지 리더를 위한 글…
- OKKY - All That Developer
- 웹Frameworks
- 오픈튜토리얼스
- 위키독스 / 온라인 책 제작 공유 플랫폼 서비스
- 블로터
- IT OnAir
- 한 처음에 / 백창92
- Divide my knowledge / 완프최
- SERI.org
Bigfat
[Java 예제] 채팅(chatting) 본문
자바로 서버-클라이언트 채팅 프로그램을 구현해보자
클라이언트 측에서는 AWT와 Swing을 적절히 섞어 UI를 구현하였다. 웹 프로그래밍만 하다보니 자바 GUI에 대해 무지한데, 채용공고를 보니 Swing 개발자도 구하고 있었다. 지금으로서는 API를 읽는 습관을 들이기 위해 사용한다고 생각하자.
소켓, 스레드뿐만 아니라 오버라이딩과 지네릭스까지 한 번쯤은 생각해보고 구현해보면 좋을 것 같은 예제다.
/** * ChatClientMain.java * 채팅 클라이언트 Main 클래스 * @Date 2017. 2. 22. */ package com.multichat.client; import java.util.Random; public class ChatClientMain { public static void main(String[] args) throws Exception { int randomNum = new Random().nextInt(1000); ChatClient ccd = new ChatClient(String.valueOf(randomNum)); // setBounds(int x, int y, int width, int height) ccd.setBounds(400, 100, 300, 600); ccd.setVisible(true); } }
/** * ChatClient.java * 채팅 클라이언트 기능 구현 클래스 * @Date 2017. 2. 22. */ package com.multichat.client; import java.awt.BorderLayout; import java.awt.Container; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.awt.event.WindowAdapter; import java.awt.event.WindowEvent; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; import javax.swing.JButton; import javax.swing.JFrame; import javax.swing.JLabel; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextArea; import javax.swing.JTextField; public class ChatClient extends JFrame implements ActionListener, Runnable { private static final long serialVersionUID = 1L; JLabel topLine; // 상단부 JTextArea showArea; // 대화창 JPanel bottomLine; // 하단부 JTextField inputBox; // 입력창 JButton sendButton; // '보내기'버튼 BufferedReader br; PrintWriter pw; Socket chatSocket; boolean flag; String nickName; public ChatClient(String nickName) throws Exception { super(nickName+"의 채팅창"); this.nickName = nickName; design(); connect(); new Thread(this).start(); // Runnable 구현 // 익명클래스 > 이벤트 발생 시에만 메모리에 올라간다. this.addWindowListener(new WindowAdapter() { @Override public void windowClosing(WindowEvent e) { stop(); System.exit(0); } @Override public void windowOpened(WindowEvent e) { // TODO Auto-generated method stub inputBox.requestFocus(); } }); } public void design() { Container ct = this.getContentPane(); // 상단부 topLine = new JLabel(nickName); ct.add(topLine, BorderLayout.PAGE_START); // 대화창 showArea = new JTextArea(""); showArea.setEditable(false); showArea.setLineWrap(true); // 자동 줄바꿈 ct.add(new JScrollPane(showArea), BorderLayout.CENTER); // scroll-bar 붙임 // 하단부 // bottomLine = new JPanel(new GridLayout(1,2)); // int rows, int cols bottomLine = new JPanel(); inputBox = new JTextField(15); sendButton = new JButton("보내기"); bottomLine.add(inputBox); bottomLine.add(sendButton); ct.add(bottomLine, BorderLayout.PAGE_END); // Action inputBox.addActionListener(this); // JTextField의 Enter키 이벤트 발생 sendButton.addActionListener(this); } // 소켓 연결 public void connect() { try { chatSocket = new Socket("127.0.0.1", 8889); br = new BufferedReader(new InputStreamReader(chatSocket.getInputStream())); pw = new PrintWriter(chatSocket.getOutputStream(), true); } catch (Exception e) { System.out.println("연결 실패"); } this.sendMessage("["+nickName+"] 님이 입장하셨습니다."); } // Runnable의 run() 메서드 구현 @Override public void run() { try { // Server로부터 받는 데이터를 읽어들임 while(!flag) { String msg = br.readLine(); if(msg != null && !msg.equals("")) { showArea.append(msg+"\n"); showArea.setCaretPosition(showArea.getText().length()); } } } catch (Exception e) { e.printStackTrace(); } } // 메시지 전송 public void sendMessage(String msg) { pw.println(msg); } @Override public void actionPerformed(ActionEvent e) { String msg = inputBox.getText(); if(!msg.equals("")) { sendMessage("[" + nickName + "]: " + msg); inputBox.setText(""); } } // 접속 종료 public void stop() { String endOfMsg = "!@#$"; // EOM : End of Message try { sendMessage("["+nickName+"] 님이 퇴장하셨습니다." + endOfMsg); chatSocket.close(); flag = true; } catch (Exception e) { e.printStackTrace(); } } }
/** * ChatServerMain.java * 채팅 서버 Main 클래스 * @Date 2017. 2. 22. */ package com.multichat.server; import java.net.ServerSocket; import java.net.Socket; public class ChatServerMain { private ServerSocket serverSocket; private Socket socket; static ChatManage chatManage = new ChatManage(); public void startServer() { try { serverSocket = new ServerSocket(8889); while(true) { System.out.println("서버 대기중"); socket = serverSocket.accept(); ChatThread chatThread = new ChatThread(socket); chatManage.addChatThread(chatThread); chatThread.start(); } } catch (Exception e) { e.printStackTrace(); } } public static void main(String[] args) { ChatServerMain chatServer = new ChatServerMain(); chatServer.startServer(); } }
/** * ChatThread.java * 각각의 클라이언트와 소켓 통신하도록 구현한 클래스 * @Date 2017. 2. 22. */ package com.multichat.server; import java.io.BufferedReader; import java.io.InputStreamReader; import java.io.PrintWriter; import java.net.Socket; public class ChatThread extends Thread { Socket socket; PrintWriter pw; BufferedReader br; boolean flag; public ChatThread(Socket socket) { this.socket = socket; } @Override public void run() { // "] 님이 퇴장하셨습니다."를 사용해도 무방하다. String endOfMsg = "!@#$"; try { br = new BufferedReader(new InputStreamReader(socket.getInputStream())); pw = new PrintWriter(socket.getOutputStream(), true); while(!flag) { String msg = br.readLine(); if(msg != null && !msg.equals("")) { if(msg.endsWith(endOfMsg)) { ChatServerMain.chatManage.removeChatThread(this); msg = msg.substring(0, msg.length() - 4); br.close(); pw.close(); socket.close(); flag = true; } System.out.println(msg); ChatServerMain.chatManage.sendAllMessage(msg); } } } catch (Exception e) { try { if(br != null) br.close(); if(pw != null) pw.close(); if(socket != null) socket.close(); } catch (Exception e2) { e2.printStackTrace(); } } } public void sendMessage(String msg) { pw.println(msg); } }
/** * ChatManage.java * 클라이언트를 관리하는 클래스 * @Date 2017. 2. 22. */ package com.multichat.server; import java.util.Iterator; import java.util.Vector; public class ChatManage extends Vector<ChatThread> { private static final long serialVersionUID = 1L; public synchronized void addChatThread(ChatThread thread) { this.add(thread); System.out.println("[서버]: " + this.size() + "명 접속해있습니다."); } public synchronized void removeChatThread(ChatThread thread) { this.remove(thread); System.out.println("[서버]: " + this.size() + "명 접속해있습니다."); } public synchronized void sendAllMessage(String msg) { // Iterator를 사용하는 방법 Iterator<ChatThread> iterator= this.iterator(); while(iterator.hasNext()) { ChatThread chatThread = iterator.next(); chatThread.sendMessage(msg); } } }
Server측에서 while(true)로 무한루프를 타기 때문에 아래와 같은 에러가 날 때도 있다. 정상적으로 출력되면서도 이런 에러가 나오길래 검색해봤더니, 스레드 덤프 분석하기 중 원격 서버로부터 메시지 수신을 받기 위해 계속 대기하는 경우에 해당하는 것 같다. 순간적으로 CPU 사용량도 50%까지 올라가는 것을 확인했다.
// 이런 문구들과 함께 Error가 발생할 때도 있다. Software caused connection abort: recv failed at java.net.SocketInputStream.socketRead0(Native Method) ...
'Java' 카테고리의 다른 글
[Java] 데이터베이스 연동하기 (0) | 2017.02.23 |
---|---|
[Java 예제] 로또(a lottery) (0) | 2017.02.13 |
[Java 예제] 홀수 마방진(a magic square of odd order) (3) | 2017.02.11 |
[Java 예제] 다이아몬드(a diamond) (0) | 2017.02.11 |