Linux Network Programming TCP Application Example

Keywords: socket Linux Unix Windows

Use TCP sockets to develop a program that simulates a user's remote login.

(1) Design of server-side programs

  • Server-side concurrency
    • This program uses a multi-threaded approach to achieve the server's response to multiple client connection requests.
    • The main program binds the socket to a self-selected port after creating the socket.
    • Leave the socket listening and call the accept function to wait for a connection request from the client.
      • Each time a new client connection request is accepted, the server-side process creates a subprocess that processes the connection request.
      • Server-side processes continue to wait for connection requests from other clients.
  • data format
    • TCP data is streamed and has no fixed format.
    • Define a certain data format in the application, this program uses the carriage return character ('\n') as the end of the data.
  • User Information
    • Save user information in a global array.
    • When the server receives a login user name from the client, it queries the global array for the existence of the user name.
      • If it does not exist, the response character'N'+ end tag'\n'
      • If present, the response character'Y'+ end tag'\n'
    • Waiting for client's password data
      • If the passwords don't match, the response character'N'+ end tag'\n'
      • If the passwords match, the response character'Y'+ End Flag'\n' is sent and a welcome login string is sent to the client

(2) Design of client program

A client's application is simpler than a server's, and the client's main program calls the connect function to connect to it after creating a socket
Server-side self-selected ports use connection sockets returned from the connect function to communicate with the server-side and exchange data.

(3) Client process analysis

(1) Parameter input method

For compiled executables, we use the command line to enter the server IP address and port number.The main function's parameters argc(int) and argv(char **) are used to determine and read the input parameters, where the function atoi is used to convert strings to integer variables.

  • *argc(int)* denotes the number of parameters that are valid for input.
  • *argv(char **)*Stores valid input parameters as strings.
#include <stdio.h>
#include <stdlib.h>

int main(int argc, char **argv)
{
    printf("The number of arguments is %d\n", argc);

    int i;
    for(i = 0; i < argc; ++i) {
        printf("The %d-th argument entered is %s\n",i+1, argv[i]);
    }
    return 0;
}
  • Design of input format: ***. /Client.exe [-a] [Server Address] [-p] [Server Port]***
$ ./Client.exe -a 127.0.0.1 -p 4507
The number of arguments is 5
The 1-th argument entered is ./Client.exe
The 2-th argument entered is -a
The 3-th argument entered is 127.0.0.1
The 4-th argument entered is -p
The 5-th argument entered is 4507

So first check if the number of parameters meets 5. If not, you need to make an error and give a hint to explain the input format.

/*  Check the number of arguments   */
if(argc != 5) {
    printf("Usage: [-a] [Server's IP address] [-p] [Server's Port]\n");
    exit(1);
}

(2) Initialize the server-side address structure

Because our program is TCP-based, here we set the struct sockaddr_in structure type, the protocol family is AF_INET, or IPv4.Then get the port number and server address from *argv(char **)*Finally, check the integrity of the input parameters.

/*  Initialize the server-side address structure    */
struct sockaddr_in Server_addr;
//  memset() will also clear the padding field __pad to zero
memset(&Server_addr, 0, sizeof(struct sockaddr_in));
Server_addr.sin_family = AF_INET;

//  Get server-side ports and addresses from command line arguments
int i;
for(i = 1; i < argc; ++i) { //  Skip executable command
    //  First find the server's IP address
    if(strcmp("-a", argv[i]) == 0) {
        //  One position is [-a], then the next position is [Server's IP address]
        //  inet_pton():convert IPv4 addresses from text to binary form
        if(inet_aton(argv[i+1], &Server_addr.sin_addr) == 0) {
            printf("Invalid Server IP Address\n");
            exit(1);
        }
        continue;
    }
    //  Then find the server port number
    if(strcmp("-p", argv[i]) == 0) {
        //  One position is [-p], then the next position is [Server's Port]
        int Server_port = atoi(argv[i+1]);
        //  Check the validity of the port number
        //  The port number ranges from 1 to 65535.
        if(Server_port < 0 || 65535 < Server_port) {
            printf("Server's port number is invalid.\n");
            exit(1);
        }
        //  host to internet short
        Server_addr.sin_port = htons(Server_port);
        continue;
    }
}

//  Check if an argument is missing
if(Server_addr.sin_port == 0 || Server_addr.sin_addr.s_addr == 0) {
    printf("Usage: [-p] [Server_addr.sin_port] [-a] [Server_addr.sin_addr.s_addr]\n");
    exit(1);
}

