[CNet-01] Basic C socket programming

Habibie Faried
Nov 3 · 5 min read

Welcome!

This article unfortunately requires you to have at least basic C programming knowledge. If you wanna see the result without touching any of this technical, then I recommend you to directly jump into CNet-03 article

Disclaimer: This article refers to https://www.geeksforgeeks.org/tcp-server-client-implementation-in-c/ with slightly modification and understanding

Introduction to socket programming

The word “socket” means by the tool or way to enable 2 or more process to be connected each other. The word “connected” here means by communication over TCP stack. We’ll cover about socket programming in UDP later on (not in this series).

It means, this allows you to build distributed app that can be accessed and used from internet.

And now, let’s talk about TCP behaviour

[1] Way TCP connection forms up in C language

Refers to image [1], you need to implement at least those functions in respective client and server in order to be functional.

As addition, from “Send/Recv” it’s possible to exit the socket and re-listen again to receive another connections from client. We’ll see that on the source code. At this time, we only code the server side only. For client, we will be using netcat or telnet. Let’s inspect this code!

Refer to: https://github.com/habibiefaried/sysprousingc/blob/master/1.srv.c

1. Socket creation

// socket create and verification 
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
printf("socket creation failed...\n");
exit(0);
}
else
printf("Socket successfully created..\n");

Refers to the manual of function socket: http://man7.org/linux/man-pages/man2/socket.2.html. Parameters explanation:

  • domain, we chose AF_INET for IPv4 Internet protocols
  • type, we chose SOCK_STREAM that means TCP, for UDP is SOCK_DGRAM
  • protocol, just set it to 0

2. Binding the port

// assign IP, PORT 
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(PORT);
// Binding newly created socket to given IP and verification
if ((bind(sockfd, (struct sockaddr *)&servaddr, sizeof(servaddr))) != 0) {
printf("socket bind failed...\n");
exit(0);
}
else
printf("Socket successfully binded..\n");

Refers to the manual of function socket: http://man7.org/linux/man-pages/man2/bind.2.html. Parameters explanation:

  • sockfd, refers to socket file descriptor that created above
  • struct sockaddr, refers to the type struct. you can see this above the bind function. It contains 3 sub parameters, pay attention on INADDR_ANY and PORT. It means that, your port will be listened on port number specified in PORT variable to all interface (0.0.0.0/0).
  • addrlen, we put the length of the struct

3. Listening to incoming tcp connections

printf("Listen failed...\n"); 
exit(0);
}
else
printf("Server listening..\n");
len = sizeof(cli);

Refers to the manual of function socket: http://man7.org/linux/man-pages/man2/listen.2.html. Parameters explanation:

  • sockfd, refers to socket file descriptor that created above
  • backlog, how many pending connections that able to be queued up in the process. Some process just get overwhelmed by so many connections that happens in same time. This queue also takes resources. So, put the number wisely.

4. Accepting the incoming packet

connfd = accept(sockfd, (struct sockaddr *)&cli, &len); 
if (connfd < 0) {
printf("server acccept failed...\n");
exit(0);
}
else
printf("server acccept the client...\n");
// Function for chatting between client and server
func(connfd);

Refers to the manual of function socket: http://man7.org/linux/man-pages/man2/accept.2.html. Parameters explanation:

  • sockfd, refers to socket file descriptor that created above
  • struct sockaddr, refers to the type struct, but it’s different with the one on “binding port”. Should be empty variable
  • *addrlen, put the address of the length of the struct (type int variable)

It returns the connfd, this variable will hold the file descriptor that we can use to communicate with them.

It’s really important and we will be using for communicating with client, until the destruction of the socket

read(sockfd, buff, sizeof(buff)); 
printf("From client: %s\t To client : ", buff);

Refers to the manual of function socket: http://man7.org/linux/man-pages/man2/read.2.html. Parameters explanation:

  • fd, the file descriptor that taken from function accept before.
  • buff, reads up and store the N bytes from file descriptor fd. “N” is taken from the third parameter
  • count, integer type variable

After that, I tried to print whatever the client sent me.

5. Send data to client

n = 0; 
// copy server message in the buffer
while ((buff[n++] = getchar()) != '\n');
// and send that buffer to client
write(sockfd, buff, sizeof(buff));

Refers to the manual of function socket: http://man7.org/linux/man-pages/man2/write.2.html. Parameters explanation:

  • fd, the file descriptor that taken from function accept before.
  • buff, reads up and write the N bytes from file descriptor fd. “N” is taken from the third parameter
  • count, integer type variable

6. Close the connection

close(sockfd)

Refers to the manual of function socket: http://man7.org/linux/man-pages/man2/close.2.html. Parameters explanation:

  • sockfd, the file descriptor that created.

This closes the file descriptor that ready to be used again in the future.

Testing

Let’s compile it with command

gcc 1.srv.c && ./a.out
Server-client communication on port 31337

It’s working!

Concurrency Problem

As you can see on source code, there’s no way to receive the second connection until it finishes with the first one.

No data received from this client2

After I exited from client1, then the data from client2 arrives. I forgot to tell you that I put while(1) on block where accept function resides. This makes the app immortal (run as forever loop) and keep receiving connections, unless it’s killed intentionally by the user.

while (1) {
// Accept the data packet from client and verification
// Always listening to the port, but blocking mechanism
connfd = accept(sockfd, (struct sockaddr *)&cli, sizeof(cli));
if (connfd < 0) {
printf("server acccept failed...\n");
exit(0);
}
else
printf("server acccept the client...\n");

// Function for chatting between client and server
func(connfd);
}
data received from client2, right after client1 closed

Conclusion

So now, we are thinking about scaling the app, in order to make it usable by thousands or even millions of concurrent connection.

In order to do so, you need to know about another important thing called “forking”. Let’s jump to this article -> https://medium.com/@habibiefaried/cnet-02-forking-for-concurrent-access-db5962f14e2b

    Habibie Faried

    Written by

    https://www.habibiefaried.com

    Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
    Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
    Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade