/*********************************************************************************
Copyright (cpp) Novatek Microelectronics Corp, Ltd. All Rights Reserved.
\file client.cpp
\brief achieve two terninal communication
\for Liunx
\chip none
\Date 2024-08-22
\version v0.0.1
*********************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include <time.h>
#include <signal.h>
#include "PrintLevel.h"
#define BUFFER_SIZE (1024)
#define NAMESIZE (50)
#define INITSOCKETE (-1)
#define TIMENAMESIZE (20)
#define LOGNAMESIZE (100)
#define MYPORT (2222)
#define IP ("127.0.0.1")
typedef unsigned int u32;
typedef unsigned short u16;
typedef char u8;
typedef int s32;
s32 gPrintAPILevel = EN_PRINTAPI_MSG;
static s32 giClientSocket = INITSOCKETE;
static FILE *gpLogFile = NULL;
static u8 gcUserName[NAMESIZE] = {0};
static s32 iQuitNum = 1;
static pthread_mutex_t gLogMutex = PTHREAD_MUTEX_INITIALIZER;//Static initilization
static pthread_mutex_t gQuitMutex = PTHREAD_MUTEX_INITIALIZER;//Static initilization
volatile sig_atomic_t vsClientRun = 1;
typedef enum _EN_ERRO_RETURN
{
EN_FGETS_FAIL = -6,
EN_NULL_POINTER = -5,
EN_CREATSOC_FAIL = -4,
EN_CONNSOC_FAIL = -3,
EN_THREAD_JOINFAIL = -2,
EN_THREAD_CREATFAIL = -1,
EN_SUCCESS = 0
}EN_ERRO_RETURN;
/*!
@brisf:Signal processing program, used to process SIGINT and SIGTERM signals
*@para[in] s32 iSigNum:
*@return void:
*/
void SigHandler(s32 iSigNum)
{
PRINTAPI_TRC("Enter SigHandler API\n");
PRINTAPI_DBG("Signum is %d\n", iSigNum);
if(iSigNum == SIGINT)
{
PRINTAPI_ERR("Received signal Ctr+C, shutting down client...\n");
vsClientRun = 0;
}
else if(iSigNum == SIGTERM)
{
PRINTAPI_ERR("Received signal Ctr+Z shutting down client...\n");
vsClientRun = 0;
}
PRINTAPI_TRC("Out SigHandler API\n");
}
/*!
@brisf:log_message of user at different time
*@para[in] const u8 *message:comminication context
*@return void:
*/
void LogMess(const u8 *pMess)
{
PRINTAPI_TRC("Enter LogMess API\n");
time_t tCurrTime = 0;
struct tm *stpLocalTime = NULL;
u8 cTimeStr[TIMENAMESIZE] = {0};
u8 cLogFileName[LOGNAMESIZE] = {0};
if(nullptr == pMess)
{
PRINTAPI_ERR("LogMess pMess is nullpointer\n");
PRINTAPI_TRC("Out LogMess API\n");
return;
}
PRINTAPI_DBG("LogMess pMess is %s\n", pMess);
time(&tCurrTime);
stpLocalTime = localtime(&tCurrTime);
strftime(cTimeStr, sizeof(cTimeStr), "%Y-%m-%d %H:%M:%S", stpLocalTime);
//Define log name using username and only socket id
snprintf(cLogFileName, sizeof(cLogFileName), "chat_log_%s_%d.txt", gcUserName, giClientSocket);//Auto add '\0'
pthread_mutex_lock(&gLogMutex);
gpLogFile = fopen(cLogFileName, "a");
if (gpLogFile == NULL)
{
PRINTAPI_ERR("LogMess pMess fopen gpLogFile fail\n");
PRINTAPI_TRC("Out LogMess API\n");
return;
}
fprintf(gpLogFile, "[%s]: %s\n", cTimeStr, pMess);//Not add '\0'
fflush(gpLogFile);
pthread_mutex_unlock(&gLogMutex);
PRINTAPI_TRC("Out LogMess API\n");
}
/*!
@brisf:read_input:user input communication context
*@para[in] void *arg: NULL
*@return void*:
*/
void *ReadInput(void *pArg)
{
PRINTAPI_TRC("Enter read_input API\n");
s32 iClientSocket = 0;//If pArg is not EN_NULL_POINTER and then give value to iClientSocket
u8 cBuff[BUFFER_SIZE] = {0};
u8 cTmpBuff[BUFFER_SIZE] = {0};
if(nullptr == pArg)
{
PRINTAPI_ERR("ReadInput API pArg is nullpointer\n");
return NULL;
}
iClientSocket = *(s32 *)pArg;//If pArg is not EN_NULL_POINTER and then give value to iClientSocket
PRINTAPI_DBG("ReadInput API iClientSocket is %d\n", iClientSocket);
PRINTAPI_MSG("\n*********Type 'quit' to exit*******\n");
PRINTAPI_DBG("ReadInput API iQuitNum is %d . vsClientRun is %d \n", iQuitNum, vsClientRun);
while(vsClientRun && iQuitNum)
{
memset(cBuff, 0, BUFFER_SIZE);
memset(cTmpBuff, 0, BUFFER_SIZE);
PRINTAPI_MSG("%s input : ", gcUserName);
snprintf(cBuff, sizeof(cBuff), "%s: ", gcUserName);//Uername write to cBuff auto add "\0" end
fgets(cTmpBuff, sizeof(cTmpBuff), stdin);
cTmpBuff[strcspn(cTmpBuff, "\n")] = '\0'; //Remove '\n'
if(0 == (strcmp(cTmpBuff,"quit")))
{
pthread_mutex_lock(&gQuitMutex);
iQuitNum = 0;
pthread_mutex_unlock(&gQuitMutex);
continue;
}
strncat(cBuff,cTmpBuff,strlen(cTmpBuff));//Auto add '\0' at end
cBuff[strlen(cBuff)] = '\0';
PRINTAPI_MSG(" Send context : %s\n", cBuff);
LogMess(cBuff);
if((send(iClientSocket, cBuff, strlen(cBuff), 0)) < 0)
{
PRINTAPI_ERR("ReadInput API send erro\n");
pthread_mutex_lock(&gQuitMutex);
iQuitNum = 0;
pthread_mutex_unlock(&gQuitMutex);
continue;// iQuitNum = 0 jump loop
}
else if(0 == (send(iClientSocket, cBuff, strlen(cBuff), 0)))
{
PRINTAPI_ERR("Server disconnect\n");
pthread_mutex_lock(&gQuitMutex);
iQuitNum = 0;
pthread_mutex_unlock(&gQuitMutex);
continue;// iQuitNum = 0 jump loop
}
}
if(iClientSocket != INITSOCKETE)
{
close(iClientSocket);
iClientSocket = INITSOCKETE;
}
pthread_exit(NULL);
PRINTAPI_TRC("Out ReadInput API\n");
return NULL;
}
/*!
@brisf:read_input:receive_messages from server
*@para[in] void *pArg: NULL
*@return void*:
*/
void *RecvMess(void *pArg)
{
PRINTAPI_TRC("Enter RecvMess API\n");
s32 iClientSocket = 0;
u8 cBuff[BUFFER_SIZE] = {0};
s32 iBytesRead = 0;
if(nullptr == pArg)
{
PRINTAPI_ERR("RecvMess API pArg is NullPointer\n");
return NULL;
}
iClientSocket = *(s32 *)pArg;
PRINTAPI_DBG("RecvMess API iClientSocket is %d\n", iClientSocket);
PRINTAPI_DBG("RecvMess API iQuitNum is %d vsClientRun is %d \n", iQuitNum, vsClientRun);
while(vsClientRun && iQuitNum)
{
iBytesRead = recv(iClientSocket, cBuff, BUFFER_SIZE - 1, 0);//Recv in loop
PRINTAPI_DBG("RecvMess API iBytesRead is %d\n", iBytesRead);
if(iBytesRead < 0)
{
PRINTAPI_ERR("RecvMess API recv erro\n");
pthread_mutex_lock(&gQuitMutex);
iQuitNum = 0;
pthread_mutex_unlock(&gQuitMutex);
continue;
}
else if(0 == iBytesRead)
{
PRINTAPI_ERR("Server Disconnect\n");
pthread_mutex_lock(&gQuitMutex);
iQuitNum = 0;
pthread_mutex_unlock(&gQuitMutex);
continue;
}
cBuff[iBytesRead] = '\0';
PRINTAPI_MSG("\n");
PRINTAPI_MSG(" %s\n", cBuff);
LogMess(cBuff);
}
if(iClientSocket != INITSOCKETE)
{
close(iClientSocket);
iClientSocket = INITSOCKETE;
}
pthread_exit(NULL);
PRINTAPI_TRC("Out RecvMess API\n");
return NULL;
}
/*!
@brisf:main
*@return s32:
*\retval -6 If EN_FGETS_FAIL
*\retval -5 If EN_NULL_POINTER
*\retval -4 If EN_CREATSOC_FAIL.
*\retval -3 If ConnSocFai
*\retval -2 If EN_THREAD_JOINFAIL.
*\retval -1 If EN_THREAD_CREATFAIL
*\retval 0 If EN_SUCCESS.
*/
s32 main()
{
PRINTAPI_TRC("Enter main API\n");
//Init
EN_ERRO_RETURN enRet = EN_SUCCESS;
pthread_t tidInput = 0;
pthread_t tidRecv = 0;
//Register signal handler
struct sigaction stSig = {};
memset(&stSig, 0, sizeof(stSig));
stSig.sa_handler = SigHandler;
sigaction(SIGINT, &stSig, NULL);
sigaction(SIGTERM, &stSig, NULL);
//Set server address
struct sockaddr_in stSockServAddr = {};
memset(&stSockServAddr, 0, sizeof(stSockServAddr));// Use zero fill
stSockServAddr.sin_family = AF_INET;//Use IPV4 address
stSockServAddr.sin_port = htons(MYPORT);
inet_pton(AF_INET, IP, &stSockServAddr.sin_addr);
//Input client name
PRINTAPI_MSG("Enter your name: ");
if((fgets(gcUserName, sizeof(gcUserName), stdin)) == NULL)
{
PRINTAPI_ERR("Fgets username fail\n");
enRet = EN_FGETS_FAIL;
PRINTAPI_DBG("enRet = %d\n", enRet);
goto FREE_END;
}
gcUserName[strcspn(gcUserName, "\n")] = '\0'; //Remove newline character
//Create client socket
giClientSocket = socket(AF_INET, SOCK_STREAM, 0);
if(giClientSocket < 0)
{
PRINTAPI_ERR("Creat socket fail\n");
enRet = EN_CREATSOC_FAIL;
PRINTAPI_DBG("enRet = %d\n", enRet);
goto FREE_END;
}
//Connect to server
if((connect(giClientSocket, (struct sockaddr *)&stSockServAddr, sizeof(stSockServAddr))) < 0)
{
PRINTAPI_ERR("Connect server fail\n");
enRet = EN_CONNSOC_FAIL;
PRINTAPI_DBG("enRet = %d\n", enRet);
goto FREE_END;
}
PRINTAPI_MSG("Connected to server\n");
//Creat recv pthread
if((pthread_create(&tidRecv, NULL, RecvMess, &giClientSocket)) != 0)
{
PRINTAPI_ERR("Create pthread tidRecv fail\n");
enRet = EN_THREAD_CREATFAIL;
PRINTAPI_DBG("enRet = %d\n", enRet);
goto THREAD_END;//If creat fail, then dont need join,goto end;
}
//Creat write pthread
if((pthread_create(&tidInput, NULL, ReadInput, &giClientSocket)) != 0)
{
PRINTAPI_ERR("Create pthread tidInput fail\n");
enRet = EN_THREAD_CREATFAIL;
PRINTAPI_DBG("enRet = %d\n", enRet);
goto THREAD_END;
}
THREAD_END:
if((pthread_join(tidRecv, NULL)) != 0)
{
PRINTAPI_ERR("Thread tidRecv join fail\n");
enRet = EN_THREAD_JOINFAIL;
PRINTAPI_DBG("enRet = %d\n", enRet);
goto THREAD2_END;
}
THREAD2_END:
if((pthread_join(tidInput, NULL)) != 0)
{
PRINTAPI_ERR("Thread tidInput join fail\n");
enRet = EN_THREAD_JOINFAIL;
PRINTAPI_DBG("enRet = %d\n", enRet);
return enRet;
}
FREE_END:
if(giClientSocket != INITSOCKETE)
{
close(giClientSocket);
giClientSocket = INITSOCKETE;
}
pthread_mutex_lock(&gLogMutex);
if(nullptr != gpLogFile)
{
fclose(gpLogFile);
gpLogFile = nullptr;
}
pthread_mutex_unlock(&gLogMutex);
PRINTAPI_TRC("Out main API, enRet = %d\n", enRet);
return enRet;
}