【网络编程】 基于UDP的网络聊天室

前言

        将前面的数据结构,多线程,网络的内容加在一起的一个项目,比较综合,在代码部分采用了分文件编译并且写了比较详细的注释(个人觉得)。

ps:希望对大家有用

项目需求:

  1. 如果有用户登录,其他用户可以收到这个人的登录信息
  2. 如果有人发送信息,其他用户可以收到这个人的群聊信息
  3. 如果有人下线,其他用户可以收到这个人的下线信息
  4. 服务器可以发送系统信息

项目原理分析图

【网络编程】 基于UDP的网络聊天室_第1张图片

服务端

chatser.h

#ifndef CHATSER_H
#define CHATSER_H
#include
//从客户端接收消息结构体
typedef struct msgTyp
{
   char type;//消息类型
   char userName[20];//用户名
   char msgText[1024];//消息数据
}msgTyp,*msgTypPtr;

//用户信息
typedef struct users
{
    char userName[20];//用户名
	struct sockaddr_in cin;//客户端的信息
}Users,*UsersPtr;
//链表的结构体
typedef struct Node
{
	union 
	{
		int len;//链表长度
        UsersPtr data;//用户信息
	};
	struct Node *next;//指针域
}Node, *NodePtr;
//创建传递给线程函数的结构体
typedef struct argTyp
{
	NodePtr L;//链表
    struct sockaddr_in cin;//客户端的信息
	struct sockaddr_in sin;//服务器的信息
    int sfd;//套接字
	msgTyp msg;//客户端接收消息结构体
}argTyp,*argTypPtr;

//创建链表
NodePtr carete_link();
//创建节点
NodePtr create_node(char *username,struct sockaddr_in cin);
//服务器发送消息
void *send_msg(void *arg);
//服务器接收并转发消息
void *recv_msg(void *arg);
//删除节点
void delete_node(NodePtr L, char *username);


#endif

chatser.c

#include"chatser.h"
//创建链表
NodePtr carete_link()
{
    //创建头节点
    NodePtr L = (NodePtr)malloc(sizeof(Node));
    if (NULL == L)
    {
        printf("创建头节点失败\n");
        return NULL;
    }
    //初始化头节点
    L->len = 0;
    L->next = NULL;
    return L;
}
//创建节点
NodePtr create_node(char *username,struct sockaddr_in cin)
{
    //创建节点
    NodePtr p =(NodePtr)malloc(sizeof(Node));
    if (NULL == p)
    {
        printf("创建节点失败\n");
        return NULL;
    }
    //初始化节点信息
    p->data = (UsersPtr)malloc(sizeof(Users));//给data用户信息分配空间
    strcpy(p->data->userName,username);//将用户名复制到data的username
    p->data->cin = cin;//将客户端信息复制到data的cin
    p->next = NULL;
    return p;
}
//服务器发送消息
void *send_msg(void *arg)//传入线程函数的结构体
{
    //获取结构体
    argTypPtr argTyp = (argTypPtr)arg;//结构体
    NodePtr L = argTyp->L;//链表
    struct sockaddr_in sin = argTyp->sin;//服务器信息
    int sfd = argTyp->sfd;//socket
    while (1)
    {
       //从终端输入消息
        char buf[1024] = "";//未处理消息
        char wbuf[1024] = "";//处理后的消息
        fgets(buf,sizeof(buf),stdin);
        buf[strlen(buf)-1] = 0;
        //处理消息
        snprintf(wbuf,sizeof(wbuf),"**system**:%s",buf);
        //遍历链表发送消息
        NodePtr p = L->next;
        while(p!=NULL)
        {
            if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0)
            {
                printf("发送消息失败\n");
                return NULL;
            }
            p = p->next;
        }
        printf("**system** [%s:%d]:chat成功\n", inet_ntoa(sin.sin_addr), ntohs(sin.sin_port)); 
    }
    
    
    pthread_exit(NULL);//退出线程       
    return NULL;
}
//服务器接收并转发消息
void *recv_msg(void *arg)//传入线程函数的结构体
{
    //获取结构体
    argTypPtr argTyp = (argTypPtr)arg;
    NodePtr L = argTyp->L;//链表
    struct sockaddr_in cin = argTyp->cin;//客户端信息
    int sfd = argTyp->sfd;//socket
    char username[20] = "";//用户名
    char buf[1024] = "";//未处理的消息
    char wbuf[1024] = "";//处理后的消息
    
    //获取用户名
    strcpy(username,argTyp->msg.userName);  

    //获取是什么类型的消息
    if(argTyp->msg.type == 'L')//登录   
    {
       
        //判断是否有该用户
        NodePtr q = L->next;
        // while(q!=NULL)
        // {
        //     if(strcmp(q->data->userName,username) == 0)
        //     {
        //         printf("该用户已存在\n");
        //         break;
        //         return NULL;
        //     }
        // }
        //创建节点
        NodePtr p = create_node(username,cin);
        //将节点添加到链表(我采用头插)
        p->next = L->next;
        L->next = p;
        L->len++;//链表长度加1
        //处理消息
        snprintf(wbuf,sizeof(wbuf),"**%s已登录**",username);
        //遍历链表发送消息
        p = L->next;
        while(p!=NULL)
        {
            if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0)
            {
                printf("发送消息失败\n");
                return NULL;
            }
            p = p->next;
        }
        printf("%s [%s:%d]:登录成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
    }else if(argTyp->msg.type == 'C')//聊天
    {
        //处理消息
        snprintf(wbuf,sizeof(wbuf),"%s:%s",username,argTyp->msg.msgText);
        //遍历链表转发消息
        NodePtr p = L->next;
        while(p!=NULL)
        {
            if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0)
            {
                printf("发送消息失败\n");
                return NULL;
            }
            p = p->next;
        }
        printf("%s [%s:%d]:chat成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
    }else if(argTyp->msg.type == 'Q')//退出
    {
        //删除节点
        delete_node(L,username);
        //处理消息
        snprintf(wbuf,sizeof(wbuf),"**%s已退出**",username);
        //循环遍历链表发送消息
        NodePtr p = L->next;
        while(p!=NULL)
        {
            if(sendto(sfd,wbuf,strlen(wbuf),0,(struct sockaddr*)&p->data->cin,sizeof(p->data->cin)) < 0)
            {
                printf("发送消息失败\n");
                return NULL;
            }
            p = p->next;
        }  
        printf("%s [%s:%d]:退出成功\n", username, inet_ntoa(cin.sin_addr), ntohs(cin.sin_port));
        L->len--;//链表长度减1
    }
    pthread_exit(NULL);//退出线程   
    return NULL;
}
//删除节点
void delete_node(NodePtr L, char *username)
{
   //删除节点
void delete_node(NodePtr L, char *username)
{
    //找到要删除节点的前驱节点
    NodePtr p = L;
    while (p->next != NULL && strcmp(p->next->data->userName, username) != 0)
    {
        p = p->next;
    }
    //删除节点
    if (p->next != NULL)
    {
        NodePtr temp = p->next; // 保存要删除的节点
        p->next = p->next->next; // 删除节点
        free(temp->data); // 释放节点数据
        free(temp); // 释放节点
    }
}
}

