[SOLVED] Computer Networks  Program Solution

50.00 $

Category: Tags: , , , ,
Click Category Button to View Your Next Assignment | Homework

You will receive the following solution file(s) instantly after successful payment:

zip file icon CSE434_Project_Assgn3.zip (7.6 KB)
Assignment Instructions Updated Recently? Submit Below and we will provide new Solution!
Submit New Instructions
🔒 Securely Powered by:
Secure Checkout
5/5 - (1 vote)

Note that in this assignment you can reuse and augment the code you wrote for the first part of assignment 3. No need to write everything from scratch. Alternatively, you may use a programming language other than C/C++, i.e., we do not restrict the usage of C/C++ and the Linux system anymore.

 

  1. UDP based message sending and receiving.

 

In the first part of this assignment, you can communicate with the server using a UDP protocol invented by you. However, in that protocol, the server only echoes back whatever you sent. In the second part, you are going to use the server as a “hub” to allow clients on the “spoke” to communicate with each other, and every message is forwarded by the server. See the following instructions and the sequence diagram after the instructions for the details.

 

  • Write a client-server application. They may be deployed on the same machine or multiple machines as shown in the figure above.
  • Now consider you are the “​client_a​”.
  • You can type a line “​client_a->server#login<password>​”. Here “​client_a​” is the client ID and “​password​” is the password of this client. Each client has a unique ID. Each client also has a password (which may not be unique). You can make up these IDs and passwords as you like. For example, I may type “​duolu->server#login<123456>”​ if my ID is “​duolu​” and my password “​123456​”. For privacy reasons, please do not use some ID and password you use in your daily life. The client will read this line and send to the server in a UDP datagram. Note that the “​red​” lines are typed, not the message sent. Instead, the “blue​ ” lines are sent.​
  • The server keeps a list of all users with their passwords, stored in plain text in a file. At the start of the server program, it reads each line into the memory and stores them in an array. You can define your own data structure in memory and the format in the file to hold the user account information.
  • When the server received the line “client_a->server#login<password><ip><port>​ “,​ it compare with the user information read from the file, and authenticate the user. If the password matches, the server sends back a line “​server->client_a#Success<XYZABC>”,​ and if the password does not match, the server sends back “server->client_a#Error:​ password does not match!​”. Here “XYZABC​ “​ is a six-character token randomly generated by the server containing letters, numbers, and special characters. It is usually unique for each client. For example, it can be something like “05​ &a7F”.​ The “ip​ “​ and “​port​” in the login message is the IP address and port number of the client, and the client program will receive an incoming message from that address and port number. The server needs to remember the token, the IP address, the port number for each client, and it keeps a record on which user is online and which user is offline.
  • Once the client receives the response on a successful login, it also needs to remember the token.
  • You can type a line “​client_a->client_b#<XYZABC><msg_id>some_message”​ on the client, and this line will be sent to the server in a UDP datagram. Here “XYZABC​ “​ is the token that the client obtains from the server at login. Generally, the client will put this token in every message that it sends to the server until logoff, as an indication of the login state. The “​msg_id​” is a randomly generated 10-digit number to uniquely identify a message. Note that both the token and the message ID has fixed length. You may not need to type the token and the message id if the client software remembers them for you. Instead, the client software can append it​. This means you may just type “​client_a->client_b#some_message”.​
  • Once the server receives “​client_a->client_b#<XYZABC><msg_id>some_message”​ from client_a, the server compares the token “​XYZABC​” in the message and the token stored on the server at login time of client_a. There are three possible cases.
    1. If they     do        not       match,             the       server sends back    a          line

“​server->client_a#<XYZABC><msg_id>Error: token error!​” to client_a.

  1. If they match and client_b is offline (i.e., client_b does not log in or has sent logoff or its login session has expired, which will be discussed later), the server sends back a line “​server->client_a#<XYZABC><msg_id>Error: destination offline!​” to client_a.
  2. If they match and client_b is also online (i.e., client_b has successfully logged in), the server forward this message to client_b, using the IP address and port number remembered at login of client_b. Then, the server sends back a line “​server->client_a#<XYZABC><msg_id>Success: some_message”​ to client_a. Like that in the first part of this assignment “​some_message​” is echoed back. Note that this success message does not necessarily indicate that the message is successfully delivered to client_b. It only indicates that the message is successfully delivered to the server
  • The forwarded message to client_b has the exact format as the message sent by client_a, i.e., “client_a->client_b#<ABCXYZ><msg_id>some_message​ “.​ However, the token “XYZABC​ ​” in this message is substituted by the token “ABCXYZ​ “​ generated for client_b at login. The token is used as a secret between each client and the server to authenticate that each message is sent by the legitimate client or the server. No one can spoof a client or the server as long as he or she does not know the token.

10)Once client_b receives the message, it displays it on the screen, and sends back a response “​client_b->server#<ABCXYZ><msg_id>Success: some_message”.​ Note that in this message the token “​ABCXYZ”​ is the token of client_b, the “​msg_id​” is never changed, and the message body is echoed back to the server.

11)You can type a line “​client_a->server#logoff​” to instruct the client to log off. In this case, the client_a can send a message “client_a->server#logoff<XYZABC>​ “​ to the server to log off, and the server sends back a line “​server->client_a#Success<XYZABC>​”. Once the client finishes log off, it will not receive any message from the server.

12) The server regularly checks the last time that a client sends and receives a message. If the client is not active for 5 minutes, the server considers it offline.

13)Note that ​the client may need to run two threads,​ one monitoring the keyboard and the other keeping receiving messages from the server. Alternatively, you may use the I/O multiplexing method shown the socket programming lecture.​ The server does not need to use multiple threads because UDP is connectionless. In this case, you will still block at recvfrom(). However, you can configure timeout on a socket to set up a limit of the waiting time. See this link:

https://stackoverflow.com/questions/2876024/linux-is-there-a-read-or-recv-from-socket-w ith-timeout

14)More details will be added to clarify the requirements based on your comments on the document.

 

Here is a sequence diagram explaining the protocol.

 

 

 

Please write a document that briefly explains your program and show a demo with a few screenshots. You also need to submit your code on the blackboard.

 

Some of you have difficulty in the programming because of the code structure. Here are some detailed instructions.

 

Both the client program and the server program are “event-driven”, which means they respond to certain events and if there are no events the program merely waits on events. Moreover, the protocol is “stateful”, which means that both the client and the server need to remember something happened before, i.e., they need to declare a few variables to hold the “states” of communication. Typically we call this a “session”, i.e., a stateful connection between a client and a server. One client can hold one session to a specific server, but one server needs to hold multiple sessions, one for each client.

 

In our protocol, a session can have two states, either “online” or “offline”. See the following diagram for the client.

 

 

You might wonder what triggers the session state to change. It is the event, i.e., events on the edge of the state diagram. This is similar to the TCP state machine we saw in the lecture. The client may encounter six different types of events from two different sources:

 

  • Initially, the client software is in the “OFFLINE​ ” state.​
  • At the “​OFFLINE“​ state, the user can types ​”​client_a->server#login<password>”,​ and this event is from the keyboard input. Upon this event, the client software sends the login message like “​client_a->server#login<password><ip><port>”​ and transits to the “​LOGIN_SENT​” state. All other events happen in this state will make the client software hold in this state.
  • At the “​LOGIN_SENT​” state, the client may receive the login response sent back by the server like “​server->client_a#Success<XYZABC>​” if the login is successful when the client is at the “​LOGIN_SENT​” state. This is an event from the network side. Upon this successful event, the client software transits to the “ONLINE​ “​ If the login response indicates a failure or an error, it goes back the “​OFFLINE​” state. (Think about what if the server is down and it does not send back any response?) All other events happen in this state will make the client software hold in this state.
  • At the “​ONLINE​” sate, the user can type a line “client_a->client_b#some_message​ ​”, which is an event from the keyboard input. The corresponding action is sending a message like “​client_a->client_b#<XYZABC><msg_id>some_message”​ to the server. Then, the client software transits to the “MESSAGE_SENT​ “​ If some error happens, e.g., the client may receive some spurious message, the client may go back to the “​OFFLINE​” state. This is a design issue and it depends on you. The protocol does not specify every detail.
  • At the “​MESSAGE_SENT​” state, if the client receives the response from the server successfully, it transits to the “​ONLINE​” state. This is an event from the network side. Note that the client can only send one message at a time.
  • Similar to log in, at “​ONLINE​” sate, the user can type a line “client_a->server#logoff​ “,​ and this event is from the keyboard input. Upon this event, the client software sends the logoff message like “​client_a->server#logoff<XYZABC>​”, and transits to the “​LOGOUT_SENT” state.​
  • At the “​LOGOUT_SENT​” state, the client may receive the log off response from the server like “​server->client_a#Success<XYZABC>​”, and transits to the initial “OFFLINE​ “​ Upon successful logout, the client software clears any variables used for the session and restores to a status like just started.

 

