基于(LinuxC语言)的udp局域网聊天室

【使用说明与相关缺陷】

    //1.关于两个文件夹的说明:(这部分是源文件压缩包的说明,这边没发所以可以忽略 )
      文件夹 udp_聊天室 的内容是本次项目的内容.
      
      文件夹 tcp_select 的内容是基于tcp和select写的服务器转发代码.
      
      udp_聊天室的代码是通过tcp_select的代码修改和添加功能得到的.
      
      
    2.在使用udp_聊天室时需要注意:
        1)server端需要用命令行传参的形式输入端口号.
        
        2)client端需要用命令行传参的形式输入服务器的ip地址和端口号.
        
        3)本聊天室仅限于同一个网段内的通信.
        
        
    3.主要功能:
        1)用户名不能重复,如果重复会提示重新输入,用户登录有提示.
        
        2)服务器转发客户端发来的消息给其他客户端.
        
        3)当前客户端输入quit可以退出自己,并且服务器转发退出提示.
        
        4)火力覆盖:服务器输入quit可以退出所有客户端并关闭自己.
        
        5)定点打击:服务器输入quit 用户名 可以指定让某一个用户退出.
        
        6)服务器会转发自己从终端输入的内容给所有客户端.
        
        7)有两个非法用户名(客户端输入会提示重新输入)一个是 name 一个是server
        name:是用户名关键字.
        server:是服务器的用户名.
        
        8)客户端使用Ctr+C强制退出也会被捕捉,之后进行正常的退出流程.
        
        
    4.小缺陷:
        1)在输入和输出的时候,如果输入一半,收到消息的话,会优先打印收到的内容.
           这会导致在多人聊天的时候,输入内容不够明确的问题.
           (目前没想到啥好办法,好像这是终端的缺陷)
           
        2)发送消息时,正式消息前的前缀太长,占用不必要的空间.
           解决方法(可以用一个字符作为通信前缀,但是代码已经成型修改起来太过耗时)
        
        3)目前用户名的可用长度为15个字节,比较短且还有两个非法用户名.
           解决方法(可以增加用户名所占的字节数,但是会增加占用的空间)
        
        4)如果服务器运行前还有残留的客户端没有退出的话,可能会导致服务器端崩溃
           当这个不受管制的客户端退出时会给服务器发送客户端退出消息,但是服务器的
           用户链表内并没有该用户,所以会访问非法空间导致段错误
           解决方法(在每次服务器退出的时候使用quit使所有客户端退出)
           
        5)服务器端使用的是select实现的多路复用,代码量与进程实现相比要大,并且不是
           特别好理解,但是不需要考虑父子进程之间通信的问题。
           
        6)客户端使用的虽然是进程实现多路复用,但是代码量也一百多行,而且由于信号和
           父子进程之间退出等问题,添加了很多不好理解的代码,降低了代码的易读性。

server(客户端代码)(注释不多。。。。。。因为在下懒)

#include<stdio.h>
#include<string.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <sys/select.h>
#include <sys/time.h>
#include <unistd.h>
#include <stdlib.h>
typedef struct node_t
{
	char name[16];
	struct sockaddr_in linkclient;
	struct node_t *next;
}linklist;

//建立有头链表的头
linklist *create();
//添加内容
int add(linklist *p,char *name,struct sockaddr_in linkclient);
//删除指定的数据
int del_post(linklist *p,char *name);
//查看重复
int look_name(linklist *p,char *name);
//根据用户名获取保存ip的结构体
struct sockaddr_in *name_struct(linklist *p,char *name);
//转发消息
void show(linklist *p,int sockfd,char *name,char *buf);

