C语言实现基于TCP的多线程聊天室

基于TCP的服务器/客户机的模型

1.基本特征

面向连接的,可靠的,保证数据完整性和有序性
在这里插入图片描述
每个发送都有应答,若在时间窗口内没有收到A的应答,则从A开始重新发送。

编程模型

在这里插入图片描述

三次握手四次分手

在这里插入图片描述

服务器的实现

服务器的思路是等待客户端的连接,并且实时接收每个连接上来了的客户端发送的消息,并将这个消息包打包发送给其他客户端。

1.创建套接字

int sfd = socket(AF_INET,SOCK_STREAM,0);
//AF_INET: 基于TCP/IPv4(32位IP地址)的网络通信;
//SOCKET_STREAM:数据流协议,即TCP协议;
if(sfd == -1){
	perror("socket");
	return -1;
}

2.准备通信地址

struct sockaddr_in addr;
/*
struct sockaddr_in 
19.{
20.    // 地址族
21.    sa_family_t sin_family;
22.
23.    // 端口号
24.    // unsigned short, 0-65535
25.    // 逻辑上表示一个参与通信的进程
26.    // 使用时需要转成网络字节序
27.    // 0-1024端口一般被系统占用
28.    // 如:21-FTP、23-Telnet、80-WWW
29.    in_port_t sin_port;
30.
31.    // IP地址
32.    struct in_addr sin_addr;
33.};
*/

addr.sin_family = AF_INET;

addr.sin_port = htons(port);
addr.sin_addr.s_addr = inet_addr(ip);
socketlen_t addrlen = sizeof(addr);

3.绑定套接字

int ret = bind(sfd,(const struct sockaddr*)(&addr),addrlen);
if(ret == -1){
	perror("bind");
	return -1;
}

4.监听套接字
将sockfd参数所标识的套接字标记为被动模式,使之可用于接受连接请求。

if(listen(sfd,10)==-1){
//10表示最大的客户端排队数为10
	perror("listen");
	return -1;
}

5.接受连接

自定义客户端类型

#define MAX 100
typedef struct Client{
	int cfd;
	char name[40];
}Client;

Client client[MAX] = {};
size_t cnt = 0;

对线程互斥锁初始化

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
while(1){
	struct sockaddr_in caddr;
	socklen_t clen = sizeof(caddr);
	printf("等待客户机连接...\n");
	int cfd = accept(sfd,(struct sockaddr*)(&caddr),&clen);
	/*从sockfd参数所标识套接字的未决连接请求队列中,提取第一个连接请求。
同时创建一个新的套接字,用于在该连接中通信,返回该套接字的描述符。
caddr和clen参数用于输出连接请求发起者的地址信息。
*/
	if(cfd == -1){
		perror("accept");
		return -1;
	}
	//向其他客户端发送上线消息
	char buf[100] = {};
	//把客户端的网名和套接字放在客户端的类型中
	recv(cfd,&client[cnt].name,40,0);
	client[cnt].cfd = cfd;
	strcpy(buf,"您的好友");
	strcat(buf,client[cnt].name);
	strcat(buf,"上线啦!");
	//广播消息
	broadcast(buf,client[cnt]);
	pthread_t id;
	ret = pthread_create(&id,NULL,pthread_run,(void*)(&client[cnt]));
	//每当有一个客户机连接上来,变创建一个线程,该线程用于接收这个客户机的消息,并将这些消息加工广播给其他连接上来的客户机。
	cnt++;
	if(ret != 0){
		printf("pthread_create:%s\n",strerror(ret));
		continue;
	}
	printf("有一个客户机成功连接:ip <%s> port [%hu]\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
}

6.线程处理函数
实现消息的接收处理转发功能

void *pthread_run(void *arg){
	Client cl = *(Client*)(arg);
	while(1){
		char buf[1024] = {};
		strcpy(buf,cl.name);
		strcat(buf," :");
		int ret = recv(cl.cfd,buf+strlen(buf),1024-strlen(buf),0);
		//j接收消息
		if(ret <= 0 ){
			size_t i;
			for(i=0;i<cnt;i++){
				if(client[i].cfd == cl.cfd){
					client[i] = client[cnt-1];
					cnt--;
					strcpy(buf,"您的好友");
					strcat(buf,cl.name);
					strcat(buf,"退出了!");
					break;
				}
			}
			broadcast(buf,cl);
			return NULL;
		}else{
			broadcast(buf,cl);	
		}
	}
}

7.广播函数

void broadcast(char *msg,Client c){
	size_t i;
	pthread_mutex_lock(&mutex);
	for(i=0;i<cnt;i++){
		if(client[i].cfd != c.cfd){
			if(send(client[i].cfd,msg,strlen(msg),0)<=0){
				break;
			}
		}
	}
	pthread_mutex_unlock(&mutex);
}

8.服务器源码

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

#define MAX 100
typedef struct Client{
	int cfd;
	char name[40];
}Client;

Client client[MAX] = {};
size_t cnt = 0;

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

void broadcast(char *msg,Client c){
	size_t i;
	pthread_mutex_lock(&mutex);
	for(i=0;i<cnt;i++){
		if(client[i].cfd != c.cfd){
			if(send(client[i].cfd,msg,strlen(msg),0)<=0){
				break;
			}
		}
	}
	pthread_mutex_unlock(&mutex);
}

void *pthread_run(void *arg){
	Client cl = *(Client*)(arg);
	while(1){
		char buf[1024]={};
		strcpy(buf,cl.name);
		strcat(buf," :");
		int ret = recv(cl.cfd,buf+strlen(buf),1024-strlen(buf),0);
		if(ret <= 0){
			size_t i;
			for(i=0;i<cnt;i++){
				if(client[i].cfd == cl.cfd){
					client[i] = client[cnt-1];
					--cnt;
					strcpy(buf,"您的好友");
					strcat(buf,cl.name);
					strcat(buf,"退出了");
					break;
				}	
			}
			broadcast(buf,cl);
			close(cl.cfd);
			return NULL;
		}else{
			broadcast(buf,cl);
		}
	}
}

int main(int argc,char *argv[]){
	if(argc != 3){
		fprintf(stderr,"use: %s <ip> [port]\n",argv[0]);
		return -1;
	}
	const char *ip = argv[1];
	unsigned short int port = atoi(argv[2]);

	int sfd = socket(AF_INET,SOCK_STREAM,0);
	if(sfd == -1){
		perror("socket");
		return -1;
	}

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);
	socklen_t addrlen = sizeof(addr);

	int ret = bind(sfd,(struct sockaddr*)(&addr),addrlen);
	if(ret == -1){
		perror("bind");
		return -1;
	}
	if(listen(sfd,10)==-1){
		perror("listen");
		return -1;
	}
	while(1){
		struct sockaddr_in caddr;
		socklen_t len = sizeof(caddr);
		
		printf("等待客户机连接....\n");
		int cfd = accept(sfd,(struct sockaddr*)(&caddr),&len);
		if(cfd == -1){
			perror("accept");
			return -1;
		}
		char buf[100]={};
		recv(cfd,&client[cnt].name,40,0);
		client[cnt].cfd = cfd;
		pthread_t id;
		strcpy(buf,"您的好友");
		strcat(buf,client[cnt].name);
		strcat(buf,"上线啦");
		broadcast(buf,client[cnt]);
		ret = pthread_create(&id,NULL,pthread_run,(void*)(&client[cnt]));
		cnt++;
		if(ret != 0){
			printf("pthread_create:%s\n",strerror(ret));
			continue;
		}
		printf("有一个客户机成功连接:ip <%s> port [%hu]\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port));
	}
	return 0;
}

客户机的实现

1.准备套接字

int sfd = socket(AF_INET,SOCK_STREAM,0);
if(sfd == -1){
	perror("socket");
	return -1;
}

2.准备通信地址

	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);
	socklen_t addrlen = sizeof(addr);

