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.