FIT2100 2018 Prac 05

FIT2100 Practical #5 Interprocess Communication in Unix/Linux Week 9 Semester 2 2018
Dr Jojo Wong
Lecturer, Faculty of IT
Email: © 2016-2018, Monash University
September 11, 2018
© 2016-2018, Faculty of IT, Monash University

Revision Status:
$Id: FIT2100-Practical-05.tex, Version 2.0 2018/09/11 16:50 Jojo $
The contents presented in this practical (including the practical tasks) were adapted from David Curry’s texts.
• David A. Curry (1989). C on the UNIX System, O’Reilly.
• David A. Curry (1996). UNIX Systems Programming for SVR4, O’Reilly.
© 2016-2018, Faculty of IT, Monash University

CONTENTS 3
1 Background 5
2.1 Creatingapipe……………………………. 5 2.2 Closingthepipe …………………………… 6 2.3 TryItYourself(0.5marks)………………………. 6
3 Named Pipes (FIFO) 8
3.1 Creatinganamedpipe………………………… 8 3.2 Usinganamedpipe …………………………. 9 3.3 TryItYourself(1.5marks)………………………. 9
4 Message Queues 11
4.1 Settingupamessagequeue……………………… 12 4.2 Exchangingdatawithamessagequeue………………… 12 4.3 PracticalTask1(3marks)………………………. 14
5 Sockets 17
5.1 Howdoesthesocketwork? ……………………… 17
5.2 Creatingasocket ………………………….. 18 5.2.1 Whatdoestheserverprocessneedtodo? . . . . . . . . . . . . . . . 18 5.2.2 Whatdoestheclientprocessneedtodo?. . . . . . . . . . . . . . . . 20
5.3 Transferringdatawithasocket ……………………. 20
© 2016-2018, Faculty of IT, Monash University

CONTENTS 4
5.4 PracticalTask2(3marks)………………………. 21
© 2016-2018, Faculty of IT, Monash University

1 Background 5
1 Background
This practical is aimed to extend your knowledge on the concepts of interprocess communica- tion in the Unix/Linux environment. We will explore four different mechanisms which enable two processes executing on the same computer system to communicate with each other: basic pipes, named pipes (FIFO), message queues, and sockets.
The pre-lab preparation (under the Try It Yourself subsections in Section 2.3 and Section 3.3) should be completed before attending the lab session. The two practical tasks presented under Section 4.3 and Section 5.4 are to be assessed in the lab.
Before you attempt any of the tasks in this prac, create a folder named PRAC05 under the FIT2100 folder (~/Document/FIT2100). Save all your source files under this PRAC05 folder.
A pipe is the most basic form of interprocess communication that can be used to join two processes together. It is setup by a special pair of file descriptors which connect the two processes in communication.
When Process A writes data to its pipe file descriptor, Process B can read that data from its pipe file descriptor. Likewise, when Process B writes to its pipe file dsecriptor, Process A can then read the data from its pipe file descriptor.
In the Unix shell, you would have used the pipeline command (|). For example, the following Unix command sends the output of ls to wc -l to indicate the number of files or directories found in the current directory.
$ ls | wc −l
2.1 Creating a pipe
Programmatically, a pipe is created with the built-in function pipe() defined under the library. The function returns 0 if a pipe is successfully created; it returns -1 otherwise indicating a pipe cannot be created, and the reason for failure is stored in the external variable errno.
© 2016-2018, Faculty of IT, Monash University

2.2 Closing the pipe 6
#include
int pipe(int pipefd[2]);
By invoking the pipe() function, two file descriptors are created: (i) pipefd[0] is open for reading; (ii) pipefd[1] is open for writing. Basically, the two file descriptors are connected as a pipe — which allows data written at the write end of the pipe (pipefd[1]) to be readable from the read end of the pipe (pipefd[0]).
After creating a pipe, the calling process (who makes the call to pipe()) usually creates a child process by calling fork(). The two related processes can then communicate using the pipe in one direction. Thus, either the parent may send data to the child or the child may send data to the parent; but not in both directions. If a bi-directional communication is required, two pipes must be setup: one for the parent to use to send data to the child; and one for the child to use to send data to the parent.
Note: Each pipe has a limited buffer size described by the constant PIPE_BUF defined under the library. The size limit may differ from version to version of Unix implemen- tations; for Linux, the minimum capacity is 4096 bytes. Also, it is possible to have more than one process writing to a pipe by applying the function dup() or dup2() (from ) on the file descriptor.
2.2 Closing the pipe
Communication can take place as long as both the read and write ends of a pipe are open. If one end of a pipe is closed, that would affect the communication between the two processes:
• If the read end of a pipe has been closed, any attempt to write to the pipe will result in a SIGPIPE signal being sent to the process attempting to write.
• If the write end of the pipe has been closed, any further reads from the pipe will return 0 as an indicator of the end-of-file.
2.3 Try It Yourself (0.5 marks)
Create the program “pipecat.c” presented in the next page and run it. Your task is to describe what the program does by adding a comment at the beginning of the source file.
© 2016-2018, Faculty of IT, Monash University