(3) Create sockets and send connection requests to the server

/*  Create a TCP socket  */
int conn_fd = socket(AF_INET, SOCK_STREAM, 0);
if(conn_fd < 0) {
    ErrorHanding("socket", __LINE__);
}

/*  Send a connection request to the server  */
if(connect(conn_fd, (struct sockaddr *)&Server_addr, sizeof(struct sockaddr)) < 0) {
    ErrorHanding("connect", __LINE__);
}

(4) Enter user name and password after successful connection and determine whether login is successful

  • The GetUserInfo function is used to pass messages entered by the client

  • The InputUserInfo function is used to send client-side input data to the server and determine if the login was successful

  • The ErrorHanding function is a custom error handling function.

  • The RecvData function is used to retrieve the received data from a custom cache and return the number of received data.

  • The latter two functions are not designed to be considered by the client program.

/*  Enter username and password  */
InputUserInfo(conn_fd, "Username");
InputUserInfo(conn_fd, "Password");

---

int GetUserInfo(char *buf, int len)
{
    /*  User input will be stored in buf  */
    //  Null pointer cannot transmit user input
    if(buf == NULL) {
        return -1;
    }
    //  Start saving user input data
    int i = 0;
    char msg;
    while(((msg = getchar()) != '\n') && (msg != EOF) && (i < len-2)) {
        buf[i++] = msg;
    }
    //  You have to leave two spaces here for the end sign
    buf[i++] = '\n';
    buf[i] = '\0';
    return 0;
}

void InputUserInfo(int conn_fd, const char *MsgString)
{
    //  The username entered by the client is sent through conn_fd
    char input_buf[32];
    char recei_buf[BUFSIZE];
    int flag_UserInfo;

    do {
        //  Here MsgString is output on the client
        printf("%s:", MsgString);

        if(GetUserInfo(input_buf, 32) < 0) {
            printf("Get User data function sending Error.\n");
            exit(1);
        }

        //  Here it means that the user's input is normal
        if(send(conn_fd, input_buf, strlen(input_buf), 0) < 0) {
            ErrorHanding("send", __LINE__);
        }

        //  The server must judge this after sending it
        if(RecvData(conn_fd, recei_buf, sizeof(recei_buf)) < 0) {
            printf("Too much data entered.\n");
            exit(1);
        }

        if(recei_buf[0] == VALID_USERINFO) {
            // The server indicates that the information entered is valid
            flag_UserInfo = VALID_USERINFO;
        } else {
            printf("The %s entered is invalid, please try again.\n");
            flag_UserInfo = INVALID_USERINFO;
        }
    } while(flag_UserInfo == INVALID_USERINFO);
}

(5) Logon successfully returned server-side welcome data and closed connection

//  Successful login and output welcome message
char recv_buf[BUFSIZE];
int msgret; //  The number of bytes read from the custom buffer
if((msgret = RecvData(conn_fd, recv_buf, sizeof(recv_buf))) < 0) {
    printf("Data is too long\n");
    exit(1);
}
//  Output the message returned by the server after successful login
for(i = 0; i < msgret; ++i) {
    printf("%c", recv_buf[i]);
}
printf("\n");

//  Close connection after successfully receiving welcome message
close(conn_fd);

(4) Server-side process resolution

(1) Create a socket

//  Create a socket
int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
if(sock_fd < 0) {
    ErrorHanding("scoket", __LINE__);
}

(2) Initialize the server-side address structure

/*  Initialize the server-side address structure  */
struct sockaddr_in Server_addr;
memset(&Server_addr, 0, sizeof(struct sockaddr_in));
Server_addr.sin_family = AF_INET;
Server_addr.sin_port = htons(SERVER_PORT);
Server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

(3) Set up the socket and bind the socket to the corresponding port

/*  Set up the socket so that it can reuse and bind ports
 *  If the socket that is already in the connected state
 *  is forcibly closed after calling close() without
 *  going through the process of TIME_WAIT, please set OptVal = 0
 */
int OptVal = 1;
if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&OptVal, sizeof(int)) < 0) {
    ErrorHanding("setsockopt",__LINE__);
}

/*  Bind socket to local port  */
if(bind(sock_fd, (struct sockaddr *)&Server_addr, sizeof(struct sockaddr_in)) < 0) {
    ErrorHanding("bind", __LINE__);
}

(4) Make a socket a listening socket

/*  Converting a socket to a listening socket  */
if(listen(sock_fd, LISTEN_SIZE) < 0) {
    ErrorHanding("listen", __LINE__);
}

