基于UDP的简单聊天室

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_34938530/article/details/79946692

聊天室功能:

        1.其他用户上线通知。

        2.其他用户下线通知。

        3.获取在线列表。

        4.用户之间点对点聊天。

实现思想:

        1.服务器端主要是解析客户端发送过来的各种指令,并作出相应的处理和回应。

        2.客户端采用select管理套接口IO和标准输入IO,当有事件发生,做出相应的处理。

        3.采用链表存储每个客户端的网络信息,登录对应聊表插入(使用头插法),退出对应链表删除。点对点对应链表的遍历查找。

源代码:

    服务器端:

#include "pub.h"
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <signal.h>
#include <poll.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <iostream>
#include <vector>
#include <algorithm> 

			
#define ERR_EXIT(m)                     \
        do                              \
        {                               \
                perror(m);              \
                exit(EXIT_FAILURE);     \
        }while(0);    

//聊天室成员列表
USER_LIST client_list;

void chat_server(int server_fd);
void do_login(MESSAGE msg,int sock,struct sockaddr_in *clientaddr);
void do_logout(MESSAGE msg,int sock,struct sockaddr_in *clientaddr);
void do_online_user(int sock,struct sockaddr_in *clientaddr);

int main(void)
{
	int sock;
	struct sockaddr_in servaddr;

	if ((sock = socket(AF_INET,SOCK_DGRAM,0)) < 0)
		ERR_EXIT("socket");
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(9999);
	servaddr.sin_addr.s_addr = htonl(INADDR_ANY);

	if (bind(sock,(struct sockaddr*)&servaddr,sizeof(servaddr)) < 0)
		ERR_EXIT("bind");
	chat_server(sock);

	return 0;
}

void chat_server(int server_fd)
{
        struct sockaddr_in clientaddr;
        socklen_t clientlen;
        int n;
        MESSAGE msg;
        while (1)
        {
                memset(&msg,0,sizeof(msg));
                clientlen = sizeof(clientaddr);
                n = recvfrom(server_fd,&msg,sizeof(msg),0,(struct sockaddr*)&clientaddr,&clientlen);
                if (n < 0)
                {
                        if (errno == EINTR)
                                continue;
                        ERR_EXIT("recvfrom");
                }
                int cmd = ntohl(msg.cmd);
                switch (cmd)
                {
                        case C2S_LOGIN:
                                do_login(msg,server_fd,&clientaddr);
                                break;
			case C2S_LOGOUT:
				do_logout(msg,server_fd,&clientaddr);
                                break;
			case C2S_ONLINE_USER:
				do_online_user(server_fd,&clientaddr);
				break;
			default:
				break;
       
                }

        }
}

void do_login(MESSAGE msg,int sock,struct sockaddr_in *clientaddr)
{
	USER_INFO user;
	strcpy(user.username,msg.body);
	user.ip = clientaddr->sin_addr.s_addr;
	user.port = clientaddr->sin_port;
	
	/*查找用户*/
	USER_LIST::iterator it;
	for (it=client_list.begin(); it!=client_list.end(); ++it)
	{
		if (strcmp(it->username,msg.body) == 0)
		{
			break;
		}
	}
	
	if (it == client_list.end())	//没找到用户
	{
		printf("has a user login:%s <-> %s:%d\n",msg.body,inet_ntoa(clientaddr->sin_addr),ntohs(clientaddr->sin_port));
		client_list.push_back(user);
	
		//登录成功应答
		MESSAGE reply_msg;
		memset(&reply_msg,0,sizeof(reply_msg));
		reply_msg.cmd = htonl(S2C_LOGIN_OK);
		sendto(sock,&reply_msg,sizeof(reply_msg),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in));

		int count = htonl((int)client_list.size());
		//发送在线人数
		sendto(sock,&count,sizeof(int),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in));
		printf("sending user list information to:%s <-> %s:%d\n",msg.body,inet_ntoa(clientaddr->sin_addr),ntohs(clientaddr->sin_port));
		//发送在线列表
		for (it=client_list.begin(); it != client_list.end(); ++it)
		{
			 sendto(sock,&*it,sizeof(USER_INFO),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in));

		}
		//向其他用户通知
		for (it=client_list.begin(); it != client_list.end(); ++it)
		{
			if (strcmp(it->username,msg.body) == 0)
				continue;
			struct sockaddr_in peeraddr;
			memset(&peeraddr,0,sizeof(peeraddr));
			peeraddr.sin_family = AF_INET;
			peeraddr.sin_port = it->port;
			peeraddr.sin_addr.s_addr = it->ip;

			msg.cmd = htonl(S2C_SOMEONE_LOGIN);
			memcpy(msg.body,&user,sizeof(user));

			if ( sendto(sock,&msg,sizeof(msg),0,(struct sockaddr*)&peeraddr,sizeof(struct sockaddr_in)) )
			{
				printf("sending user list information to:%s <-> %s:%d\n",it->username,inet_ntoa(peeraddr.sin_addr),ntohs(peeraddr.sin_port));
			}
				
		}		
	}
	else
	{
		printf("user %s has already logined\n",msg.body);

		MESSAGE reply_msg;
		memset(&reply_msg,0,sizeof(reply_msg));
		reply_msg.cmd = htonl(S2C_ALREADY_LOGINED);
		sendto(sock,&reply_msg,sizeof(reply_msg),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in));
	}
}

