인터넷 채팅 시스템 구축

웹에 등장한 많은 Java 기반 채팅 시스템 중 하나를 보셨을 것입니다. 이 기사를 읽고 나면 작동 방식을 이해하고 간단한 채팅 시스템을 구축하는 방법을 알게 될 것입니다.

클라이언트 / 서버 시스템의이 간단한 예제는 표준 API에서 사용 가능한 스트림 만 사용하여 애플리케이션을 빌드하는 방법을 보여주기위한 것입니다. 채팅은 TCP / IP 소켓을 사용하여 통신하며 웹 페이지에 쉽게 포함 할 수 있습니다. 참고로이 애플리케이션과 관련된 Java 네트워크 프로그래밍 구성 요소를 설명하는 사이드 바를 제공합니다. 여전히 속도를 내고 있다면 먼저 사이드 바를 살펴보십시오. 이미 Java에 능숙하다면 바로 들어가서 사이드 바를 참조하여 참조 할 수 있습니다.

채팅 클라이언트 구축

간단한 그래픽 채팅 클라이언트로 시작합니다. 연결할 서버 이름과 포트 번호의 두 가지 명령 줄 매개 변수가 필요합니다. 소켓 연결을 만든 다음 큰 출력 영역과 작은 입력 영역이있는 창을 엽니 다.

ChatClient 인터페이스

사용자가 입력 영역에 텍스트를 입력하고 Return을 누르면 텍스트가 서버로 전송됩니다. 서버는 클라이언트가 보낸 모든 것을 반향합니다. 클라이언트는 서버에서받은 모든 것을 출력 영역에 표시합니다. 여러 클라이언트가 하나의 서버에 연결되면 간단한 채팅 시스템이됩니다.

ChatClient 클래스

이 클래스는 설명 된대로 채팅 클라이언트를 구현합니다. 여기에는 기본 사용자 인터페이스 설정, 사용자 상호 작용 처리 및 서버에서 메시지 수신이 포함됩니다.

