The Makefiles are an iconic approach for compiling and building C/C++ programs. It’s mostly used by developers on Unix, Linux, and similar platforms. It aims to simplify the build process for program executables that may include several modules. In this Makefile tutorial, we’ll briefly discuss, what are its features and how to create a client-server program in C using Makefile.
In the first section of this post, we are covering the Makefile essentials so that you can easily use them in your projects. And then, there is the full source code of the client-server program written in C language. We’ve used socket programming concepts to implement this basic application. Since the objective of this Makefile tutorial is to train you in using Make as a build tool, we’ll create a Makefile to build the socket program.
Before you dive in further to read the post, it’s important to know the target platform version and the tools specification used.
- OS Used: Linux Ubuntu
- OS Version: Ubuntu-13.10- amd64
- Make Version: GNU Make 4.0
- GCC Version: 4.8
- GDB Version: 7.6.1
The above information was also necessary as you may need to create a similar build environment for practice. However, it’s not mandatory to have a one-on-one match of the tools version. The code and concepts mentioned here are generic and should work ideally with any version.
Need your attention here, while working on a C/C++ project, it’s inevitable to use GDB for debugging. So if you want to learn a few quick GDB tips, then please go through the below post.
Next, reading a few C/C++ tips won’t bother you much. Find out if our tutorial does have something new for you.
Now, carefully watch out for how to create a client-server program in C and build it using the Makefile.
Makefile Tutorial – How to Use Makefiles?
What is a Makefile and how does it work?
Makefile is a common build tool for the core C/C++ programmers. It performs the following build tasks.
- It manifests a set of rules to locate the dependencies.
- It produces object codes and builds the target modules.
Processing a Makefile requires the use of the Make tool. It can automatically select a Makefile available in the current directory or we can specify one as its command line argument. The first step for using the Make tool is to prepare the user-defined makefiles.
As referred to above, the makefile drives its significance by compiling/building a project that has multiple C/C++ files to build. Here is the syntax to use the Make tool from the command line.
make [options] [target] -f: With this option, we can specify a custom Makefile name.
Usually, we apply the “-f” option when there are more than one Makefiles in a directory.
e.g. make –f run.mk Where <run.mk> is a Makefile.
Note- If we don’t give any file name, then the Make tool will search for a file that could have names like Makefile or makefile (generally this is the default name of the makefile.)
How to write a Makefile?
A Makefile typically begins with a few variable definitions. Then, there comes a set of target entries for build-specific targets (e.g. “.o” and executable files in C/C++, and “.class” files in Java). Next, there could be a group of commands to execute for the target label.
Below is a generic Makefile template that anyone can follow to create his own.
#Hash for commenting. #Note- The <tab> in the command line is necessary for make to work. target: dependency1 dependency2 ... <tab> command
#target entry for building program executable from program and object files. program: testapp.o gcc -o testapp testapp.o
Create a client-server program in C using Makefile.
In this part of the Makefile tutorial, we are going to implement Client-Server communication using socket programming in C. For this, we’ll create the following two separate modules.
- Client Socket Module (
client.c
) - Server Socket Module (
server.c
)
For establishing a connection, we need to perform the following steps.
First are the steps involved in establishing a socket on the client side.
1. Create a socket with the <socket()> system call.
2. Connect the socket to the address of the server using the <connect()> system call.
3. Start sending and receiving data. There are many ways you can do this. But the simplest is by using the <read()> and <write()> system calls.
Next are the steps required to implement a socket on the server side.
1. Create a socket with the <socket()> system call.
2. Bind the socket to an address using the <bind()> system call. For a server socket on the Internet, an address consists of a port number on the host machine.
3. Accept a connection by making the <accept()> system call. It’ll usually block until a client connects to the server.
4. Send and receive data.
Please note that it’s the following socket structure that we’ve used in both client and server programs.
#include <netinet/in.h> struct sockaddr_in { short sin_family; // e.g. AF_INET unsigned short sin_port; // e.g. htons(3490) struct in_addr sin_addr; // see structin_addr, below char sin_zero[8]; // zero this if you want to }; struct in_addr { unsigned long s_addr; // load with inet_aton() };
Let’s now check out the source code of the client socket.
Client Socket Program.
#include <stdio.h> #include <stdlib.h> #include <unistd.h> #include <string.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <netdb.h> void error(const char * msg) { perror(msg); exit(0); } int main() { int sockfd, portno, n; struct sockaddr_in serv_addr; struct hostent *server; char *hostName = "localhost"; char buffer[256]; portno = 5570; // socket function which return the file descriptor which we will further bind or connect to address of the host machine or server . sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); // here we search the host machine by their name (i.e called hostName). // on linux we find out host name by command – hostname // on window we find out the host name by command – ipconfig/all server = gethostbyname(hostName); if (server == NULL) { fprintf(stderr, "ERROR, no such host\n"); exit(0); } bzero((char *) &serv_addr, sizeof(serv_addr)); serv_addr.sin_family = AF_INET; bcopy((char *) server->h_addr, (char *) &serv_addr.sin_addr.s_addr, server->h_length); serv_addr.sin_port = htons(portno); // here we connect to thefile descriptor with socket address if (connect(sockfd, (structsockaddr *) &serv_addr, sizeof(serv_addr)) < 0) error("ERROR connecting"); printf("Please enter the message: "); bzero(buffer, 256); // fgets() is used for the getting the message from the user or client . fgets(buffer, 255, stdin); //read or write function is used for the writing or reading the message in the socket stream. n = write(sockfd, buffer, strlen(buffer)); if (n < 0) error("ERROR writing to socket"); bzero(buffer, 256); n = read(sockfd, buffer, 255); if (n < 0) error("ERROR reading from socket"); printf("%s\n", buffer); close(sockfd); return 0; }
Now, let’s see what’s inside the server-side code.
Server Socket Program.
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> void error(const char * msg) { perror(msg); exit(1); } int main() { int sockfd, newsockfd, portno; socklen_t clilen; char buffer[256]; struct sockaddr_in serv_addr, cli_addr; int n; sockfd = socket(AF_INET, SOCK_STREAM, 0); if (sockfd < 0) error("ERROR opening socket"); bzero((char *) &serv_addr, sizeof(serv_addr)); portno = 5570; serv_addr.sin_family = AF_INET; serv_addr.sin_addr.s_addr = inet_addr("127.0.0.1"); serv_addr.sin_port = htons(portno); if (bind(sockfd, (structsockaddr * ) &serv_addr, sizeof(serv_addr)) < 0) error("ERROR on binding"); listen(sockfd, 5); clilen = sizeof(cli_addr); // accept function is called whose purpose is to accept the client request and return the new fileDescriptor or and the old file descriptor is for another (i.esockfd) client connections. newsockfd = accept(sockfd, (structsockaddr *) &cli_addr, &clilen); if (newsockfd < 0) error("ERROR on accept"); bzero(buffer, 256); n = read(newsockfd, buffer, 255); if (n < 0) error("ERROR reading from socket"); printf("Here is the message:\n"); n = write(newsockfd, buffer, 30); if (n < 0) error("ERROR writing to socket"); close(newsockfd); close(sockfd); return 0; }
We are now attaching the Makefile created for building the above two modules simultaneously.
Makefile to build Client-Server Programs.
#The pond symbol is used for writing the comments. #make file overview :-- #you can add a little description here. #variable declaration :- cc=gcc // this is the variable which is used in the target. MAKE=make RM =rm #targets . all: client.cserver.c $(cc) -o client client.c $(cc) -o server server.c gnome-terminal -t server --working-directory=/home/techbeamers -e "./server" sleep 10s $(MAKE) client_target #another target for client client_target: ./client clean:server client $(RM) server $(RM) client
Here are a few notes on the Makefile given above.
- To use a variable defined in the Makefile, prefix it with the dollar symbol.
$(variable_name) - All, client_target & clean are the targets present in the Makefile.
- If you don’t provide any target in the Makefile, then the Make tool will build all of them from top to bottom.
Summary – Makefile Tutorial to Create Client-Server Program.
We are hopeful that the above Makefile tutorial will help you immensely. And you’ll use it efficiently in your new C/C++ projects.
It would be great if you let us know your feedback on this post.
Also, you can ask us to write on a topic of your choice. We’ll add it to our writing roadmap.
Lastly, if you enjoyed the post, then please care to share it with friends and on social media.
Enjoy Learning,
TechBeamers.