基于UDP的网络聊天室

服务端

功能

1.将客户端登录信息发送给其他已登录的用户
2.将客户端发送的消息发送给其他已登录的用户
3.将客户端退出信息发送给其他已登录的用户
4.将客户端的信息显示在本机上

代码

ser.h

#ifndef __UDPSER_H__
#define __UDPSER_H__


//链表结构体
typedef struct node
{
	union
	{
		int num;           			//群人数
		struct sockaddr_in cin;     //群员地址信息结构体
	};
	struct node *next;
}linkList;



//错误返回
#define ERR_MSG(msg) do{\
			fprintf(stderr,"line:  %d ",__LINE__);\
			perror(msg);}while(0);



//创建链表
linkList * link_create();
//头插
int link_insert_head(linkList *L,struct sockaddr_in s);
//遍历发送
void list_show(int sfd,linkList *L,char* buf,int size ,struct sockaddr_in *rcv);
//删除退出成员的地址信息结构体
int link_del(linkList *L,struct sockaddr_in s);

//子线程函数
void *handler1(void* arg);

//获得名字
void getname(char* name,char* buf1);

//收到登入后的回应函数
void load_res(int sfd,linkList *L,struct sockaddr_in rcv,char* name,char *buf);

//收到退出后的回应函数
void quit_res(int sfd,linkList *L,struct sockaddr_in rcv,char* name,char *buf);

#endif

ser.c

子线程函数
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<pthread.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include"ser.h"


//子线程函数
void *handler1(void* arg)
{
	extern int sfd;
	linkList *L=(linkList*)arg;
	char buf2[128]="";
	int size=sprintf(buf2,"\n%s","--------------服务器:");
	while(1)
	{
		bzero(buf2+size,sizeof(buf2)-size);
		fgets(buf2+size,sizeof(buf2)-size,stdin);

		//遍历发送 
		list_show(sfd,L,buf2,strlen(buf2),NULL);

	}

}

