728x90
반응형
SMALL
메인 서버
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | package chat; import java.io.IOException; import java.io.Writer; import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.ServerSocket; import java.net.Socket; import java.net.SocketException; import java.util.ArrayList; import java.util.List; public class ChatServer { private static ServerSocket serverSocket; private static final int SERVER_PORT = 5000; public static void main(String[] args) { try { List<Writer> listWriters = new ArrayList<>(); serverSocket = new ServerSocket(); String ipAddress = InetAddress.getLocalHost().getHostAddress(); serverSocket.bind(new InetSocketAddress(ipAddress, SERVER_PORT)); consoleLog( "binding " + ipAddress + ":" + SERVER_PORT ); while(true) { Socket socket = serverSocket.accept(); new ChatServerProcessThread(socket, listWriters).start(); } } catch (SocketException e) { System.out.println("[server] socket connect failed"); } catch (IOException e) { e.printStackTrace(); } } private static void consoleLog(String log) { System.out.println("[server] : " + log); } } | cs |
서버를 가지고 통신을 할 수 있도록 ServerSocket 객체를 선언했습니다.
bind이라는 것은 소켓에 ip와 포트를 할당할 수 있도록 연결해주는 역할을 합니다.
그리고 accept는 클라이언트가 올 때까지 기다립니다. 만약에 클라이언트가 오게 된다면 "ChatServerProcessThread"라는 쓰레드 객체가 만들어지면서 시작하게 됩니다.
서버에서 돌아가는 쓰레드
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 | package chat; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.io.Writer; import java.net.InetSocketAddress; import java.net.Socket; import java.net.SocketException; import java.util.List; public class ChatServerProcessThread extends Thread{ private Socket socket; private String nickname; private List<Writer> listWriters; public ChatServerProcessThread(Socket socket, List<Writer> listWriters) { this.socket = socket; this.listWriters = listWriters; } @Override public void run() { InetSocketAddress remoteSocketAddress = (InetSocketAddress) socket.getRemoteSocketAddress(); int remoteHostPort = remoteSocketAddress.getPort(); String remoteHostAddress = remoteSocketAddress.getAddress().getHostAddress(); consoleLog("connected from " + remoteHostAddress + ":" + remoteHostPort); try { PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true); BufferedReader br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8")); while(true) { String request = br.readLine(); String tokens[] = request.split(":"); if("join".equals(tokens[0])) { doJoin(pw, tokens[1]); } else if("message".equals(tokens[0])) { doMessage(tokens[1]); } else if("quit".equals(tokens[0])) { doQuit(pw); break; } } } catch(SocketException e) { System.out.println("[server] socket disconnected"); } catch(IOException e) { e.printStackTrace(); } finally { try { if(socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { e.printStackTrace(); } } } private void doQuit(PrintWriter pw) throws IOException { removeWriter(pw); String data = nickname + "님이 종료되었습니다."; broadcast(data); } private void removeWriter(PrintWriter pw) { listWriters.remove(pw); } private void doMessage(String message) throws IOException { String data = nickname + ":" + message; broadcast(data); } private void doJoin(Writer writer, String nickname) throws IOException { this.nickname = nickname; String message = nickname + "이(가) 채팅에 참여했습니다."; broadcast(message); addWriter(writer); ((PrintWriter) writer).println("join:ok"); writer.flush(); } private void addWriter(Writer writer) { synchronized (listWriters) { listWriters.add(writer); } } private void broadcast(String message) throws IOException { synchronized (listWriters) { for(Writer writer : listWriters) { ((PrintWriter) writer).println(message); writer.flush(); } } } private void consoleLog(String log) { System.out.println("[client :" + getId() + "] " + log); } } |
서버에 클라이언트가 올 때마다 이 쓰레드 객체가 생성이 됩니다. 클라이언트에서 채팅을 하게 되면 상황에 따라 모든 클라이언트들에게 브로드캐스트를 하거나 종료를 하게 됩니다.
소켓으로 통신하다가 종료할 때가 있는데 자동으로 닫히는 것이 아니기 때문에 close() 함수를 통해 꼭 닫아줘야 합니다. 닫아주지 않으면 소켓이 프로세스 자원에 남게 되어 나중에는 서버 프로세스에서 "더 이상 파일을 열 수 없다"라는 메시지를 보게 될 것입니다.
메인 클라이언트
package chat;import java.io.IOException;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.InetSocketAddress;import java.net.Socket;import java.net.SocketException;import java.util.Scanner;public class ChatClientApp {private static Socket socket;private static final String IP_ADDRESS = "192.168.1.11";private static final int SERVER_PORT = 5000;public static void main(String[] args) {String name = null;Scanner scanner = new Scanner(System.in);while( true ) {System.out.println("대화명을 입력하세요.");System.out.print(">>> ");name = scanner.nextLine();if (name.isEmpty() == false ) {break;}System.out.println("대화명은 한글자 이상 입력해야 합니다.\n");}scanner.close();socket = new Socket();try {socket.connect(new InetSocketAddress(IP_ADDRESS, SERVER_PORT));PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);pw.println("join:" + name);new ChatWindow(socket, name).show();} catch (SocketException e) {System.out.println("[Client] socket disconnected");} catch (IOException e) {e.printStackTrace();} finally {try {if(socket != null && !socket.isClosed()) {socket.close();}} catch(IOException e) {e.printStackTrace();}}}}클라이언트에서 서버에 접속하기 위해서는 connect라는 메소드를 쓰고, 아이피와 포트을 할당받습니다. 그리고 클라이언트에서 스트림을 통해 출력을 할 수 있도록 printWriter 객체를 선언하였습니다.보통 처음 접속할 때 들리는 클래스이며, 코드가 끝나면 클라이언트 쓰레드 객체가 만들어 집니다.클라이언트 쓰레드ChatClientThread 클래스 위주로 설명하겠습니다. BufferedReader은 InputStreamReader의 단점을 보완한 클래스입니다.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127 package chat;import java.awt.BorderLayout;import java.awt.Button;import java.awt.Color;import java.awt.Frame;import java.awt.Panel;import java.awt.TextArea;import java.awt.TextField;import java.awt.event.ActionEvent;import java.awt.event.ActionListener;import java.awt.event.KeyAdapter;import java.awt.event.KeyEvent;import java.awt.event.WindowAdapter;import java.awt.event.WindowEvent;import java.io.BufferedReader;import java.io.IOException;import java.io.InputStreamReader;import java.io.OutputStreamWriter;import java.io.PrintWriter;import java.net.Socket;public class ChatWindow {private Frame frame;private Panel pannel;private Button buttonSend;private TextField textField;private TextArea textArea;private Socket socket;public ChatWindow(Socket socket, String name) {frame = new Frame(name);pannel = new Panel();buttonSend = new Button("Send");textField = new TextField();textArea = new TextArea(30, 80);this.socket = socket;new ChatClientThread().start();}public void show() {// ButtonbuttonSend.setBackground(Color.GRAY);buttonSend.setForeground(Color.WHITE);buttonSend.addActionListener(new ActionListener() {@Overridepublic void actionPerformed(ActionEvent actionEvent) {new ChatClientThread().start();}});// TextfieldtextField.setColumns(80);textField.addKeyListener(new KeyAdapter() {public void keyReleased(KeyEvent e) {char keyCode = e.getKeyChar();if (keyCode == KeyEvent.VK_ENTER) {sendMessage();}}});// Pannelpannel.setBackground(Color.LIGHT_GRAY);pannel.add(textField);pannel.add(buttonSend);frame.add(BorderLayout.SOUTH, pannel);// TextAreatextArea.setEditable(false);frame.add(BorderLayout.CENTER, textArea);// Frameframe.addWindowListener(new WindowAdapter() {public void windowClosing(WindowEvent e) {System.exit(0);}});frame.setVisible(true);frame.pack();}private void sendMessage() {try {PrintWriter pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);String message = textField.getText();if ("quit".equals(message) == true) {pw.println("quit" + ":" + frame.getTitle());System.exit(0);} else {pw.println("message:" + message);}} catch (IOException e) {}}class ChatClientThread extends Thread {@Overridepublic void run() {BufferedReader br = null;PrintWriter pw = null;try {br = new BufferedReader(new InputStreamReader(socket.getInputStream(), "UTF-8"));pw = new PrintWriter(new OutputStreamWriter(socket.getOutputStream(), "UTF-8"), true);while (true) {String request = br.readLine();textArea.append(request + "\n");textField.setText("");textField.requestFocus();}} catch (IOException e) {e.printStackTrace();}}}}cs InputStreamReader은 byte 크기를 가지고 통신을 합니다. 하지만 byte단위로 통신을 하다보면 효율적이지 못한 부분이 있기 때문에 버퍼를 추가해서 라인 크기로 한꺼번에 보낼 수 있다는 장점을 가졌습니다.
728x90
반응형
LIST
'Java' 카테고리의 다른 글
자바 11과 17 버전의 차이점 (0) | 2023.05.03 |
---|---|
가비지 컬렉션(Garbage Collection) 이해하기 (0) | 2023.05.02 |
JVM(Java Virtual Machine) 이해하기 - 동작 원리 및 구성 요소 (0) | 2023.04.30 |
Java는 Call by Value인가, Call by Reference인가? (0) | 2023.04.30 |
ClassNotFoundException과 NoClassDefFoundError (0) | 2018.03.22 |