import java.net. *; import java.io. *; import java.awt. *; public class ChatClient extends Frame implements Runnable {// public ChatClient (String title, InputStream i, OutputStream o) ... // public void run () ... // public boolean handleEvent (Event e) ... // public static void main (String args []) throws IOException ...}

ChatClient클래스는 확장 Frame; 이것은 그래픽 응용 프로그램에서 일반적입니다. 서버에서 메시지를 수신하는를 Runnable시작할 수 있도록 인터페이스를 구현 Thread합니다. 생성자는 GUI의 기본 설정을 수행하고 run()메소드는 서버로부터 메시지를 수신하며 handleEvent()메소드는 사용자 상호 작용을 처리하며 main()메소드는 초기 네트워크 연결을 수행합니다.

보호 된 DataInputStream i; protected DataOutputStream o; 보호 된 TextArea 출력; 보호 된 TextField 입력; 보호 된 스레드 리스너; 공용 ChatClient (문자열 제목, InputStream i, OutputStream o) {슈퍼 (제목); this.i = 새로운 DataInputStream (새로운 BufferedInputStream (i)); this.o = 새로운 DataOutputStream (새로운 BufferedOutputStream (o)); setLayout (새 BorderLayout ()); add ( "Center", 출력 = new TextArea ()); output.setEditable (false); add ( "South", 입력 = new TextField ()); 팩 (); 보여 주다 (); input.requestFocus (); 리스너 = 새 스레드 (this); listener.start (); }

생성자는 창 제목, 입력 스트림 및 출력 스트림의 세 가지 매개 변수를 사용합니다. ChatClient지정된 스트림을 통해 통신; 버퍼링 된 데이터 스트림 i 및 o를 생성하여 이러한 스트림을 통해 효율적인 고수준 통신 기능을 제공합니다. 그런 다음 TextArea출력과 TextField입력 으로 구성된 간단한 사용자 인터페이스를 설정합니다 . 창을 레이아웃하고 표시 Thread하고 서버에서 메시지를 받는 리스너를 시작 합니다.

public void run () {try {while (true) {String line = i.readUTF (); output.appendText (줄 + "\ n"); }} catch (IOException 예) {ex.printStackTrace (); } 마지막으로 {listener = null; input.hide (); 확인 (); 시도 {o.close (); } catch (IOException 예) {ex.printStackTrace (); }}}

리스너 스레드가 run 메소드에 들어가면 String입력 스트림에서 s를 읽는 무한 루프에 있습니다. 가 String도착하면 출력 영역에 추가하고 루프를 반복합니다. 은 IOException서버에 연결이 손실 된 경우 발생할 수 있습니다. 이 경우 예외를 인쇄하고 정리를 수행합니다. 이것은 메서드 EOFException에서 신호를 보냅니다 readUTF().

정리하려면 먼저 리스너 참조를 이에 할당 Thread합니다 null. 이것은 스레드가 종료되었음을 코드의 나머지 부분에 나타냅니다. 그런 다음 입력 필드를 숨기고 호출 validate()하여 인터페이스를 다시 배치하고 OutputStreamo를 닫아 연결이 닫혔는지 확인합니다.

finally절 에서 모든 정리를 수행 하므로 IOException여기 에서 발생하거나 스레드가 강제로 중지 되든 관계없이 발생합니다. 창을 즉시 닫지 않습니다. 연결이 끊어진 후에도 사용자가 세션을 읽으려고 할 수 있다고 가정합니다.

public boolean handleEvent (Event e) {if ((e.target == input) && (e.id == Event.ACTION_EVENT)) {try {o.writeUTF ((String) e.arg); o.flush (); } catch (IOException 예) {ex.printStackTrace (); listener.stop (); } input.setText ( ""); true를 반환하십시오. } else if ((e.target == this) && (e.id == Event.WINDOW_DESTROY)) {if (listener! = null) listener.stop (); 숨기기 (); true를 반환하십시오. } return super.handleEvent (e); }

에서 handleEvent()방법, 우리는 두 가지 중요한 UI 이벤트를 확인해야합니다 :

첫 번째는의 작업 이벤트로 TextField, 사용자가 Return 키를 눌렀 음을 의미합니다. 이 이벤트를 포착하면 메시지를 출력 스트림에 쓴 다음 호출 flush()하여 즉시 전송되도록합니다. 출력 스트림은이다 DataOutputStream우리가 사용할 수 있도록, writeUTF()을 보낼 String. 이 IOException발생하면 연결이 실패 했어야하므로 리스너 스레드를 중지합니다. 그러면 필요한 모든 정리가 자동으로 수행됩니다.

두 번째 이벤트는 창을 닫으려는 사용자입니다. 이 작업을 처리하는 것은 프로그래머에게 달려 있습니다. 리스너 스레드를 중지하고 Frame.

public static void main (String args []) throws IOException {if (args.length! = 2) throw new RuntimeException ( "Syntax : ChatClient"); 소켓 s = 새 소켓 (args [0], Integer.parseInt (args [1])); new ChatClient ( "채팅"+ args [0] + ":"+ args [1], s.getInputStream (), s.getOutputStream ()); }

main()메서드는 클라이언트를 시작합니다. 올바른 개수의 인수가 제공되었는지 확인 Socket하고 지정된 호스트 및 포트에 대한를 열고 ChatClient소켓의 스트림에 연결된 것을 만듭니다 . 소켓을 생성하면이 메서드를 종료하고 표시되는 예외가 발생할 수 있습니다.

다중 스레드 서버 구축

이제 여러 연결을 허용하고 모든 클라이언트에서 읽는 모든 것을 브로드 캐스트 할 수있는 채팅 서버를 개발합니다. Strings를 UTF 형식 으로 읽고 쓰는 것은 고정 배선되어 있습니다.

이 프로그램에는 두 가지 클래스가 있습니다. 기본 클래스 인 ChatServer은 클라이언트의 연결을 수락하고이를 새 연결 처리기 개체에 할당하는 서버입니다. ChatHandler클래스는 실제로 메시지를 듣고 연결된 모든 클라이언트에 방송하는 작업을 수행합니다. 하나의 스레드 (메인 스레드)가 새 연결을 처리 ChatHandler하고 각 클라이언트에 대한 스레드 ( 클래스)가 있습니다.

모든 새는에 ChatClient연결됩니다 ChatServer. 이렇게 ChatServer하면 ChatHandler새 클라이언트에서 메시지를받을 클래스 의 새 인스턴스에 대한 연결 이 전달 됩니다 . ChatHandler클래스 내에서 현재 처리기 목록이 유지됩니다. 이 broadcast()방법은이 목록을 사용하여 연결된 모든에 메시지를 전송합니다 ChatClient.

클래스 ChatServer

이 클래스는 클라이언트로부터 연결을 수락하고이를 처리하기 위해 핸들러 스레드를 시작하는 것과 관련이 있습니다.

import java.net. *; import java.io. *; import java.util. *; public class ChatServer {// public ChatServer (int port) throws IOException ... // public static void main (String args []) throws IOException ...}

이 클래스는 간단한 독립 실행 형 응용 프로그램입니다. 클래스에 대한 모든 실제 작업을 수행하는 생성자와 main()실제로이를 시작 하는 메서드를 제공합니다.

public ChatServer (int port) throws IOException {ServerSocket server = new ServerSocket (port); while (true) {소켓 클라이언트 = server.accept (); System.out.println ( "Accepted from"+ client.getInetAddress ()); ChatHandler c = 새 ChatHandler (클라이언트); c. 시작 (); }}

서버의 모든 작업을 수행하는이 생성자는 매우 간단합니다. 우리는을 만들 ServerSocket클라이언트를 받아들이는 루프에 앉아 다음과 accept()방법 ServerSocket. 각 연결에 대해 ChatHandler클래스 의 새 인스턴스를 만들고 새 인스턴스를 Socket매개 변수로 전달합니다 . 이 핸들러를 만든 후에는 해당 start()메서드로 시작합니다 . 이것은 우리의 메인 서버 루프가 새로운 연결을 계속 기다릴 수 있도록 연결을 처리하는 새로운 스레드를 시작합니다.

public static void main (String args []) throws IOException {if (args.length! = 1) throw new RuntimeException ( "Syntax : ChatServer"); 새로운 ChatServer (Integer.parseInt (args [0])); }

The main() method creates an instance of the ChatServer, passing the command-line port as a parameter. This is the port to which clients will connect.

Class ChatHandler

This class is concerned with handling individual connections. We must receive messages from the client and re-send these to all other connections. We maintain a list of the connections in a

static

Vector.

import java.net.*; import java.io.*; import java.util.*; public class ChatHandler extends Thread { // public ChatHandler (Socket s) throws IOException ... // public void run () ... } 

We extend the Thread class to allow a separate thread to process the associated client. The constructor accepts a Socket to which we attach; the run() method, called by the new thread, performs the actual client processing.

 protected Socket s; protected DataInputStream i; protected DataOutputStream o; public ChatHandler (Socket s) throws IOException { this.s = s; i = new DataInputStream (new BufferedInputStream (s.getInputStream ())); o = new DataOutputStream (new BufferedOutputStream (s.getOutputStream ())); } 

The constructor keeps a reference to the client's socket and opens an input and an output stream. Again, we use buffered data streams; these provide us with efficient I/O and methods to communicate high-level data types -- in this case, Strings.

protected static Vector handlers = new Vector (); public void run () { try { handlers.addElement (this); while (true) { String msg = i.readUTF (); broadcast (msg); } } catch (IOException ex) { ex.printStackTrace (); } finally { handlers.removeElement (this); try { s.close (); } catch (IOException ex) { ex.printStackTrace(); } } } // protected static void broadcast (String message) ... 

The run() method is where our thread enters. First we add our thread to the Vector of ChatHandlers handlers. The handlers Vector keeps a list of all of the current handlers. It is a static variable and so there is one instance of the Vector for the whole ChatHandler class and all of its instances. Thus, all ChatHandlers can access the list of current connections.

Note that it is very important for us to remove ourselves from this list afterward if our connection fails; otherwise, all other handlers will try to write to us when they broadcast information. This type of situation, where it is imperative that an action take place upon completion of a section of code, is a prime use of the try ... finally construct; we therefore perform all of our work within a try ... catch ... finally construct.

The body of this method receives messages from a client and rebroadcasts them to all other clients using the broadcast() method. When the loop exits, whether because of an exception reading from the client or because this thread is stopped, the finally clause is guaranteed to be executed. In this clause, we remove our thread from the list of handlers and close the socket.

protected static void broadcast (String message) { synchronized (handlers) { Enumeration e = handlers.elements (); while (e.hasMoreElements ()) { ChatHandler c = (ChatHandler) e.nextElement (); try { synchronized (c.o) { c.o.writeUTF (message); } c.o.flush (); } catch (IOException ex) { c.stop (); } } } } 

This method broadcasts a message to all clients. We first synchronize on the list of handlers. We don't want people joining or leaving while we are looping, in case we try to broadcast to someone who no longer exists; this forces the clients to wait until we are done synchronizing. If the server must handle particularly heavy loads, then we might provide more fine-grained synchronization.

이 동기화 된 블록 내 Enumeration에서 현재 처리기 중 하나 를 얻습니다 . 이 Enumeration클래스는의 모든 요소를 ​​반복하는 편리한 방법을 제공합니다 Vector. 루프는 단순히 Enumeration. 에 쓰는 동안 예외가 발생 ChatClient하면 클라이언트의 stop()메서드를 호출합니다 . 이렇게하면 클라이언트 스레드가 중지되므로 처리기에서 클라이언트 제거를 포함하여 적절한 정리가 수행됩니다.