3.连接服务器

int ret = connect(sfd,(const struct sockaddr*)(&addr),addrlen);
if(ret == -1){
	perror("connect");
	return -1;
}

4.创建一个子进程接发消息
创建一个子进程,总共两个进程,其中一个进程用来接收客户端发送过来的消息,另外一个进程用于向服务器发送消息。

pid_t pid = fork();
if(pid == -1){
	perror("fork");
	return -1;
}
if(pid == 0){	//子进程,用于发送消息
	while(1){
		char buf[1024]={};
		gets(buf);
		if(send(sfd,buf,strlen(buf)+1,0)<=0){
			break;	
		}
	}
}else{	//父进程,用于接收消息
	while(1){
		char buf[1024]={};
		if(recv(sfd,buf,102410)<=0){
			break;
		}
		time_t timep;
		time(&timep);
		printf("%s\n",ctime(&timep));	//显示接收消息时间
		printf("%s\n",buf);
	}
}

5.客户机源码

#include <stdio.h>
#include <time.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>


int main(int argc,char *argv[]){
	if(argc != 3){
		fprintf(stderr,"use: %s <ip> [port]\n",argv[0]);	
		return -1;
	}
	const char *ip = argv[1];
	unsigned short int port = atoi(argv[2]);

	int sfd = socket(AF_INET,SOCK_STREAM,0);
	if(sfd == -1){
		perror("socket");
		return -1;
	}
	struct sockaddr_in addr;
	addr.sin_family = AF_INET;
	addr.sin_port = htons(port);
	addr.sin_addr.s_addr = inet_addr(ip);
	socklen_t addrlen = sizeof(addr);

	int ret = connect(sfd,(const struct sockaddr*)(&addr),addrlen);
	if(ret == -1){
		perror("connect");
		return -1;
	}
	printf("连接服务器成功!\n");
	printf("请输入你的网民:");
	char name[100];
	gets(name);
	send(sfd,name,strlen(name)+1,0);
	pid_t pid = fork();
	if(pid == -1){
		perror("fork");	
		return -1;
	}
	if(pid == 0){
		while(1){
			char buf[1024]={};
			gets(buf);
			if(send(sfd,buf,strlen(buf)+1,0)<=0){
				break;	
			}
		}
	}else{
		while(1){
			char buf[1024]={};
			if(recv(sfd,buf,1024,0)<=0){
				break;
			}
			time_t timep;
			time(&timep);
			printf("%s\n",ctime(&timep));
			printf("%s\n",buf);
		}
	}
	close(sfd);
	return 0;	
}

测试

在这里插入图片描述

发布了14 篇原创文章 · 获赞 84 · 访问量 2787

猜你喜欢

转载自blog.csdn.net/weixin_42617375/article/details/103832704