思维导图
服务器代码
#include<myhead.h>#define PORT 4567
#define IP "192.168.6.225"struct msg //接收到的客户端信息结构体
{char type;char name[20];char txt[128];
};//定义节点类型
typedef struct Node
{union{struct sockaddr_in cin; //数据域int len; //头结点数据域};struct Node *next; //指针域
}Node, *LinkListPtr;struct task
{LinkListPtr L;int sfd;
};//创建链表
LinkListPtr list_create();//判空操作
int list_empty(LinkListPtr L);//申请结点封装数据函数
LinkListPtr node_buy(struct sockaddr_in cin);//头插
int list_insert_head(LinkListPtr L, struct sockaddr_in cin);//遍历链表,发送数据
int list_show(LinkListPtr L, int sfd, char *buf);void *send_cli_msg(void *arg);int main(int argc, const char *argv[])
{//创建报式套接字int sfd = socket(AF_INET, SOCK_DGRAM, 0);if(sfd < 0){ERR_MSG("socket");return -1;}printf("socket success\n");//允许端口能被快速复用int reuse = 1;if(setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){ERR_MSG("setsockopt");return -1;}printf("允许端口被快速复用成功\n");//填充服务器地址信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(PORT);sin.sin_addr.s_addr = inet_addr(IP);//bind函数绑定服务器地址信息if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) < 0){ERR_MSG("bind");return -1;}printf("bind success\n");struct sockaddr_in cin; //定义客户端地址信息结构体socklen_t addrlen = sizeof(cin); struct msg climsg; //定义存放客户端信息结构体pthread_t tid;LinkListPtr L = list_create();struct task taskin;taskin.L = L;taskin.sfd = sfd;while(1){//接受信息if(recvfrom(sfd, (struct msg*)&climsg, sizeof(climsg), 0, (struct sockaddr*)&cin, &addrlen) < 0){ERR_MSG("recv");return -1;}if(climsg.type == 'L'){printf("----%s----已上线\n",climsg.name); //打印客户端上线char buf2[128];sprintf(buf2, "----%s----已上线", climsg.name);list_insert_head(L, cin);//加入链表list_show(L, sfd, buf2);}else if(climsg.type == 'C'){printf("%s: %s\n",climsg.name, climsg.txt);char buf2[128];strcpy(buf2, climsg.name);strcat(buf2, climsg.txt);list_show(L, sfd, buf2);}else if(climsg.type == 'Q'){printf("----%s----已下线\n",climsg.name); //打印客户端上线char buf2[128];sprintf(buf2, "----%s----已下线", climsg.name);list_show(L, sfd, buf2);}if(pthread_create(&tid, NULL, send_cli_msg, (void*)&taskin) != 0){fprintf(stderr, "pthread_create failed__%d__\n", __LINE__);return -1;}//线程分离pthread_detach(tid);}if(close(sfd) < 0) //关闭文件描述符{ERR_MSG("close");return -1;}return 0;
}void *send_cli_msg(void *arg)
{LinkListPtr L = ((struct task*)arg)->L;int sfd = ((struct task*)arg)->sfd;//群发消息char buf1[128] = "";bzero(buf1, sizeof(buf1));scanf("%s", buf1);list_show(L, sfd, buf1);
}//创建链表
LinkListPtr list_create()
{//在堆区申请一个头结点类型LinkListPtr L = (LinkListPtr)malloc(sizeof(Node));if(NULL == L){printf("创建失败\n");return NULL;}//创建成功,对节点进行初始化L->len = 0; //初始链表长度为0L->next = NULL; //链表上没有任何结点printf("创建链表成功\n");return L;
}//判空操作
int list_empty(LinkListPtr L)
{//判断逻辑if(NULL == L){printf("所给链表不合法\n");return -1;}//判断指针域的内容return L->next == NULL && L->len==0;
}//申请结点封装数据函数
LinkListPtr node_buy(struct sockaddr_in cin)
{//在堆区申请结点LinkListPtr p = (LinkListPtr)malloc(sizeof(Node));if(NULL == p){printf("结点申请失败\n");return NULL;}//结点申请成功,将数据封装进去p->cin = cin;p->next = NULL;return p;
}//头插
int list_insert_head(LinkListPtr L, struct sockaddr_in cin)
{//判断逻辑if(NULL == L){printf("所给链表不合法\n");return 0;}//调用申请结点封装数据LinkListPtr p = node_buy(cin);if(NULL==p){return 0;}//结点已经准备好,头插逻辑p->next = L->next;L->next = p;//表的变化L->len++;printf("插入成功\n");return 1;
}//遍历链表,发送数据
int list_show(LinkListPtr L, int sfd, char *buf)
{//判断逻辑if(NULL==L || list_empty(L)){printf("遍历失败\n");return -1;}//遍历逻辑printf("群发开始");//1、定义遍历指针从第一个结点开始LinkListPtr q = L->next;while(q != NULL) //2、只要当前结点存在{//发送if(sendto(sfd, buf, 128, 0, (struct sockaddr*)&(q->cin), sizeof(q->cin)) < 0){ERR_MSG("send");return -1;}q = q->next; //4、指针后移}printf("\n");
}
客户端代码
#define PORT 4567
#define IP "192.168.6.225"struct msg //客户端信息结构体
{char type;char name[20];char txt[128];
};struct serinfo //线程函数传参结构体
{int cfd;struct sockaddr_in sin;char name[20];
};void *send_ser_msg(void *arg);int main(int argc, const char *argv[])
{//创建报式套接字int cfd = socket(AF_INET, SOCK_DGRAM, 0);if(cfd < 0){ERR_MSG("socket");return -1;}printf("socket success\n");//允许端口能被快速复用int reuse = 1;if(setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0){ERR_MSG("setsockopt");return -1;}printf("允许端口被快速复用成功\n");//填充服务器地址信息结构体struct sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(PORT);sin.sin_addr.s_addr = inet_addr(IP);char buf[128] = ""; //定义字符串容器struct sockaddr_in cin; //定义客户端地址信息结构体socklen_t addrlen = sizeof(cin); pthread_t tid;struct serinfo info;info.cfd = cfd;info.sin = sin;struct msg climsg;//登录操作char name[20];printf("请输入用户名\n");scanf("%s", name);climsg.type = 'L';strcpy(climsg.name, name);strcpy(climsg.txt, "");strcpy(info.name, name);//发送if(sendto(cfd, (struct msg*)&climsg, sizeof(climsg), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0){ERR_MSG("sendto");return -1;}while(1){if(pthread_create(&tid, NULL, send_ser_msg, (void*)&info) != 0){fprintf(stderr, "pthread_create failed__%d__\n", __LINE__);return -1;}bzero(buf, sizeof(buf));//接受信息if(recvfrom(cfd, buf, sizeof(buf), 0, (struct sockaddr*)&sin, &addrlen) < 0){ERR_MSG("recv");return -1;}printf("server[%s:%d] rcvdata = %s\n", inet_ntoa(sin.sin_addr),\ntohs(sin.sin_port), buf);//线程分离pthread_detach(tid);}if(close(cfd) < 0) //关闭文件描述符{ERR_MSG("close");return -1;}return 0;
}void *send_ser_msg(void *arg)
{int cfd = ((struct serinfo*)arg)->cfd;struct sockaddr_in sin = ((struct serinfo*)arg)->sin;char name[20];strcpy(name, ((struct serinfo*)arg)->name);char buf1[128] = "";struct msg climsg;while(1){scanf("%s", buf1);if(strcmp((buf1), "quit") == 0){climsg.type = 'Q';strcpy(climsg.name, name);strcpy(climsg.txt, "quit");if(sendto(cfd, (struct msg*)&climsg, sizeof(climsg), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0){ERR_MSG("sendto");return NULL;}exit(0);}else{climsg.type = 'C';strcpy(climsg.name, name);strcpy(climsg.txt, buf1);//发送if(sendto(cfd, (struct msg*)&climsg, sizeof(climsg), 0, (struct sockaddr*)&sin, sizeof(sin)) < 0){ERR_MSG("sendto");return NULL;}}}
}