C语言实现LINUX多线程聊天

C语言实现LINUX多线程聊天(服务器端和客户端)

简要说明

实现多线程聊天,具体功能包括用户加入、发言及离开
用户上限小于100
先运行服务器端 后运行客户端进行连接
每当有客户端加入时,每个客户端都将展示当前加入的用户和当前用户数
输入消息开头为leave信息则默认当前用户退出,其它客户端将展示当前退出用户和当前用户数;服务器端将展示当前所有用户,并提示退出用户信息
每个客户端发出的信息所有其它客户端都将接收

(当时做的时候参考了不少材料 代码几乎全部注释了 仔细看应该很容易能看懂)

服务器端实现

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>

const unsigned short LOCALPORT = 8888;
const char *LOCALIP = "127.0.0.1";
char SendBuffer[5120];
char RecvBuffer[5120];

struct ExchMsg {
	int Socket; //套接字
	ushort Alarm;//当前状态
	ushort CurrNum;//在线人数
};

pthread_t RecvThread;//声明接收线程id
pthread_t SendThread;//声明发送线程id
int ClientSocArr[100];//存放用户名(套接字名)
struct ExchMsg *OutputMsg, *InputMsg;//输出和接收的信息 声明
int CurrNum = 0;//当前无人在线

void SocCount(int DealSoc) {
	//统计当前用户(存在的套接字)
	for (int i = 0; i < (CurrNum + 1); i++) {
		if (ClientSocArr[i] == DealSoc) {
			for (; i < CurrNum; i++) {
				ClientSocArr[i] = ClientSocArr[i + 1];
			}
			//打印当前在线所有用户
			printf("——————————————————————————\n");
			printf("NOW We Have:\n");
			printf("——————————————————————————\n");
			for (int j = 0; j < CurrNum; j++) {
				if (j != (CurrNum - 1)) {
					printf("User%d ", ClientSocArr[j]);
				}
				else {
					printf("User%d\n", ClientSocArr[j]);
					printf("——————————————————————————\n");
				}

			}
		}
	}
}

void SendToClient(char *buffer, int DealSoc) {
	// 发送信息给客户端的线程
	for (int j = 0; j < CurrNum; j++) {
		if (ClientSocArr[j] == DealSoc) {
			//若为当前处理的套接字 则继续
			continue;
		}
		if (send(ClientSocArr[j], buffer, 5120, 0) == -1) {
			//如果未能发送成功
			fprintf(stderr, "%s\n", strerror(errno));//输出错误提示
		}
	}
	bzero(buffer, 5120);//缓存区置零
}


void *RecvFromClient(void *recvsoc) {
	//从客户端接收消息的线程
	int DealSoc = *(int *)recvsoc;
	while (1) {
		if (recv(DealSoc, RecvBuffer, 5120, 0) == -1) {
			//未能接收成功
			fprintf(stderr, "Receive Message Error: %s\n", strerror(errno));//打印错误提示
		}
		InputMsg = (struct ExchMsg *)RecvBuffer;//从接收缓存提取信息
		InputMsg->Socket = DealSoc;//套接字置为当前处理的套接字
		if (InputMsg->Alarm == 1) {//当用户离开情况发生
			CurrNum--;
			InputMsg->CurrNum = CurrNum;//统计数量减一
			SocCount(DealSoc);//将离开的套接字从当前数组移除,统计并打印当前套接字
			printf("User %d has left the room\n", DealSoc);
			SendToClient(RecvBuffer, DealSoc);		// 将此用户的消息发给其他用户
			close(DealSoc);//关闭当前处理套接字
			return NULL;
		}
		SendToClient(RecvBuffer, DealSoc);		// 将此用户的消息发给其他用户
	}
	close(DealSoc);
	return NULL;
}

