利用select函数实现在Linux环境下实现一个聊天室程序

C写的

要求:
用户默认处于广播模式,一个客户在其客户端发送的消息,其它客户端用户全部可以收到;
程序支持下列命令
	/help:显示帮助信息(思考:信息是放在客户端还是服务器端);
/quit:用户退出聊天室,同时将退出信息广播给其他用户;
 /who:显示在线用户;
   
 /send 用户名 消息:向指定用户发送点到点消息

chatserver.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<sys/socket.h>
#include<netdb.h>
#include<sys/time.h>
#include<sys/types.h>

#define PORT 1573
#define BACKLOG 10
#define BUFSIZE 2048

//定义一个结构体,使得客户的信息可以结合到一起
struct client_info{
	int id;  //表示用户现在接入的套接字
	char name[256];
	int first;  //表示用户是不是第一次访问,用于传入名字
};

int main(){
	fd_set allset;   //需要扫描的所有套接字
	fd_set rset;  //select过后的套接字
	struct sockaddr_in server;
	struct sockaddr_in client;
	int maxfd;
	int sockfd;
	int confd;
	char recvbuf[BUFSIZE];
	char sendbuf[BUFSIZE];
	int recvnum;
	int sendnum;
	int opt;   //定义套接字属性
	int length;   //用于connect函数

	opt = SO_REUSEADDR;
	length = sizeof(struct sockaddr);

	int tmp_i;
	int tmp_j;
	char str1[256];
	char str2[256];
	char str3[256];
	int tmpid=-1;   //用于进行实际处理的套接字,在关系上是通过tmpfd得到的
	int tmpfd=-1;   //用来在新一轮循环中替换掉tmp_i,使得tmp_i的信息可以保存
	struct client_info clientinfo[BACKLOG];

	//初始化套接字集合
	FD_ZERO(&allset);
	FD_ZERO(&rset);

	if(-1 == (sockfd=socket(AF_INET,SOCK_STREAM,0)))
	{
		perror("create socket error!\n");
		exit(1);
	}

	setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));

	memset(&server,0,sizeof(server));
	memset(sendbuf,0,BUFSIZE);
	memset(recvbuf,0,BUFSIZE);
	int i;
	//初始化客户的信息
	for(i=0;i<BACKLOG;i++)
	{
		clientinfo[i].id = -1;
		clientinfo[i].name[0] = '\0';
		clientinfo[i].first = -1;
	}
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = htonl(INADDR_ANY);
	server.sin_port = htons(PORT);

	if(-1 == bind(sockfd,(struct sockaddr*)&server,sizeof(struct sockaddr)))
	{
		perror("bind socket error!\n");
		exit(1);
	}

	if(-1 == listen(sockfd,BACKLOG))
	{
		perror("listen error!\n");
		exit(1);
	}

	FD_SET(sockfd,&allset);
	maxfd = sockfd;
	printf("server is ok!\n");

	while(1)
	{
		rset = allset;
		if(-1 == select(maxfd+1,&rset,NULL,NULL,NULL))
		{
			perror("select function error!\n");
			exit(1);
		}

		for(tmp_i = sockfd;tmp_i <= maxfd;tmp_i++)
		{
			//处理:如果是监听套接字被激活
			if(FD_ISSET(tmp_i,&rset))
			{
				if(tmp_i == sockfd)
				{
					confd = accept(sockfd,(struct sockaddr*)&client,&length);
					if(confd == -1)
					{
						perror("accept error!\n");
						exit(1);
					}
					clientinfo[confd].id = confd;
					clientinfo[confd].first = 1;  //将first置为1,用于第一个接收包的名字

					FD_SET(confd,&allset);
					if(confd > maxfd)
						maxfd = confd;
				}
				else{
					//处理:如果是连接套接字被激活
					recvnum = read(tmp_i,recvbuf,sizeof(recvbuf));
					if(clientinfo[tmp_i].first == 1)  //由上,得到客户的名字
					{
						strcpy(clientinfo[tmp_i].name,recvbuf);
						clientinfo[tmp_i].first = -1;
					}
					if(0>recvnum)
					{
						perror("recieve error!\n");
						exit(1);
					}
					if(recvbuf[0]=='/')
					{
						//处理:以‘/’开始的接收包表示现在是指令
						if(strcmp(recvbuf,"/who\n")==0){
							//请求现在有哪些用户在线
							for(tmpfd = sockfd;tmpfd<=maxfd;tmpfd++)
							{
								if(FD_ISSET(tmpfd,&allset))	
									strcat(sendbuf,clientinfo[tmpfd].name);
							}
							//因为只是当前输入“/who”指令的用户想要知道谁在线
							//只把内容返回给他/她,用continue重新新的一轮循环
							write(tmp_i,sendbuf,sizeof(sendbuf));
							continue;
						}
						if(strcmp(recvbuf,"/quit\n")==0)
						{
							//当前客户请求退出
							printf("client:%s exit!\n",clientinfo[tmp_i].name);
							FD_CLR(tmp_i,&allset);
							close(tmp_i);
							strcat(sendbuf,clientinfo[tmp_i].name);
							strcat(sendbuf," was exit!");
						}
						//初始化字符串,用于分别存储/send usr msg中的各个部分
						memset(str1,0,sizeof(str1));
						memset(str2,0,sizeof(str2));
						memset(str3,0,sizeof(str3));
						sscanf(recvbuf,"%s %s %s",str1,str2,str3);
						strcat(str2,"\n");
						if(strcmp(str1,"/send")==0)
						{
							tmpid = -1;  //以防在新的循环中tmpid的值被上一次循环所改变
							int j = 0;
							for(tmpfd = sockfd;tmpfd<=maxfd;tmpfd++)
							{
								//查询到指定名字下的客户的套接字
								if(FD_ISSET(tmpfd,&allset))
								{
									if(strcmp(str2,clientinfo[tmpfd].name)==0)
										tmpid = tmpfd;
								}
							}
							if(tmpid==-1)
							{
								//表示并没有当前客户与之匹配,返回消息给发送端
								strcat(sendbuf,"user isn't online!");
								write(tmp_i,sendbuf,sizeof(sendbuf));
								continue;
							}
							strcat(sendbuf,clientinfo[tmp_i].name);
							strcat(sendbuf,str3);
							//因为这里是点对点,所以不用进入下面的部分,continue跳过
							write(tmpid,sendbuf,sizeof(sendbuf));
							continue;
						}		
					}
					else
					{
						strcat(sendbuf,clientinfo[tmp_i].name);
						strcat(sendbuf," said: ");
						strcat(sendbuf,recvbuf);
					}
					for(tmp_j = sockfd+1; tmp_j<=maxfd;tmp_j++)
					{
						//实现信息的广播
						if(FD_ISSET(tmp_j,&allset))
						{
							write(tmp_j,sendbuf,strlen(sendbuf));
						}
					}
			
				}
			}
		}
		//清空sendbuf和recvbuf
		memset(&sendbuf,0,BUFSIZE);
		memset(&recvbuf,0,BUFSIZE);
	}

	return 0;
}


