-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatClient.java
More file actions
239 lines (223 loc) · 11.7 KB
/
ChatClient.java
File metadata and controls
239 lines (223 loc) · 11.7 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
import java.io.*;
import java.net.*;
import java.nio.charset.StandardCharsets;
import java.time.*;
import java.time.format.DateTimeFormatter;
public class ChatClient extends Thread{
public static DataInputStream serverOutput;
public static DataOutputStream userOutput;
public static String nickname;
public static boolean leave = false;
public static String server;
public static int port;
public static String room;
public static int numRooms = 1;
public static String userID;
public static String startDate;
public static String endDate;
public static int chatSent = 0;
public static int chatRcv = 0;
public static int pmRcv = 0;
public static int pmSent = 0;
public static int charSent = 0;
public static int charRcv = 0;
public static long lastPing = System.currentTimeMillis();
public static int pulseTimer=0;
public static DateTimeFormatter timeFormat = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss");
public static Object lock = new Object();
public static synchronized void pingSent(){
lastPing = System.currentTimeMillis();
pulseTimer = 0;
}
//thread to handle heartbeat pings to server
private static class pingThread extends Thread{
public void run(){
//declare time variable and string to hold message
long currTime;
String pingMsg;
while(!leave){
currTime = System.currentTimeMillis();
pulseTimer += (int) currTime - lastPing;
lastPing = currTime;
if(pulseTimer>10000){
pingMsg = "type:ping,nickname:"+ nickname +",userID:" + userID + ",timestamp:" + LocalDateTime.now().format(timeFormat);
try {
userOutput.writeInt(pingMsg.getBytes().length);
userOutput.write(pingMsg.getBytes());
charSent += pingMsg.length();
chatSent ++;
// the catch doesnt do anything because it really just catches socket closed errors from the socket closing. not good practice but it works so, whatever
} catch (IOException e) {}
// tell the client the ping has been sent
pingSent();
}
}
}
}
public static void main(String argv[]) throws Exception {
if (argv.length!=4) {
System.out.println("ERR - arg " + String.valueOf(argv.length));
}
//parse arguments to variables
server = argv[0];
//test if the ports are valid on the linux server
if(Integer.parseInt(argv[1])<=11000&&Integer.parseInt(argv[1])>=10000){
port = Integer.parseInt(argv[1]);
} else {
System.out.println("ERR - arg 2");
return;
}
nickname = argv[2];
userID = argv[3];
//date and time stuff
LocalDateTime timeKeeper;
//create socket to communicate with server
Socket connection = new Socket(server,port);
//create reader for user input, path to output user input to server, and path for messages from server
userOutput = new DataOutputStream(connection.getOutputStream());
BufferedReader userInput = new BufferedReader (new InputStreamReader(System.in));
serverOutput = new DataInputStream(connection.getInputStream());
//create and start new thread to read input
ChatClient readingThread = new ChatClient();
readingThread.start();
//declare message variable to store message to be sent, size variable to store size of package
String userMessage;
//send register message
timeKeeper = LocalDateTime.now();
userMessage = "type:register,nickname:" + nickname + ",userID:" + userID + ",timestamp:" + timeKeeper.format(timeFormat);
synchronized (lock) {
userOutput.writeInt(userMessage.getBytes().length);
userOutput.write(userMessage.getBytes(), 0, userMessage.getBytes().length);
}
chatSent ++;
charSent += userMessage.length();
room = "lobby";
//set up heartbeat thread
pingThread heartbeat = new pingThread();
heartbeat.start();
//accept new messages forever
while(!leave){
//get user message
userMessage = userInput.readLine();
//check if message is a pm for pm count
if( userMessage.substring (0,4).equals("/msg")){
pmSent++;
}
//checks if user message is a disconnect, send disconnect message instead
//also get current datetime
timeKeeper = LocalDateTime.now();
if(userMessage.length()>=11&&userMessage.substring(0,11).equals("/disconnect")){
userMessage = "type:disconnect,nickname:" + nickname + ",userID:" + userID + ",timestamp:"+ timeKeeper.format(timeFormat);
charSent += userMessage.length();
chatSent ++;
leave = true;
} else {
//otherwise, add metadata to user message and send it
userMessage = "type:text,room:"+ room +",nickname:" + nickname + ",userID:" + userID + ",text:" + userMessage + ",timestamp:" + timeKeeper.format(timeFormat);
charSent += userMessage.length();
chatSent ++;
} // end if else
//send message as length in bytes and then a series of bytes
synchronized(lock){
userOutput.writeInt(userMessage.getBytes().length);
userOutput.write(userMessage.getBytes());
}
//tell the heartbeat timer that a message has been sent
pingSent();
}//end while loop
//on disconnect, print summary and close connection
System.out.println("Summary: start:" + startDate + ", end:" + LocalDateTime.now().format(timeFormat)+", room:"+room+", rooms joined:" + numRooms + ", chat sent:" + chatSent + ", chat rcv:"+ chatRcv + ", pm sent:" + pmSent + ", pm rcv:"+ pmRcv + ", char sent:" + charSent + " char rcv:" + charRcv);
try {
connection.close();
} catch (IOException e){}
}
public void run(){
//initialize string variables
String msg1 = "error";
String payload = "";
String date;
String type;
//initializing this to null is probably bad practice but doing the fifty lines of string manipulation inside a try catch statement strikes me as worse
//so we ball
byte[] messageBytes = null;
while(!leave){
try {
//get size of message and set up appropriate byte array
int n = serverOutput.readInt();
messageBytes = new byte[n];
//read message to previously established array, interpret as string
serverOutput.read(messageBytes, 0, n);
msg1 = new String(messageBytes, StandardCharsets.UTF_8);
//output to user
} catch (IOException e) {
//exit loop so that the thread doesnt nuke my terminal with error messages
leave = true;
break;
}
charRcv += msg1.length();
chatRcv ++;
//if the message got read properly, we gotta spend forever parsing it
if(msg1.equals("error")!=true){
//start by getting type and date since those are present in all packages
date = msg1.substring(msg1.lastIndexOf(",timestamp:")+11);
type = msg1.substring(5,msg1.indexOf(','));
//add date to start of payload, since that needs doing no matter what
payload = date + " :: ";
//switch case statement to determine what to do based on the type
switch (type) {
case "ok":
//on the thing of print starting info
payload = "ChatClient started with server IP: " + server + ", port: " + port + ", nickname:" + nickname + ", client ID: " + userID + ", Date/Time: " + date + "\n";
//also save start date here
startDate = date;
break;
case "error":
//check if error message comes from registration failure, then break
if(msg1.substring(msg1.indexOf(",message:")+9,msg1.lastIndexOf(",timestamp:")).equals("Error: Username already registered")){
leave=true;
}
payload += msg1.substring(msg1.indexOf(",message:")+9,msg1.lastIndexOf(",timestamp:"));
break;
//this is supposed to be "deliver" but ill be honest i dont see a reason to futz with the message framing for this kind of message
//on a real chat client it might be a security risk i guess? idk i dont think it matters
//the server can just relay the message directly from other chat clients
case "text":
//append the room name and the sender to the payload, then append the actual message
payload += "[" + msg1.substring(msg1.indexOf(",room:")+6,msg1.lastIndexOf(",nickname:")) + "] " + msg1.substring(msg1.indexOf(",nickname:")+10,msg1.lastIndexOf(",userID")) + ": " + msg1.substring(msg1.indexOf(",text:")+6, msg1.lastIndexOf(",timestamp:"));
break;
case "pm":
//i probably could have processed the pm message indicator here instead of on the server side in retrospect
//whatever
//just get the text from the message and append it to the payload
payload += msg1.substring(msg1.indexOf(",text:")+6, msg1.lastIndexOf(",timestamp:"));
break;
case "system":
//add system signifier
payload += "* ";
//if system confirms nickname change with message starting in nick, change local nickname variable to rest of message
if(msg1.substring(msg1.indexOf(",message:")+9,msg1.indexOf(",message:")+9+4).equals("nick")){
nickname = msg1.substring(msg1.indexOf(",message:")+9+4,msg1.lastIndexOf(",timestamp:"));
} else {
//if system confirms room change, change the room variable for the client, increase number of rooms visited
if(msg1.substring(msg1.indexOf(",message:")+9,msg1.indexOf(",message:")+9+4).equals("room")){
room = msg1.substring(msg1.indexOf(",message:")+9+4,msg1.lastIndexOf(",timestamp:"));
numRooms++;
} else {
payload += msg1.substring(msg1.indexOf(",message:")+9,msg1.lastIndexOf(",timestamp:"));
}}
break;
case "history":
payload+=msg1.substring(22,msg1.lastIndexOf(",timestamp:"));
break;
default:
//shouldnt ever happen but procs a funny statement if it does for troubleshooting
System.out.println("something hapen,\nruh roh gamers");
break;
}
msg1 = payload;
} //end of if statement
//output chat messages
System.out.println(msg1);
} // end while loop
}// end loop
}// end class