Linux--socket实现群聊程序(多路复用IO)

参考了网上的代码,自己改了一下,并不完善但可以实现我想要的功能。记事本中的代码排序是好的,粘贴在这里就乱了。
在这里插入图片描述
服务器代码:

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

#define BACKLOG			100
#define SERIP			"192.168.149.133"
#define SERPORT			9021
#define CONCURRENT_MAX	10		//并发的连接数
#define QUIT_CMD		"quit"

#define CMD_REGISTER	1001	// 注册指令
#define CMD_CHAT		1002	// 聊天指令

typedef struct
{
	int fd;				//客户端对应的描述符
	char name[20];		//姓名
	char ip[20];		//IP地址
	char msg[100];		//收发的消息
	int cmd;			//命令码
}comstr;

comstr client_info[CONCURRENT_MAX];	//存储用户区信息
comstr client_root = {0};			//服务器消息缓冲区,主要用来发送数据
comstr client_buf = {0};			//缓存接收信息

void transmit(int num);

int main(int argc, char **argv)
{
	struct sockaddr_in seraddr;
	struct sockaddr_in cliaddr; 
	
	fd_set server_fd_set;  
    int max_fd = -1;  
    struct timeval tv;	//超时时间设置 
	time_t tNow;		//时间相关
	struct tm *pt;
	
	//1.打开socket
	int sockfd = socket(AF_INET, SOCK_STREAM, 0); 
	if (-1 == sockfd) 
	{
		perror("socket");
		return -1;
	}
	//assigning a name to a socket
	seraddr.sin_family = AF_INET;
	seraddr.sin_port = htons(SERPORT);
	seraddr.sin_addr.s_addr = inet_addr(SERIP);
	
	//2.绑定socket端口
	int ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr));
	if (ret < 0)
	{
		perror("bind error");
		return -1;
	}
	
	//3.监听
	ret = listen(sockfd, BACKLOG); 
	if (ret < 0)
	{
		perror("listen error");
		return -1;
	}
	
	strcpy(client_root.name, "root"); 
	strcpy(client_root.ip, SERIP);
	
	while (1) 
	{			
		//1.添加标准输入IO到集合
		tv.tv_sec = 20;  
        tv.tv_usec = 0;  
        FD_ZERO(&server_fd_set);  
        FD_SET(STDIN_FILENO, &server_fd_set); //STDIN_FILENO类型为int(open、read、write等使用),stdin类型为FILE*(fopen、fread、fwrite等使用)
        if(max_fd < STDIN_FILENO)  		//记录描述符最大值
        {  
            max_fd = STDIN_FILENO;  
        }  
		//2.添加socket服务端IO到集合
		FD_SET(sockfd, &server_fd_set); //服务端sockfd
		if (max_fd < sockfd) {
			max_fd = sockfd;
		}
		//3.for循环添加客户端IO到集合
		for (int i = 0; i < CONCURRENT_MAX; i++) 
		{
			if (client_info[i].fd != 0) 
			{
				FD_SET(client_info[i].fd, &server_fd_set);
				if (max_fd < client_info[i].fd) 
				{
					max_fd = client_info[i].fd;
				}
			}
		}
		//4.select等待IO事件
		int ret = select(max_fd + 1, &server_fd_set, NULL, NULL, &tv);  
		if (ret < 0) 
		{
			perror("select error");
			return -1;
		}
		else if (ret == 0)
		{
			printf("超时.\n");
		}
		else  //返回IO数量 
		{
			//5.键盘输入事件
            if(FD_ISSET(STDIN_FILENO, &server_fd_set))  
			{
				memset(client_root.msg, 0, sizeof(client_root.msg));
				read(STDIN_FILENO, client_root.msg, sizeof(client_root.msg));    //读键盘
				if (strcmp(client_root.msg, QUIT_CMD) == 0) //输入quit则退出服务器
				{
					exit(0);
				}
				printf("服务器发送的消息:\n");
				for (int i = 0; i < CONCURRENT_MAX; i++) 
				{
					if (client_info[i].fd != 0)
					{
                        printf("client_fds[%d]=%d\n", i, client_info[i].fd);  
						send(client_info[i].fd, &client_root, sizeof(client_root), 0); //发送结构体信息
					}
					
				}
			}
			//6.客户端连接事件,得到fd,解析后得到IP和端口号
			if(FD_ISSET(sockfd, &server_fd_set))
			{
				socklen_t len = sizeof(struct sockaddr);  //len必须初始化为第二个参数的字节长度
				int clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len); //得到客户端socket描述符 
				if (clifd > 0)
				{
					int index = -1;
					for(int i = 0; i < CONCURRENT_MAX; i++)  //将文件描述符放入结构体数组 
                    {  
                        if(client_info[i].fd == 0)  
                        {  
                            index = i;  
                            client_info[i].fd = clifd;
							strcpy(client_info[i].ip, inet_ntoa(cliaddr.sin_addr));
                            break;  
                        }  
                    }  
					if(index >= 0)
					{
						printf("新客户端(%d)加入成功 %s:%d\n", index, inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
					}
					else  //没有位置放置新的客户端信息
					{
						memset(client_root.msg, 0, sizeof(client_root.msg)); //清空输入buf缓冲区
						strcpy(client_root.msg, "服务器加入的客户端数达到最大值,无法加入!\n");  
                        send(clifd, &client_root, sizeof(client_root), 0);   //发送服务器的信息
                        printf("客户端连接数达到最大值,新客户端加入失败 %s:%d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));  
					}
				}					
			}
			//7.客户端发过来消息事件
 			for(int i = 0; i < CONCURRENT_MAX; i++)
			{
				if(0 != client_info[i].fd)  
				{
					if(FD_ISSET(client_info[i].fd, &server_fd_set))  //判断每个客户端是否发了消息
					{
						memset(&client_buf, 0, sizeof(client_buf));   //清空输入buf缓冲区
						long byte_num = recv(client_info[i].fd, &client_buf, sizeof(client_buf), 0); //将收到的信息放到client_buf中
						if (byte_num > 0)  
                        {  
							if (client_buf.cmd == CMD_REGISTER)  //用户注册
							{
								strcpy(client_info[i].name, client_buf.name);  //存储客户端发送过来的姓名
							}
							else if (client_buf.cmd == CMD_CHAT) //聊天
							{
								memset(client_info[i].msg, 0, sizeof(client_info[i].msg));
								strcpy(client_info[i].msg, client_buf.msg);    //存储了客户端发送过来的消息
								tNow = time(NULL);	  //获取时间戳
								if (tNow == -1) 
								{
									perror("time error");
									return -1;
								}								
								pt = localtime(&tNow);  //本地时间
								printf("%s <%s> %d/%d/%d %d:%d:%d\n", client_info[i].name, client_info[i].ip, pt->tm_year+1900, pt->tm_mon+1, \
																pt->tm_mday, pt->tm_hour, pt->tm_min, pt->tm_sec);
								printf("- %s", client_info[i].msg); 
								transmit(i);  //转发消息到其它客户端
							}
 
														
                        }  
                        else if(byte_num < 0)  
                        {  
                            printf("从客户端(%d)接受消息出错.\n", i);  
                        }  
                        else  
                        {  
                            FD_CLR(client_info[i].fd, &server_fd_set);  
                            client_info[i].fd = 0;  
                            printf("客户端(%d)退出了\n", i);  
                        }  
					}
				}
			}
		}
	}
	
	return 0;
}

// 转发消息给其他客户端
void transmit(int num)
{
	for(int i = 0; i < CONCURRENT_MAX; i++)
	{
		if((0 != client_info[i].fd) && (i != num)) //当前客户端需要转发消息
		{
			send(client_info[i].fd, &client_info[num], sizeof(client_info[num]), 0);  //向socket文件转发信息
		}
	}
}




客户端代码:

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

#define SERIP       	"192.168.149.133"
#define SERPORT			9021

#define CMD_REGISTER	1001	// 注册指令
#define CMD_CHAT		1002	// 聊天指令

typedef struct
{
	int fd;				//客户端对应的描述符
	char name[20];		//姓名
	char ip[20];        //IP地址
	char msg[100];      //收发的消息
	int cmd;			//命令码
}comstr;


int main(int argc, const char * argv[])  
{  
    struct sockaddr_in seraddr;  
	fd_set client_fd_set;  
	struct timeval tv;  
	comstr chat;  		//存储用户自身的信息
	comstr serinfo;  	//存储服务器发送过来的消息
	
	time_t tNow;		//时间相关
	struct tm *pt;
		
    seraddr.sin_family = AF_INET;  
    seraddr.sin_port = htons(SERPORT);  
    seraddr.sin_addr.s_addr = inet_addr(SERIP);  
  
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    if(sockfd == -1)  
    {  
		perror("socket error");  
		return 1;  
    }  
  
    if(connect(sockfd, (struct sockaddr *)&seraddr, sizeof(struct sockaddr_in)) == 0)  
    {  
		printf("请输入您的姓名: ");
		scanf("%s", chat.name);
		printf("欢迎%s来到本群.\n", chat.name);
		chat.cmd = CMD_REGISTER;  //向服务器发送注册信息
		int ret = send(sockfd, &chat, sizeof(chat), 0);
		if(ret == -1)  
		{  
			perror("发送消息出错!\n");  
		}  
		
		while(1)  
		{  
			tv.tv_sec = 20;  
			tv.tv_usec = 0;  
			FD_ZERO(&client_fd_set);  
			FD_SET(STDIN_FILENO, &client_fd_set);  
			FD_SET(sockfd, &client_fd_set);  
	  
			select(sockfd + 1, &client_fd_set, NULL, NULL, &tv); 
			if(FD_ISSET(STDIN_FILENO, &client_fd_set))  //客户端发送消息 
			{  
				memset(chat.msg, 0, sizeof(chat.msg));
				read(STDIN_FILENO, chat.msg, sizeof(chat.msg));		//读键盘
				chat.cmd = CMD_CHAT;								//聊天信息指令
				ret = send(sockfd, &chat, sizeof(chat), 0);  		//发送用户区信息
				if(ret == -1)  
				{  
					perror("发送消息出错!\n");  
				}  
			}  
			if(FD_ISSET(sockfd, &client_fd_set))  	//客户端接收消息
			{  
				memset(&serinfo, 0, sizeof(serinfo));
				long byte_num = recv(sockfd, &serinfo, sizeof(serinfo), 0);  
				if(byte_num > 0)  
				{  
					tNow = time(NULL);
					if (tNow == -1) {
						perror("time error");
						return -1;
					}								
					pt = localtime(&tNow);  //本地时间
					printf("%s <%s> %d/%d/%d %d:%d:%d\n", serinfo.name, serinfo.ip, pt->tm_year+1900, pt->tm_mon+1, pt->tm_mday, \
											pt->tm_hour, pt->tm_min, pt->tm_sec);
					printf("- %s", serinfo.msg);
				}  
				else if(byte_num < 0)  
				{  
					printf("接受消息出错!\n");  
				}  
				else  
				{  
					printf("服务器端退出!\n");  
					exit(0);  
				}  
			}  
		}  
	}  
    return 0;  
} 


实际效果:
在这里插入图片描述


参考网址: https://blog.csdn.net/Ctrl_qun/article/details/52524086

猜你喜欢

转载自blog.csdn.net/Meteor_s/article/details/84656553
今日推荐