Simple TCP Server and Client

A Simple TCP Server and Client

Just like we how we start any programming language, we will implement a simple hello world program in socket programming. In this article, we will implement a simple TCP server and client. The server will listen on a specific port and IP address. The client will connect to the server and send data to the server. After the server receives the data, it will send a response to the client. The client will receive the response and print it out.

Program Structure

image

Source: Socket Programming in C/C++ - GeeksforGeeks

Server Workflow

  1. Create a socket
  2. Bind the socket to a specific IP address and port
  3. Listen for incoming connections
  4. Accept the incoming connection
  5. Receive data from the client
  6. Send data to the client
  7. Close the socket

Server Demo

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>

#define IPADDR "127.0.0.1" /* Server IP */
#define PORT 43866         /* Server Port */
#define STRING_LEN_16 16
#define STRING_LEN_64 64
#define BACKLOG 10

char *_inet_ntoa(struct in_addr *addr, char *ipAddr, int len)
{
    // convert binary IP address to readable IP address
    if (NULL == addr || NULL == ipAddr || 16 > len)
    {
        printf("invalid param\n");
        return NULL;
    }
    unsigned char *tmp = (unsigned char *)addr;
    snprintf(ipAddr, len, "%d.%d.%d.%d", tmp[0], tmp[1], tmp[2], tmp[3]);
    return ipAddr;
}

int main(int argc, char *argv[])
{
    // 1. create a socket socket()
    int sockfd = socket(AF_INET, SOCK_STREAM, 0); // AF_INET: IPv4, SOCK_STREAM: TCP
    if (sockfd == -1)
    {
        printf("fail to call socket, errno[%d, %s]\n", errno, strerror(errno));
        exit(0);
    }

    // build a server address
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));    // initialize the address to 0
    addr.sin_family = AF_INET;         // IPv4
    addr.sin_port = htons(PORT);       // port
    inet_aton(IPADDR, &addr.sin_addr); // IP address
    // inet_aton is deprecated, use inet_pton instead

    // 2. bind the socket to a specific IP address and port
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        printf("fail to call bind, errno[%d, %s]\n", errno, strerror(errno));
        close(sockfd);
        exit(0);
    }

    // 3. listen for incoming connections
    if (listen(sockfd, BACKLOG) == -1)
    {
        printf("fail to call listen, errno[%d, %s]\n", errno, strerror(errno));
        close(sockfd);
        exit(0);
    }

    printf("server listen on %s:%u\n", IPADDR, PORT);

    // 4. accept the incoming connection
    struct sockaddr_in peerAddr;
    socklen_t peerAddrLen = sizeof(struct sockaddr_in);
    int connfd = accept(sockfd, (struct sockaddr *)&peerAddr, &peerAddrLen);
    if (connfd == -1)
    {
        printf("fail to call accept, errno[%d, %s]\n", errno, strerror(errno));
        close(sockfd);
        exit(0);
    }
    char peerIPAddr[STRING_LEN_16];
    _inet_ntoa(&peerAddr.sin_addr, peerIPAddr, STRING_LEN_16);
    printf("peer client address [%s:%u]\n", peerIPAddr, ntohs(peerAddr.sin_port));

    while (1)
    {
        // 5. receive data from the client
        char buf[STRING_LEN_64];
        int n = recv(connfd, buf, STRING_LEN_64 - 1, 0);
        buf[n] = '\0';

        if (0 == n) // == 0 means peer close
        {
            printf("peer close\n");
            break;
        }

        printf("recv msg from client : %s\n", buf);

        sleep(2);

        // 6. send data to the client
        char str[] = "recved!";
        printf("send msg to client : %s\n", str);
        send(connfd, str, strlen(str), 0);
    }

    // 7. close the socket
    close(connfd);
    close(sockfd);

    return 0;
}

Client Workflow

  1. Create a socket
  2. Connect to the server
  3. Send data to the server
  4. Receive data from the server
  5. Close the socket

Client Demo

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <unistd.h>

#define IPADDR "127.0.0.1" /* Server IP */
#define PORT 43866         /* Server Port */
#define STRING_LEN_64 64

int main()
{
    // 1. create a socket socket()
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    printf("sockfd = %d\n", sockfd);
    if (-1 == sockfd)
    {
        printf("fail to call socket, errno[%d, %s]\n", errno, strerror(errno));
        exit(0);
    }

    // 2. connect to the server connect()
    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(addr));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(PORT);
    inet_aton(IPADDR, &addr.sin_addr);
    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) == -1)
    {
        printf("fail to call connect, errno[%d, %s]\n", errno, strerror(errno));
        close(sockfd);
        exit(0);
    }

    // 3. send data to the connected server
    char str[] = "hello world";
    printf("send msg to server : %s\n", str);
    send(sockfd, str, strlen(str), 0);

    // 4. receive data from the connected server
    char buf[STRING_LEN_64];
    int n = recv(sockfd, buf, STRING_LEN_64 - 1, 0);
    buf[n] = '\0';
    printf("recv msg from server : %s\n", buf);

    // 5. close the socket
    close(sockfd);
}

Compiling the program

Compiling:
gcc client.c -o client
gcc server.c -o server

Output of the program

$ ./server # run server on server terminal
server listen on 127.0.0.1:43866
peer client address [127.0.0.1:33844]
recv msg from client : hello world
send msg to client : recved!
peer close
$ ./client # run client on client terminal
sockfd = 3
send msg to server : hello world
recv msg from server : recved!

Potential Issues

  1. The server program can only serve one client at a time.
  2. The code is not capable for IPv6.(inet_ntoa, inet_aton)
  3. The program does not have any security measures in place. It does not authenticate clients or encrypt communication, which could leave it vulnerable to attacks such as spoofing or eavesdropping.

Improvements

  1. Use select() to handle multiple clients.
  2. Use getaddrinfo(), ntop() and pton() to support IPv6.
  3. Use SSL/TLS to encrypt communication.

References

  1. Socket Programming in C/C++ - GeeksforGeeks

  2. Beej’s Guide to Network Programming

  3. Chapter 6 - Advanced UNIX Programming


Last modified on 2023-01-16


Thank you for your support!