void do_logout(MESSAGE msg,int sock,struct sockaddr_in *clientaddr)
{	
	USER_INFO user;

	strcpy(user.username,msg.body);
	user.ip = clientaddr->sin_addr.s_addr;
	user.port = clientaddr->sin_port;
	
	/*查找用户*/
	USER_LIST::iterator it;
	for (it=client_list.begin(); it!=client_list.end(); ++it)
	{
		if (!strcmp(it->username,msg.body))
		{
			printf("client %s logout server.\n",msg.body);
			client_list.erase(it); 
			break;
		}
	}
	/*向其他用户通知*/
	for (it=client_list.begin(); it != client_list.end(); ++it)
	{
		struct sockaddr_in peeraddr;
		memset(&peeraddr,0,sizeof(peeraddr));
		peeraddr.sin_family = AF_INET;
		peeraddr.sin_port = it->port;
		peeraddr.sin_addr.s_addr = it->ip;

		msg.cmd = htonl(S2C_SOMEONE_LOGOUT);
		memcpy(msg.body,&user,sizeof(user));

		sendto(sock,&msg,sizeof(msg),0,(struct sockaddr*)&peeraddr,sizeof(struct sockaddr_in));
	}		
}

void do_online_user(int sock,struct sockaddr_in *clientaddr)
{
	MESSAGE msg;
	memset(&msg,0,sizeof(msg));

	msg.cmd = htonl(S2C_ONLINE_USER);
	
	if ( sendto(sock,(const char*)&msg,sizeof(msg),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)) )
	{
		  printf("sending user list information to: <-> %s:%d\n",inet_ntoa(clientaddr->sin_addr),ntohs(clientaddr->sin_port));
	}
	USER_LIST::iterator it;


        int count = htonl((int)client_list.size());
        //发送在线人数
        sendto(sock,&count,sizeof(int),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in)); 
        //发送在线列表
        for (it=client_list.begin(); it != client_list.end(); ++it)
        {
            sendto(sock,&*it,sizeof(USER_INFO),0,(struct sockaddr*)clientaddr,sizeof(struct sockaddr_in));

        }

}

    客户端:

#include <unistd.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>

#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include "pub.h"

#define	ERR_EXIT(m)			\
	do				\
	{				\
		perror(m);		\
		exit(EXIT_FAILURE);	\
	}				\
	while(0);			\

//当前用户名
char username[16];

//聊天室成员列表
USER_LIST client_list;


void do_someone_login(MESSAGE &msg);
void do_someone_logout(MESSAGE &msg);
void do_getlist(int sock);
void parse_cmd(char *cmdline,int sock,struct sockaddr_in *servaddr);
bool sendmsgto(int sock,char *name,char *msg);
void do_chat(const MESSAGE &msg);

void chat_cli(int sock);


int main(void)
{
	int sock;
	if ((sock = socket(PF_INET,SOCK_DGRAM,0)) < 0)
		ERR_EXIT("socket");

	chat_cli(sock);

	return 0;
}