int main(int argc, char const *argv[])
{
	int serversoc, recvsoc;
	socklen_t sockleng;
	struct sockaddr_in serveraddr, clientaddr;
	int perrno;

	if ((serversoc = socket(AF_INET, SOCK_STREAM, 0)) == -1) {
		fprintf(stderr, "ERROR IN Creating server socket, %s\n", strerror(errno));
		exit(0);//创建服务器套接字出错 提示错误信息 正常退出
	}

	serveraddr.sin_family = AF_INET;// 服务器端地址信息/协议簇为tcpip协议
	serveraddr.sin_port = htons(LOCALPORT);//保存端口号
	serveraddr.sin_addr.s_addr = inet_addr(LOCALIP);//ip地址信息 将LOCALIP转换为长整型

	if (bind(serversoc, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr)) == -1) {
		fprintf(stderr, "ERROR IN Binding socket, %s\n", strerror(errno));
		exit(0);//绑定套接字出错 提示错误信息 正常退出
	}

	if (listen(serversoc, 100) == -1) {
		fprintf(stderr, "ERROR IN Listening socket, %s\n", strerror(errno));//监听套接字出错 提示错误信息 
	}
	//无异常情况下 欢迎界面
	printf("\n——————Welcome to the CHATROOM——————\n");
	printf("\nCurrent port is %d, waiting for people to enter.\n", LOCALPORT);

	while (1) {
		if ((recvsoc = accept(serversoc, (struct sockaddr *)&clientaddr, &sockleng)) == 0) {
			fprintf(stderr, "Connection wrong, %s\n", strerror(errno));//提示错误信息 
			continue;//连接出错
		}
		
		ClientSocArr[CurrNum++] = recvsoc;// 将该套接字保存进数组
		printf("USER %d has shown up\n", recvsoc);//展示该用户已进入房间

		OutputMsg = (struct ExchMsg *)SendBuffer;//将发送缓存区数据存放入outputmsg
		OutputMsg->Socket = recvsoc;
		OutputMsg->Alarm = 0;//表示为有用户进入聊天室
		OutputMsg->CurrNum = CurrNum;

		for (int j = 0; j < CurrNum; j++) {
			// 当有用户加入时群发通知给所有当前用户
			if (send(ClientSocArr[j], SendBuffer, 5120, 0) == -1) {
				fprintf(stderr, "%s\n", strerror(errno));//如果发送出错 打印错误提示
			}
		}
		bzero(SendBuffer, 5120);//发送缓存置零


		if ((perrno = pthread_create(&RecvThread, NULL, RecvFromClient, &recvsoc)) != 0) {
			// 创建子用户消息接收处理的线程
			fprintf(stderr, "Failed to create receive user message processing thread, %s\n", strerror(perrno));
			exit(perrno);//若创建失败 打印错误提示
		}
	}
	close(serversoc);
	return 0;
}

客户端实现

//client
//2020unix大作业+918106840611
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <unistd.h>

extern int errno;

#define SERVER_PORT 8888
const char *SERVERIP = "127.0.0.1";

char SendBuffer[5120];
char RecvBuffer[5120];

struct ExchMsg {
	int Socket;//套接字
	ushort Alarm;//当前状态
	ushort CurrNum;//当前人数

};

int clisocket;//客户端套接字声明
struct ExchMsg *OutputMsg, *InputMsg;//输出和接收的信息 声明

void *sendMsg(void *msg) {
	//发送消息 
	while (1) {
		OutputMsg = (struct ExchMsg *)SendBuffer;//缓存区信息转换为左述类型赋给outputmsg
		OutputMsg->Socket = clisocket;

		fgets(SendBuffer + sizeof(struct ExchMsg), 5120 - sizeof(struct ExchMsg), stdin);
		//从标准输入读取一行并存储到缓冲区中
		if (strncmp(SendBuffer + sizeof(struct ExchMsg), "leave", 5) == 0) {
			//探查用户输入是否为leave,若是则视为用户选择离开
			OutputMsg->Alarm = 1;//警示值标为1
			if (send(clisocket, SendBuffer, 5120, 0) == -1) {//如果发送失败
				fprintf(stderr, "%s\n", strerror(errno));//errno对应的错误提示字符串
			}
			close(clisocket);//关闭套接字
			exit(0);//正常状态退出
		}
		else {
			OutputMsg->Alarm = 2;// 否则认为用户想发送数据 将警示值标记为2
		}

		if (send(clisocket, SendBuffer, 5120, 0) == -1) {
			fprintf(stderr, "%s\n", strerror(errno));//如果此时发送失败 输出错误提示
		}
		bzero(SendBuffer, 5120);//将缓冲区置零
	}
	return NULL;
}

