项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
服务器需要给多个用户发送信息,需要创建链表保存各用户的网络信息。
服务器端
代码
#include <myhead.h>
#define ERR_text(text) do{\
fprintf(stderr,"__%d__:",__LINE__);\
perror(text);\
}while(0)
//用户信息结构体
typedef struct text
{
char type; //登录'L' ,群聊'C' ,下线'Q'
char name[20]; //用户姓名
char text[128]; //聊天信息
}INFO;
//客户端信息链表结构体
typedef struct Node
{
struct sockaddr_in cliaddr;
struct Node *next;
}List;
List *L;
struct sockaddr_in cin;
socklen_t addrlen=sizeof(cin);
List list_create();
int list_insert_head(struct sockaddr_in cliaddr);
void list_del(struct sockaddr_in cliaddr);
void request_login(int sfd,INFO info,struct sockaddr_in cin);
void request_chat(int sfd,INFO info,struct sockaddr_in cin);
void request_quit(int sfd,INFO info,struct sockaddr_in cin);
void ser_send(int sfd,struct sockaddr_in cin);
void *fun(void *arg);
int main(int argc, const char *argv[])
{
if(argc<3){
fprintf(stderr,"请输入IP PORT\n");
return -1;
}
list_create();
//创建报式套接字 socket
int sfd=socket(AF_INET,SOCK_DGRAM,0);
if(sfd<0){
ERR_text("socket");
return -1;
}
printf("socket create success sfd=%d\n",sfd);
//允许端口快速复用
int reuse=1;
if(setsockopt(sfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse))<0){
ERR_text("setsockopt");
return -1;
}
int port=atoi(argv[2]);
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(port);
sin.sin_addr.s_addr =inet_addr(argv[1]);
//绑定地址信息结构体 bind
if(bind(sfd,(struct sockaddr*)&sin,sizeof(sin))<0){
ERR_text("bind");
return -1;
}
printf("bind success\n");
//创建子线程
pthread_t tid;
pthread_create(&tid,NULL,fun,&sfd);
//填充对端地址信息结构体
while(1){
ser_send(sfd,cin);
}
//关闭套接字
close(sfd);
return 0;
}
//创建链表
List list_create()
{
L=(List *)malloc(sizeof(List)) ;
L->next=NULL;
}
//链表插入
int list_insert_head(struct sockaddr_in cliaddr)
{
if(NULL==L){
printf("所给链表不合法\n");
return -1;
}
List *q=L;
List *p=(List *)malloc(sizeof(List));
p->cliaddr=cliaddr;
p->next=q->next;
q->next=p;
return 1;
}
//删除结点
void list_del(struct sockaddr_in cliaddr)
{
if(NULL==L){
printf("删除失败\n");
return ;
}
List *q=L;
List *p=L->next;
while((p->next)!=NULL){
if(p->cliaddr.sin_port==cliaddr.sin_port){
break;
}
p=p->next;
q=q->next;
}
q->next=p->next;
free(p);
}
//登录请求
void request_login(int sfd,INFO info,struct sockaddr_in cin)
{
if(NULL==L){
printf("登录失败\n");
return ;
}
List *p=L->next;
char buf[32]="";
strcat(buf,info.name);
strcat(buf,"-->>已登录");
printf("<<--%s\n",buf);
while(p){
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->cliaddr),sizeof(cin));
p=p->next;
}
list_insert_head(cin);
}
//群聊请求
void request_chat(int sfd,INFO info,struct sockaddr_in cin)
{
List *p=L->next;
char buf[128]="";
strcat(buf,info.name);
strcat(buf,":");
strcat(buf,info.text);
buf[strlen(buf)-1]=0;
while(p){
if((p->cliaddr.sin_port!=cin.sin_port)||(p->cliaddr.sin_addr.s_addr!=cin.sin_addr.s_addr)){
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->cliaddr),sizeof(cin));
}
p=p->next;
}
}
//退出请求
void request_quit(int sfd,INFO info,struct sockaddr_in cin)
{
List *p=L->next;
char buf[32]="";
strcat(buf,info.name);
strcat(buf,"退出聊天室");
printf("%s退出聊天室\n",info.name);
while(p){
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->cliaddr),sizeof(cin));
p=p->next;
}
list_del(cin);
}
//发送系统消息
void ser_send(int sfd,struct sockaddr_in cin)
{
List *p=L->next;
char buf[128]="";
char text[64]="";
strcpy(buf,"system text:");
fgets(text,sizeof(text),stdin);
text[strlen(text)-1]=0;
strcat(buf,text);
printf("%s\n",buf);
while(p){
sendto(sfd,buf,sizeof(buf),0,(struct sockaddr*)&(p->cliaddr),sizeof(cin));
p=p->next;
}
if(strcmp(text,"quit")==0){
exit(0);
}
}
//线程收发客户端信息
void *fun(void *arg)
{
pthread_detach(pthread_self());
int sfd=*(int *)arg;
ssize_t res;
while(1){
INFO info;
res=recvfrom(sfd,&info,sizeof(info),0,(struct sockaddr*)&cin,&addrlen);
if(res<0){
ERR_text("recvfrom");
break;
}
printf("接收来自%s的信息\n",info.name);
switch(info.type){
case 'L': //登录请求
request_login(sfd,info,cin);
break;
case 'C': //群聊请求
request_chat(sfd,info,cin);
break;
case 'Q': //退出请求
request_quit(sfd,info,cin);
break;
}
}
}
客户端
代码
#include <myhead.h>
#define ERR_text(text) do{\
fprintf(stderr,"__%d__:",__LINE__);\
perror(text);\
}while(0)
typedef struct
{
char type;
char name[20];
char text[128];
}INFO;
void *rcv_fun(void *arg);
INFO create_text(INFO info,char *name);
int main(int argc, const char *argv[])
{
INFO info;
if(argc<3){
fprintf(stderr,"请输入 IP port\n");
return -1;
}
int port=atoi(argv[2]);
if(port<1024||port>49151){
fprintf(stderr,"port %d input error(1024~49151)\n",port);
return -1;
}
//创建报式套接字 socket
int cfd=socket(AF_INET,SOCK_DGRAM,0);
if(cfd<0){
ERR_text("socket");
return -1;
}
printf("socket create success cfd=%d\n",cfd);
//填充服务器的地址信息结构体
struct sockaddr_in sin;
sin.sin_family =AF_INET;
sin.sin_port =htons(port);
sin.sin_addr.s_addr =inet_addr(argv[1]);
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,rcv_fun,&cfd);
pthread_create(&tid2,NULL,rcv_fun,&cfd);
char name[20]="";
printf("请输入用户名>>>");
fgets(name,sizeof(name),stdin);
name[strlen(name)-1]=0;
strcpy(info.name,name);
info.type='L'; //登录请求
sendto(cfd,&info,sizeof(info),0,(struct sockaddr*)&sin,sizeof(sin));
printf("*****************************\n");
char buf[128]="";
while(1){
INFO info;
char text[128]="";
fgets(text,sizeof(text),stdin);
strcpy(info.name,name);
strcpy(info.text,text);
if(strcmp(text,"quit\n")==0){
info.type='Q';
}else{
info.type='C';
}
//发送数据
if((sendto(cfd,&info,sizeof(info),0,(struct sockaddr*)&sin,sizeof(sin)))<0){
ERR_text("sendto");
return -1;
}
if(info.type=='Q'){
break;
}
memset(&info,0,sizeof(info));
}
//关闭套接字
close(cfd);
return 0;
}
INFO create_text(INFO info,char *name)
{
char text[64]="";
fgets(text,sizeof(text),stdin);
strcpy(info.name,name);
strcpy(info.text,text);
if(strcmp(text,"quit\n")==0){
info.type='Q'; //退出请求
}else{
info.type='C';
}
return info;
}
void *rcv_fun(void *arg)
{
pthread_detach(pthread_self());
int cfd=*(int *)arg;
while(1){
char buf[128]="";
if(recvfrom(cfd,buf,sizeof(buf),0,NULL,NULL)<0){
ERR_text("recvfrom");
return NULL;
}
if(strcmp(buf,"system text:quit")==0){
printf("服务器下线\n");
exit(0);
}
printf("%s\n",buf);
}
}
测试结果