You might also wonder how to write the code to implement this state diagram on the client. Here is some example code that may help. Note that this code uses I/O multiplexing. You need to understand the behavior of the code before use it.

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <errno.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

int main() {

 

int ret;

int ​sockfd_tx​ = 0;     int ​sockfd_rx​ = 0;     char send_buffer[1024];     char recv_buffer[1024];     struct sockaddr_in ​serv_addr​;     struct sockaddr_in ​my_addr​;

int ​maxfd​;     fd_set ​read_set​;     ​FD_ZERO​(&​read_set​);

 

sockfd_tx​ = ​socket​(​AF_INET​, ​SOCK_DGRAM​, 0);     if (sockfd_tx < 0) { printf(“socket() error: %s.\n”, strerror(errno));

return -1;

}

 

// The “serv_addr” is the server’s address and port number,

// i.e, the destination address if the client need to send something.

// Note that this “serv_addr” must match with the address in the

// UDP receive code.

// We assume the server is also running on the same machine, and

 

    // hence, the IP address of the server is just “127.0.0.1”.     memset(&​serv_addr​, 0, sizeof(serv_addr));     serv_addr.sin_family = ​AF_INET​;

serv_addr.sin_addr.s_addr = inet_addr(“​127.0.0.1​“);     serv_addr.sin_port = htons(​32000​);

 

sockfd_rx​ = ​socket​(​AF_INET​, ​SOCK_DGRAM​, 0);     if (sockfd_rx < 0) { printf(“socket() error: %s.\n”, strerror(errno));

return -1;

}

// The “my_addr” is the client’s address and port number used for

// receiving responses from the server.

// Note that this is a local address, not a remote address.     memset(&​my_addr​, 0, sizeof(my_addr));     my_addr.sin_family = ​AF_INET​;

my_addr.sin_addr.s_addr = inet_addr(“​127.0.0.1​“);     my_addr.sin_port = htons(​some_semi_random_port_number​);

 

​// Bind “my_addr” to the socket for receiving message from the server.     bind​(​sockfd_rx​,

(struct sockaddr *) &​my_addr​,           sizeof(my_addr));

 

    maxfd​ = ​sockfd_rx​ + 1; ​// Note that the file descriptor of stdin is “0”

 

int state = 0;​// You can use 0 to denote “OFFLINE”,

// and 1 to denote another state, and so on.

int event;          // You can use an integer to represent the type of                    ​// the event you are working on, e.g., 0 is log in, etc.     char token[6]; ​// Assume the token is a 6-character string

 

while (1) {

 

​// Use select to wait on keyboard input or socket receiving.

FD_SET​(fileno(stdin), &​read_set​);

FD_SET​(​sockfd_rx​, &​read_set​);

 

select​(​maxfd​, &​read_set​, NULL, NULL, NULL);

 

if (​FD_ISSET​(fileno(stdin), &​read_set​)) {

 

// Now we know there is a keyboard input event

// ​TODO:​ Figure out which event and process it according to the

// current state

 

fgets​(send_buffer, sizeof(send_buffer), stdin);

event = ​your_function_to_parse_the_event_from_the_string​(…)

if (event == 0 ​/* login event */​) {

if (state == 0 ​/* OFFLINE state */​) {

 

// ​TODO​: take the corresponding action

} else if (state == …) {

 

// ​TODO​: hand errors if the event happens in some state

// that is not expected.

 

}

 

} else if (event == …) {

 

// ​TODO​: process other events

 

}

 

}

if (​FD_ISSET​(​sockfd_rx​, &​read_set​)) {

 

// Now we know there is an event from the network

// ​TODO:​ Figure out which event and process it according to the

// current state

 

ret = ​recv​(​sockfd_rx​, recv_buffer, sizeof(recv_buffer), 0);

 

event = ​your_function_to_parse_the_event_from_the_string​(…)

 

if (event == …) {

 

// ​TODO​: process the event

 

}

}

 

// Now we finished processing the pending event. Just go back to the

// beginning of the loop and wait for another event.

 

 

} ​// This is the end of the while loop

} ​// This is the end of main()

 

See the following diagram for the state transition of a client session on the server. Note that the states of a session are different on the client and on the server because they are not synchronized, i.e., two pieces of programs running in two places.

 

 

