In the C language, establishing a client-server connection through a socket requires several steps and functions. Some of these are used to retrieve data from the server you want to connect to, others are used to create the socket or convert addresses.
Although there is no particular order, this series of steps and function calls must be done in an order because their results are used in the input arguments of the subsequent function.
In this Linux Hint article, you will learn how to use the connect() function and create a socket from scratch to connect remotely to a server.
We begin with a description of the syntax, the input and output arguments that make up this function, and a theoretical explanation of how it works. Then, we will look at an example that shows step-by-step process on how to create and connect a socket.
Since using this function requires a knowledge of the data structures that make up its input arguments, in a special section we will look at its composition, the data type of its members, and the parameters that define it in each.
Syntax of the connect() Function in C Language
Description of the Connect Function in C Language
The connect() function establishes the connection of a socket between a client and a server.
This function connects the socket, whose descriptor is sockfd, to the server and whose address is stored in the serv_addr structure, which has a length of addrlen.
If the connection is successful, connect() returns 0 as the result and if the connection could not be established, -1.
Below is a detailed description of each input argument and its purpose in the connect() function.
sockfd: This is an integer with the socket descriptor. This descriptor is the result returned by the socket() function and is used to connect, send and receive data from the server.
serv_addr: This is the pointer to a structure of type sockaddr which stores the address of the server to connect to.
addrlen: This is the size of the array member sa_data of the structure sockaddr, where the address, port number, etc. are stored.
Steps Before Calling the connect() Function to Connect to a Server.
The call to the connect() function is the last of a series of steps necessary to establish a successful connection. These steps consist of defining the variables and structures that the functions will use: creating a socket and getting the IP address of the server we want to connect etc. We explain in five steps how to create a socket and connect it to a server using the connect() function.
How to Create, Configure, and Connect a Socket Step by Step from Scratch in the C Language
Step 1: In the first step, we will include the following headers and define the following variables:
#include <string.h>
#include <stdlib.h>
#include <uistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
int socket_id ;
int port = 80;
Int error;
char buffer [1025);
The integer socket_id is the identifier of the socket through which a connection is made and through which data is sent and received. The variable port specifies the port number of the socket. In this case 80 for service http and the integer error is used to detect errors in the various functions.
Then, we will define the pointer to a structure of type hostent named server and one of type sockaddr_in named client. These structures store information such as IP addresses, connection type, etc. Those of type hostent store the server information, while those of type sockadrr_in store the data of connection. We use the memcpy() function to clear all the data in the hostent structure.
Step 2: Once the hostent structure is defined, the next step is to determine the IP address of the hostname we want to connect to. For this, C provides several functions that can resolve an address. In this example, we will use gethostbyname(). This function returns a hostent structure with the domain data passed in the input argument as a string or a pointer to it. The if condition will break the connection in case of an error, for example, a non-existent domain name.
if(server_id == NULL)
{
printf("\nError getting domain data.\n");
return 1;
}
Step 3: Create a socket sockket_id and get its identifier. The socket() function returns the identifier of the created socket based on the address family, type, and connection protocol. In this case, AF _INET, SOCK _STREAM for an IP4 and TCP connection.
Step 4: Set the connection configuration in the sockaddr_in structure. To do this, access its members and assign them values to specify the service and address family. The port number is specified in direct binary format and therefore must be converted using the htons() function.
In this example, we are going to set AF_INET in the sin_family field for IPv4 addresses and in sin_port we assign port 80 for the “http” service.
client.sin_port = htons(port);
Step 5: The next step is to copy the address from the server structure to the client structure to use as the connection address for the socket. One way to do this is to use the bcopy() function as follows:
(char *) &client.sin_addr.s_addr,
sizeof(server->h_length));
Copy the address into the structure sockaddr, which is used by the connect() function as input argument for serv_addr.
We do this by a cast of structures and to save lines of code. We will do this in the call to the connect() function in the input argument serv_addr.
Below, you can see how to correctly convert the information of the client structure:
Next, call the connect() function and pass the identifier socket_id as the first argument. As the second argument, the sockaddr structure and finally the size of the structure.
The integer error is passed as the input argument, which we use to check if the connection was successful. The following snippet shows how to call the connect() function and check if it was successful:
If (error h_addr,
(char *) &client.sin_addr.s_addr,
sizeof(server->h_length));
error=connect(socket_id, (struct sockaddr *) &client,sizeof(client));
if (error <0){
printf ("\nCould not establish connection to the server\n");
close( socket_id);
return 1;
}
printf ("\nConnectto : %s\n", inet_ntoa( client.sin_addr ));
}
The image shows the compilation and execution of the code where it connects to the "www.google.com" server. This code establishes the connection and then closes the application by disconnecting.
The following snippet uses the fgets() functions to enter strings via the command console and the send() and recv() functions to send http commands to the server and receive the response:
The image we see below shows the execution of the complete code with the sending of the http “GET” command and the response of the server:
The sockaddr_in Structure
Structures of type sockaddr_in store the configuration of the connection type and address. Their members are used to specify the family, the address to use, and the port number.
Below, you can see this structure type along with a detailed list of each member:
{
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
sin_family: This integer specifies one of the family options to be returned by the server. The address family options are:
AF_INET for IPv4 addresses.
AF_INET6 for IPv6 addresses.
sin_port: Sets the port number of the service to use.
sin_addr: This is the abbreviation for Internet address. This is a structure of type in_addr where the internet address is stored.
sin_zero: This is an array of 8 unsigned characters with the value 0, which serve as padding to compensate for the 14 bytes size of the sockaddr structure.
The Hostent Structure
This type of structure stores useful data provided by the server when a query is made with functions such as gethostbynam(). These socket connection prequeries resolve addresses and return information such as the domain name, address family, and a list of addresses if more than one exists. Below you can see this structure along with a detailed description of each element:
{
char *h_name;
char **h_aliases;
int h_addrtype;
int h_length;
char **h_addr_list;
#define h_addr h_addr_list[0]
};
h_name: This is a pointer to a string with the domain name for which it was queried.
h_aliases: This is a list of alternative names.
h_addrtype: This member specifies the address family for which it was queried.
h_length: This specifies the size of the address.
h_addr_list: This is the pointer to a list of address pointers. If the server returns more than one address for the queried name, each is accessed via this list pointer.
h_addr : This structure defines h_addr as a pointer to the first address in the h_addr_list, so access is through this definition.
Conclusion
In this Linux Hint article, we explained everything about the connect() function in the C language.
We looked at the syntax of this function, as well as the types of arguments and structures it uses in its inputs. Also, we have explained step by step how to create a socket and connect to a remote server using this function.
In order for you to have a deeper knowledge of sockets in this language, we have created a special section that explains the structures used by socket functions, detailing each of their members the function they perform in the input arguments of the different functions.