Java

자바 소켓 프로그래밍 예제(채팅 프로그램)

개발만파볼까 2018. 3. 8. 20:44
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 객체를 선언하였습니다.
보통 처음 접속할 때 들리는 클래스이며, 코드가 끝나면 클라이언트 쓰레드 객체가 만들어 집니다.


클라이언트 쓰레드

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
115
116
117
118
119
120
121
122
123
124
125
126
127
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(3080);
        this.socket = socket;
        new ChatClientThread().start();
    }
 
    public void show() {
        // Button
        buttonSend.setBackground(Color.GRAY);
        buttonSend.setForeground(Color.WHITE);
        buttonSend.addActionListener(new ActionListener() {
            @Override
            public void actionPerformed(ActionEvent actionEvent) {
                new ChatClientThread().start();
            }
        });
 
        // Textfield
        textField.setColumns(80);
        textField.addKeyListener(new KeyAdapter() {
            public void keyReleased(KeyEvent e) {
                char keyCode = e.getKeyChar();
                if (keyCode == KeyEvent.VK_ENTER) {
                    sendMessage();
                }
            }
        });
 
        // Pannel
        pannel.setBackground(Color.LIGHT_GRAY);
        pannel.add(textField);
        pannel.add(buttonSend);
        frame.add(BorderLayout.SOUTH, pannel);
 
        // TextArea
        textArea.setEditable(false);
        frame.add(BorderLayout.CENTER, textArea);
 
        // Frame
        frame.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 {
        @Override
        public 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
ChatClientThread 클래스 위주로 설명하겠습니다. BufferedReader은 InputStreamReader의 단점을 보완한 클래스입니다.

InputStreamReader은 byte 크기를 가지고 통신을 합니다. 하지만 byte단위로 통신을 하다보면 효율적이지 못한 부분이 있기 때문에 버퍼를 추가해서 라인 크기로 한꺼번에 보낼 수 있다는 장점을 가졌습니다.




728x90
반응형
LIST