#include "common.h"
int sockfd = -1;
atomic_bool bRunning = ATOMIC_VAR_INIT(true);
atomic_bool bClientShutdown = ATOMIC_VAR_INIT(false);
pthread_t readThread, writeThread;
char 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(&bRunning, false, memory_order_release);
atomic_store_explicit(&bClientShutdown, true, memory_order_release);
shutdown(sockfd, SHUT_RDWR);
if (0 <= sockfd)
{
close(sockfd);
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;
while (atomic_load_explicit(&bRunning, memory_order_acquire) && !atomic_load_explicit(&bClientShutdown, memory_order_acquire))
{
pthread_testcancel();
recvLen = recv(sockfd, &chatMsg, sizeof(ChatMessage), 0);
if (0 < recvLen)
{
TEST_PRINT(EN_MSG, "\n[%s]:\n%s", chatMsg.sender,chatMsg.content);
fflush(stdout);
}
else if (0 == recvLen)
{
TEST_PRINT(EN_MSG, "Server disconnected");
atomic_store_explicit(&bRunning, false, memory_order_release);
goto end;
}
else
{
if (errno != EAGAIN && errno != EWOULDBLOCK)
{
TEST_PRINT(EN_ERROR, "Recv error: %s", strerror(errno));
atomic_store_explicit(&bRunning, false, memory_order_release);
goto end;
}
}
}
end:
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};
while (atomic_load_explicit(&bRunning, memory_order_acquire) && !atomic_load_explicit(&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.sender, sClientName, MAX_NAME_LEN);
chatMsg.sender[MAX_NAME_LEN - 1] = '\0';
TEST_PRINT(EN_MSG, "Please enter the recipient's username: ");
fgets(chatMsg.receiver, MAX_NAME_LEN, stdin);
chatMsg.receiver[strcspn(chatMsg.receiver, "\n")] = '\0';
TEST_PRINT(EN_MSG, "Input message: ");
fgets(chatMsg.content, MAX_MSG_LEN, stdin);
chatMsg.content[strcspn(chatMsg.content, "\n")] = '\0';
chatMsg.type = MSG_TEXT;
break;
case 2:
chatMsg.type = MSG_GROUP;
strncpy(chatMsg.sender, sClientName, MAX_NAME_LEN);
chatMsg.sender[MAX_NAME_LEN - 1] = '\0';
TEST_PRINT(EN_MSG, "Enter group messaging: ");
fgets(chatMsg.content, MAX_MSG_LEN, stdin);
chatMsg.content[strcspn(chatMsg.content, "\n")] = '\0';
strncpy(chatMsg.receiver, "ALL", MAX_NAME_LEN);
chatMsg.receiver[MAX_NAME_LEN - 1] = '\0';
break;
case 3:
chatMsg.type = MSG_LIST;
break;
case 4:
atomic_store_explicit(&bRunning, false, memory_order_release);
goto end;
default:
TEST_PRINT(EN_ERROR, "invalid Choice");
}
TEST_PRINT(EN_DEBUG, "send msg");
if (0 > send(sockfd, &chatMsg, sizeof(ChatMessage), 0))
{
TEST_PRINT(EN_ERROR, "Send failed: %s", strerror(errno));
goto end;
}
}
end:
TEST_PRINT(EN_TRACE, "out");
return NULL;
}
bool client_login()
{
int iChoice = 0;
ChatMessage chatMsg = {0};
bool bRes = false;
while (!atomic_load_explicit(&bClientShutdown, memory_order_acquire))
{
start:
TEST_PRINT(EN_MSG, "\n1. Login in\n2. Register\n3. Exit");
scanf("%d", &iChoice);
getchar();
switch (iChoice)
{
case 1:
chatMsg.type = MSG_LOGIN;
TEST_PRINT(EN_MSG, "Enter user name");
fgets(chatMsg.sender, MAX_NAME_LEN, stdin);
chatMsg.sender[strcspn(chatMsg.sender, "\n")] = '\0';
TEST_PRINT(EN_DEBUG, "User name %s", chatMsg.sender);
TEST_PRINT(EN_MSG, "Enter user password");
fgets(chatMsg.content, MAX_PASSWORD_LEN, stdin);
send(sockfd, &chatMsg, sizeof(ChatMessage), 0);
break;
case 2:
chatMsg.type = MSG_REGIST;
TEST_PRINT(EN_MSG, "Enter user name");
fgets(chatMsg.sender, MAX_NAME_LEN, stdin);
chatMsg.sender[strcspn(chatMsg.sender, "\n")] = '\0';
TEST_PRINT(EN_MSG, "Enter user password");
fgets(chatMsg.content, MAX_PASSWORD_LEN, stdin);
send(sockfd, &chatMsg, sizeof(ChatMessage), 0);
break;
case 3:
bRes = false;
goto end;
default:
TEST_PRINT(EN_ERROR, "invalid Choice");
goto start;
}
int recvLen = recv(sockfd, &chatMsg, sizeof(ChatMessage), 0);
if (0 < recvLen)
{
TEST_PRINT(EN_MSG, "\n[%s]:\n%s", chatMsg.sender,chatMsg.content);
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.content, "Login success", MAX_MSG_LEN))
{
strncpy(sClientName, chatMsg.receiver, MAX_NAME_LEN);
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};
int iChoice = 0;
ChatMessage chatMsg = {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;
}
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (0 > sockfd)
{
TEST_PRINT(EN_ERROR, "Socket creation failed");
return EXIT_FAILURE;
}
tv.tv_sec = TIMEOUT_SEC;
tv.tv_usec = TIMEOUT_USEC;
setsockopt(sockfd, SOL_SOCKET, SO_RCVTIMEO, &tv, sizeof(tv));
setsockopt(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(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(&readThread, NULL, read_thread, NULL))
{
TEST_PRINT(EN_ERROR, "Failed to create read thread");
goto end;
}
if (0 != pthread_create(&writeThread, NULL, write_thread, NULL))
{
TEST_PRINT(EN_ERROR, "Failed to create write thread");
goto end;
}
if (0 != pthread_join(readThread, NULL))
{
TEST_PRINT(EN_ERROR, "Failed to join read thread");
goto end;
}
if (0 != pthread_join(writeThread, NULL))
{
TEST_PRINT(EN_ERROR, "Failed to join write thread");
goto end;
}
end:
cleanup_client();
TEST_PRINT(EN_TRACE, "out");
return EXIT_SUCCESS;
}
在写线程中,fgets会阻塞,导致读线程退出后,写线程无法退出