1. 需求
项目需求:
-
如果有用户登录,其他用户可以收到这个人的登录信息
-
如果有人发送信息,其他用户可以收到这个人的群聊信息
-
如果有人下线,其他用户可以收到这个人的下线信息
-
服务器可以发送系统信息
服务器代码实现
#include<stdio.h>
#include <sys/types.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <stdlib.h>
#include <pthread.h>
#define ERR_MSG(msg) do{\
fprintf(stderr,"__%d__",__LINE__);\
perror(msg);\
}while(0)
//定义数据包类型
typedef struct datapack
{
char text[128] ;
char type;
char name[8];
}datapack;
//定义链表结构体
typedef struct node{
union{
int len;
struct sockaddr_in sin;
};
struct node *next;
}linklist;
int sfd;//报式套接字存放处
//创建链表函数
linklist *create()
{
linklist *p = (linklist *)malloc(sizeof(linklist));
if(p == NULL)
{
printf("创建链表失败\n");
return NULL;
}
//初始化头结点,防止野指针
p->len = 0;
p->next = NULL;
return p;
}
//申请节点存放数据函数
linklist *apply(linklist *p, struct sockaddr_in sin)
{
linklist *q = (linklist *)malloc(sizeof(linklist));
if(NULL == q)
{
printf("申请节点失败\n");
return NULL;
}
q->sin = sin;
q->next = NULL;
p->len++;
linklist *q1 = p; //定义一个遍历指针将新用户的信息尾插到链表的最后一个
while(q1->next != NULL)
{
q1 = q1->next;
}
q1->next = q;
return p;
}
//删除节点函数
linklist *shanchu(linklist *p, struct sockaddr_in sin)
{
linklist *q = p;
linklist *q1 = NULL;
while(q->next != NULL)
{
if(q->next->sin.sin_port == sin.sin_port)
{
q1 = q->next;
q->next = q->next->next;
free(q1);
q1 = NULL;
break;
}
q = q->next;
}
}
linklist *p=NULL;
void *callBack(void *arg)//子线程执行区域
{
datapack send;
while(1)
{
bzero(&send,sizeof(send));
fgets(send.text, sizeof(send.text), stdin);
send.text[strlen(send.text)-1] = 0; //把最后一位回车改成0
send.type='C';
strcpy(send.name,"system");
linklist *q = p;
while(q->next != NULL)
{
if(sendto(sfd,&send, sizeof(send), 0, (struct sockaddr *)&q->next->sin, sizeof(q->next->sin)) < 0)
{
ERR_MSG("sendto");
}
q = q->next;
}
q=p;
if(!strcasecmp(send.text, "quit"))
exit(0);
}
}
int main(int argc, const char *argv[])
{
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;
}
//创建报式套接字
sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success\n");
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]); //将字符串式的点分十进制转换成网络字节序
struct sockaddr_in rcv_addrmsg; //存储接收到的数据包来自哪里
socklen_t addrlen = sizeof(rcv_addrmsg);
//绑定服务器的地址信息结构体
if(bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) < 0)
{
ERR_MSG("bind");
}
printf("绑定成功\n");
datapack rcv;//定义接收数据包的结构体
pthread_t pid;
p=create();
linklist *q=NULL;
if(pthread_create(&pid, NULL, callBack, NULL) != 0)//创建子线程用于发送消息
{
ERR_MSG("pthread");
return -1;
}
printf("创建子线程成功\n");
while(1)
{
bzero(&rcv,sizeof(rcv));
if(recvfrom(sfd,&rcv,sizeof(rcv),0,(struct sockaddr*)&rcv_addrmsg,&addrlen)<0)//接收客户端发送的数据
{
ERR_MSG("recvfrom");
return -1;
}
if(rcv.type=='L')//如果发送的是登录请求
{
printf("%s%s\n",rcv.name,rcv.text);
q=apply(p,rcv_addrmsg);//创建新节点
while(NULL!=q->next->next)
{
sin=q->next->sin;//取其他节点地址信息结构体给他们发送上线消息
if(sendto(sfd,&rcv,sizeof(rcv),0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
q=q->next;
}
q=p;//将q指针重新指向头结点等待重新利用
}
else if(rcv.type=='C')
{
printf("%s:%s\n",rcv.name,rcv.text);//打印出消息内容
while(q->next!=NULL)
{
if(q->next->sin.sin_port != rcv_addrmsg.sin_port)//给非输入数据的端口发送消息内容
{
if(sendto(sfd,&rcv,sizeof(rcv),0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
}
q=q->next;
}
q=p;
}
else if(rcv.type=='Q')//接收到退出指令
{
printf("%s%s\n",rcv.name,"已退出");//打印出消息内容
while(q->next!=NULL)
{
if(q->next->sin.sin_port != rcv_addrmsg.sin_port)//给非输入数据的端口发送消息内容
{
if(sendto(sfd,&rcv,sizeof(rcv),0,(struct sockaddr*)&sin,sizeof(sin))<0)
{
ERR_MSG("sendto");
return -1;
}
}
else
{
shanchu(q,rcv_addrmsg);
continue;
}
q=q->next;
}
q=p;
}
}
return 0;
}
客户端代码实现
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <string.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
#include <signal.h>
//打印错误新的宏函数
#define ERR_MSG(msg) do{\
fprintf(stderr, " __%d__ ", __LINE__);\
perror(msg);\
}while(0)
int main(int argc, const char *argv[])
{
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;
}
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
printf("socket create success\n");
//绑定客户端自身的地址信息结构体 ---> 非必须绑定
//填充服务器的IP地址以及端口号 -->因为客户端要主动发送数据包给服务器
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(port);
sin.sin_addr.s_addr = inet_addr(argv[1]);
struct sockaddr_in rcv_addrmsg; //存储接收到的数据包来自哪里
socklen_t addrlen = sizeof(rcv_addrmsg);
typedef struct datapack
{
char text[128] ;
char type;
char name[8];
}datapack;
datapack user;
datapack rcv;
printf("输入登录的用户名>>>");
scanf("%s",user.name);
getchar();
printf("录入名字成功\n");
char buf[137] = "";
size_t size =sizeof(user);
printf("_____\n");
user.type='L';
strcpy(user.text,"已上线");
if(sendto(sfd,&user, sizeof(user), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
pid_t pid = fork();
if (pid>0)//父进程进行发送
{
while(1)
{
char st;
bzero(user.text, sizeof(user.text));
printf("请输入>>>");//录入聊天信息内容
// getchar();
// printf("__________%d",__LINE__);
fgets(user.text, sizeof(user.text), stdin);
user.text[strlen(user.text)-1] = 0;
if(strcasecmp("quit",user.text)!=0)
{
user.type='C';
}
else
{
user.type='Q';
}
//将数据包发送给服务器,所以地址信息结构体需要填服务器的信息
if(sendto(sfd,&user, sizeof(user), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
if(strcmp(user.text,"quit")==0)
{
printf("______");
exit(0);
}
printf("sendto success\n");
}
}
if(pid==0)//子进程接收
{
while(1)
{
//接收
bzero(user.text, sizeof(user.text));
addrlen = sizeof(sin);
//接收服务器发送过来的数据包
//if(recvfrom(sfd, buf, sizeof(buf), 0, NULL, NULL) < 0)
if(recvfrom(sfd, &rcv, sizeof(rcv), 0, (struct sockaddr*)&sin, &addrlen) < 0)
{
ERR_MSG("recvfrom");
return -1;
}
if(rcv.type=='L')
{
printf("%s%s\n",rcv.name,"已上线");
}
else if(rcv.type=='C')
{
if(0==strcasecmp(rcv.text,"quit"))
{
kill(getppid(),2);
printf("服务器已退出");
exit(0);
}
printf("%s:%s\n",rcv.name,rcv.text);
}
else if(rcv.type=='Q')
{
printf("%s%s\n",rcv.name,"已下线");
}
}
}
//关闭套接字
close(sfd);
return 0;
}
运行结果