sermain.c

#include"chatser.h"
int main(int argc, const char *argv[])
{
	
	//创建链表
	NodePtr L = carete_link();

	//创建管道
	int sfd = socket(AF_INET,SOCK_DGRAM,0);
	if (sfd == -1)
	{
		perror("socket");
		return 1;
	}
	if(argc != 3)
	{
		printf("请输入ip地址和端口号\n");//argv[1]是 ip地址 argv[2]是端口号
		return 1;
	}
	//填充服务端地址结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;	
	sin.sin_port = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	//绑定
	if (bind(sfd,(struct sockaddr*)&sin,sizeof(sin)) == -1)
	{
		perror("bind error");
		return 1;
	}
	

	//获取客户端地址结构体
	struct sockaddr_in cin;
	socklen_t len = sizeof(cin);
	
	//线程实现收发数据
	pthread_t pid1,pid2;


	//填充结构体信息
	struct argTyp arg = {L,cin,sin,sfd};
	//创建发送线程
	if(pthread_create(&pid1,NULL,send_msg,&arg)==-1)
	{
		perror("pthread_create error");
		return 1;
	}
	//将线程设置为分离状态
	pthread_detach(pid1);
	while(1)
	{
		//接收客户端发送的数据
		if(recvfrom(sfd,&arg.msg,sizeof(arg.msg),0,(struct sockaddr*)&cin,&len)==-1)
		{
			perror("recvfrom error");
			return 1;
		}
		//填充结构体
		struct argTyp arg1 = {L,cin,sin,sfd};

		arg1.msg = arg.msg;

		//创建接收线程
		if(pthread_create(&pid2,NULL,recv_msg,&arg1)==-1)
		{
			perror("pthread_create error");
			return 1;
		}
		//将线程设置为分离状态
		//pthread_detach(pid1);
		pthread_detach(pid2);
	}
	close(sfd);
	return 0;
}

客户端

chatcli.h

#ifndef CHATCLI_H
#define CHATCLI_H
#include
//创建发送消息结构体
typedef struct msgTyp
{
   char type;//消息类型
   char userName[20];//用户名
   char msgText[1024];//消息数据
}msgTyp,*msgTypPtr;

//创建线程函数传输的结构体
typedef struct argTyp
{
	struct sockaddr_in sin;//服务器的信息
    int cfd;//套接字
	msgTyp msg;//客户端接收消息结构体
}argTyp,*argTypPtr;


