基于UDP的网络聊天室

项目需求:

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

代码实现如下:

服务器:

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

#define ERR_MSG(msg) do{\
	fprintf(stderr, "line:%d\n", __LINE__);\
	perror(msg);\
}while(0)

#define PORT 6666
#define IP "192.168.8.77"

typedef struct sockaddr_in datatype;

//组协议
struct DataPacket
{
    char code;
    char name[20];
    char txt[128];
};                   

//创建一个链表用于存储客户端消息包
typedef struct Node
{
	union{
		int len;     //头结点数据域
		datatype data;    //普通结点数据
	};
	struct Node* next;     //指针域
}LinkList;

//arg传参
struct MSG
{
    int sfd;
    struct sockaddr_in cin;
    struct DataPacket packet;
	LinkList *L;
};

void* funcA(void *arg);
//创建链表
LinkList *list_create();
//判空
int list_empty(LinkList*L);
//申请结点分装数据
LinkList *buy_node(datatype e);
//头插
int list_insert_head(LinkList *L, datatype e);
//遍历发送登入信息
void list_show_L(LinkList *L, int sfd, struct DataPacket packet);
//遍历发送群聊信息
void list_show_C(LinkList *L, int sfd, struct DataPacket packet, struct sockaddr_in cin);
//遍历发送群聊信息
void list_show_Q(LinkList *L, int sfd, struct DataPacket packet);
//头删                          
int list_delete_head(LinkList*L);
//按位置查找返回结点
LinkList *find_node(LinkList *L, int pos);
//按值查找返回结点位置
int list_search_val(LinkList *L, datatype e);
//任意位置删除
int list_delete_pos(LinkList *L, int pos);
//释放链表
void list_free(LinkList *L);

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}

	//填充服务器自身的地址信息结构体,AF_INET: man 7 ip
	struct sockaddr_in sin;
	sin.sin_family 			= AF_INET;
	sin.sin_port 			= htons(PORT);
	sin.sin_addr.s_addr 	= inet_addr(IP);
	
	//绑定服务器的地址信息结构体
	if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("bind success\n");

	char buf[128] = "";
	ssize_t res = 0;
	struct sockaddr_in cin;       //储存收到的数据包的地址信息
	socklen_t addrlen = sizeof(cin);
	struct DataPacket packet;

	//创建链表用于存储用户信息
	LinkList *L = list_create();
	if(NULL == L)
		return -1; 

	struct MSG arg_msg;
	arg_msg.sfd = sfd;
	arg_msg.cin = cin;
	arg_msg.packet = packet;
	arg_msg.L = L;

	//创建一个分支线程用于向客户端发送信息
	pthread_t A;
	if(pthread_create(&A, NULL, funcA, &arg_msg) != 0)
	{
		fprintf(stderr, "pthread_create failed\n");
		return -1;
	}

	while(1)
	{
		//接收
		res = recvfrom(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&cin, &addrlen);
		if(res <0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}

		//登入信息处理
		if('L' == packet.code)
		{
			//将用户登入信息发送至目前已在线所有用户
			list_show_L(L, sfd, packet);
			//将用户信息存入链表
			list_insert_head(L, cin);
			printf("[%s:%d]:用户[%s] 已登录\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), packet.name);
		}

		//群聊信息处理
	 	if('C' == packet.code)
		{
			printf("[%s:%d]:用户[%s]: %s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), packet.name, packet.txt);
			//将群聊信息发送至其他所有在线用户
			list_show_C(L, sfd, packet, cin);
		}

		//下线信息处理
		if('Q' == packet.code)
		{
			//将下线信息发送至所有在线用户
			list_show_Q(L, sfd, packet);

			//删除该下线用户地址信息
			int pos = list_search_val(L, cin);
			list_delete_pos(L, pos);

			printf("[%s:%d]****[%s]已下线****\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), packet.name);
		}
	}

	//释放链表
	list_free(L);
	//关闭套接字
	close(sfd);

	return 0;
}

//分支线程程序
void* funcA(void *arg)
{
    int sfd = ((struct MSG*)arg)->sfd;
    struct sockaddr_in cin = ((struct MSG*)arg)->cin;
    struct DataPacket packet = ((struct MSG*)arg)->packet;
	LinkList *L = ((struct MSG*)arg)->L;

	char Code;
	char buf[128] = "";

    while(1)
    {
		//从终端获取发送内容
		scanf("%c %s", &Code, buf);
		while(getchar() != 10);

		//发送服务器消息
		if('C' == Code)
		{
			packet.code = 'C';
			strcpy(packet.name, "server");
			strcpy(packet.txt, buf);
			list_show_C(L, sfd, packet, cin);
		}
		else
		{
			printf("发送格式错误\n");
			printf("发送格式:C 消息内容\n");
		}
    }
    pthread_exit(NULL);
}

//创建链表
LinkList *list_create()
{
    //申请一个空间                                     
    LinkList *L = (LinkList*)malloc(sizeof(LinkList)); 
    if(NULL == L)                                      
    {                                                  
        printf("创建失败\n");                          
        return NULL;                                   
    }                                                  
    L->len = 0;                                        
    L->next = NULL;                                    
                                                       
    return L;                                          
}
//判空
int list_empty(LinkList*L)
{
	return L->next == NULL;
}
//申请结点分装数据
LinkList *buy_node(datatype e)
{
    //在堆区申请一个结点的空间                        
    LinkList *p = (LinkList*)malloc(sizeof(LinkList));
    if(NULL == p)                                     
    {                                                 
        printf("空间申请失败\n");                     
        return NULL;                                  
    }                                                 
    //将数据封装到结点的数据域中                      
    p->data = e;                                      
    p->next = NULL;                                   
    return p;                                         
}
//头插
int list_insert_head(LinkList *L, datatype e)
{
     //判断逻辑                 
     if(NULL == L)              
     {                          
         printf("链表不合法\n");
         return 0;              
     }                          
     //申请结点封装数据         
     LinkList *p = buy_node(e); 
                                
     //头插逻辑                 
     p->next = L->next;         
     L->next = p;               
                                
     //表的变化                 
     L->len++;                  
     printf("客户端地址信息存储成功\n");      
     return 1;                  
}
//遍历发送登入信息
void list_show_L(LinkList *L, int sfd, struct DataPacket packet)
{
	//判断逻辑
	if(NULL == L || list_empty(L))
		return ;

	//遍历逻辑
	LinkList *q = L->next;   //定义指针指向第一个结点
	while(q != NULL)
	{
		//发送登入信息
		if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&(q->data), sizeof(q->data)) < 0)
		{
			ERR_MSG("sendto");
			return ;
		}
		q = q->next;
	}
	printf("\n");
}
//遍历发送群聊信息
void list_show_C(LinkList *L, int sfd, struct DataPacket packet, struct sockaddr_in cin)
{
	//判断逻辑
	if(NULL == L || list_empty(L))
		return ;

	//遍历逻辑
	LinkList *q = L->next;   //定义指针指向第一个结点
	while(q != NULL)
	{
		if(memcmp(&cin, &q->data, sizeof(cin)) == 0)
		{
			q = q->next;
			continue;
		}
		//发送群聊信息
		if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&(q->data), sizeof(q->data)) < 0)
		{
			ERR_MSG("sendto");
			return ;
		}
		q = q->next;
	}
	printf("\n");
}
//遍历发送下线信息
void list_show_Q(LinkList *L, int sfd, struct DataPacket packet)
{
	//判断逻辑
	if(NULL == L || list_empty(L))
		return ;

	//遍历逻辑
	LinkList *q = L->next;   //定义指针指向第一个结点
	while(q != NULL)
	{
		//发送下线信息
		if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&(q->data), sizeof(q->data)) < 0)
		{
			ERR_MSG("sendto");
			return ;
		}
		//printf("[%s:%d]sendto success\n", inet_ntoa(q->data.sin_addr), htons(q->data.sin_port));
		q = q->next;
	}
	printf("\n");
}
//按位置查找返回结点
LinkList *find_node(LinkList *L, int pos)
{
	//判断逻辑                              
	if(pos < 0 || pos > L->len)             
	{                                       
		printf("查找失败\n");               
		return NULL;                        
	}                                       
	//查找逻辑                              
	//定义遍历指针                          
	LinkList *q = L;                        
	for(int i = 0; i < pos; i++)            
		q = q->next;    //遍历指针往后偏移  
	return q;       //将找到的结点返回      
}
//头删
int list_delete_head(LinkList*L)
{
    //判断逻辑                                 
    if(NULL == L || list_empty(L))             
    {                                          
        printf("头删失败\n");                  
        return 0;                              
    }                                          
                                               
    //删除逻辑                                 
    LinkList *p = L->next;  //标记要删除的结点 
    L->next = p->next;                         
    free(p);                                   
    p == NULL;                                 
                                               
    //表长变化                                 
    L->len--;                                  
                                               
    printf("头删成功\n");                      
    return 1;                                  
}
//按值查找返回结点位置
int list_search_val(LinkList *L, datatype e)
{
    //判断逻辑                                      
    if(NULL == L || list_empty(L))                  
    {                                               
        printf("查找失败\n");                       
        return -1;                                  
    }                                               
                                                    
    //查找逻辑                                      
    LinkList *p = L->next;      //从第一个结点出发  
    for(int i = 1; i <= L->len; i++)                
    {                                               
        if(p->data.sin_port ==  e.sin_port)                            
            return i;                               
        p = p->next;    //继续判断下一个结点        
    }                                               
    return 0;       //说明没找到                    
}
//任意位置删除
int list_delete_pos(LinkList *L, int pos)
{
    //判断逻辑                                                
    if(NULL == L || list_empty(L) || pos < 1 || pos > L->len) 
    {                                                         
        printf("删除失败\n");                                 
        return 0;                                             
    }                                                         
                                                              
    //找到任意位置的前驱结点                                  
    LinkList *p = find_node(L, pos-1);                        
    if(NULL == p)                                             
    {                                                         
        printf("删除失败\n");                                 
        return 0;                                             
    }                                                         
                                                              
    //删除逻辑                                                
    LinkList *q = p->next;                                    
    p->next = q->next;                                        
    free(q);                                                  
    q = NULL;                                                 
                                                              
    //表长变化                                                
    L->len--;                                                 
                                                              
    printf("客户端地址信息删除成功\n");                                     
    return 1;                                                 
}
//释放链表
void list_free(LinkList *L)
{
    //判断逻辑                            
    if(NULL != L)                         
    {                                     
        //循环进行头删,直到链表中无结点  
        while(L->next != NULL)            
            list_delete_head(L);          
    }                                     
    //释放头结点                          
    free(L);                              
    L = NULL;                             
    printf("释放成功\n");                 
    return ;                              
}

 客户端:

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

