项目需求:
- 如果有用户登录,其他用户可以收到这个人的登录信息
- 如果有人发送信息,其他用户可以收到这个人的群聊信息
- 如果有人下线,其他用户可以收到这个人的下线信息
- 服务器可以发送系统信息
代码实现如下:
服务器:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <pthread.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "line:%d\n", __LINE__);\
perror(msg);\
}while(0)
#define PORT 6666
#define IP "192.168.8.77"
typedef struct sockaddr_in datatype;
//组协议
struct DataPacket
{
char code;
char name[20];
char txt[128];
};
//创建一个链表用于存储客户端消息包
typedef struct Node
{
union{
int len; //头结点数据域
datatype data; //普通结点数据
};
struct Node* next; //指针域
}LinkList;
//arg传参
struct MSG
{
int sfd;
struct sockaddr_in cin;
struct DataPacket packet;
LinkList *L;
};
void* funcA(void *arg);
//创建链表
LinkList *list_create();
//判空
int list_empty(LinkList*L);
//申请结点分装数据
LinkList *buy_node(datatype e);
//头插
int list_insert_head(LinkList *L, datatype e);
//遍历发送登入信息
void list_show_L(LinkList *L, int sfd, struct DataPacket packet);
//遍历发送群聊信息
void list_show_C(LinkList *L, int sfd, struct DataPacket packet, struct sockaddr_in cin);
//遍历发送群聊信息
void list_show_Q(LinkList *L, int sfd, struct DataPacket packet);
//头删
int list_delete_head(LinkList*L);
//按位置查找返回结点
LinkList *find_node(LinkList *L, int pos);
//按值查找返回结点位置
int list_search_val(LinkList *L, datatype e);
//任意位置删除
int list_delete_pos(LinkList *L, int pos);
//释放链表
void list_free(LinkList *L);
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//填充服务器自身的地址信息结构体,AF_INET: man 7 ip
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(PORT);
sin.sin_addr.s_addr = inet_addr(IP);
//绑定服务器的地址信息结构体
if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("bind");
return -1;
}
printf("bind success\n");
char buf[128] = "";
ssize_t res = 0;
struct sockaddr_in cin; //储存收到的数据包的地址信息
socklen_t addrlen = sizeof(cin);
struct DataPacket packet;
//创建链表用于存储用户信息
LinkList *L = list_create();
if(NULL == L)
return -1;
struct MSG arg_msg;
arg_msg.sfd = sfd;
arg_msg.cin = cin;
arg_msg.packet = packet;
arg_msg.L = L;
//创建一个分支线程用于向客户端发送信息
pthread_t A;
if(pthread_create(&A, NULL, funcA, &arg_msg) != 0)
{
fprintf(stderr, "pthread_create failed\n");
return -1;
}
while(1)
{
//接收
res = recvfrom(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&cin, &addrlen);
if(res <0)
{
ERR_MSG("recvfrom");
return -1;
}
//登入信息处理
if('L' == packet.code)
{
//将用户登入信息发送至目前已在线所有用户
list_show_L(L, sfd, packet);
//将用户信息存入链表
list_insert_head(L, cin);
printf("[%s:%d]:用户[%s] 已登录\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), packet.name);
}
//群聊信息处理
if('C' == packet.code)
{
printf("[%s:%d]:用户[%s]: %s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), packet.name, packet.txt);
//将群聊信息发送至其他所有在线用户
list_show_C(L, sfd, packet, cin);
}
//下线信息处理
if('Q' == packet.code)
{
//将下线信息发送至所有在线用户
list_show_Q(L, sfd, packet);
//删除该下线用户地址信息
int pos = list_search_val(L, cin);
list_delete_pos(L, pos);
printf("[%s:%d]****[%s]已下线****\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), packet.name);
}
}
//释放链表
list_free(L);
//关闭套接字
close(sfd);
return 0;
}
//分支线程程序
void* funcA(void *arg)
{
int sfd = ((struct MSG*)arg)->sfd;
struct sockaddr_in cin = ((struct MSG*)arg)->cin;
struct DataPacket packet = ((struct MSG*)arg)->packet;
LinkList *L = ((struct MSG*)arg)->L;
char Code;
char buf[128] = "";
while(1)
{
//从终端获取发送内容
scanf("%c %s", &Code, buf);
while(getchar() != 10);
//发送服务器消息
if('C' == Code)
{
packet.code = 'C';
strcpy(packet.name, "server");
strcpy(packet.txt, buf);
list_show_C(L, sfd, packet, cin);
}
else
{
printf("发送格式错误\n");
printf("发送格式:C 消息内容\n");
}
}
pthread_exit(NULL);
}
//创建链表
LinkList *list_create()
{
//申请一个空间
LinkList *L = (LinkList*)malloc(sizeof(LinkList));
if(NULL == L)
{
printf("创建失败\n");
return NULL;
}
L->len = 0;
L->next = NULL;
return L;
}
//判空
int list_empty(LinkList*L)
{
return L->next == NULL;
}
//申请结点分装数据
LinkList *buy_node(datatype e)
{
//在堆区申请一个结点的空间
LinkList *p = (LinkList*)malloc(sizeof(LinkList));
if(NULL == p)
{
printf("空间申请失败\n");
return NULL;
}
//将数据封装到结点的数据域中
p->data = e;
p->next = NULL;
return p;
}
//头插
int list_insert_head(LinkList *L, datatype e)
{
//判断逻辑
if(NULL == L)
{
printf("链表不合法\n");
return 0;
}
//申请结点封装数据
LinkList *p = buy_node(e);
//头插逻辑
p->next = L->next;
L->next = p;
//表的变化
L->len++;
printf("客户端地址信息存储成功\n");
return 1;
}
//遍历发送登入信息
void list_show_L(LinkList *L, int sfd, struct DataPacket packet)
{
//判断逻辑
if(NULL == L || list_empty(L))
return ;
//遍历逻辑
LinkList *q = L->next; //定义指针指向第一个结点
while(q != NULL)
{
//发送登入信息
if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&(q->data), sizeof(q->data)) < 0)
{
ERR_MSG("sendto");
return ;
}
q = q->next;
}
printf("\n");
}
//遍历发送群聊信息
void list_show_C(LinkList *L, int sfd, struct DataPacket packet, struct sockaddr_in cin)
{
//判断逻辑
if(NULL == L || list_empty(L))
return ;
//遍历逻辑
LinkList *q = L->next; //定义指针指向第一个结点
while(q != NULL)
{
if(memcmp(&cin, &q->data, sizeof(cin)) == 0)
{
q = q->next;
continue;
}
//发送群聊信息
if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&(q->data), sizeof(q->data)) < 0)
{
ERR_MSG("sendto");
return ;
}
q = q->next;
}
printf("\n");
}
//遍历发送下线信息
void list_show_Q(LinkList *L, int sfd, struct DataPacket packet)
{
//判断逻辑
if(NULL == L || list_empty(L))
return ;
//遍历逻辑
LinkList *q = L->next; //定义指针指向第一个结点
while(q != NULL)
{
//发送下线信息
if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&(q->data), sizeof(q->data)) < 0)
{
ERR_MSG("sendto");
return ;
}
//printf("[%s:%d]sendto success\n", inet_ntoa(q->data.sin_addr), htons(q->data.sin_port));
q = q->next;
}
printf("\n");
}
//按位置查找返回结点
LinkList *find_node(LinkList *L, int pos)
{
//判断逻辑
if(pos < 0 || pos > L->len)
{
printf("查找失败\n");
return NULL;
}
//查找逻辑
//定义遍历指针
LinkList *q = L;
for(int i = 0; i < pos; i++)
q = q->next; //遍历指针往后偏移
return q; //将找到的结点返回
}
//头删
int list_delete_head(LinkList*L)
{
//判断逻辑
if(NULL == L || list_empty(L))
{
printf("头删失败\n");
return 0;
}
//删除逻辑
LinkList *p = L->next; //标记要删除的结点
L->next = p->next;
free(p);
p == NULL;
//表长变化
L->len--;
printf("头删成功\n");
return 1;
}
//按值查找返回结点位置
int list_search_val(LinkList *L, datatype e)
{
//判断逻辑
if(NULL == L || list_empty(L))
{
printf("查找失败\n");
return -1;
}
//查找逻辑
LinkList *p = L->next; //从第一个结点出发
for(int i = 1; i <= L->len; i++)
{
if(p->data.sin_port == e.sin_port)
return i;
p = p->next; //继续判断下一个结点
}
return 0; //说明没找到
}
//任意位置删除
int list_delete_pos(LinkList *L, int pos)
{
//判断逻辑
if(NULL == L || list_empty(L) || pos < 1 || pos > L->len)
{
printf("删除失败\n");
return 0;
}
//找到任意位置的前驱结点
LinkList *p = find_node(L, pos-1);
if(NULL == p)
{
printf("删除失败\n");
return 0;
}
//删除逻辑
LinkList *q = p->next;
p->next = q->next;
free(q);
q = NULL;
//表长变化
L->len--;
printf("客户端地址信息删除成功\n");
return 1;
}
//释放链表
void list_free(LinkList *L)
{
//判断逻辑
if(NULL != L)
{
//循环进行头删,直到链表中无结点
while(L->next != NULL)
list_delete_head(L);
}
//释放头结点
free(L);
L = NULL;
printf("释放成功\n");
return ;
}
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <unistd.h>
#include <string.h>
#include <pthread.h>
#define ERR_MSG(msg) do{\
fprintf(stderr, "line:%d\n", __LINE__);\
perror(msg);\
}while(0)
#define CLI_PORT 6666
#define CLI_IP "192.168.8.77"
//组协议
struct DataPacket
{
char code;
char name[20];
char txt[128];
};
//arg传参
struct MSG
{
int sfd;
struct sockaddr_in sin;
struct DataPacket packet;
};
void* funcA(void *arg)
{
int sfd = ((struct MSG*)arg)->sfd;
struct sockaddr_in sin = ((struct MSG*)arg)->sin;
struct DataPacket packet = ((struct MSG*)arg)->packet;
char buf[128] = "";
while(1)
{
//组群聊数据包
packet.code = 'C';
bzero(buf, sizeof(buf));
scanf("%s", buf);
while(getchar() != 10);
strcpy(packet.txt,buf);
if(strcmp(packet.txt, "quit") == 0)
{
//组下线数据包
packet.code = 'Q';
if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
break;
}
//发送
if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return NULL;
}
}
pthread_exit(NULL);
}
int main(int argc, const char *argv[])
{
//创建报式套接字
int sfd = socket(AF_INET, SOCK_DGRAM, 0);
if(sfd < 0)
{
ERR_MSG("socket");
return -1;
}
//填充服务器的地址信息结构体,AF_INET:man 7 ip
//供于下方sendto使用
struct sockaddr_in sin;
sin.sin_family = AF_INET;
sin.sin_port = htons(CLI_PORT);
sin.sin_addr.s_addr = inet_addr(CLI_IP);
socklen_t addrlen = sizeof(sin);
char buf[128] = "";
struct DataPacket packet;
ssize_t res = 0;
//组登录数据包
packet.code = 'L';
bzero(buf, sizeof(buf));
printf("请输入用户名>>>");
scanf("%s", buf);
while(getchar() != 10);
strcpy(packet.name,buf);
//发送
if(sendto(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0)
{
ERR_MSG("sendto");
return -1;
}
struct MSG arg_msg;
arg_msg.sfd = sfd;
arg_msg.sin = sin;
arg_msg.packet = packet;
//创建一个分支线程A用于向服务器发送消息
pthread_t A;
if(pthread_create(&A, NULL, funcA, &arg_msg) != 0)
{
fprintf(stderr, "pthread_create failed\n");
return -1;
}
while(1)
{
//接收服务器信息
res = recvfrom(sfd, &packet, sizeof(packet), 0, (struct sockaddr*)&sin, &addrlen);
if(res < 0)
{
ERR_MSG("recvfrom");
return -1;
}
if('L' == packet.code)
printf("----[%s]----已上线\n", packet.name);
if('C' == packet.code)
printf("[%s]: %s\n", packet.name, packet.txt);
if('Q' == packet.code)
{
if(strcmp(buf, packet.name) == 0)
break;
printf("****[%s]****已下线\n", packet.name);
}
}
//关闭套接字
close(sfd);
return 0;
}
运行结果如下: