C语言Socket编程TCP简单聊天室
简介
这是一个使用C语言进行套接字编程实现的简单聊天室, 使用Pthread库进行多线程执行
代码
服务端:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h> //Unix/Linux系统的基本系统数据类型的头文件,含有size_t,time_t,pid_t等类型
#include <sys/socket.h> //套接字基本函数相关
#include <netinet/in.h> //IP地址和端口相关定义,比如struct sockaddr_in等
#include <arpa/inet.h>
#include <signal.h>
#include <errno.h>
#include <sys/wait.h>
#include <pthread.h>
#define MAX_MSG_SIZE 128
#define MAX_CLIENT_CONNECTION 128
#define MAX_FORWARD_MSG_SIZE (2 * MAX_MSG_SIZE + 32)
#define LOGIN 0
#define LOGOUT 1
int sigint_flag = 0;
typedef struct _client
{
int clientfd;
char nickname[MAX_MSG_SIZE];
struct sockaddr_in clientsock;
struct _client *next;
} Client;
pthread_mutex_t client_list_lock;
// 创建空白的节点
Client *createNode();
// 按照文件描述符删除节点
int deleteNode(int targetfd);
// 将节点插入链表(头部)
void insertNode(Client *newNode);
// 显示链表中所有的节点
void displayAllClients();
// 全局广播
void broadcast(const char *content);
// 获取当前时间字符串
char *getCurrentTime();
// 去除末尾的换行符
void trim_linefeed(char *str);
// 获取登录消息
char *getInfo(const char *nickname, int mode);
Client *head;
void *clientHandler(void *arg);
void sigpipe_handler(int signum, siginfo_t *info, void *context)
{
printf("SIGPIPE detected!\n");
}
void sigint_handler(int signum, siginfo_t *info, void *context)
{
printf("[svr] Server is shutting down...\n");
sigint_flag = 1;
}
int main(int argc, char const *argv[])
{
struct sigaction sig_pipe, sig_int;
sig_pipe.sa_flags = SA_SIGINFO;
sig_pipe.sa_sigaction = sigpipe_handler;
sigfillset(&sig_pipe.sa_mask);
sig_int.sa_flags = SA_SIGINFO;
sig_int.sa_sigaction = sigint_handler;
sigfillset(&sig_int.sa_mask);
// 为 SIGPIPE 和 SIGINT 设置信号处理函数
if (sigaction(SIGPIPE, &sig_pipe, NULL) < 0)
{
perror("sigaction");
return 1;
}
if (sigaction(SIGINT, &sig_int, NULL) < 0)
{
perror("sigaction");
return 1;
}
// 处理命令行参数
if (argc != 3)
{
printf("Usage: %s <ip_address> <port>\n", argv[0]);
exit(1);
}
int port = atoi(argv[2]);
int listenfd, connectfd;
struct sockaddr_in server, client;
int sin_size;
if ((listenfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Create socket failed.");
exit(-1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(atoi(argv[2]));
inet_pton(AF_INET, argv[1], &server.sin_addr);
if (bind(listenfd, (struct sockaddr *)&server, sizeof(server)) == -1)
{
perror("bind");
exit(1);
}
if (listen(listenfd, MAX_CLIENT_CONNECTION) == -1)
{
perror("listen");
exit(1);
}
// 初始化链表
head = createNode();
pthread_mutex_init(&client_list_lock, NULL);
printf("[svr](%d)[svr_sa](%s:%d) Server has initialized!\n", getpid(), inet_ntoa(server.sin_addr), ntohs(server.sin_port));
sin_size = sizeof(client);
while (!sigint_flag)
{
if ((connectfd = accept(listenfd, (struct sockaddr *)&client, &sin_size)) < 0)
{
if (errno == EINTR)
{
continue;
}
perror("accept error");
exit(1);
}
else
{
printf("[svr](%d)[cli_sa](%s:%d) Client is accepted!\n", getpid(), inet_ntoa(client.sin_addr), ntohs(client.sin_port));
Client *newClient = createNode();
newClient->clientfd = connectfd;
newClient->clientsock = client;
newClient->next = NULL;
read(connectfd, newClient->nickname, MAX_MSG_SIZE);
trim_linefeed(newClient->nickname);
printf("[svr](%d)[cli_sa](%s:%d)[nickname](%s) User logged in!!\n", getpid(), inet_ntoa(client.sin_addr), ntohs(client.sin_port), newClient->nickname);
char *info = getInfo(newClient->nickname, LOGIN);
broadcast(info);
free(info);
insertNode(newClient);
displayAllClients();
pthread_t tid;
if (pthread_create(&tid, NULL, clientHandler, newClient) != 0)
{
perror("pthread_create");
exit(EXIT_FAILURE);
}
pthread_detach(tid);
}
}
pthread_mutex_destroy(&client_list_lock);
close(listenfd);
return 0;
}
void *clientHandler(void *arg)
{
Client *cli = (Client *)arg;
pthread_t self = pthread_self();
printf("[chd](%ld) Child thread is created!\n", self);
int size;
char buffer[MAX_MSG_SIZE + 2];
char forward_buffer[MAX_FORWARD_MSG_SIZE];
while (!sigint_flag)
{
bzero(buffer, sizeof(buffer));
if ((size = read(cli->clientfd, buffer, MAX_MSG_SIZE + 2)) < 0)
{
if (errno == EINTR)
{
continue;
}
perror("read");
exit(1);
}
else if (size == 0)
{
printf("[chd](%ld)[cli_sa](%s:%d)[nickname](%s) Client is closed!\n", self, inet_ntoa(cli->clientsock.sin_addr), ntohs(cli->clientsock.sin_port), cli->nickname);
break;
}
else
{
bzero(forward_buffer, sizeof(forward_buffer));
char *currentTime = getCurrentTime();
snprintf(forward_buffer, MAX_FORWARD_MSG_SIZE - 1, "%s\n[%s]: %s", currentTime, cli->nickname, buffer);
printf("[chd](%ld)[cli_sa](%s:%d)\n%s", self, inet_ntoa(cli->clientsock.sin_addr), ntohs(cli->clientsock.sin_port), forward_buffer);
free(currentTime);
fflush(stdout);
broadcast(forward_buffer);
}
}
char tmp[MAX_MSG_SIZE];
bzero(tmp, sizeof(tmp));
strcpy(tmp, cli->nickname);
close(cli->clientfd);
printf("[chd](%ld)[cli_sa](%s:%d) Client file descriptor is closed!\n", self, inet_ntoa(cli->clientsock.sin_addr), ntohs(cli->clientsock.sin_port));
deleteNode(cli->clientfd);
char *info = getInfo(tmp, LOGOUT);
broadcast(info);
free(info);
displayAllClients();
printf("[chd](%ld) Child thread is to return!\n", self);
pthread_exit(NULL);
}
Client *createNode()
{
Client *__client = (Client *)malloc(sizeof(Client));
__client->next = NULL;
bzero(__client->nickname, sizeof(__client->nickname));
return __client;
}
int deleteNode(int targetfd)
{
pthread_mutex_lock(&client_list_lock);
Client *current = head->next, *prev = head;
while (head != NULL)
{
if (current->clientfd == targetfd)
{
prev->next = current->next;
free(current);
pthread_mutex_unlock(&client_list_lock);
return 1;
}
else
{
prev = current;
current = current->next;
}
}
pthread_mutex_unlock(&client_list_lock);
return 0;
}
void insertNode(Client *newNode)
{
pthread_mutex_lock(&client_list_lock);
newNode->next = head->next;
head->next = newNode;
pthread_mutex_unlock(&client_list_lock);
}
void displayAllClients()
{
pthread_mutex_lock(&client_list_lock);
printf("====================\nClients:\n");
for (Client *current = head->next; current != NULL; current = current->next)
{
printf("{nickname: %s, fd: %d, socket address: %s:%d}", current->nickname, current->clientfd, inet_ntoa(current->clientsock.sin_addr), ntohs(current->clientsock.sin_port));
if (current->next != NULL)
{
printf("->");
}
fflush(stdout);
}
printf("\n====================\n");
pthread_mutex_unlock(&client_list_lock);
}
void broadcast(const char *content)
{
pthread_mutex_lock(&client_list_lock);
Client *current = head->next;
while (current != NULL)
{
write(current->clientfd, content, strlen(content));
current = current->next;
}
pthread_mutex_unlock(&client_list_lock);
}
char *getCurrentTime()
{
time_t currentTime;
struct tm *localTime;
char *timeString = (char *)malloc(sizeof(char) * 128);
// 获取当前时间
currentTime = time(NULL);
// 转换为本地时间
localTime = localtime(¤tTime);
// 格式化时间字符串
strftime(timeString, 128, "%Y-%m-%d %H:%M:%S", localTime);
return timeString;
}
void trim_linefeed(char *str)
{
if (str[strlen(str) - 1] == '\n')
{
str[strlen(str) - 1] = '\0';
}
}
char *getInfo(const char *nickname, int mode)
{
char *str = (char *)malloc(MAX_FORWARD_MSG_SIZE);
bzero(str, sizeof(str));
char *currentTime = getCurrentTime();
;
if (mode == LOGIN)
{
snprintf(str, MAX_FORWARD_MSG_SIZE, "%s\n[svr] User [%s] logged in\n", currentTime, nickname);
}
else
{
snprintf(str, MAX_CLIENT_CONNECTION, "%s\n[svr] User [%s] logged out\n", currentTime, nickname);
}
free(currentTime);
return str;
}
客户端:
#include <stdio.h>
#include <stdlib.h> //exit()函数,atoi()函数
#include <unistd.h> //C 和 C++ 程序设计语言中提供对 POSIX 操作系统 API 的访问功能的头文件
#include <sys/types.h> //Linux系统的基本系统数据类型的头文件,含有size_t,time_t,pid_t等类型
#include <sys/socket.h> //套接字基本函数
#include <netinet/in.h> //IP地址和端口相关定义,比如struct sockaddr_in等
#include <arpa/inet.h> //inet_pton()等函数
#include <string.h> //bzero()函数
#include <pthread.h>
#define MAX_MSG_SIZE 128
#define MAX_FORWARD_MSG_SIZE (MAX_MSG_SIZE + 64)
int exit_flag = 0;
int serverfd;
pthread_t sendTid, rcvdTid;
typedef struct _arg
{
int serverfd;
} Arg;
void handleMessage(int socket);
void *sendHandler();
void *rcvdHandler();
int main(int argc, char *argv[])
{
struct sockaddr_in server_addr; // 存放服务器端地址信息,connect()使用
if (argc != 3)
{ // 如果命令行用法不对,则提醒并退出
printf("usage: %s <server IP address> <server port>\n", argv[0]);
exit(0);
}
char nickname[MAX_MSG_SIZE];
while (!exit_flag)
{
bzero(nickname, sizeof(nickname));
printf("enter your nickname to log in...\n>");
fgets(nickname, MAX_MSG_SIZE, stdin);
if (strlen(nickname) == 1)
{
printf("nickname cannot be null!\n");
}
else
{
break;
}
}
if ((serverfd = socket(AF_INET, SOCK_STREAM, 0)) == -1)
{
perror("Create socket failed.");
exit(1);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = AF_INET;
// argv[1] 为服务器IP字符串,需要用inet_pton转换为IP地址
if (inet_pton(AF_INET, argv[1], &server_addr.sin_addr) == 0)
{
perror("Server IP Address Error");
exit(1);
}
// argv[2] 为服务器端口,需要用atoi及htons转换
server_addr.sin_port = htons(atoi(argv[2]));
if (connect(serverfd, (struct sockaddr *)&server_addr, sizeof(struct sockaddr)) == -1)
{
perror("connect failed");
exit(1);
}
printf("[cli](%d)[srv_sa](%s:%s) Server is connected!\n", getpid(), argv[1], argv[2]);
write(serverfd, nickname, strlen(nickname));
handleMessage(serverfd);
close(serverfd);
printf("[cli] serverfd is closed!\n");
printf("[cli] client is going to exit!\n");
return 0;
}
void handleMessage(int socket)
{
pthread_create(&sendTid, NULL, sendHandler, NULL);
pthread_create(&rcvdTid, NULL, rcvdHandler, NULL);
pthread_join(sendTid, NULL);
pthread_join(rcvdTid, NULL);
}
void *sendHandler()
{
pthread_t self = pthread_self();
char buffer[MAX_MSG_SIZE + 2];
int size, msgLen;
while (!exit_flag)
{
bzero(buffer, sizeof(buffer));
fflush(stdout);
fgets(buffer, MAX_MSG_SIZE, stdin);
if (strlen(buffer) == 1)
{
continue;
}
if (strcmp(buffer, "EXIT\n") == 0)
{
exit_flag = 1;
break;
}
printf("[cli] Sending: %s", buffer);
write(serverfd, buffer, strlen(buffer));
}
pthread_cancel(rcvdTid);
pthread_exit(NULL);
}
void *rcvdHandler()
{
pthread_t self = pthread_self();
char buffer[MAX_FORWARD_MSG_SIZE];
int size;
while (!exit_flag)
{
bzero(buffer, sizeof(buffer));
if ((size = read(serverfd, buffer, sizeof(buffer))) == -1)
{
perror("read");
break;
}
else if (size == 0)
{
printf("[cli] server is closed!\n");
exit_flag = 1;
break;
}
else
{
printf("%s", buffer);
}
}
pthread_cancel(sendTid);
pthread_exit(NULL);
}
Makefile:
.PHONY : all clean test
server=svr
client=cli
tarfile=chat.tar
all : $(server).c $(client).c
gcc -o $(server) $(server).c -lpthread
gcc -o $(client) $(client).c -lpthread
clean :
rm -f ./$(server)
rm -f ./$(client)
rm -f ./$(tarfile)
package: clean all
tar -cvf $(tarfile) $(server).c $(client).c
演示
- 执行编译
- 启动服务器
- 启动客户端
- 聊天
文章来源:https://www.toymoban.com/news/detail-778088.html
- 退出
文章来源地址https://www.toymoban.com/news/detail-778088.html
到了这里,关于C语言Socket编程TCP简单聊天室的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!