int main(int argc, char const *argv[])
{
	ssize_t sendLen;//被执行数据块(发送消息)大小
	struct sockaddr_in seraddr, recvaddr;//地址
	pthread_t Psend;//声明线程id

	if ((clisocket = socket(AF_INET, SOCK_STREAM, 0)) == -1) {// 创建一客户端套接字 ip地址类型ipv4 套接字类型面向连接
		fprintf(stderr, "%s\n", strerror(errno));//如果失败 输出错误提示
		exit(errno);//错误退出
	}

	bzero(&seraddr, sizeof(struct sockaddr_in));//输出地址清空
	// 服务器端地址信息
	seraddr.sin_family = AF_INET;//协议簇为tcpip协议
	seraddr.sin_addr.s_addr = inet_addr(SERVERIP);//ip地址信息 将SERVERIP转换为长整型
	seraddr.sin_port = htons(SERVER_PORT);//保存端口号

	if (connect(clisocket, (struct sockaddr *)&seraddr, sizeof(struct sockaddr)) == -1) {
		// 请求连接服务器进程
		fprintf(stderr, "请求连接服务器失败, %s\n", strerror(errno));//连接失败 输出提示
		exit(errno);//错误退出
	}
	printf("——Connection to server succeeded——\n");//连接成功
	printf("Current Ip&Port_%s:%d\n", inet_ntoa(seraddr.sin_addr), ntohs(seraddr.sin_port));//当前ip地址和端口号

	pthread_create(&Psend, NULL, sendMsg, NULL);//新线程创建 用于消息发送

	while (1) {
		//接收消息
		bzero(RecvBuffer, 5120);//缓冲区置零
		if (recv(clisocket, RecvBuffer, 5120, 0) == -1) {
			fprintf(stderr, "%s\n", strerror(errno));//当接收消息失败 输出错误信息
		}

		InputMsg = (struct ExchMsg *)RecvBuffer;
		if (InputMsg->Alarm == 0) {//有用户进入聊天室
			fprintf(stdout, "		User%d Entered This ROOM,NOW We Have %d USERS		\n", InputMsg->Socket, InputMsg->CurrNum);
		}
		else if (InputMsg->Alarm == 1) {//有用户离开聊天室
			printf("		User%d Left This ROOM,NOW We Have %d USERS		\n", InputMsg->Socket, InputMsg->CurrNum);
		}
		else if (InputMsg->Alarm == 2) {//其他用户发送消息
			fprintf(stdout, "USER %d Said>> %s\n", InputMsg->Socket, RecvBuffer + sizeof(struct ExchMsg));
		}

	}
	return 0;
}
/*发送失败时errno有如下可能
EACCES:不许对目标套接字文件写,或者路径前驱的一个目录节点不可搜索
EAGAIN,EWOULDBLOCK: 套接字已标记为非阻塞,而发送操作被阻塞
EBADF:sock不是有效的描述词 ECONNRESET:连接被用户重置
EDESTADDRREQ:套接字不处于连接模式,没有指定对端地址
EFAULT:内存空间访问出错 EINTR:操作被信号中断 EINVAL:参数无效
EISCONN:基于连接的套接字已被连接上,同时指定接收对象
EMSGSIZE:消息太大 ENOMEM:内存不足 ENOTCONN:套接字尚未连接,目标没有给出
ENOTSOCK:sock索引的不是套接字 EPIPE:本地连接已关闭*/

配置环境

VMware-ubuntu虚拟机64位

测试图片

server:
server
client1:
client1
client2:
client2

有错欢迎探讨指正~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值