(4) Processing information sent by clients

struct sockaddr_in Client_addr;
while(1) {
    /*  Receive the client's connection request through accept()
         *  send and receive data using the returned connection socket
         */
    socklen_t Client_len = sizeof(struct sockaddr_in);
    int conn_fd = accept(sock_fd, (struct sockaddr *)&Client_addr, &Client_len);
    if(conn_fd < 0) {
        ErrorHanding("accept",__LINE__);
    }

    printf("Accept a New Client, IP: %s\n", inet_ntoa(Client_addr.sin_addr));

    /*  Create a new child process to handle the connection request just received.
         *  Closed connection by parent process.
         *  The return value of the child process is zero.
         */
    pid_t pid;
    if((pid = fork()) != 0) {
        close(conn_fd);
        continue;
    }

    //  Child process starts here
    int flag_recv = USERNAME;
    int nameIdx;
    while(1) {
        char recv_buf[USERINFOSIZE];
        int msglen;
        if((msglen = recv(conn_fd, recv_buf, sizeof(recv_buf), 0)) < 0) {
            perror("recv");
            exit(1);
        }
        //  Turn end of received data into end of string
        recv_buf[msglen-1] = '\0';

        if(flag_recv == USERNAME) {
            nameIdx = FindName(recv_buf);
            switch(nameIdx) {
                case -2:
                    exit(1);
                case -1:
                    Send_data(conn_fd, "N\n");
                    break;
                default:
                    Send_data(conn_fd, "Y\n");
                    flag_recv = PASSWORD;
                    break;
            }
        } else if(flag_recv == PASSWORD) {
            if(strcmp(users[nameIdx].password, recv_buf) == 0) {
                Send_data(conn_fd, "Y\n");
                Send_data(conn_fd, "Welcom Login My TCP Server.\n");
                printf("%s Login.\n",users[nameIdx].username);
                break;
            } else {
                Send_data(conn_fd,"N\n");
            }
        }
    }
    close(conn_fd);
    close(sock_fd);
    exit(0);
}

(5) Source code examples

(1)Client.c

#include "CustomFun.h"

#define INVALID_USERINFO 'N'    // User information is invalid
#define   VALID_USERINFO 'Y'    // User information is valid

const char *CLIENT_USAGE =
        "Usage: [-a] [Server's IP address] [-p] [Server's Port]";

int GetUserInfo(char *buf, int len)
{
    /*  User input will be stored in buf  */
    //  Null pointer cannot transmit user input
    if(buf == NULL) {
        return -1;
    }
    //  Start saving user input data
    int i = 0;
    char msg;
    while(((msg = getchar()) != '\n') && (msg != EOF) && (i < len-2)) {
        buf[i++] = msg;
    }
    //  You have to leave two spaces here for the end sign
    buf[i++] = '\n';
    buf[i] = '\0';
    return 0;
}

void InputUserInfo(int conn_fd, const char *MsgString)
{
    //  The username entered by the client is sent through conn_fd
    char input_buf[USERINFOSIZE];
    char recv_buf[BUFSIZE];
    int flag_UserInfo;

    do {
        //  Here MsgString is output on the client
        printf("%s:", MsgString);

        if(GetUserInfo(input_buf, USERINFOSIZE) < 0) {
            printf("function GetUserInfo() return Error.\n");
            exit(1);
        }

        //  Here it means that the user's input is normal
        if(send(conn_fd, input_buf, strlen(input_buf), 0) < 0) {
            ErrorHanding("send", __LINE__);
        }

        //  The server must judge this after sending it
        if(RecvMsg(conn_fd, recv_buf, sizeof(recv_buf)) < 0) {
            printf("Too much data entered.\n");
            exit(1);
        }

        if(recv_buf[0] == VALID_USERINFO) {
            // The server indicates that the information entered is valid
            flag_UserInfo = VALID_USERINFO;
        } else {
            printf("The [%s] entered is invalid, please try again.\n", MsgString);
            flag_UserInfo = INVALID_USERINFO;
        }
    } while(flag_UserInfo == INVALID_USERINFO);
}

