1 import java.io.BufferedReader;
2 import java.io.IOException;
3 import java.io.InputStream;
4 import java.io.InputStreamReader;
5 import java.io.OutputStream;
6 import java.io.OutputStreamWriter;
7 import java.io.PrintWriter;
8 import java.net.InetAddress;
9 import java.net.ServerSocket;
10 import java.net.Socket;
11 import java.util.ArrayList;
12 import java.util.List;
13
14 /**
15 * 聊天室服务端
16 *
17 * @author HeFahai
18 *
19 */
20 public class Server {
21 /*
22 * java.net.ServerSocket 运行在服务端的ServerSocket主要有两个作用 1:申请服务端口
23 * 2:监听服务端口,一旦一个客户端通过该端口建立链接,则创建一个Socket用于与该客户端通讯
24 */
25 private ServerSocket server;
26 /*
27 * 该集合用来存放所有客户端的输出流,用于将信息广播给所有客户端。
28 */
29 private List<PrintWriter> allOut;
30
31 public Server() throws IOException {
32 /*
33 * 初始化ServerSocket的同事需要指定服务端口 该端口号不能与系统其他应用程序已申请的端口 号重复,否则会抛出异常。
34 */
35 server = new ServerSocket(8088);
36 // 初始化链表
37 allOut = new ArrayList<PrintWriter>();
38 }
39
40 public void start() {
41 try {
42 /*
43 * ServerSocket提供方法: Socket accept() 该方法会监听ServerSocket申请的服务端口。
44 * 是一个阻塞方法,直到一个客户端通过该端口链接 才会返回一个Socket。这个返回的Scoket是用于 链接客户端进行通讯的。
45 */
46 while (true) {
47 System.out.println("等待客户端连接...");
48 Socket socket = server.accept();
49 System.out.println("客户端已连接!");
50 /*
51 * 启动一个线程与该客户端交互
52 */
53 ClientHander clientHander = new ClientHander(socket);
54 Thread t = new Thread(clientHander);
55 t.start();
56 }
57 } catch (Exception e) {
58 e.printStackTrace();
59 }
60 }
61
62 /**
63 * 将给定的消息广播给所有客户端
64 *
65 * @param message
66 */
67 private void sendMessage(String message) {
68 synchronized (allOut) {
69 // 转发给所有客户端
70 for (PrintWriter o : allOut) {
71 o.println(message);
72 }
73 }
74 }
75
76 public static void main(String[] args) {
77 try {
78 Server server = new Server();
79 server.start();
80 } catch (Exception e) {
81 e.printStackTrace();
82 }
83 }
84
85 /**
86 * 该线程用来完成与指定客户端加护的工作
87 *
88 */
89 private class ClientHander implements Runnable {
90 /*
91 * 当前线程通过这个Socket与指定客户端交互
92 */
93 private Socket socket;
94 /*
95 * 远程计算机地址信息,这里是客户端的地址
96 */
97 private String host;
98
99 public ClientHander(Socket socket) {
100 this.socket = socket;
101 /*
102 * 通过Socket可以远程获取计算机的地址信息
103 */
104 InetAddress address = socket.getInetAddress();
105 /*
106 * 获取远端计算机IP地址的字符串格式
107 */
108 host = address.getHostAddress();
109 }
110
111 public void run() {
112 PrintWriter pw = null;
113 try {
114
115 /*
116 * InputStream getInputStream() Socket提供的该方法可以获取一个输入流,
117 * 通过该流,服务端读取到远端计算机发送过 来的数据。
118 */
119 InputStream in = socket.getInputStream();
120
121 InputStreamReader isr = new InputStreamReader(in, "UTF-8");
122
123 BufferedReader br = new BufferedReader(isr);
124
125 /*
126 * 通过Socket获取输出流,用于将数据发送给客户端
127 */
128 OutputStream os = socket.getOutputStream();
129
130 OutputStreamWriter osw = new OutputStreamWriter(os, "UTF-8");
131
132 pw = new PrintWriter(osw, true);
133
134 /*
135 * 将该客户端的输出流存入到共享集合中
136 *
137 *
138 * 由于多个线程都会调用该集合的add方法向其中添加输出流,所以 为了保证线程安全,可以将该集合上锁。
139 */
140 synchronized (allOut) {
141 allOut.add(pw);
142 }
143
144 System.out.println(host + "上线了!");
145
146 sendMessage(host + "上线了!当前在线" + allOut.size() + "人");
147
148 String message = null;
149 while ((message = br.readLine()) != null) {
150 // System.out.println(host + "说:" + message);
151 // 回复给当前客户端
152 // pw.println(host + "说:" + message);
153
154 sendMessage(host + "说:" + message);
155 }
156 } catch (Exception e) {
157
158 } finally {
159 // 处理客户端断开连接以后的工作
160
161 synchronized (allOut) {
162 // 将该客户端的输出流从共享集合中删除
163 allOut.remove(pw);
164 }
165 System.out.println(host + "下线了!");
166
167 sendMessage(host + "下线了!当前在线" + allOut.size() + "人");
168 if (socket != null) {
169 try {
170 socket.close();
171 } catch (IOException e) {
172 e.printStackTrace();
173 }
174 }
175
176 }
177 }
178 }
179 }