创建链表函数
//创建链表
linkList * link_create()
{
	linkList *L=(linkList *)malloc(sizeof(linkList));
	if(NULL==L)
	{
		printf("创建列表失败\n");
		exit(-1);                                  //直接退出程序
	}

	//初始化
	L->num=0;
	L->next=NULL;

	printf("链表创建成功\n");
	return L;
}
头插函数
//头插
int link_insert_head(linkList *L,struct sockaddr_in s)
{
	//判断条件
	if(L==NULL)
	{
		printf("链表不合法\n");
		return -1;
	}
	
	
	//判断客户端是否重复输入
	//链表查重
	linkList *q=L->next;
	while(q!=NULL)
	{

		if( (q->cin.sin_port==s.sin_port) &&  (q->cin.sin_addr.s_addr==s.sin_addr.s_addr))
		{
			return -2;                       //代表客户端重复登入
		}
		q=q->next;
	}

	

	//申请结点
	linkList *p=(linkList *)malloc(sizeof(linkList));
	if(p==NULL)
	{
		printf("节点申请失败\n");
		return -2;
	}

	//写入数据
	p->next=NULL;
	p->cin=s;

	
	//头插
	p->next=L->next;
	L->next=p;

	//表的变化
	L->num++;

//	printf("存入数据成功\n");
	return 0;

}
删除退出成员的地址信息结构体函数
//删除退出成员的地址信息结构体
int link_del(linkList *L,struct sockaddr_in s)
{
	//判断条件
	if(L==NULL)
	{
		printf("链表不合法\n");
		return -1;
	}
	
	//查找删除对象
	linkList *p=L;                //目标前驱
	linkList *q=p->next;         //目标

	while(q!=NULL)
	{

		if( (q->cin.sin_port==s.sin_port) &&  (q->cin.sin_addr.s_addr==s.sin_addr.s_addr))
		{

			//删除客户端地址信息结构体
			p->next=q->next;
			//释放空间
			free(q);
			q=NULL;

			//表的变化
			L->num--;

			printf("删除数据成功\n");

			return 0;
		}
		p=q;
		q=q->next;
	}

	if(q==NULL)
	{
		return -2;            //表未找到目标
	}
}
遍历发送函数
//遍历发送
void list_show(int sfd,linkList *L,char* buf,int size,struct sockaddr_in *rcv)
{
	//判断逻辑
	if(NULL==L || L->next==NULL)
	{
		printf("表空,无发送对象\n");
		return;
	}

	//遍历
	linkList *q=L->next;
	while(q!=NULL)
	{

		if(rcv==NULL || (q->cin.sin_port!=rcv->sin_port) ||  (q->cin.sin_addr.s_addr!=rcv->sin_addr.s_addr))
		{
			if(sendto(sfd,buf,size,0,(struct sockaddr*)&(q->cin),sizeof(q->cin))==-1)
			{
				ERR_MSG("sendto");
				exit(-1);
			}
		}
		q=q->next;
	}

	
}
获得名字函数
//获得名字
name实际最大字节数为的19;  
void getname(char* name,char* buf1)       
{
	bzero(name,sizeof(name));
	int i=1;
	while(buf1[i]!=0 && i!=20)
	{
		name[i-1]=buf1[i];
		i++;
	}
	name[i]=0;
}
收到登入后的回应函数
//收到登入后的回应函数
void load_res(int sfd,linkList *L,struct sockaddr_in rcv,char* name,char *buf)
{

	//插入客户端地址信息结构体
	int x=link_insert_head(L,rcv);
	if(x!=0)
	{
		if(x==-2)
		{
			printf("[%s:%d]%s:重复登入\n",inet_ntoa(rcv.sin_addr),ntohs(rcv.sin_port),name);
			return;
		}

		if(x==-1)
			exit(-1);
	}

	//在服务器上显示消息
	printf("[%s:%d]%s:登入成功\n",inet_ntoa(rcv.sin_addr),ntohs(rcv.sin_port),name);
	printf("现在有%d个人在群内\n",L->num);

	//发送客户端登入信息
	int size=sprintf(buf,"\n------------------%s%c%s",name,':',"上线\n");
	list_show(sfd,L,buf,size,&rcv);


}
收到退出后的回应函数
//收到退出后的回应函数
void quit_res(int sfd,linkList *L,struct sockaddr_in rcv,char* name,char *buf)
{

	//删除退出成员的地址信息结构体
	int x=link_del(L,rcv);
	if(x!=0)
	{
		if(x==-2)
		{
			printf("重复退出,链表内无该对象[%s:%d]%s\n",inet_ntoa(rcv.sin_addr),ntohs(rcv.sin_port),name);
			return;
		}

	}


	//在服务器上显示消息
	printf("[%s:%d]%s:退出群聊\n",inet_ntoa(rcv.sin_addr),ntohs(rcv.sin_port),name);
	printf("现在有%d个人在群内\n",L->num);

	//发送客户端登入信息
	int size=sprintf(buf,"\n-----------------%s%c%s",name,':',"下线\n");
	list_show(sfd,L,buf,size,&rcv);


}

main.c

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



//全局变量
int sfd;

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

	//允许端口快速重用 
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) 
	{
		ERR_MSG("setsockopt"); 
		return -1;
	}

	//服务器的地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family      =AF_INET;
	sin.sin_port        =htons(8888);
	sin.sin_addr.s_addr =inet_addr("192.168.31.10");

	//客户端的地址信息结构体
	struct sockaddr_in rcv;
	socklen_t addrlen=sizeof(rcv);

	//绑定套接字
	if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))==-1)
	{
		ERR_MSG("bind");
		return -1;
	}
	printf("bind success\n");
	//创建列表
	linkList *L=link_create();


	//创建分支线程
	pthread_t tid;
	pthread_create(&tid,NULL,handler1,(void*)L);