int main(int argc, char **argv)
{
    /*  Check the number of arguments  */
    if(argc != 5) {
        printf("%s\n", CLIENT_USAGE);
        exit(1);
    }

    /*  Initialize the server-side address structure  */
    struct sockaddr_in Server_addr;
    //  memset() will also clear the padding field __pad to zero
    memset(&Server_addr, 0, sizeof(struct sockaddr_in));
    Server_addr.sin_family = AF_INET;

    //  Get server-side ports and addresses from command line arguments
    int i;
    for(i = 1; i < argc; ++i) { //  Skip executable command
        //  First find the server's IP address
        if(strcmp("-a", argv[i]) == 0) {
            //  One position is [-a], then the next position is [Server's IP address]
            //  inet_pton():convert IPv4 addresses from text to binary form
            if(inet_aton(argv[i+1], &Server_addr.sin_addr) == 0) {
                printf("Invalid Server IP Address\n");
                exit(1);
            }
            continue;
        }
        //  Then find the server port number
        if(strcmp("-p", argv[i]) == 0) {
            //  One position is [-p], then the next position is [Server's Port]
            int Server_port = atoi(argv[i+1]);
            //  Check the validity of the port number
            //  The port number ranges from 1 to 65535.
            if(Server_port < 0 || 65535 < Server_port) {
                printf("Server's port number is invalid.\n");
                exit(1);
            }
            //  host to internet short
            Server_addr.sin_port = htons(Server_port);
        }
    }

    //  Check if an argument is missing
    if(Server_addr.sin_port == 0 || Server_addr.sin_addr.s_addr == 0) {
        printf("%s\n", CLIENT_USAGE);
        exit(1);
    }

    /*  Create a TCP socket  */
    int conn_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(conn_fd < 0) {
        ErrorHanding("socket", __LINE__);
    }

    /*  Send a connection request to the server  */
    if(connect(conn_fd, (struct sockaddr *)&Server_addr, sizeof(struct sockaddr)) < 0) {
        ErrorHanding("connect", __LINE__);
    }

    /*  Enter username and password  */
    //  This step will determine whether the server
    //  is legal according to the user's input.
    InputUserInfo(conn_fd, "Username");
    InputUserInfo(conn_fd, "Password");

    //  Successful login and output welcome message
    char recv_buf[BUFSIZE];
    int msglen; //  The number of bytes read from the custom buffer
    if((msglen = RecvMsg(conn_fd, recv_buf, sizeof(recv_buf))) < 0) {
        printf("data is too long\n");
        exit(1);
    }

    //  Output the message returned by the server after successful login
    for(i = 0; i < msglen; ++i) {
        printf("%c", recv_buf[i]);
    }
    printf("\n");

    //  Close connection after successfully receiving welcome message
    close(conn_fd);
    return 0;
}

(2)Server.c

#include "CustomFun.h"

#define SERVER_PORT 8989  // Server-side port
#define LISTEN_SIZE 12    // Maximum connection request queue length
#define USERNAME    0     // Received username
#define PASSWORD    1     // Received password

struct UserInfo {
    char username[USERINFOSIZE];
    char password[USERINFOSIZE];
};

struct UserInfo users[4] = {
        {"Unix", "System"},
        {"Linux", "System"},
        {"Windows", "System"},
        //  End of array with a string that only means one space
        {" "," "}
};

int FindName(const char *ptrNameString)
{
    /*  Find if the user name exists.
     *  If there is an index, the user name will be returned.
     */
    if(ptrNameString == NULL) {
        printf("In Function FindName, NULL ptrNameString\n");
        return -2;
    }
    int i;
    for(i = 0; users[i].username[0] != ' '; ++i) {
        if(strcmp(users[i].username, ptrNameString) == 0) {
            return i;
        }
    }
    return -1;
}

void Send_data(int conn_fd, const char *MsgString)
{
    if(send(conn_fd, MsgString, strlen(MsgString), 0) < 0) {
        ErrorHanding("send", __LINE__);
    }
}

