【使用说明与相关缺陷】
//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]='\0';
printf("%s\n",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("%s\n",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("%s\n",tmpbuf);
show(p,sockfd,buf,tmpbuf);
del_post(p,buf);
}
else
{
strncpy(name,buf,16);
printf("buf:%s\n",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]='\0';
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:%s\n",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]='\0';
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%s\n",Nbuf);
printf("请输入:");
fflush(stdout);
}
}
close(sockfd);
return 0;
}