2.3 Try It Yourself (0.5 marks) 7
#include #include #include #include #include
int main(int argc, char ∗argv[]) {
pid_t pid ;
int pipefd [2];
int status ;
∗ Create a pipe . ∗/
if (pipe(pipefd) < 0) { perror ("pipe") ; exit (1) ; } ∗ Create a child process . if ((pid = fork()) < 0) { perror("fork"); exit (1) ; } ∗ The child process executes "cat". ∗/ if (pid == 0) { /∗ ∗ Attach the standard input to the pipe. dup2(pipefd[0], 0); close(pipefd [1]) ; execl ("/bin/cat" , "cat" , NULL) ; perror ("exec") ; _exit (127) ; } ∗ The parent process is not reading from the pipe. close(pipefd [0]) ; 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 © 2016-2018, Faculty of IT, Monash University 3 Named Pipes (FIFO) 8 ∗ Write the mail message to the pipe. write(pipefd[1], "Greetings.\n\n", 12); write ( pipefd [1] , "This is your program saying hello .\n" , 35) ; write ( pipefd [1] , "Hope you enjoy this week ' s prac .\n\n" , 34) ; ∗ Close the pipe and wait for the child to exit. close(pipefd [1]) ; waitpid(pid, &status, 0); exit (0) ; 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 3 Named Pipes (FIFO) Pipes introduced in the previous section work just like files. They are associated with file descriptors and are accessed by the low-level I/O functions — read() and write(). However, pipes do not exist in the file system and hence they do not have filenames or path names. One major limitation of pipes is that they can only be used between related processes (parent and child pairs). Named pipes, also known as FIFO special files, are a variation that does associate with an entry in the file system. With a name attached, named pipes can then be used by processes that are unrelated processes (not parent and child pairs) for both reading and writing. 3.1 Creating a named pipe To create a named pipe, the built-in function mkfifo() is used. #include #include
int mkfifo ( const char ∗pathname , mode_t mode) ;
Upon successful creation of a named pipe, 0 is returned. In the case of a failure, -1 is returned (and errno is set accordingly to indicate the error).
© 2016-2018, Faculty of IT, Monash University

3.2 Using a named pipe 9
The first parameter pathname specifies the path name of the named pipe to be created, where the path name must not already exist. The second parameter mode indicates the set of file permissions for the named pipe. For example, the following statement creates a named pipe “/etc/mypipe” that is readable and writable by all users.
#include #include
mkfifo (“/etc/mypipe” , 0666) ;
3.2 Using a named pipe
Once a named pipe (FIFO) has been created, it must be opened for use with the open() function which we have been using for opening or creating a file for reading and/or writing (recall the tasks that we have done in Practical 2). Once the named pipe has been opened, the pipe can be accessed for reading and writing with the low-level I/O functions — read() and write() .
Just like the unnamed pipes, an attempt to read from an empty FIFO will cause the process to block and waiting for a write. An attempt to write to a FIFO that has no processes reading it will result in a SIGPIPE signal. When the last writer on a FIFO closes it, the reader will receive an end-of-file indication.
3.3 Try It Yourself (1.5 marks)
The following two programs deploy a FIFO (named pipe) for communication as a server and a client. First, understand what the server program “fifoserver.c” does. Your task then is to complete the partial code presented for the client program “fifoclient.c” and submit the completed program as part of the pre-lab submission.
The server program creates a FIFO for reading and displays anything it receives from the named pipe to the standard output. The client program, on the other hand, opens the FIFO created by the server program for writing, and writes anything that it reads from the standard input to the named pipe.
© 2016-2018, Faculty of IT, Monash University

3.3 Try It Yourself (1.5 marks) 10
The server program “fifoserver.c”:
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
#include #include
#include #include #include #include
#define FIFONAME “myfifo”
int main(int argc, char ∗argv[]) {
int n, fd;
char buffer [1024];
∗ Remove any previous FIFO. ∗/
unlink (FIFONAME) ; /∗
∗ Create the FIFO.
i f ( mkfifo (FIFONAME,
perror(“server :
exit (1) ; }
0666) < 0) { mkfifo"); ∗ Open the FIFO for reading . ∗/ if ((fd = open(FIFONAME, O_RDONLY)) < 0) { perror("server: open"); exit (1) ; } ∗ Read from the FIFO until end−of−file and ∗ print what we get on the standard input. while ((n = read(fd, buffer , sizeof(buffer))) > 0) {
write(1, buffer , n); }
close ( fd ) ;
exit (0) ; }
© 2016-2018, Faculty of IT, Monash University

4 Message Queues 11
The client program “fifoclient.c”:
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
#include #include
#include #include #include #include
#define FIFONAME “myfifo”
int main(int argc, char ∗argv[]) {
int n, fd;
char buffer [1024];
∗ Open the FIFO for writing . It was created by the server . ∗/
if ((fd = open(FIFONAME, O_WRONLY)) < 0) { perror("client : open"); exit (1) ; ∗ YOUR TASK: ∗ Read from the standard input, and copy the data to the FIFO. ∗/ close ( fd ) ; exit (0) ; } To execute these two programs, run the server program in the background before running the client program. For example: $ ./fifoserver & $ ./ fifoclient < /etc/passwd 4 Message Queues Message queues is another mechanism for interprocess communication in Unix. A message queue is represented as a linked list of message “packets” exchanged between processes, where each of a fixed maximum size. To preserve the order in which messages arrive, messages are © 2016-2018, Faculty of IT, Monash University 1 2 3 4 5 6 4.1 Setting up a message queue 12 added to the end of the queue. However, messages can be received in any order determined by the receiving processes based on the message type. 4.1 Setting up a message queue Amessagequeueisdescribedasastructureoftypestruct msqid_dsdefinedinthe library and each queue is defined by a unique identifier (queue id). Before a process attempts
to use a message queue, it must obtain the queue identifier by calling the msgget() function.
#include #include #include
int msgget(key_t key, int msgflg);
The first parameter key specifies the key to be associated with the message queue. If the key contains the value of zero (defined by the constant IPC_PRIVATE), a new message queue is always created. If the key contains a non-zero value, either a new queue is created, or the identifier of an existing queue is returned. This depends on whether the second parameter msgflg is set to the constant IPC_CREAT and whether the given key already exists.
If the msgflg parameter is set to both IPC_CREAT and IPC_EXCL and a message queue already exists with the given key, -1 is returned indicating that the message queue cannot be created and the error that occurred is set in errno. However, if the creation is successful, the message queue identifier is returned.
4.2 Exchanging data with a message queue
Two functions are used for sending and receiving messages on a message queue — msgsnd() and msgrcv().
#include #include #include
int msgsnd(int msqid, const void ∗msgbuf, size_t msgsz, int msgflg); int msgrcv( int msqid , void ∗msgbuf , size_t msgsz , long msgtype , int
msgflg ) ;
© 2016-2018, Faculty of IT, Monash University

4.2 Exchanging data with a message queue 13 Sending data on a message queue
The msgsnd() function takes on four arguments: a message queue identifier (queue id), a reference to a user-defined message buffer, an integer indicating the size of the message, and a flag option. A message pointed by msgbuf (the second parameter) is sent on the message queue identified by msqid (the first parameter). If the message is successfully sent, the function return 0; -1 is returned otherwise highlighting an error has occurred.
Messages exchanged on a message queue defined using the following structure:
struct msgbuf {
long mtype ; /∗ message type , must be > 0 ∗/ char mtext [ ] ; /∗ message data ∗/
The mtype field of the structure contains a positive integer value which can be used by the receiving process to select which type of message to receive. The mtext field represents the message buffer whose size is defined by msgsz in terms of bytes (the third parameter of the msgsnd() function).
Note: By default, if the message queue is full, the msgsnd() call will block until the queue becomes empty. If the msgflg parameter is set to IPC_NOWAIT, the msgsnd() call will fail with a failure code being returned.
Receiving data from a message queue
The msgrcv() function takes on five arguments: a message queue identifier (queue id), a reference to a user-defined message buffer, an integer indicating the size of the message re- ceived, an integer specifying the message type, and a flag option. If the message is successfully received, the number of bytes stored in the message buffer referenced by msgbuf is returned. If an error occurs, -1 is returned and the error is set in errno accordingly.
When receiving messages, the receiving process must specify the message type (msgtype) in the msgrcv() function.
• If msgtype is set to the value of 0, the first message in the queue will be read.
• If msgtype is set to a value greater than 0, the first message in the queue of type
msgtype will be read.
• If msgtype is set to a value less than 0, the first message in the queue with the lowest
type less than or equal to the absolute value of msgtype will be read.
© 2016-2018, Faculty of IT, Monash University
浙大学霸代写 加微信 cstutorcs
4.3 Practical Task 1 (3 marks) 14
Note: If no message of the requested type is available in the message queue and the parameter msgflg is set to IPC_NOWAIT, the msgrcv() call will fail with a failure code being returned. Otherwise, the msgrcv() call will block until a message of the specified type arrives in the message queue or the queue is removed from the system.
4.3 Practical Task 1 (3 marks)
In this first practical task, we will re-implement the client-server program from Section 3.3 using a message queue. Your task is to complete the partial code given for the server (“msqserver.c”) program and the client program (“msqclient.c”).
The server program creates a message queue in order to receive messages sent from the client program. Messages of “Type 1” received by the server program will be displayed on the standard output. A message of “Type 2” is used by the client to inform the server that there are no more messages. Note that the client program reads from the standard input and sends the data as messages of “Type 1” to the server.
Note: You should run these two programs as before with the server program first running at the background. You should also find out the purpose of calling the msgctl() function in the server program.
© 2016-2018, Faculty of IT, Monash University
Computer Science Tutoring
4.3 Practical Task 1 (3 marks) 15
The server program “msqserver.c”:
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
#include #include
#include #include #include #include
#define MSQKEY 34858
#define MSQSIZE 32
struct msgbuf { long mtype ;
char mtext[MSQSIZE]; };
int main(int argc, char ∗argv[]) {
key_t key ;
int n, msqid;
struct msgbuf mbuf;
∗ Create a new message queue. IPC_CREAT is used to create it , ∗ and IPC_EXCL to make sure it does not exist already.
∗ If you get an error on this , something on your system is
∗ using the same key −−− change MSQKEY to something else . ∗/
key = MSQKEY;
if ((msqid = msgget(key , IPC_CREAT | IPC_EXCL | 0666)) < 0) { perror("server: msgget"); exit (1) ; } ∗ Receive messages from the queue . ∗ Messages of type 1 are to be printed on the standard output; ∗ a message of type 2 indicates that no more data. ∗/ while((n = msgrcv(msqid, &mbuf, MSQSIZE, 0, 0)) > 0) { if (mbuf.mtype == 1) {
∗ YOUR TASK:
∗ Write messages of type 1 to the standard output. ∗/
© 2016-2018, Faculty of IT, Monash University
Programming Help, Add QQ: 749389476
4.3 Practical Task 1 (3 marks) 16
else if (mbuf.mtype == 2) { /∗
∗ Remove the message queue from the system.
if (msgctl(msqid, IPC_RMID, (struct msqid_ds ∗) 0) < 0) { perror("server : msgctl"); exit (1) ; } exit (0) ; } 50 51 52 53 54 55 56 57 58 59 60 61 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 The client program “msqclient.c”: #include #include #include #include #include #include
#include
#define MSQKEY 34858 #define MSQSIZE 32
struct msgbuf {
long mtype ;
char mtext[MSQSIZE]; };
int main(int argc, char ∗argv[])
key_t key ;
int n, msqid;
struct msgbuf mbuf;
∗ Get a message queue. The server must have created it. ∗/
key = MSQKEY;
if ((msqid = msgget(key , 0666)) < 0) { perror("client : msgget"); exit (1) ; © 2016-2018, Faculty of IT, Monash University 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 5 Sockets 17 ∗ YOUR TASK: ∗ Read data from the standard input and send as messages of type 1. ∗/ ∗ Send a message of type 2 to indicate no more data. ∗/ mbuf.mtype = 2; memset(mbuf.mtext, 0, MSQSIZE); if (msgsnd(msqid, &mbuf, MSQSIZE, 0) < 0) { perror("client : msgsnd"); exit (1) ; exit (0) ; Sockets are similar to named pipes in which they provide an address in the file system where unrelated processes may use for communication. However, sockets are different from named pipes in terms of how they are accessed. Sockets are implemented using the socket interface which consists of a set of functions to create, destroy and transfer data. Interprocess communication with sockets is often described in terms of the client-server model which we have seen. The server program first requests the operating system (OS) for a socket. Once it is given with a socket, it then assigns a well-known name (address) to that socket. The name should always be the same, such that the client programs will know how to contact to the server program via that socket. 5.1 How does the socket work? After the socket has been named, the server process listens on the socket for any connection requests from the client processes. When a connection request arrives, the server can choose to accept or reject. If the server accepted the connection, the OS connects the server and the client together at the socket. The client process, on the other hand, begins by requesting for a socket from the OS. It then asks the OS to join its socket to a socket of a specific name that it would want to establish a connection. The OS attempts to search for a socket with the specific name. If such socket © 2016-2018, Faculty of IT, Monash University 5.2 Creating a socket 18 was found, the OS sends a connection request to the process which is listening on that socket (the server process). Once the server and the client are connected at the socket, they can then communicate with each other by reading and writing data to and from the socket, just like a pipe. 5.2 Creating a socket A socket is created using the built-in function socket() defined in the library. A socket descriptor represented as a small non-negative integer (similar to a file descriptor) is returned when a socket is successfully created. If the function failed to create the socket, -1 is returned and the reason for the failure is indicated in errno.
#include #include
int socket ( int domain , int type , int protocol ) ;
The first parameter domain specifies the address family in which the address of the socket should be interpreted. For communication between processes on the same computer system, the domain parameter is assigned with the constant AF_UNIX (the Unix domain) — in which the addresses are treated as Unix path names.
The second parameter type defines which type of communication semantic (channel) that the socket supports. The two types which are of interest are SOCK_STREAM and SOCK_DGRAM. SOCK_STREAM supports a bi-directional communication with continuous byte streams — it is guaranteed that the data is delivered in the order it was sent, and no data can be sent until the connection is established. SOCK_DGRAM supports datagrams which are distinct packets of data — it is not guaranteed that the data is delivered in the order it was sent, and it is even not guaranteed that the data is delivered at all.
The last parameter protocol specifies a particular protocol to be used with the socket. The protocol number to use is specific to the communication domain (the address family). For the Unix domain (AF_UNIX), the corresponding protocol family is defined by the constant PF_UNIX. However, if the protocol parameter is set to 0, the OS will determine which is the suitable protocol.
5.2.1 What does the server process need to do?
In order for the server process to exchange data with the client process, it has to invoke the following set of functions.
© 2016-2018, Faculty of IT, Monash University

5.2 Creating a socket 19
Naming a socket Once the socket has been created, the server process must provide a name (or a known address) to the socket by using the bind() function; otherwise the socket cannot be used by the client processes. If binding is successful, 0 is returned; if it fails to bind (often due to the address has already been used), -1 is returned and the errno is set accordingly.
#include #include
int bind(int sockfd , const struct sockaddr ∗name, int addrlen);
Once the binding has been completed, the communication channel referenced by the first parameter sockfd is assigned with the address described by the second parameter name. The length of the address is indicated by the last parameter addrlen.
Note: The name parameter is a structure of a generic type (sockaddr) for socket addresses. In the Unix domain, the socket address is actually of type sockaddr_un with the following structure — where the sun_family represents the address family (set to AF_UNIX) and the sun_path represents the path name of the socket.
struct sockaddr_un {
short sun_family ; /∗ AF_UNIX ∗/ char sun_path [108]; /∗ pathname ∗/
Waiting for connections On a stream-based communication via a socket, the server process must notify the OS when it is ready to accept any connection requests from client processes. The function listen() is used for this purpose. On success, the function returns 0; -1 is returned otherwise.
#include #include
int listen(int sockfd , int backlog);
The socket that the server is listening on for connection requests is referenced by the first parameter sockfd. The second parameter backlog indicates the number of pending connec- tion requests allowed. If a connection request arrives when the queue of pending connections is full, the client process will received an error indicating that the connection is refused.
Accepting connections When the server process has decided to accept the connection request, it uses the function accept() to achieve this. A new socket descriptor (a non-negative integer) will be returned, in which the server process can use this new socket descriptor to
© 2016-2018, Faculty of IT, Monash University

5.3 Transferring data with a socket 20
communicate with the client process. The existing socket descriptor from the server — the one with a known address assigned — is used for accepting other connection requests.
#include #include
int accept(int sockfd , struct sockaddr ∗name, int ∗addrlen);
If the connection is accepted successfully, the socket address of the client process will be referenced by the second parameter name and its address length is stored in the last parameter addrlen. If unsuccessful, -1 is returned and the reason for the failure is set in errno.
5.2.2 What does the client process need to do?
The client process, on the other hand, is required to connect to the server process that it intends to communicate on a stream-based socket (SOCK_STREAM). To do this, the client process should invoke the connect() function.
#include #include
int connect(int sockfd , const struct sockaddr ∗name, int addrlen);
The first parameter sockfd refers to a socket to be connected to the server process which the client intends to communicate with. The known address of the socket from the server end is referenced by the second paramater (name) where the length of the address is specified by the last parameter (addrlen). When the connection is successful, 0 is returned; -1 is returned otherwise and the error is set in errno accordingly.
5.3 Transferring data with a socket
On a stream-based communcation via a socket, the low-level I/O functions read() and write() can be used by the client and server processes. However, there are two functions specifically used with stream-based sockets — recv() and send().
#include #include
int recv(int sockfd , char ∗buffer , int len , int flags);
int send(int scokfd , const char ∗buffer , int len , int flags);
© 2016-2018, Faculty of IT, Monash University

5.4 Practical Task 2 (3 marks) 21
These two functions are in fact identical to read() and write(). However, there is a fourth argument which allows the program to specify options (described by flags) that would affect the way in which the data is sent or received.
One such option is defined by the constant MSG_PEEK — if this option is set in the first call to recv(), the data is copied to the buffer as usual but it is not “consumed” (the data is not yet read). The next call to recv() will return the same data. In a way, this enables a program to have a peek at the received data and can then decide what to do with the data without having to actually “read” it.
Destroying the socket The communication channel established by a socket can be termi- nated by using the close() system call, except that if the socket is stream-based, the closure on the socket will block until all the data has been transmitted. Alternatively, the shutdown() function can be used.
#include #include
int shutdown(int sockfd , int how);
The first parameter sockfd refers to the socket to be shut down and the second parameter how indicates the way the socket should be shut down. If how is set to 0, further reads will be disallowed; if how is 1, further writes will be disallowed; if how is 2, further reads and writes will be disallowed.
5.4 Practical Task 2 (3 marks)
In this practical task, we will re-implement the client-server program from Section 3.3 and Section 4.3. Complete the partial code given for the server program (“socketserver.c”) and the client program (“socketclient.c”), and run both programs as before with the server program first running at the background.
© 2016-2018, Faculty of IT, Monash University

5.4 Practical Task 2 (3 marks) 22
The server program “socketserver.c”:
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
#include #include