int main()
{
    /*  Create a socket  */
    int sock_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(sock_fd < 0) {
        ErrorHanding("scoket", __LINE__);
    }

    /*  Set up the socket so that it can reuse and bind ports
     *  If the socket that is already in the connected state
     *  is forcibly closed after calling close() without
     *  going through the process of TIME_WAIT, please set OptVal = 0
     */
    int OptVal = 1;
    if(setsockopt(sock_fd, SOL_SOCKET, SO_REUSEADDR, (void *)&OptVal, sizeof(int)) < 0) {
        ErrorHanding("setsockopt",__LINE__);
    }

    /*  Initialize the server-side address structure  */
    struct sockaddr_in Server_addr;
    memset(&Server_addr, 0, sizeof(struct sockaddr_in));
    Server_addr.sin_family = AF_INET;
    Server_addr.sin_port = htons(SERVER_PORT);
    Server_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    /*  Bind socket to local port  */
    if(bind(sock_fd, (struct sockaddr *)&Server_addr, sizeof(struct sockaddr_in)) < 0) {
        ErrorHanding("bind", __LINE__);
    }

    /*  Converting a socket to a listening socket  */
    if(listen(sock_fd, LISTEN_SIZE) < 0) {
        ErrorHanding("listen", __LINE__);
    }

    struct sockaddr_in Client_addr;
    while(1) {
        /*  Receive the client's connection request through accept()
         *  send and receive data using the returned connection socket
         */
        socklen_t Client_len = sizeof(struct sockaddr_in);
        int conn_fd = accept(sock_fd, (struct sockaddr *)&Client_addr, &Client_len);
        if(conn_fd < 0) {
            ErrorHanding("accept",__LINE__);
        }

        printf("Accept a New Client, IP: %s\n", inet_ntoa(Client_addr.sin_addr));

        /*  Create a new child process to handle the connection request just received.
         *  Closed connection by parent process.
         *  The return value of the child process is zero.
         */
        pid_t pid;
        if((pid = fork()) != 0) {
            close(conn_fd);
            continue;
        }

        //  Child process starts here
        int flag_recv = USERNAME;
        int nameIdx;
        while(1) {
            char recv_buf[USERINFOSIZE];
            int msglen;
            if((msglen = recv(conn_fd, recv_buf, sizeof(recv_buf), 0)) < 0) {
                perror("recv");
                exit(1);
            }
            //  Turn end of received data into end of string
            recv_buf[msglen-1] = '\0';

            if(flag_recv == USERNAME) {
                nameIdx = FindName(recv_buf);
                switch(nameIdx) {
                    case -2:
                        exit(1);
                    case -1:
                        Send_data(conn_fd, "N\n");
                        break;
                    default:
                        Send_data(conn_fd, "Y\n");
                        flag_recv = PASSWORD;
                        break;
                }
            } else if(flag_recv == PASSWORD) {
                if(strcmp(users[nameIdx].password, recv_buf) == 0) {
                    Send_data(conn_fd, "Y\n");
                    Send_data(conn_fd, "Welcom Login My TCP Server.\n");
                    printf("%s Login.\n",users[nameIdx].username);
                    break;
                } else {
                    Send_data(conn_fd,"N\n");
                }
            }
        }
        close(conn_fd);
        close(sock_fd);
        exit(0);
    }
}

(3)CustomFun.h

#ifndef __C_S_CustomFun_H
#define __C_S_CustomFun_H

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#define BUFSIZE 1024
#define USERINFOSIZE 32

void ErrorHanding(const char *ErrorFunName, int Position)
{
    fprintf(stderr, "line:%d\n", Position);
    perror(ErrorFunName);
    exit(1);
}

int  RecvMsg(int conn_fd, char *data_buf, int len)
{
    static char recv_buf[BUFSIZE];  // Custom buffer
    static char *pread = NULL;      // Point to the next data read position
    static int  remain_len = 0;     // Number of bytes remaining in the custom buffer

    // If there is no data in the custom buffer
    // then read data from the socket
    if(remain_len <= 0) {
        //  Store data into a custom buffer and update the remaining bytes
        if((remain_len = recv(conn_fd, recv_buf, sizeof(recv_buf), 0)) < 0) {
            ErrorHanding("recv", __LINE__);
        } else if(remain_len == 0) {
            //  End symbol
            return 0;
        }
        //  The pointer needs to be updated in the normal storage area
        pread = recv_buf;
    }

    //  Read data from custom buffer once
    int i;
    for(i = 0; *pread != '\n'; ++i) {
        if(len < i) {
            //  Can't read that much at once
            return -1;
        }
        data_buf[i] = *pread++;
        remain_len--;
    }
    // Skip end glyph
    remain_len--;
    pread++;

    //  Returns the number of bytes successfully read
    return i;
}

#endif //  __C_S_CustomFun_H

(6) Examples

This application only gives a basic example of C/S model, but there are some details in the implementation of the program.

$ ./Client.exe -a 127.0.0.1 -p 8989
Username:Linux
Password:book
The [Password] entered is invalid, please try again.
Password:System
Welcom Login My TCP Server.
---
$ ./Server.exe
Accept a New Client, IP: 127.0.0.1
Linux Login.
Published 5 original articles. Authorized 18. Visited 30,000+
Private letter follow

Posted by egalp on Sun, 09 Feb 2020 20:37:26 -0800