/**************************************************************************************************
\Copyright (c) Novatek Microelectronics Corp., Ltd. All Rights Reserved.
\file server.c
\brief server of full duplex chat tools
\project Training
\chip NA
\Date 2025.08.15
**************************************************************************************************/
#include "common.h"
int g_sockfd = -1;
atomic_bool g_bRunning = ATOMIC_VAR_INIT(true);
atomic_bool g_bClientShutdown = ATOMIC_VAR_INIT(false);
pthread_t g_readThread, g_writeThread;
char g_sClientName[MAX_NAME_LEN] = {'\0'};
/*!
\fn void cleanup_client()
\brief Clean up client resources
*/
void cleanup_client()
{
TEST_PRINT(EN_TRACE, "in");
TEST_PRINT(EN_MSG, "Cleaning up client resources");
atomic_store_explicit(&g_bRunning, false, memory_order_release);
atomic_store_explicit(&g_bClientShutdown, true, memory_order_release);
shutdown(g_sockfd, SHUT_RDWR);
pthread_cancel(g_readThread);
pthread_cancel(g_writeThread);
if (0 <= g_sockfd)
{
close(g_sockfd);
g_sockfd = -1;
}
TEST_PRINT(EN_TRACE, "out");
return;
}
/*!
\fn void handle_signal(int sig)
\brief signal processing function
\param sig: Signal value
*/
void handle_signal(int sig)
{
TEST_PRINT(EN_TRACE, "in");
TEST_PRINT(EN_MSG, "Received signal %d, exiting...", sig);
cleanup_client();
TEST_PRINT(EN_TRACE, "out");
}
/*!
\fn void* read_thread(void* arg)
\brief Receive server messages
\param arg: unused
\return NULL
*/
void* read_thread(void* arg)
{
TEST_PRINT(EN_TRACE, "in");
ChatMessage chatMsg = {0};
int recvLen = 0;
(void)arg;
while (atomic_load_explicit(&g_bRunning, memory_order_acquire) && !atomic_load_explicit(&g_bClientShutdown, memory_order_acquire))
{
pthread_testcancel();
recvLen = recv(g_sockfd, &chatMsg, sizeof(ChatMessage), 0);
if (0 < recvLen)
{
TEST_PRINT(EN_MSG, "\n[%s]:\n%s", chatMsg.sSender,chatMsg.sContent);
fflush(stdout);
}
else if (0 == recvLen)
{
TEST_PRINT(EN_MSG, "Server disconnected");
atomic_store_explicit(&g_bRunning, false, memory_order_release);
}
else
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
TEST_PRINT(EN_ERROR, "Recv error: %s", strerror(errno));
atomic_store_explicit(&g_bRunning, false, memory_order_release);
}
}
}
TEST_PRINT(EN_TRACE, "out");
return NULL;
}
/*!
\fn void* write_thread(void* arg)
\brief Read user input
\param arg: unused
\return NULL
*/
void* write_thread(void* arg)
{
TEST_PRINT(EN_TRACE, "in");
int iChoice = 0;
ChatMessage chatMsg = {0};
(void)arg;
while (atomic_load_explicit(&g_bRunning, memory_order_acquire) && !atomic_load_explicit(&g_bClientShutdown, memory_order_acquire))
{
pthread_testcancel();
TEST_PRINT(EN_MSG, "\n==== Chat menu ====\n");
TEST_PRINT(EN_MSG, "\n1. Private chat\n2. Group chat\n3. Online users\n4. Exit");
fflush(stdout);
scanf("%d", &iChoice);
getchar();
switch (iChoice)
{
case 1:
chatMsg.type = MSG_TEXT;
strncpy(chatMsg.sSender, g_sClientName, MAX_NAME_LEN);
chatMsg.sSender[MAX_NAME_LEN - 1] = '\0';
TEST_PRINT(EN_MSG, "Please enter the recipient's username: ");
fgets(chatMsg.sReceiver, MAX_NAME_LEN, stdin);
chatMsg.sReceiver[strcspn(chatMsg.sReceiver, "\n")] = '\0';
TEST_PRINT(EN_MSG, "Input message: ");
fgets(chatMsg.sContent, MAX_MSG_LEN, stdin);
chatMsg.sContent[strcspn(chatMsg.sContent, "\n")] = '\0';
chatMsg.type = MSG_TEXT;
break;
case 2:
chatMsg.type = MSG_GROUP;
strncpy(chatMsg.sSender, g_sClientName, MAX_NAME_LEN);
chatMsg.sSender[MAX_NAME_LEN - 1] = '\0';
TEST_PRINT(EN_MSG, "Enter group messaging: ");
fgets(chatMsg.sContent, MAX_MSG_LEN, stdin);
chatMsg.sContent[strcspn(chatMsg.sContent, "\n")] = '\0';
strncpy(chatMsg.sReceiver, "ALL", MAX_NAME_LEN);
chatMsg.sReceiver[MAX_NAME_LEN - 1] = '\0';
break;
case 3:
chatMsg.type = MSG_LIST;
break;
case 4:
atomic_store_explicit(&g_bRunning, false, memory_order_release);
goto end;
default:
TEST_PRINT(EN_ERROR, "invalid Choice");
}
TEST_PRINT(EN_DEBUG, "send msg");
if (0 > send(g_sockfd, &chatMsg, sizeof(ChatMessage), 0))
{
TEST_PRINT(EN_ERROR, "Send failed: %s", strerror(errno));
goto end;
}
}
end:
TEST_PRINT(EN_TRACE, "out");
return NULL;
}
/*!
\fn bool client_login()
\brief client login function
\return login success : true; failed : false
*/
bool client_login()
{
int iChoice = 0;
ChatMessage chatMsg = {0};
bool bRes = false;
while (!atomic_load_explicit(&g_bClientShutdown, memory_order_acquire))
{
start:
TEST_PRINT(EN_MSG, "\n1. Login in\n2. Register\n3. Exit");
fflush(stdout);
scanf("%d", &iChoice);
getchar();
switch (iChoice)
{
case 1:
chatMsg.type = MSG_LOGIN;
TEST_PRINT(EN_MSG, "Enter user name");
fflush(stdout);
fgets(chatMsg.sSender, MAX_NAME_LEN, stdin);
chatMsg.sSender[strcspn(chatMsg.sSender, "\n")] = '\0';
TEST_PRINT(EN_DEBUG, "User name %s", chatMsg.sSender);
TEST_PRINT(EN_MSG, "Enter user password");
fflush(stdout);
fgets(chatMsg.sContent, MAX_PASSWORD_LEN, stdin);
send(g_sockfd, &chatMsg, sizeof(ChatMessage), 0);
break;
case 2:
chatMsg.type = MSG_REGIST;
TEST_PRINT(EN_MSG, "Enter user name");
fflush(stdout);
fgets(chatMsg.sSender, MAX_NAME_LEN, stdin);
chatMsg.sSender[strcspn(chatMsg.sSender, "\n")] = '\0';
TEST_PRINT(EN_MSG, "Enter user password");
fflush(stdout);
fgets(chatMsg.sContent, MAX_PASSWORD_LEN, stdin);
chatMsg.sSender[strcspn(chatMsg.sContent, "\n")] = '\0';
send(g_sockfd, &chatMsg, sizeof(ChatMessage), 0);
break;
case 3:
bRes = false;
goto end;
default:
TEST_PRINT(EN_ERROR, "invalid Choice");
goto start;
}
int recvLen = recv(g_sockfd, &chatMsg, sizeof(ChatMessage), 0);
if (0 < recvLen)
{
TEST_PRINT(EN_MSG, "\n[%s]:\n%s", chatMsg.sSender,chatMsg.sContent);
fflush(stdout);
}
else if (0 == recvLen)
{
TEST_PRINT(EN_MSG, "Server disconnected");
bRes = false;
goto end;
}
else
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
TEST_PRINT(EN_ERROR, "Recv error: %s", strerror(errno));
bRes = false;
goto end;
}
}
if (0 == strncmp(chatMsg.sContent, "Login success", MAX_MSG_LEN))
{
strncpy(g_sClientName, chatMsg.sReceiver, MAX_NAME_LEN);
g_sClientName[MAX_NAME_LEN - 1] = '\0';
bRes = true;
break;
}
}
end:
TEST_PRINT(EN_TRACE, "out");
return bRes;
}
int main(int argc, char* argv[])
{
TEST_PRINT(EN_TRACE, "in");
struct timeval tv = {0};
struct sockaddr_in servAddr = {0};
signal(SIGINT, handle_signal);
signal(SIGTSTP, handle_signal);
if (2 != argc)
{
TEST_PRINT(EN_ERROR, "Usage: %s <server_ip>\n", argv[0]);
return EXIT_FAILURE;
}
g_sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > g_sockfd)
{
TEST_PRINT(EN_ERROR, "Socket creation failed");
return EXIT_FAILURE;
}
tv.tv_sec = TIMEOUT_SEC;
tv.tv_usec = TIMEOUT_USEC;
setsockopt(g_sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(g_sockfd, SOL_SOCKET, SO_SNDTIMEO, &tv, sizeof(tv));
servAddr.sin_family = AF_INET;
servAddr.sin_port = htons(SERVER_PORT);
if (0 >= inet_pton(AF_INET, argv[1], &servAddr.sin_addr))
{
TEST_PRINT(EN_ERROR, "Invalid address");
goto end;
}
if (0 > connect(g_sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)))
{
TEST_PRINT(EN_ERROR, "Connection failed: %s", strerror(errno));
goto end;
}
TEST_PRINT(EN_MSG, "Connected to %s:%d", argv[1], SERVER_PORT);
if (!client_login())
{
TEST_PRINT(EN_ERROR, "Client login failed");
goto end;
}
if (0 != pthread_create(&g_readThread, NULL, read_thread, NULL))
{
TEST_PRINT(EN_ERROR, "Failed to create read thread");
goto end;
}
if (0 != pthread_create(&g_writeThread, NULL, write_thread, NULL))
{
TEST_PRINT(EN_ERROR, "Failed to create write thread");
goto end;
}
if (0 != pthread_join(g_readThread, NULL))
{
TEST_PRINT(EN_ERROR, "Failed to join read thread");
goto end;
}
if (0 != pthread_join(g_writeThread, NULL))
{
TEST_PRINT(EN_ERROR, "Failed to join write thread");
goto end;
}
end:
cleanup_client();
TEST_PRINT(EN_TRACE, "out");
return EXIT_SUCCESS;
}
在main()函数结束后,会cleanup_client();在cleanup_client();中有线程取消操作,这样有问题吗?