C语言Socket编程TCP简单聊天室

该文章介绍了一个使用C语言编写的基于TCP协议的聊天室程序,利用Socket进行网络通信,并通过Pthread库实现了多线程来处理客户端连接。服务端可以接收多个客户端连接,管理用户登录和广播消息,而客户端则负责发送和接收消息。程序还包含了信号处理机制,支持中断和错误处理。
摘要由CSDN通过智能技术生成

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(&currentTime);

    // 格式化时间字符串
    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;
}
svr.c

客户端:

#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);
}
cli.c

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
	
Makefile

演示

  1. 执行编译

请添加图片描述

  1. 启动服务器

请添加图片描述

  1. 启动客户端

请添加图片描述

  1. 聊天

请添加图片描述

  1. 退出

请添加图片描述

实现双人聊天室需要使用socket编程中的TCP协议。以下是一个简单c语言程序,实现了基本的双人聊天室功能。 ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define PORT 8888 // 聊天室端口号 int main(int argc, char *argv[]) { int fd, ret; struct sockaddr_in server_addr, client_addr; socklen_t client_addr_size = sizeof(client_addr); // 创建socket fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { perror("socket"); exit(EXIT_FAILURE); } // 设置服务端地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = htonl(INADDR_ANY); server_addr.sin_port = htons(PORT); // 绑定socket到地址 ret = bind(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (ret == -1) { perror("bind"); exit(EXIT_FAILURE); } // 监听socket ret = listen(fd, 1); // 同时只能连接一个客户端 if (ret == -1) { perror("listen"); exit(EXIT_FAILURE); } printf("Waiting for connection...\n"); // 接受客户端连接 int client_fd = accept(fd, (struct sockaddr *)&client_addr, &client_addr_size); if (client_fd == -1) { perror("accept"); exit(EXIT_FAILURE); } printf("Connected!\n"); // 开始聊天 char buf[1024]; while (1) { // 接收客户端消息 ret = recv(client_fd, buf, sizeof(buf), 0); if (ret == -1) { perror("recv"); exit(EXIT_FAILURE); } else if (ret == 0) { printf("Client disconnected.\n"); break; } else { printf("Received: %s", buf); } // 发送消息给客户端 printf("Say something: "); fgets(buf, sizeof(buf), stdin); ret = send(client_fd, buf, strlen(buf), 0); if (ret == -1) { perror("send"); exit(EXIT_FAILURE); } } // 关闭socket close(client_fd); close(fd); return 0; } ``` 运行该程序后,服务端将等待客户端连接。当客户端连接成功后,服务端和客户端就可以开始聊天了。 客户端程序也非常简单,只需要连接到服务端并发送和接收消息即可。以下是客户端程序的代码: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <errno.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #define SERVER_IP "127.0.0.1" // 服务端IP地址 #define PORT 8888 // 聊天室端口号 int main(int argc, char *argv[]) { int fd, ret; struct sockaddr_in server_addr; // 创建socket fd = socket(AF_INET, SOCK_STREAM, 0); if (fd == -1) { perror("socket"); exit(EXIT_FAILURE); } // 设置服务端地址 memset(&server_addr, 0, sizeof(server_addr)); server_addr.sin_family = AF_INET; server_addr.sin_addr.s_addr = inet_addr(SERVER_IP); server_addr.sin_port = htons(PORT); // 连接到服务端 ret = connect(fd, (struct sockaddr *)&server_addr, sizeof(server_addr)); if (ret == -1) { perror("connect"); exit(EXIT_FAILURE); } // 开始聊天 char buf[1024]; while (1) { // 发送消息给服务端 printf("Say something: "); fgets(buf, sizeof(buf), stdin); ret = send(fd, buf, strlen(buf), 0); if (ret == -1) { perror("send"); exit(EXIT_FAILURE); } // 接收服务端消息 ret = recv(fd, buf, sizeof(buf), 0); if (ret == -1) { perror("recv"); exit(EXIT_FAILURE); } else if (ret == 0) { printf("Server disconnected.\n"); break; } else { printf("Received: %s", buf); } } // 关闭socket close(fd); return 0; } ``` 运行服务端程序后,再运行两个客户端程序,就可以在各个客户端之间进行聊天了。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值