2014. 12. 24. 04:26
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.

package prac;

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

public class ChatServer {

    private static final int PORT_NO = 8010;
    private ServerSocket listener;
    private List<Connection> clients;

    ChatServer() throws IOException{
        listener=new ServerSocket(PORT_NO);
        clients=new ArrayList();
        System.out.println("listening on port " + PORT_NO);
    }

    void runServer() {
        try {
            while (true) {

                Socket socket = listener.accept();
                System.out.println("accepted connetion");
                Connection con =new Connection(socket);
                synchronized (clients) {
                    clients.add(con);
                    con.start();
                    if(clients.size()==1)
                        con.send("welcome... you're the first user");
                    else
                        con.send("welcome...you're the latest of" + clients.size()+ "users");
                }

            }
        } catch (IOException ioe) {
            System.err.println("I/O error: " + ioe.getMessage());
            return;
        }

    }

    private class Connection extends Thread {
        private volatile BufferedReader br;
        private volatile PrintWriter pw;
        private String clientName;

        Connection(Socket s) throws IOException {
            br = new BufferedReader(new InputStreamReader(s.getInputStream()));
            pw = new PrintWriter(s.getOutputStream(), true);
        }

        @Override
        public void run() {
            String line;
            try {
                clientName = br.readLine();
                sendClientsList();
                while ((line = br.readLine()) != null)
                    broadcast(clientName + ": " + line);

            } catch (IOException ioe) {
                System.err.println("I/O error: " + ioe.getMessage());
            } finally {
                System.out.println(clientName + ": " + "finished");
                synchronized (clients) {
                    clients.remove(this);
                    broadcast("now " + clients.size() + "finished");
                    sendClientsList();
                }
            }

        }

        private void broadcast(String message) {
            System.out.println("broadcasting " + message);
            synchronized (clients) {
                for (Connection con : clients)
                    con.send(message);
            }
        }

        private void send(String message) {
            pw.println(message);
        }

        private void sendClientsList() {
            StringBuilder sb = new StringBuilder();
            synchronized (clients) {
                for (Connection con : clients) {
                    sb.append(con.clientName);
                    sb.append(" ");
                }
                broadcast("!" + sb.toString());
            }
        }
    }

    public static void main(String[] args) {
        try {
            System.out.println("ChatServer starting");
            new ChatServer().runServer();
        } catch (IOException ioe) {
            System.err.println("unable to create server socket");
        }

    }

}


=====================================


ChatServer 클래스는 private 상수 필드, private 필드들, 생성자, void runServer()메서드, Thread의 서브 클래스인 private Connection내부 클래스 그리고 runServer() 메소드를 호출하여 연결된 생성자를 호출하는 main()메소드로 구성되어 있다.


생성자에서는 서버 소켓을 생성한 후 채팅 클라이언트로부터 들어오는 연결 요청인 Connection객체를 저장할 목록 배열을 생성한다.


runServer()메소드에서는 무한 반복이 실행되는데 무한 반복을 하면서 먼저 연결 요청을 기다리기 위해 accept()메소드를 호출하며 accept()메소드에서는 연결 요청이 있으면 요청한 클라이언트와 커뮤니케이션 하기 위해 Socket인스턴스를 반환한다. 다음, Socket 인스턴스와 연결된 Connection 객체를 생성하고 Connection 객체를 clients 배열에 추가한 후 Conneciton 스레드를 시작한다. 커뮤니케이션이 시작되면 환영 메시지를 Connection 객체의 소켓과 연결된 클라이언트로 전송한다.


Connetion 스레드의 run()메소드가 실행을 시작하면 먼저 readLine()메소드를 호출해 클라이언트의 이름을 획득하고 다음에 Connection의 void sendClientsList() 메소드를 호출해 연결되어 있는 모든 클라이언트에 새로운 클라이언트가 연결되었다는 통보한다.


sendClientList() 메소드에서는 느낌표(!)와 클라이언트 이름 그리고 빈 문자열(" ")을 하나의 String 객체로 만든 후 채팅에 연결되어 있는 모든 클라이언트에 문자열을 통보하기 위해 Connection의 void broadcast(String message)메소드를 호출한다.


broadcast() 메소드에서는 clients 배열에 저장되어 있는 각각의 Connection 객체가 자신의 void send(message)메소드를 호출하도록 한다.


환영 메시지의 전송이 끝나면 Connection스레드의 run()메소드에서는 반복문을 통해 각각의 클라이언트로부터 한 줄의 데이터를 읽기 위해 readLine()메소드를 호출하여 입력 받은 줄을 모든 클라이언트에 브로드캐스트 한다.


만일 클라이언트의 채팅 사용자가 채팅을 종료하면 클라이언트의 소켓이 닫히게 될 것이다. 클라이언트의 소켓이 닫히면 readLine()메소드가 null을 반환하게 되므로 무한 반복을 수행하는 while문이 종료된다. 반복문이 종료되면 try문의 finally 절로 실행 지점이 옮겨진다. finally절에서는 clients 배열에서 클라이언트 Connection 객체를 제거하고 현재 연결되어 있는 클라이언트 수와 이름을 클라이언트에 브로드캐스트 한다.


비록 ChatServer의 개념이 간단해 보이지만 쉽지 않은 개념인 volatile과 스레드 동기화를 사용하고 있다. 이 예제에서는 다중 스레드에서 접근할 수 있도록 volatile변수들을 선언하고 있는데 이렇게 volatile변수를 선언하여 ChatServer 애플리케이션은 멀티 코어/멀티 프로세서 컴퓨터에서 이 변수에 대한 각각의 캐쉬 복사본을 사용할 수 있게 된다.













'Java > Working-level Java' 카테고리의 다른 글

Thread 2  (0) 2015.01.14
Thread  (0) 2015.01.06
System  (0) 2015.01.06
StringBuffer / StringBuilder  (0) 2015.01.03
ChatClient  (0) 2014.12.26
Posted by af334