int main(int argc, const char *argv[])
{
    if(argc!=2)
    {
        printf("请输入端口号n");
        return -1;
    }
	int quitserver=0;
	linklist *p=create();
	if(p==NULL)
	{
		perror("linklist create err.");
		return -1;
	}
	int sockfd;
	sockfd=socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd<0)
	{
		perror("socket err.");
		return -1;
	}
	struct sockaddr_in serveraddr;
	serveraddr.sin_family=AF_INET;
	serveraddr.sin_port=htons(atoi(argv[1]));
	serveraddr.sin_addr.s_addr=htonl(INADDR_ANY);
	struct sockaddr_in clientaddr;
	socklen_t len=sizeof(clientaddr);
	if(bind(sockfd,(struct sockaddr *)&serveraddr,sizeof(serveraddr))<0)
	{
		perror("bind err.");
		return -1;
	}
	fd_set readfds,tmpfds;
	FD_ZERO(&readfds);
	FD_SET(0,&readfds);
	FD_SET(sockfd,&readfds);
	char buf[128]="",name[16]="",tmpbuf[128]="";
	int ch,maxfd,i,recvch,j;
	maxfd=sockfd;
	pid_t pid;
	while(1)
	{
		tmpfds=readfds;
		ch=select(maxfd+1,&tmpfds,NULL,NULL,NULL);
		if(ch<0)
		{
			perror("select err.");
			return -1;
		}
		for(i=0;i<=maxfd;i++)
		{
			if(FD_ISSET(i,&tmpfds))
			{
				if(i==0)
				{
					memset(buf,0,sizeof(buf));
					strcpy(buf,"server说: ");
					fgets(buf+11,128,stdin);
					if(buf[strlen(buf)-1]=='n')
						buf[strlen(buf)-1]='';
					printf("%sn",buf);
					if(!strncmp(buf+11,"quit",4))
					{
						if(!strcmp(buf+11,"quit"))
						{
							printf("强制退出所有客户端和本服务器n");
							show(p,sockfd,"name",buf);
							quitserver=1;
							break;
						}
						else
						{
							sendto(sockfd,buf,128,0,(struct sockaddr *)name_struct(p,buf+11+5),len);
						}

					}
					show(p,sockfd,"name",buf);
				}
				else if(i==sockfd)
				{
					recvch=recvfrom(sockfd,buf,128,0,(struct sockaddr *)&clientaddr,&len);
					if(recvch<0)
					{
						perror("recv err.");
						return -1;
					}
					else
					{
						if(!strncmp(buf,"name",4))
						{
							if(look_name(p,buf+4))
								sendto(sockfd,"quit",4,0,(struct sockaddr *)&clientaddr,len);
							else
							{
								add(p,buf+4,clientaddr);
								strcpy(tmpbuf,"用户 ");
								strcat(tmpbuf,buf+4);
								strcat(tmpbuf," 登录。");
								printf("%sn",tmpbuf);
								show(p,sockfd,buf+4,tmpbuf);
								sendto(sockfd,"ok..",4,0,(struct sockaddr *)&clientaddr,len);
							}

						}
						else if(!strcmp(buf+16+strlen("说: ")+strlen(buf),"quit"))
						{
							strcpy(tmpbuf,"用户 ");
							strcat(tmpbuf,buf);
							strcat(tmpbuf," 退出。");
							printf("%sn",tmpbuf);
							show(p,sockfd,buf,tmpbuf);
							del_post(p,buf);
						}
						else
						{

							strncpy(name,buf,16);
							printf("buf:%sn",buf+16);
							show(p,sockfd,name,buf+16);
						}
					}
				}
			}
		}
		if(quitserver==1)
			break;
	}
	close(sockfd);
	return 0;
}

//建立有头链表的头
linklist *create()
{
	linklist *p=(linklist *)malloc(sizeof(linklist));
	if(NULL==p)
	{
		perror("malloc err.");
		return NULL;
	}
	p->next=NULL;
	return p;
}
//添加内容
int add(linklist *p,char *name,struct sockaddr_in linkclient)
{
	linklist *pnew=(linklist *)malloc(sizeof(linklist));
	if(NULL==pnew)
	{
		perror("malloc err.");
		return -1;
	}
	while(p->next!=NULL)
		p=p->next;
	pnew->next=NULL;
	strcpy(pnew->name,name);
	pnew->linkclient=linkclient;
	p->next=pnew;
	return 0;
}
//删除指定的数据
int del_post(linklist *p,char *name)
{
	while(p->next!=NULL)
	{
		if(!strcmp(p->next->name,name))
			break;
		p=p->next;
	}
	linklist *del=p->next;
	p->next=del->next;
	free(del);
	del=NULL;
	return 0;

}

//查看重复
int look_name(linklist *p,char *name)
{
	while(p->next!=NULL)
	{
		if(!strcmp(p->next->name,name))
		{
			printf("用户名相同n");
			return 1;
		}
		p=p->next;
	}
	printf("用户名不同n");
	return 0;
}
//根据文件描述符获取用户名(这段没用)
/*char *acceptfd_name(linklist *p,int linkacceptfd)
  {
  while(p->next!=NULL)
  {
  if(p->next->linkacceptfd==linkacceptfd)
  break;
  p=p->next;
  }
  return p->next->name;
  }*/

//根据用户名获取保存ip的结构体
struct sockaddr_in *name_struct(linklist *p,char *name)
{
	while(p->next!=NULL)
	{
		p=p->next;
		if(!strcmp(p->name,name))
			return &(p->linkclient);
	}
	printf("查无此用户n");
	return NULL;
}

