-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathServer.java
More file actions
313 lines (278 loc) · 10.9 KB
/
Copy pathServer.java
File metadata and controls
313 lines (278 loc) · 10.9 KB
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
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
import java.util.*;
import java.io.*;
import java.net.*;
/**
* The backend of the server system. It contains
* the data that is to be persistent for all
* Client Proxy instances, i.e. the users
* known by the server, the logins and the posts.
*
* @version %H%, %I%
*/
public class Server {
private Set<Account> knownUsers = new TreeSet<Account>();
private Set<Login> knownLogins = new TreeSet<Login>();
private List<Post> posts = new LinkedList<Post>();
/**
* The main function of the server. Begin by trying to
* open a new server socket, instantiate a new server
* and begin listening for connections.
*
* @param args the port for the socket will be specified
* at the first of the arguments, defaults to 8080 if
* there are none
*/
public static void main(String[] args) {
try {
ServerSocket socket = new ServerSocket(args.length > 0 ? Integer.parseInt(args[0]) : 8080);
Server server = new Server();
while (true) {
System.out.println("!! Server listening for connections: " + socket.getInetAddress() + ":" + socket.getLocalPort());
Socket clientConnection = socket.accept();
System.out.println("!! Server got a connection from: " + clientConnection.getInetAddress() + ":" + clientConnection.getPort());
try {
ClientProxy.attemptEstablishConnection(clientConnection, server);
} catch (RuntimeException e) {
System.err.println(e.getMessage());
}
}
} catch (IOException ioe) {
ioe.printStackTrace();
} catch (ClassNotFoundException cnfe) {
cnfe.printStackTrace();
}
}
/**
* Get the account associated with a specified Id
*
* @param userId the unique id of an account
* @return an account with the specified userId if one exists, else <code>null</code>
*/
public Account getAccountFor(String userId) {
for (Account a : this.knownUsers)
if (a.getUserId().equals(userId)) return a;
return null;
}
/**
* Get the login associated with a specified Id
*
* @param userId the unique id of an account
* @return a login for an account with the specified Id if one exists, else <code>null</code>
*/
public Login getLoginFor(String userId) {
for (Login l : this.knownLogins){
if (l.getAccount().getUserId().equals(userId)) return l;}
return null;
}
/**
* Add an account to the server
*/
public synchronized void addAccount(Account a) {
this.knownUsers.add(a);
}
/**
* Add an account's login on the server
*/
public synchronized void addLogin(Login l) {
this.knownLogins.add(l);
}
/**
* Remove an account from the server
*/
public synchronized void removeAccount(Account a) {
this.knownUsers.remove(a);
}
/**
* Remove an accounts login from the server
*/
public synchronized void removeLogin(Login l) {
this.knownLogins.remove(l);
}
/**
* Get a set of all known accounts stored on the server
*/
public synchronized Set<Account> getAccounts() {
return new TreeSet<Account>(this.knownUsers);
}
/**
* Get a list of all posts stored on the server
*/
public synchronized List<Post> getPosts() {
return new ArrayList<Post>(this.posts);
}
/**
* Get a list of posts that are new to a specified account
*
* @param account the account to get posts for
* @return a list of the posts that have been made since the last time the account got posts
*/
public synchronized List<Post> getNewPosts(Account account) {
int since = account.getPostAtLastSync();
account.setPostAtLastSync(this.posts.size());
return new ArrayList<Post>(this.posts.subList(since, this.posts.size()));
}
/**
* Get a list of posts that are new to a specified account, and are also made by friends of the account
*
* @param account the account to get posts for
* @return a list of posts that have been made by the account's friends since it last got posts
*/
public synchronized List<Post> getNewFriendPosts(Account account) {
List<Post> result = new ArrayList<Post>();
for (Post p : this.getNewPosts(account)) {
if (account.isFriendsWith(p.getPoster())) result.add(p);
}
return result;
}
/**
* Add a post to the server
*/
public synchronized void addPost(Post p) {
this.posts.add(p);
}
/**
* @version %I%, %G%
*
* The proxy which handles incoming messages from
* the {@link Client} and provides it with requested
* information from the {@link Server}.
*/
static class ClientProxy extends Thread {
private Account account;
private Socket socket;
private Server server;
private ObjectOutputStream outgoing;
private ObjectInputStream incoming;
private ClientProxy(Account account, Socket socket, Server server, ObjectInputStream incoming) throws IOException {
this.account = account;
this.server = server;
this.socket = socket;
this.incoming = incoming;
this.outgoing = new ObjectOutputStream(socket.getOutputStream());
System.out.println("<< Account");
this.outgoing.writeObject(account);
this.outgoing.flush();
}
/**
* Attempt to establish a new connection with a client
*
* @throws IOException
* @throws ClassNotFoundException
* @param socket the socket which the server listens for connections on
* @param server the server which the client proxy is acting as an interface for
*/
public static void attemptEstablishConnection(Socket socket, Server server) throws IOException, ClassNotFoundException {
ObjectInputStream incoming = new ObjectInputStream(socket.getInputStream());
Object handShake = incoming.readObject();
if (handShake instanceof Login) {
Account account = ((Login) handShake).getAccount();
Account knownAccount = server.getAccountFor(account.getUserId());
String password = ((Login) handShake).getPassword();
if (knownAccount == null) {
server.addAccount(account);
server.addLogin(new Login(account, password));
new ClientProxy(account, socket, server, incoming).start();
} else {
String knownPassword = server.getLoginFor(account.getUserId()).getPassword();
if (knownPassword.equals(password) == false) throw new RuntimeException("Wrong password");
new ClientProxy(knownAccount, socket, server, incoming).start();
}
} else {
System.err.println("!! Bad connection attempt from: " + socket.getInetAddress() + ":" + socket.getPort());
}
}
private int globalPostIdCounter = 0;
// The synchronised keyword is required on all methods which may
// be called in parallel on the server from multiple clients at
// the same time.
private synchronized int getUniqueGlobalPostId() {
return ++this.globalPostIdCounter;
}
private void logout(Account a) {
Login currentLogin = this.server.getLoginFor(a.getUserId());
this.server.removeLogin(currentLogin);
this.server.removeAccount(a);
System.out.println("!! " + a.getUserId() + " left the building");
try {
this.outgoing.close();
this.incoming.close();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
private void postMessage(String msg) {
this.server.addPost(new Post(this.getUniqueGlobalPostId(), this.account, msg));
}
private void addFriend(Account a) {
this.account.addFriend(a);
a.addFriend(this.account);
}
private void removeFriend(Account a) {
this.account.removeFriend(a);
a.removeFriend(this.account);
}
private void validatePassword(Login login) {
try {
System.out.println("<< ValidationResponse");
Login validLogin = server.getLoginFor(login.getAccount().getUserId());
if (login.equals(validLogin) && login.getPassword().equals(validLogin.getPassword())){
this.outgoing.writeObject(true);
} else {
this.outgoing.writeObject(false);
}
this.outgoing.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
private void updateAccount(Account old, Login neu) {
server.removeAccount(old);
server.removeLogin(server.getLoginFor(old.getUserId()));
server.addAccount(neu.getAccount());
server.addLogin(neu);
}
private void sync() {
try {
System.out.println("<< SyncResponse");
this.outgoing.
writeObject(new SyncResponse(new HashSet<Account>(this.server.getAccounts()),
new LinkedList<Post>(this.server.getNewFriendPosts(this.account))));
this.outgoing.flush();
} catch (IOException ioe) {
ioe.printStackTrace();
}
}
/**
* Continuously try to handle any incoming messages from clients
*/
public void run() {
try {
while (true) {
Object o = this.incoming.readObject();
System.err.println(">> Received: " + o.getClass().getName());
// o instanceof Account checks if o is an account
// (Account) o type casts o into an Account so that it can be used as one
if (o instanceof Login) {
this.updateAccount(this.account, (Login) o);
} else if (o instanceof ValidatePassword) {
this.validatePassword(((ValidatePassword) o).getLogin());
} else if (o instanceof PostMessage) {
this.postMessage(((PostMessage) o).getMsg());
} else if (o instanceof AddFriend) {
this.addFriend(((AddFriend) o).getFriend());
} else if (o instanceof RemoveFriend) {
this.removeFriend(((RemoveFriend) o).getFriend());
} else if (o instanceof SyncRequest) {
this.sync();
} else if (o instanceof Logout) {
this.logout(((Logout) o).getAccount());
return;
}
}
} catch (Exception e) {
// BAD Practise. Never catch "Exception"s. Too general.
e.printStackTrace();
}
}
}
}