//客户端发送消息
void *send_msg(void *arg);
//客户端接收并转发消息
void *recv_msg(void *arg);

// 将全局变量改为外部声明
extern int flag; // 标志位判断用户是否创建
extern char userName[20]; // 存放姓名
extern int num; // 记录用户是否退出

#endif

chatcli.c

#include"chatcli.h"
// 在这里定义全局变量
int flag = 0;
char userName[20] = "";
int num = 1;

//客户端发送消息
void *send_msg(void *arg)
{
	//printf("?\n");
	argTypPtr argTyp = (argTypPtr)arg;//获取传来的结构体
	struct sockaddr_in sin = argTyp->sin;
	int cfd = argTyp->cfd;
	char msgText[1024]="";//存放消息内容
	char buf[1024]="";//用来中转的
	while (num == 1)
	{
		fgets(buf,sizeof(buf),stdin);//获取终端传来的数据
		buf[strlen(buf)-1]=0;
		//表示该用户未注册
		if(flag ==0)
		{
			//printf("%d\n",flag);
			argTyp->msg.type = 'L';//发送的消息类型为登录
			strcpy(userName, buf);//将用户名记录下来
			//printf("%d\n",flag);
			strcpy(argTyp->msg.userName, buf);//给用户名赋值
			//printf("%s\n",argTyp->msg.userName);
			strcpy(argTyp->msg.msgText, msgText);//给消息数据赋值
			//printf("%d\n",flag);
			if (sendto(cfd, &argTyp->msg, sizeof(argTyp->msg), 0, (struct sockaddr*)&sin, sizeof(sin)) == -1)
			{
				perror("sendto error");
				return NULL;
			}
			flag++;//表示用户已经注册
			//printf("%d\n",flag);
		}else if (flag > 0)
		{
			if(strcmp(buf,"quit")==0)
			{
				argTyp->msg.type = 'Q';//发送的消息类型为登出
				strcpy(argTyp->msg.userName,userName);//给用户名赋值
				strcpy(argTyp->msg.msgText,msgText);//给消息数据赋值
				if(sendto(cfd,&argTyp->msg,sizeof(argTyp->msg),0,(struct sockaddr*)&sin,sizeof(sin))==-1)
				{
					perror("sendto error");
					return NULL;
				}
				num = 0;
				//表示退出
				break;
			}
			argTyp->msg.type = 'C';//发送的消息类型为聊天
			strcpy(argTyp->msg.userName,userName);//给用户名赋值
			strcpy(argTyp->msg.msgText,buf);//给消息数据赋值
			if(sendto(cfd,&argTyp->msg,sizeof(argTyp->msg),0,(struct sockaddr*)&sin,sizeof(sin))==-1)
			{
				perror("sendto error");
				return NULL;
			}
			
			
		}
	}
	
	
	pthread_exit(NULL);
}
//客户端接收消息
void *recv_msg(void *arg)
{
	argTypPtr argTyp = (argTypPtr)arg;//接收传来的结构体
	int cfd = argTyp->cfd;//获取套接字
	char buf[1024]="";//存放消息内容	
	while (num == 1)
	{
		bzero(buf,sizeof(buf));//清空容器
		if(recvfrom(cfd,buf,sizeof(buf),0,NULL,NULL)==-1)
		{
			perror("recvfrom error");
			return NULL;
		}
		printf("%s\n",buf);
	}
	pthread_exit(NULL);
}

climain.c

#include"chatcli.h"
// 声明 num 为外部变量
extern int num;
int main(int argc, const char *argv[])
{


	//创建管道
	int cfd = socket(AF_INET,SOCK_DGRAM,0);
	if (cfd == -1)
	{
		perror("socket");
		return 1;
	}
	//判断输入的参数是否正确
	if(argc != 3)
	{
		printf("请输入ip地址和端口号\n");//argv[1]是 ip地址 argv[2]是端口号
		return 1;
	}
	//填充服务端地址结构体
	struct sockaddr_in sin;
	sin.sin_family = AF_INET;	
	sin.sin_port = htons(atoi(argv[2]));
	sin.sin_addr.s_addr = inet_addr(argv[1]);
	//创建线程
	pthread_t pid1,pid2;
	//结构体赋值
	struct argTyp arg = {sin,cfd};
	
		//创建发送线程
		if(pthread_create(&pid1,NULL,send_msg,&arg) != 0)
		{
			printf("创建线程失败\n");
			return -1;
		}
		//创建接收线程
		if(pthread_create(&pid2,NULL,recv_msg,&arg) != 0)
		{
			printf("创建线程失败\n");
			return -1;
		}

		//设置线程分离
		pthread_detach(pid1);
		pthread_detach(pid2);
	while (num);	
	close(cfd);
	return 0;
}

 项目效果图

【网络编程】 基于UDP的网络聊天室_第2张图片

你可能感兴趣的:(网络,udp,网络协议)