#define ERR_MSG(msg) do{\
	fprintf(stderr, "line:%d\n", __LINE__);\
	perror(msg);\
}while(0)

#define CLI_PORT 6666
#define CLI_IP "192.168.8.77"

//组协议
struct DataPacket
{
	char code;
	char name[20];
	char txt[128];
};

//arg传参
struct MSG
{
	int sfd;
	struct sockaddr_in sin;
	struct DataPacket packet;
};

void* funcA(void *arg)
{
	int sfd = ((struct MSG*)arg)->sfd;
	struct sockaddr_in sin = ((struct MSG*)arg)->sin;
	struct DataPacket packet = ((struct MSG*)arg)->packet;
	char buf[128] = "";

	while(1)
	{
		//组群聊数据包
		packet.code = 'C';
		bzero(buf, sizeof(buf));
		scanf("%s", buf);
		while(getchar() != 10);
		strcpy(packet.txt,buf);

		if(strcmp(packet.txt, "quit") == 0)
		{
			//组下线数据包
			packet.code = 'Q';
			if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
			{
				ERR_MSG("sendto");
				return NULL;
			}
			break;
		}

		//发送
		if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
		{
			ERR_MSG("sendto");
			return NULL;
		}
	}
	pthread_exit(NULL);
}

int main(int argc, const char *argv[])
{
	//创建报式套接字
	int sfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sfd < 0)
	{
		ERR_MSG("socket");
		return -1;
	}

	//填充服务器的地址信息结构体,AF_INET:man 7 ip
	//供于下方sendto使用
	struct sockaddr_in sin;
	sin.sin_family 		= AF_INET;
	sin.sin_port 		= htons(CLI_PORT);
	sin.sin_addr.s_addr = inet_addr(CLI_IP);

	socklen_t addrlen = sizeof(sin);
	char buf[128] = "";
	struct DataPacket packet;
	ssize_t res = 0;

	//组登录数据包
	packet.code = 'L';
	bzero(buf, sizeof(buf));
	printf("请输入用户名>>>");
	scanf("%s", buf);
	while(getchar() != 10);
	strcpy(packet.name,buf);
	//发送
	if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
	{
		ERR_MSG("sendto");
		return -1;
	}

	struct MSG arg_msg;
	arg_msg.sfd = sfd;
	arg_msg.sin = sin;
	arg_msg.packet = packet;

	//创建一个分支线程A用于向服务器发送消息
	pthread_t A;
	if(pthread_create(&A, NULL, funcA, &arg_msg) != 0)
	{
		fprintf(stderr, "pthread_create failed\n");
		return -1;
	}

	while(1)
	{
		//接收服务器信息
		res = recvfrom(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&sin, &addrlen);
		if(res < 0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}
		if('L' == packet.code)
			printf("----[%s]----已上线\n", packet.name);
	 	if('C' == packet.code)
			printf("[%s]: %s\n", packet.name, packet.txt);
		if('Q' == packet.code)
		{
			if(strcmp(buf, packet.name) == 0)
				break;
			printf("****[%s]****已下线\n", packet.name);
		}
	}

	//关闭套接字
	close(sfd);
	return 0;
}

运行结果如下:

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值