For a single client session between client_a and the server,​ there are five different types of events all from the network side. Note that a server can have multiple sessions at the same time. Each session has its own states.

 

  • Initially, all sessions are in the “​OFFLINE​” state.
  • At the “OFFLINE​ “​ state, the server can receive a login message from the client_a, like “​client_a->server#login<password><ip><port>​”, and the session corresponding to the client_a transits to the “LOGIN_RECEIVED​ ​” state. Note that only the session regarding to client_a changes states. Other sessions are irrelevant to this message from client_a.
  • At the “​LOGIN_RECEIVED​” state, the server compares the client ID and the password. If they match with the stored information on the server, the server generates a token and sends back a response like “​server->client_a#Success<XYZABC>”.​ Then, the session transits to the “ONLINE​ “​ If the authentication is unsuccessful, the server sends back a response like “server->client_a#Error:​ password does not match!​” and the session transits back to the “​ OFFLINE​ ​” state.
  • At the “​ ONLINE​ “​ state, the server may receive a message from client_a targeting client_b. In this case, the server immediately sends back a message to client_a like “​server->client_a#<XYZABC><msg_id>Success: some_message”,​ and the session of client_a does not change state (i.e., remain at the​ “​ ONLINE​ “​ state). The message forwarding may not be successful, and the response may indicate a failure. But the session state of client_a does not change.
  • Meanwhile, upon receiving a message from client_a targeting client_b, the server forwards the message to client_b and the session of client_b transits to the “​MESSAGE_FORWARD​” state, if client_b is online (i.e., if the session of client_b is in the “​ONLINE​” state).
  • At the “​MESSAGE_FORWARD​” state, if the server receives a response message from client_b like “​client_b->server#<ABCXYZ><msg_id>Success: some_message​”, the session of client_b transits back to the “ONLINE​ ​” state. Note that there is a tricky case that multiple other clients can send messages all targeting client_b. In this case, even if the session of client_b is in the “​MESSAGE_FORWARD​” state, the server can still forward a message to client_b. However, the server needs to track which message gets the response and which message does not get the response (using the msg_id, which is assumed to be unique). Once all messages get the responses, the session transits back to the “​ONLINE​” state.
  • At the ​”​ONLINE​” state, the server may receive a logoff message from a client, like “​client_a->server#logoff<XYZABC>​”. Upon this event, the server sends back a response like “​server->client_a#Success<XYZABC>​” and the session transits to ​the “​OFFLINE​” state.
  • Alternatively, the server regularly checks the last time that a message is received for each session. If the client is inactive for 5 minutes, the corresponding session transits to the “​OFFLINE​” state, and the token expires. Note that the client may not know this because nothing is sent to notify the client. The client may still send a message to the server. However, from the server’s perspective, the client is put in the “​OFFLINE​” state, and the server will send back a response indicating the client must log in again.

 

The code structure is similar to the client, just all events are from the network.

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <errno.h>

#include <unistd.h>

#include <sys/socket.h>

#include <netinet/in.h>

#include <arpa/inet.h>

 

#include <time.h>

 

// This is a data structure that holds important information on a session. struct ​session​ {

 

char client_id[32]; ​// Assume the client ID is less than 32 characters.     struct sockaddr_in client_addr; ​// IP address and port of the client

// for receiving messages from the

// server.

time_t last_time; ​// The last time when the server receives a message

// from this client.     char token[6];    ​// The token of this session.

int state;        ​// The state of this session, 0 is “OFFLINE”, etc.

 

};

 