//主线程
	char buf1[128]="";       //客户端信息        
	char buf[128]="";        //发给其他客户端的消息
	char name[20]="";        //客户端名字

	while(1)
	{
		bzero(buf1,sizeof(buf1));
		bzero(buf,sizeof(buf));
		//接收信息
		if(recvfrom(sfd,buf1,sizeof(buf1),0,(struct sockaddr*)&rcv,&addrlen)<0)
		{
			ERR_MSG("recvfrom");
			return -1;
		}

		//获得名字
		getname(name,buf1);
		
		//发送客户端信息(登入,消息,退出)(遍历发送)

		//登入  将客户端的地址信息结构体储存进链表
		if(buf1[0]=='L')
		{
			load_res(sfd,L,rcv,name,buf);
		}
	
		//群聊
		if(buf1[0]=='C')
		{
			//在服务器上显示消息
			printf("[%s:%d]%s:发送信息:%s\n",inet_ntoa(rcv.sin_addr),ntohs(rcv.sin_port),name,buf1+strlen(name)+2);    
			//发送客户端登入信息
			int size=sprintf(buf,"\n%s%c%s%s",name,':',buf1+strlen(name)+2,"\n");
			list_show(sfd,L,buf,size,&rcv);
		}


		//退出(quit)  将客户端的地址信息结构体移出链表
		if(buf1[0]=='Q')
		{
				quit_res(sfd,L,rcv,name,buf);
		}

	   	
	}
	//退出服务器
	return 0;
}

客户端

功能

1.发送登录信息发送给服务端
2.将发送的消息发送给服务端
3.将退出信息发送给服务端
4.接收服务端发送的消息

代码

cli.h

#ifndef __UDPSER_H__
#define __UDPSER_H__

//错误返回
#define ERR_MSG(msg) do{\
			fprintf(stderr,"line:  %d ",__LINE__);\
			perror(msg);}while(0);


//子线程函数
void *handler1(void* arg);
//发消息函数
void my_send(int sfd,char signal,char *buf,int size,const struct sockaddr_in sin);


#endif

cli.c

子线程函数
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<pthread.h>
#include<string.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdlib.h>
#include"cli.h"

//子线程函数
void *handler1(void* arg)
{

	extern int sfd;
	char buf[128];

	//接收的地址信息结构体
	struct sockaddr_in rcv;
	socklen_t addrlen=sizeof(rcv);

	while(1)
	{
		bzero(buf,sizeof(buf));
		//接收信息
		if(recvfrom(sfd,buf,sizeof(buf),0,(struct sockaddr*)&rcv,&addrlen)<0)
		{
			ERR_MSG("recvfrom");
			return NULL;
		}
		printf("%s",buf);
	}
}
发消息函数
//发消息函数
void my_send(int sfd,char signal,char *buf,int size,const struct sockaddr_in sin)
{
	buf[0]=signal;
	if(sendto(sfd,buf,size,0,(struct sockaddr*)&sin,sizeof(sin))<0)
	{
		ERR_MSG("sendto");
		return;
	}

}

main.c

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

//全局变量
int sfd;


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

	//允许端口快速重用 
	int reuse = 1;
	if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0) 
	{
		ERR_MSG("setsockopt"); 
		return -1;
	}

	//服务器的地址信息结构体
	struct sockaddr_in sin;
	sin.sin_family      =AF_INET;
	sin.sin_port        =htons(8888);
	sin.sin_addr.s_addr =inet_addr("192.168.31.10");


	//创建分支线程                       用于接收服务器发来的信息
	pthread_t tid;           
	pthread_create(&tid,NULL,handler1,NULL);






	char buf[128]="";       //客户端信息
	char name[20];     		//客户名字
	printf("请输入用户名:");
	fgets(name,sizeof(name),stdin);
	name[strlen(name)-1]=0;
	int size=sprintf(buf+1,"%s%c",name,0)+1;


//主线程
	int flag=0;
	while(1)
	{
		bzero(buf+size,sizeof(buf)-size);


		//发送客户端信息(登入,消息,退出)
		//登入  
		if(flag==0)
		{
			my_send(sfd,'L',buf,size,sin);

			printf("上线成功\n");
			flag=1;
		}

		printf("请输入-->");                        //客户端发消息或退出
		fflush(stdout);
		fgets(buf+size,sizeof(buf)-size,stdin);
		int size1=strlen(buf+size)+size;            //buf实际长度
		buf[size1-1]=0;

		if(strcasecmp(buf+size,"quit")==0)        //退出客户端,下线
		{
			my_send(sfd,'Q',buf,size,sin);

			printf("下线成功\n");
			return 0;
		
		}

		else                                //群聊
		{
			my_send(sfd,'C',buf,size1,sin);
			printf("发送成功\n");
		}

	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值