学会项目管理 工作少点难题
作者:互联网
download:学会项目管理 工作少点难题
该客户端运行在给用户提供unix服务的服务器上。用来读取并收集该服务器上用户的上下线信息,并进行配对整理后发送给服务端汇总。
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
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
package com.dms;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.net.ServerSocket;
import java.net.Socket;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
/**
- DMS服务端,用来接收每个客户端发送过来的
- 配对日志并保存在本地文件中
- @author Administrator
-
*/
public class DMSServer {
//属性定义
//用来接收客户端连接的服务端的ServerSocket
private ServerSocket server;
//用来管理处理客户端请求的线程的线程池
private ExecutorService threadPool;
//保存所有客户端发送过来配对日志的文件
private File serverLogFile;
//消息队列
private BlockingQueue<String> messageQueue = new LinkedBlockingQueue<String>();public DMSServer() throws Exception{
try {
System.out.println("服务端正在初始化...");
//1 解析配置文件server-config.xml
Map<String,String> config = loadConfig();//2 根据配置文件内容初始化属性 init(config); System.out.println("服务端初始化完毕..."); } catch (Exception e) { System.out.println("初始化服务端失败!"); throw e; }
}
/**
- 构造方法初始化第一步,解析配置文件
- @return 返回的Map中保存的是配置文件中的
- 每一条内容,其中key:标签的名字,
- value为标签中间的文本
-
@throws Exception
*/
private Map<String,String> loadConfig() throws Exception{
try {
SAXReader reader = new SAXReader();
Document doc
= reader.read(new File("server-config.xml"));
Element root = doc.getRootElement();Map<String,String> config = new HashMap<String,String>(); /* * 获取<config>标签中的所有子标签 * 并将每一个子标签的名字作为key,中间的 * 文本作为value存入Map集合 */ List<Element> list = root.elements(); for(Element e : list){ String key = e.getName(); String value = e.getTextTrim(); config.put(key, value); } return config;
} catch (Exception e) {
System.out.println("解析配置文件异常!");
e.printStackTrace();
throw e;
}
}
/**
- 构造方法初始化第二步,根据配置项初始化属性
- @param config
-
@throws Exception
/
private void init(Map<String,String> config) throws Exception{
/- 用配置文件中的<logrecfile>初始化属性:serverLogFile
- 用配置文件中的<threadsum>初始化属性:threadPool,这里创建固定大小线程池。该值作为线程池线程数量
- 用配置文件中的<serverport>初始化属性:server,这里这个值为ServerSocket的服务端口
*/
this.server = new ServerSocket(
Integer.parseInt(config.get("serverport"))
);
this.serverLogFile = new File(
config.get("logrecfile")
);
this.threadPool = Executors.newFixedThreadPool(
Integer.parseInt(config.get("threadsum"))
);
}
/** - 服务端开始工作的方法
-
@throws Exception
/
public void start() throws Exception{
/- 实现要求:
- 首先单独启动一个线程,用来运行SaveLogHandler
- 这个任务,目的是保存所有配对日志
- 然后开始循环监听服务端端口,一旦一个客户端连接了,
- 就实例化一个ClientHander,然后将该任务交给线程池
- 使其分配线程来处理与该客户端的交互。
-
*/
try {
System.out.println("服务端开始工作...");
SaveLogHandler slh=new SaveLogHandler();
new Thread(slh).start();while(true){
Socket socket=server.accept();threadPool.execute(new ClientHandler(socket));
}
} catch (Exception e) {
e.printStackTrace();
throw e;
}
}
public static void main(String[] args) {
try {
DMSServer server = new DMSServer();
server.start();
} catch (Exception e) {
System.out.println("启动服务端失败!");
}
}
/**- 该线程负责从消息队列中取出每一条配对日志,
- 并存入到serverLogFile文件
- @author Administrator
-
*/
private class SaveLogHandler implements Runnable{
public void run(){
PrintWriter pw = null;
try {
pw = new PrintWriter(
new FileOutputStream(
serverLogFile,true
)
);
while(true){
if(messageQueue.size()>0){
pw.println(messageQueue.poll());
}else{
pw.flush();
Thread.sleep(500);
}
}
} catch (Exception e) {
e.printStackTrace();
} finally{
if(pw != null){
pw.close();
}
}
}
}
/**
- 处理一个指定客户端请求
- @author Administrator
-
/
private class ClientHandler implements Runnable{
private Socket socket;
public ClientHandler(Socket socket){
this.socket = socket;
}
public void run(){
/- 思路:
- 首先接收客户端发送过来的所有配对日志,
- 直到读取到"OVER"为止,然后将这些配对
- 日志保存到本地的文件中,并回复客户端
- "OK"
- 执行步骤:
- 1:通过Socket创建输出流,用来给客户端
- 发送响应
- 2:通过Socket创建输入流,读取客户端发送
- 过来的日志
- 3:循环读取客户端发送过来的每一行字符串,并
- 先判断是否为字符串"OVER",若不是,则是
- 一条配对日志,那么保存到本地文件,若是,
- 则停止读取。
-
4:成功读取所有日志后回复客户端"OK"
*/
PrintWriter pw = null;
try {
//1
pw = new PrintWriter(
new OutputStreamWriter(
socket.getOutputStream(),"UTF-8"
)
);
//2
BufferedReader br = new BufferedReader(
new InputStreamReader(
socket.getInputStream(),"UTF-8"
)
);//3 String message = null; while((message = br.readLine())!=null){ if("OVER".equals(message)){ break; } //将该日志写入文件保存 messageQueue.offer(message); } //4 pw.println("OK"); pw.flush();
} catch (Exception e) {
e.printStackTrace();
pw.println("ERROR");
pw.flush();
} finally{
try {
//与客户端断开连接释放资源
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
}
package com.dms;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.RandomAccessFile;
import java.net.Socket;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import com.dms.bo.LogData;
import com.dms.bo.LogRec;
/**
- 该客户端运行在给用户提供unix服务的服务器上。
- 用来读取并收集该服务器上用户的上下线信息,并
- 进行配对整理后发送给服务端汇总。
- @author Administrator
-
*/
public class DMSClient {
//属性定义//第一步:解析日志所需属性
//unix系统日志文件
private File logFile;
//保存解析后日志的文件
private File textLogFile;
//书签文件
private File lastPositionFile;
//每次解析日志的条目数
private int batch;//第二步:配对日志所需要属性
//保存配对日志的文件
private File logRecFile;
//保存未配对日志的文件
private File loginLogFile;//第三步:发送日志所需要属性
//服务端地址
private String serverHost;
//服务端端口
private int serverPort;/**
- 构造方法,用来初始化客户端
-
@throws Exception
*/
public DMSClient() throws Exception{
try {
//1 解析配置文件config.xml
Map<String,String> config = loadConfig();
//打桩
System.out.println(config);//2 根据配置文件内容初始化属性 init(config);
} catch (Exception e) {
System.out.println("初始化失败!");
throw e;
}
}
/** - 构造方法初始化第二步,根据配置项初始化属性
- @param config
-
@throws Exception
*/
private void init(Map<String,String> config) throws Exception{
try {
logFile = new File(
config.get("logfile")
);
textLogFile = new File(
config.get("textlogfile")
);
lastPositionFile = new File(
config.get("lastpositionfile")
);
batch = Integer.parseInt(
config.get("batch")
);
logRecFile = new File(
config.get("logrecfile")
);
loginLogFile = new File(
config.get("loginlogfile")
);
serverHost = config.get("serverhost");
serverPort = Integer.parseInt(
config.get("serverport")
);} catch (Exception e) {
System.out.println("初始化属性失败!");
e.printStackTrace();
throw e;
}
}
/**
- 构造方法初始化第一步,解析配置文件
- @return 返回的Map中保存的是配置文件中的
- 每一条内容,其中key:标签的名字,
- value为标签中间的文本
-
@throws Exception
*/
private Map<String,String> loadConfig() throws Exception{
try {
SAXReader reader = new SAXReader();
Document doc
= reader.read(new File("config.xml"));
Element root = doc.getRootElement();Map<String,String> config = new HashMap<String,String>(); /* * 获取<config>标签中的所有子标签 * 并将每一个子标签的名字作为key,中间的 * 文本作为value存入Map集合 */ List<Element> list = root.elements(); for(Element e : list){ String key = e.getName(); String value = e.getTextTrim(); config.put(key, value); } return config;
} catch (Exception e) {
System.out.println("解析配置文件异常!");
e.printStackTrace();
throw e;
}
}
/**
- 客户端开始工作的方法
- 循环执行三步:
- 1:解析日志
- 2:配对日志
- 3:发送日志
*/
public void start(){
parseLogs();
matchLogs();
sendLogs();
// while(true){
// //解析日志
// if(!parseLogs()){
// continue;
// }
// //配对日志
// if(!matchLogs()){
// continue;
// }
// //发送日志
// sendLogs();
// }
}
/** - 第三步:发送日志
- @return true:发送成功
-
false:发送失败
/
private boolean sendLogs(){
/- 实现思路:
- 将logRecFile文件中的所有配对日志读取
- 出来然后连接上服务端并发送过去,若服务端
- 全部接收,就可以将该文件删除,表示发送
- 完毕了。
- 实现步骤:
- 1:logRecFile文件必须存在
- 2:将所有配对日志读取出来并存入一个集合
- 等待发送
- 3:通过Socket连接服务端
- 4:创建输出流
- 5:顺序将所有配对日志按行发送给服务端
- 6:单独发送一个字符串"OVER"表示所有日志
- 均已发送完毕
- 7:创建输入流
- 8:读取服务端发送回来的响应字符串
- 9:若响应的字符串为"OK",表示服务端正常
- 接收了所有日志,这时就可以将logRecFile
- 文件删除并返回true表示发送完毕。
-
*/
Socket socket = null;
try {
//1
if(!logRecFile.exists()){
System.out.println(logRecFile+"不存在!");
return false;
}
//2
List<String> matches
= IOUtil.loadLogRec(logRecFile);//3
socket = new Socket(serverHost,serverPort);//4
PrintWriter pw = new PrintWriter(
new OutputStreamWriter(
socket.getOutputStream(),"UTF-8"
)
);//5
for(String log : matches){
pw.println(log);
}//6
pw.println("OVER");
pw.flush();//7
BufferedReader br = new BufferedReader(
new InputStreamReader(
socket.getInputStream(),"UTF-8"
)
);//8
String response = br.readLine();
//9
if("OK".equals(response)){
logRecFile.delete();
return true;
}else{
System.out.println("发送日志失败!");
return false;
}
} catch (Exception e) {
System.out.println("发送日志失败!");
e.printStackTrace();
} finally{
if(socket != null){
try {
socket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
return false;
}
/**
- 第二步:配对日志
- @return true:配对成功
-
false:配对失败
*/
private boolean matchLogs(){/* * 实现思路: * 将第一步解析的新日志,与上次为配对成功 * 的登入日志全部读取出来,然后再按照user, * pid相同,type一个是7,一个是8进行配对。 * 只要能找到类型为8的,一定可以找到一个 * 能与之配对的登入日志。 * * 实现步骤: * 1:必要的判断 * 1.1:logRecFile是否存在,存在则不再 * 进行新的配对工作,避免覆盖。 * 1.2:textLogFile文件必须存在。 * 2:读取textLogFile将日志读取出来,并 * 存入到集合中。(若干LogData实例) * 3:若loginLogFile文件若存在,则说明 * 有上次未配对成功的日志,也将其读取 * 出来存入集合等待一起配对 * 4:配对工作 * 4.1:创建一个集合,用于保存所有配对日志 * 4.2:创建两个Map分别保存登入日志与登出日志 * 4.3:遍历所有待配对的日志,按照登入与登出 * 分别存入两个Map中, * 其中key:user,pid * value:LogData实例 * 4.4:遍历登出Map,并根据每条登出日志的key * 去登入Map中找到对应的登入日志,并 * 以一个LogRec实例保存该配对日志,然后 * 存入配对日志的集合中。并将该配对日志 * 中的登入日志从登入Map中删除。这样一来 * 登入Map中应当只剩下没有配对的了。 * 5:将配对日志写入到logRecFile中 * 6:将所有未配对日志写入到loginLogFile中 * 7:将textLogFile文件删除 * 8:返回true,表示配对完毕 * */
try {
//1
//1.1
if(logRecFile.exists()){
return true;
}
//1.2
if(!textLogFile.exists()){
System.out.println(textLogFile+"不存在!");
return false;
}//2 List<LogData> list = IOUtil.loadLogData(textLogFile); //3 if(loginLogFile.exists()){ list.addAll( IOUtil.loadLogData(loginLogFile) ); } //4 //4.1 List<LogRec> matches = new ArrayList<LogRec>(); //4.2 Map<String,LogData> loginMap = new HashMap<String,LogData>(); Map<String,LogData> logoutMap = new HashMap<String,LogData>(); //4.3 for(LogData logData : list){ String key = logData.getUser()+","+ logData.getPid(); if(logData.getType()==LogData.TYPE_LOGIN){ loginMap.put(key, logData); }else if(logData.getType()==LogData.TYPE_LOGOUT){ logoutMap.put(key, logData); } } //4.4 Set<Entry<String,LogData>> entrySet = logoutMap.entrySet(); for(Entry<String,LogData> e : entrySet){ LogData logout = e.getValue(); LogData login = loginMap.remove(e.getKey()); LogRec logRec = new LogRec(login,logout); matches.add(logRec); } //5 IOUtil.saveCollection(matches, logRecFile); //6 IOUtil.saveCollection( loginMap.values(),loginLogFile ); //7 textLogFile.delete(); //8 return true;
} catch (Exception e) {
System.out.println("配对日志失败!");
e.printStackTrace();
}
return false;
}
/**
- 第一步:解析日志
- @return true:解析成功
-
false:解析失败
/
private boolean parseLogs(){
/- 实现思路:
- 循环读取batch条日志,然后将每条日志中的
- 5个信息解析出来,最终组成一个字符串,以
- 行为单位,写入到textLogFile文件中
- 实现步骤:
- 1:必要的判断工作
- 1.1:为了避免解析的日志还没有被使用,而
- 第一步又重复执行导致之前日志被覆盖
- 的问题,这里需要判断,若保存解析后
- 的日志文件存在,则第一步不再执行。
- 该日志文件会在第二步配对完毕后删除。
- 1.2:logFile文件必须存在(wtmpx文件)
- 1.3:是否还有日志可以解析
- 2:创建RandomAccessFile来读取logFile
- 3:将指针移动到上次最后读取的位置,准备
- 开始新的解析工作
- 4:解析工作
- 4.1:创建一个List集合,用于保存解析后
- 的每一条日志(LogData实例)
- 4.2:循环batch次,解析每条日志中的
- 5项内容(user,pid,type,time,host)
- 并用一个LogData实例保存,然后将
- 该LogData实例存入集合
- 5:将集合中的所有的日志以行为单位保存到
- textLogFile中
- 6:保存书签信息
- 7:返回true,表示工作完毕
-
*/
RandomAccessFile raf = null;
try {
//1
//1.1
if(textLogFile.exists()){
return true;
}//1.2
if(!logFile.exists()){
System.out.println(logFile+"不存在!");
return false;
}
//1.3
long lastPosition = hasLogs();
//打桩
// System.out.println(
// "lastPosition:"+lastPosition
// );if(lastPosition<0){
System.out.println("没有日志可以解析了!");
return false;
}//2
raf = new RandomAccessFile(logFile,"r");//3
raf.seek(lastPosition);//4
List<LogData> list
= new ArrayList<LogData>();
for(int i=0;i<batch;i++){
//每次解析前都判断是否还有日志可以解析
if(logFile.length()-lastPosition
<LogData.LOG_LENGTH
){
break;
}
//解析user
raf.seek(lastPosition+LogData.USER_OFFSET);
String user
= IOUtil.readString(
raf, LogData.USER_LENGTH
).trim();//解析PID raf.seek(lastPosition+LogData.PID_OFFSET); int pid = raf.readInt(); //解析TYPE raf.seek(lastPosition+LogData.TYPE_OFFSET); short type = raf.readShort(); //解析TIME raf.seek(lastPosition+LogData.TIME_OFFSET); int time = raf.readInt(); //解析HOST raf.seek(lastPosition+LogData.HOST_OFFSET); String host = IOUtil.readString( raf, LogData.HOST_LENGTH ).trim(); LogData log = new LogData(user, pid, type, time, host); list.add(log); //打桩
// System.out.println(log);
//当解析完一条日志后,更新lastPosition lastPosition = raf.getFilePointer();
}
//5
IOUtil.saveCollection(list, textLogFile);//6 保存书签文件
IOUtil.saveLong(
lastPosition, lastPositionFile);//7
return true;
} catch (Exception e) {
System.out.println("解析日志失败!");
e.printStackTrace();
} finally{
if(raf != null){
try {
raf.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}return false;
}
标签:难题,java,项目管理,配对,学会,import,new,日志,config 来源: https://blog.51cto.com/u_15204698/2819402