int main() {

 

int ret;     int ​sockfd​;

struct sockaddr_in ​serv_addr​, ​cli_addr​;     char recv_buffer[1024];

int recv_len;     socklen_t len;

 

// You may need to use a map to hold all the sessions to find a session

// given an ID. I use an array just for demonstration.     struct ​session​ session_array[16];

 

// This current_session is a variable temporarily hold the session upon

// an event.

struct ​session​ *current_session;

 

sockfd​ = ​socket​(​AF_INET​, ​SOCK_DGRAM​, 0);     if (sockfd < 0) {

printf(“socket() error: %s.\n”, strerror(errno));         return -1;

}

 

// The servaddr is the address and port number that the server will

// keep receiving from.

memset(&​serv_addr​, 0, sizeof(serv_addr));     serv_addr.sin_family = ​AF_INET​;

serv_addr.sin_addr.s_addr = htonl(​INADDR_ANY​);     serv_addr.sin_port = htons(​32000​);

 

bind​(​sockfd​,

(struct sockaddr *) &​serv_addr​,           sizeof(serv_addr));

 

while (1) {

 

// Note that the program will still block on recvfrom()

// You may call select() only on this socket file descriptor with

// a timeout, or set a timeout using the socket options.

 

len = sizeof(cli_addr);

recv_len = ​recvfrom​(​sockfd​, ​// socket file descriptor                  recv_buffer,       ​// receive buffer

sizeof(recv_buffer),  ​// number of bytes to be received

0,

(struct sockaddr *) &​cli_addr​,  ​// client address

&len);             ​// length of client address structure

 

if (recv_len <= 0) {

printf(“recvfrom() error: %s.\n”, strerror(errno));             return -1;

}

 

        // Now we know there is an event from the network

// ​TODO:​ Figure out which event and process it according to the

// current state of the session referred.

 

client_id = ​extract_client_id_from_the_received_string​(…)         ​current_session​ = ​find_the_session_by_client_id​(…)

event = ​your_function_to_parse_the_event_from_the_string​(…)

​// Record the last time that this session is active.         current_session->last_time = time();

 

if (event == 0 ​/* login event */​) {

if (​current_session->​state == 0 ​/* OFFLINE state */​) {

​// ​TODO​: take the corresponding action

} else if (​current_session->​state == …) {

​// ​TODO​: hand errors if the event happens in some state                 ​// that is not expected.

 

}

 

} else if (event == …) {

 

​// ​TODO​: process other events

 

}

 

time_t current_time = time();

 

// Now you may check the time of all clients.

// For each session, if the current time has passed 5 minutes plus

// the last time of the session, the session expires.         ​// ​TODO​: check session liveliness

 

} ​// This is the end of the while loop

 

return 0;

} ​// This is the end of main()

 

This event-driven method is the typical way to program a server or client using the TCP/IP network, especially when dealing with multiple connections. It is generally called the “Reactor” pattern. There is a similar pattern called “Proactor” that relies on asynchronous I/O instead of I/O multiplexing (the Proactor pattern is used less widely, which will not be discussed).

In many applications, there are many event sources other than user input, network, and timer. The states may be quite complicated and the combination of some conditions may trigger a custom defined event. While the program is handling an event, another event can be generated, especially a blocking I/O operation is needed. For example, if your server received a request for downloading a file, the request from the network is an event, and handling this event requires to generate another event of I/O operation (i.e., reading a file). Note that reading a file is a blocking I/O operation, which means your process may hang up there for a while when the operating system kernel asks the disk to spin and put the data into the main memory from an external bus outside the CPU. Even if you use SSD which does not spin, you still need to wait for the bulk data transmission on the SATA or PCI-E bus. While your process calls a blocking I/O function, it can be put into a blocking state by the operating system kernel, which means it is not running on the CPU and making any progress until that I/O operation is finished by the operating system kernel. Using this “Reactor”, you can not wait for the file I/O to be finished because you are only running one thread for all types of events. If the only thread blocks, even for a fraction of a second, it will not be able to react to other events. Assume you are managing a server that handles thousands of clients with the event loop but blocks from time to time in the event handler, you will see a weird situation that all clients are slowed down, but the CPU usage of your server is very low. That means the server program is not slowed by insufficient hardware resources, but an event handler that slacks on I/O operation. This is not noticeable if there are only a few clients and the I/O speed is fast enough.

 

Once the program is getting larger and more complicated, writing everything from scratch would consume much more time and demand much more skills for the programmer. Thus, we rely on existing event-driven frameworks such as libevent (in C), ACE (in C++), boost asio (in C++), Netty (in Java), etc. With those frameworks, first, you define all possible events and write handlers. Then, you register the events and handlers to the framework. If an event is triggered in a handler, you just call the API of the framework to inject the event.

 

Usually, the server program is not just large and complicated in lines of code, but also large at runtime, i.e., it needs many server machines to work together to cope with the heavy network traffic with millions of requests per second from the globe. Think about how many search requests are sent to Google and how many machines are needed to meet the requests. In this case, we need a set of distributed middleware. For example, there are message passing middleware such as Rabbit/MQ, Apache Kafka; there are mapreduce frameworks such as Hadoop; there are distributed data accessing and indexing middleware such as Google BigTable / GFS, Apache Hive / HBase / Hadoop HDFS, Apache Cassandra, etc. You can have a look at the “Big Data Computing” related classes in our school or online for more information. A trend in recent years is the service-oriented architecture (older) and microserver architecture (more recent). The idea is the encapsulation of segments of business logic and functions into loosely coupled services, deployment of the service inside a container, and alignment the development and operation of the services to the business structure. We will not discuss the details of the software architecture, but all of them, all those distributed pieces that make a business running, have the foundation of the TCP/IP network and socket programming.

  • CSE434_Project_Assgn3.zip