//转发消息
void show(linklist *p,int sockfd,char *name,char *buf)
{
	while(p->next!=NULL)
	{
		p=p->next;
		if(!strcmp(p->name,name))
			continue;
		sendto(sockfd,buf,128,0,(struct sockaddr *)&(p->linkclient),sizeof(p->linkclient));
	}
}

client(客户端代码)    (没注释,因为在下懒............)

#include<stdio.h>
#include<string.h>
#include <sys/types.h>               
#include <sys/socket.h>              
#include <netinet/in.h>              
#include <netinet/ip.h>              
#include <sys/select.h>              
#include <sys/time.h>                
#include <sys/types.h>               
#include <unistd.h>                  
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>


pid_t qpid;
struct sockaddr_in qserveraddr;
int sockqfd;
char NNbuf[128]="";
void handler(int sig)
{
	printf("已经退出n");
	kill(qpid,SIGQUIT);
	wait(NULL);
	sendto(sockqfd,NNbuf,128,0,(struct sockaddr *)&qserveraddr,sizeof(qserveraddr));
	exit(0);
}
int main(int argc, const char *argv[])
{
    if(argc!=3)
    {
        printf("请输入服务器端的ip地址和端口号n");
        return -1;
    }
	int sockfd;
	char name[16]="";
	char Nbuf[128]="";
	sockfd=socket(AF_INET,SOCK_DGRAM,0);
	if(sockfd<0)
	{
		perror("socket err.");
		return -1;
	}
	sockqfd=sockfd;
	struct sockaddr_in serveraddr;
	serveraddr.sin_family=AF_INET;
	serveraddr.sin_port=htons(atoi(argv[2]));
	serveraddr.sin_addr.s_addr=inet_addr(argv[1]);
	qserveraddr.sin_family=AF_INET;
	qserveraddr.sin_port=htons(atoi(argv[2]));
	qserveraddr.sin_addr.s_addr=inet_addr(argv[1]);
	socklen_t len=sizeof(serveraddr);

	char buf[64]="";

	while(1)
	{
		strcpy(Nbuf,"name");
		printf("请输入用户名:");
		fgets(name,16,stdin);
		if(name[strlen(name)-1]=='n')
			name[strlen(name)-1]='';
		if(!strncmp(name,"name",4)||!strncmp(name,"server",6))
		{
			printf("名字前几位不能是name或者server哦,请重新输入n");
		}
		else if(strlen(name)>=15)
		{
			printf("名字需要低于长度十五个字节n");
			fgets(Nbuf,128,stdin);
		}
		else
		{
			strcat(Nbuf,name);
			printf("name:%sn",name);
			sendto(sockfd,Nbuf,128,0,(struct sockaddr *)&serveraddr,len);
			recv(sockfd,Nbuf,128,0);
			if(!strncmp(Nbuf,"quit",4))
			{
				printf("用户名重复,请重新输入n");
				continue;
			}
			else if(!strncmp(Nbuf,"ok..",4))
			{
				printf("输入成功!n");
				break;
			}
		}
	}
	strcpy(NNbuf,name);
	strcat(NNbuf+16,name);
	strcat(NNbuf+16,"说: ");
	strcat(NNbuf+16,"quit");
	pid_t pid=fork();
	int ch,i;
	if(pid<0)
	{
		perror("fork err.");
	}
	else if(pid>0)
	{
		qpid=pid;
		signal(SIGINT,handler);
		while(1)
		{
			memset(Nbuf,0,sizeof(Nbuf));
			strcpy(Nbuf,name);
			strcat(Nbuf+16,name);
			strcat(Nbuf+16,"说: ");
			printf("请输入:");
			fgets(buf,64,stdin);
			if(buf[strlen(buf)-1]=='n')
				buf[strlen(buf)-1]='';
			strcat(Nbuf+16,buf);
			sendto(sockfd,Nbuf,128,0,(struct sockaddr *)&serveraddr,len);
			if(!strcmp(buf,"quit"))
			{
				kill(pid,SIGQUIT);
				break;
			}
		}
		wait(NULL);
	}
	else
	{
		while(1)
		{
			ch=recvfrom(sockfd,Nbuf,128,0,(struct sockaddr *)&serveraddr,&len);
			if(ch<0)
			{
				perror("recv err.");
			}
			if(!strncmp(Nbuf+11,"quit",4)&&!strncmp(Nbuf,"server",6))
			{
				if(!strcmp(Nbuf+11,"quit"))
					kill(getppid(),SIGINT);
				else if(!strcmp(Nbuf+11+5,name))
					kill(getppid(),SIGINT);
			}
			printf("r%sn",Nbuf);
			printf("请输入:");
			fflush(stdout);
		}
	}
	close(sockfd);
	return 0;
}

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>