chatclient.c

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/socket.h>
#include<netdb.h>
#include<sys/time.h>
#include<sys/types.h>

#define PORT 1573
#define BUFSIZE 2048

int main(int argc, char *argv[])
{
	int sockfd;
	fd_set sockset; //套接字集合,用于判断是套接字还是I/O输入
	struct sockaddr_in server;
	struct sockaddr_in client;
	int recvnum;
	char sendbuf[BUFSIZE];
	char recvbuf[BUFSIZE];
	int length;
	
	if(2>argc)
	{
		printf("please input ip!\n");
		exit(1);
	}

	if(-1==(sockfd = socket(AF_INET,SOCK_STREAM,0)))
	{
		perror("create client socket error!\n");
		exit(1);
	}
	
	memset(&server,0,sizeof(server));
	server.sin_family = AF_INET;
	server.sin_addr.s_addr = inet_addr(argv[1]);
	server.sin_port = htons(PORT);
	
	if(-1==connect(sockfd,(struct sockaddr*)&server,sizeof(struct sockaddr)))
	{
		perror("client connect error!\n");
		exit(1);
	}
	
	memset(sendbuf,0,2048);
	fprintf(stderr,"welcome to visit the chat server\n");
	fprintf(stderr,"please input your name:");
	fgets(sendbuf,256,stdin);
	
	if(0>send(sockfd,sendbuf,strlen(sendbuf),0))
	{
		perror("sending data error!\n");
		close(sockfd);
		exit(1);
	}

	//初始化集合
	FD_ZERO(&sockset);
	FD_SET(sockfd,&sockset);
	FD_SET(0,&sockset);

	while(1)
	{
		memset(recvbuf,0,sizeof(recvbuf));
		memset(sendbuf,0,sizeof(sendbuf));
		select(sockfd+1,&sockset,NULL,NULL,NULL);
		if(FD_ISSET(sockfd,&sockset))
		{
			//处理:如果是套接字被激活,表示服务器有信息传过来,进行接收处理
			recvnum=read(sockfd,recvbuf,sizeof(recvbuf));
			recvbuf[recvnum]='\0';
			printf("%s\n",recvbuf);
			printf("\n");
			fflush(stdout);
		}
		if(FD_ISSET(0,&sockset))
		{
			//处理:如果是I/O被激活,表示客户有消息要发送出去,进行发送处理
			fgets(sendbuf,sizeof(sendbuf),stdin);
			length = strlen(sendbuf);
			sendbuf[length] = '\0';
			
       		if(strcmp(sendbuf,"/help\n")==0)
       		{
				//处理,输入"/help"表示想要得到帮助信息,帮助信息是存储在客户端的,不用向服务器发送信息
				//跳过继续执行循环
				printf("\n");
               	fprintf(stderr,"/help show the help message\n");
				fprintf(stderr,"/send usage:/send user message send message to user\n");
               	fprintf(stderr,"/who show who is online\n");
               	fprintf(stderr,"/quit quit from server\n");
				printf("\n");
				continue;
       		}	
			write(sockfd,sendbuf,sizeof(sendbuf));
			if(strcmp(sendbuf,"/quit\n")==0)
			{
				//处理,客户想要退出,关闭套接字,用户程序退出
				printf("quiting from chat room!\n");
				close(sockfd);
				exit(1);
			}
		}
		FD_ZERO(&sockset);
		FD_SET(sockfd,&sockset);
		FD_SET(0,&sockset);
	}
	close(sockfd);
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
项目简介: 采用I/O复用技术select实现socket通信,采用多线程负责每个客户操作处理,完成Linux下的多客户聊天室! OS:Ubuntu 15.04 IDE:vim gcc make DB:Sqlite 3 Time:2015-12-09 ~ 2012-12-21 项目功能架构: 1. 采用client/server结构; 2. 给出客户操作主界面(注册、登录、帮助和退出)、登录后主界面(查看在线列表、私聊、群聊、查看聊天记录、退出); 3. 多客户可同时连接服务器进行自己操作; ##服务器端## 1. server.c:服务器端主程序代码文件; 2. config.h:服务器端配置文件(包含需要的头文件、常量、数据结构及函数声明); 3. config.c:服务器端公共函数实现文件; 4. list.c:链表实现文件,用于维护在线用户链表的添加、更新、删除操作; 5. register.c:服务器端实现用户注册; 6. login.c:服务器端实现用户登录; 7. chat.c:服务器端实现用户的聊天互动操作; 8. Makefile:服务器端make文件,控制台执行make命令可直接生成可执行文件server ##客户端## 1. client.c:客户端主程序代码文件; 2. config.h:客户端配置文件(包含需要的头文件、常量、数据结构及函数声明); 3. config.c:客户端公共函数实现文件; 4. register.c:客户端实现用户注册; 5. login.c:客户端实现用户登录; 6. chat.c:客户端实现用户的聊天互动操作; 7. Makefile:客户端make文件,控制台执行make命令可直接生成可执行文件client;

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值