void chat_cli(int sock)
{
	struct sockaddr_in servaddr;
	memset(&servaddr,0,sizeof(servaddr));
	servaddr.sin_family = AF_INET;
	servaddr.sin_port = htons(9999);
	servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

	struct sockaddr_in peeraddr;
	socklen_t peerlen;

	MESSAGE msg;
	while (1)
	{
		memset(username,0,sizeof(username));
		printf("please input your name:");
		fflush(stdout);
		scanf("%s",username);

		memset(&msg,0,sizeof(msg));
		msg.cmd = htonl(C2S_LOGIN);
		strcpy(msg.body,username);

		sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)&servaddr, sizeof(servaddr));
	
		memset(&msg,0,sizeof(msg));
		recvfrom(sock,&msg,sizeof(msg),0,NULL,NULL);
		int cmd = ntohl(msg.cmd);
		if (cmd == S2C_ALREADY_LOGINED)
			printf("user %s already logined server please use another username\n",username);
		else if(cmd == S2C_LOGIN_OK)
		{
			printf("user %s already logined server\n",username);
			break;
		}
	}
	int count;
	recvfrom(sock,&count,sizeof(int),0,NULL,NULL);
	//接受登录列表
	int n = ntohl(count);
	printf("has %d users logined server\n",n);
	//接受登录用户名,ip,端口
	for(int i=0; i<n;i++)
	{
		USER_INFO user;
		recvfrom(sock,&user,sizeof(USER_INFO),0,NULL,NULL);
		client_list.push_back(user);
		in_addr tmp;
		tmp.s_addr = user.ip;

		printf("%d %s <-> %s:%d\n",i,user.username,inet_ntoa(tmp),ntohs(user.port));
	}
	
	printf("**********************************COMMANDS:*******************************\n");
	printf(" 			send username msg\n");
	printf("			list\n");
	printf("			exit\n");
	
	fd_set rset;
	FD_ZERO(&rset);
	int nready;
	while(1)
	{
		//把标准输入IO添加到select集合中
		FD_SET(STDIN_FILENO,&rset);
		//把套接口IO添加到select集合中
		FD_SET(sock,&rset);
		nready = select(sock+1,&rset,NULL,NULL,NULL);

		if(nready == -1)
			ERR_EXIT("select");
		if(nready == 0)
			continue;
		//检测到套接口事件
		if(FD_ISSET(sock,&rset))
		{
			printf("检测到sock事件\n");
			peerlen = sizeof(peeraddr);
			memset(&msg,0,sizeof(msg));
			recvfrom(sock,&msg,sizeof(msg),0,(struct sockaddr*)&peeraddr,&peerlen);
			int cmd = ntohl(msg.cmd);
			printf("cmd=%d\n",cmd);
			switch(cmd)
			{
				case S2C_SOMEONE_LOGIN:
					do_someone_login(msg);
					break;
				case S2C_SOMEONE_LOGOUT:
					do_someone_logout(msg);
					break;
				case S2C_ONLINE_USER:
					do_getlist(sock);
					break;
				case C2C_CHAT:
					do_chat(msg);
					break;
				default:
					break;
			}
		}
		//如果是标准输入产生了事件
		if(FD_ISSET(STDIN_FILENO,&rset))
		{
			printf("检测到标准输入事件\n");
			char cmdline[100] = {0};
			if(fgets(cmdline,sizeof(cmdline),stdin) == NULL)
				break;
			if(cmdline[0] == '\n')
				continue;
			cmdline[strlen(cmdline) - 1] = '\0';
			//调用命令解析函数
			parse_cmd(cmdline,sock,&servaddr);
		}
		
	}


} 
//其他用户登录通知
void do_someone_login(MESSAGE &msg)
{
	USER_INFO *user = (USER_INFO*)msg.body;
	in_addr tmp;
	tmp.s_addr = user->ip;
	printf("%s <->%s:%d has logined server\n",user->username,inet_ntoa(tmp),ntohs(user->port));
	client_list.push_back(*user);		
}
//其他用户登出通知
void do_someone_logout(MESSAGE &msg)
{
	USER_LIST::iterator it;
	//找到退出的客户端
	for(it=client_list.begin(); it != client_list.end(); it++)
	{
		//找到终止循环
		if(strcmp(it->username,msg.body) == 0)
		{
			break;
		}
	}

	if (it != client_list.end())	
	{
		client_list.erase(it);
	}

	printf("user %s has logout server\n",msg.body);
}
//得到在线列表
void do_getlist(int sock)
{
	int count;
        recvfrom(sock,&count,sizeof(int),0,NULL,NULL);
        //接受登录列表
        int n = ntohl(count);
        printf("has %d users logined server\n",n);
	client_list.clear();
        //接受登录用户名,ip,端口
        for(int i=0; i<n;i++)
        {
                USER_INFO user;
                recvfrom(sock,&user,sizeof(USER_INFO),0,NULL,NULL);
                client_list.push_back(user);
                in_addr tmp;
                tmp.s_addr = user.ip;

                printf("%d %s <-> %s:%d\n",i,user.username,inet_ntoa(tmp),ntohs(user.port));
        }

}
//命令解析
void parse_cmd(char *cmdline,int sock,struct sockaddr_in *servaddr)
{
	char cmd[10] = {0};//命令
	char *p = NULL;
	//检测空格
	p = strchr(cmdline,' ');
	if(p != NULL)
		*p = '\0';

	strcpy(cmd,cmdline);

	if(strcmp(cmd,"exit") == 0)
	{
		MESSAGE msg;
		memset(&msg,0,sizeof(msg));
		msg.cmd = htonl(C2S_LOGOUT);
		strcpy(msg.body,username);

		if( (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(*servaddr))) < 0 )
			ERR_EXIT("sendto");

		printf("user %s has logout server\n",username);
		exit(EXIT_SUCCESS);
	}
	else if(strcmp(cmd,"list") == 0)
	{
		MESSAGE msg;
		memset(&msg,0,sizeof(msg));
		msg.cmd = htonl(C2S_ONLINE_USER);

		if( (sendto(sock, &msg, sizeof(msg), 0, (struct sockaddr *)servaddr, sizeof(*servaddr))) < 0 )
                        ERR_EXIT("sendto");
		return;

	}
	else if(strcmp(cmd,"send") == 0)
	{
		char peername[16] = {0};//用户名
		char msg[MSG_LEN] = {0};//要发送的消息
		if(p == NULL)		//用户名为空,重新输入
		{
			 printf("bad command\n");
                         printf("**********************************COMMANDS:*******************************\n");
                         printf("                        send username msg\n");
                         printf("                        list\n");
                         printf("                        exit\n");

			 return;

		}
		/*send user msg*/
		/*     p    p2 */
		//命令解析
		while(*p++ == ' ');

		char *p2;
		p2 = strchr(p,' ');
		if(p2 == NULL)//消息为空,重新输入
		{
			 printf("bad command\n");
               		 printf("**********************************COMMANDS:*******************************\n");
               		 printf("                        send username msg\n");
                	 printf("                        list\n");
                	 printf("			 exit\n"); 
			 return;
		}
		*p2 = '\0';
		strcpy(peername,p);

		while( *p2++ == ' ');
		if(p2 == NULL)//消息为空,重新输入
                {
                         printf("bad command\n");
                         printf("**********************************COMMANDS:*******************************\n");
                         printf("                        send username msg\n");
                         printf("                        list\n");
                         printf("                        exit\n");
                         return;
                }

		strcpy(msg,p2);

		sendmsgto(sock,peername,msg);
		
	}
	else 
	{	printf("bad command\n");
		printf("**********************************COMMANDS:*******************************\n");
        	printf("                        send username msg\n");
        	printf("                        list\n");
        	printf("                        exit\n");

	}
}

bool sendmsgto(int sock,char *name,char *msg)
{
	if(strcmp(name,username) == 0)
	{
		printf("can't send message to self\n");
		return false;
	}

	 USER_LIST::iterator it;
        //找到退出的客户端
        for(it=client_list.begin(); it != client_list.end(); it++)
        {
                //找到终止循环
                if(strcmp(it->username,name) == 0)
                {
                        break;
                }
        }


	if(it == client_list.end())
	{
		printf("user %s has not logined server\n",name);
		return false;
	}

	MESSAGE m;
	memset(&m,0,sizeof(m));
	m.cmd = htonl(C2C_CHAT);

	CHAT_MSG cm;
	strcpy(cm.username,username);
	strcpy(cm.msg,msg);

	memcpy(m.body,&cm,sizeof(cm));
	
	struct sockaddr_in peeraddr;
	memset(&peeraddr,0,sizeof(peeraddr));
	peeraddr.sin_family = AF_INET;
	peeraddr.sin_addr.s_addr = it->ip;
	peeraddr.sin_port = it->port;

	in_addr tmp;
	tmp.s_addr = it->ip;

	printf("sending message [%s] to user [%s] <->%s:%d\n",msg,name,inet_ntoa(tmp),ntohs(it->port));


	sendto(sock,(const char*)&m,sizeof(m),0,(struct sockaddr *)&peeraddr, sizeof(peeraddr));
	return true;
}

void do_chat(const MESSAGE &msg)
{
	CHAT_MSG *cm = (CHAT_MSG*)msg.body;
	printf("recv a message haha [%s] form [%s]\n",cm->msg,cm->username);
}

头文件:

#ifndef _PUB_H_
#define _PUB_H_

#include <list>
#include <algorithm>

using namespace std;

//客户端向服务器发送
#define C2S_LOGIN		0x01
#define C2S_LOGOUT		0x02
#define C2S_ONLINE_USER 	0x03

#define MSG_LEN			512

//服务器向客户端应答
#define S2C_LOGIN_OK		0x01
#define S2C_ALREADY_LOGINED	0x02
#define S2C_SOMEONE_LOGIN	0x03
#define S2C_SOMEONE_LOGOUT	0x04
#define S2C_ONLINE_USER		0x05

//客户端向客户端发送
#define C2C_CHAT		0x10

//定义消息结构
typedef struct message
{
	int cmd;
	char body[MSG_LEN];
}MESSAGE;
//用户信息
typedef struct user_info
{
	char username[16];
	unsigned int ip;
	unsigned short port;
}USER_INFO;
//客户端和客户端传递的消息结构
typedef struct chat_msg
{
	char username[16];
	char msg[100];
}CHAT_MSG;

typedef list<USER_INFO> USER_LIST;
#endif

阅读更多
想对作者说点什么?

博主推荐

换一批

没有更多推荐了,返回首页