//客户端代码
#include <myhead.h>
struct msgType
{char type; // 消息类型L:登录,Q:退出,C:聊天char usrName[20];char msgText[1024];
};
#define SER_PORT 6666 // 服务器端口
#define SER_IP "192.168.2.161" // 服务器IP
int main(int argc, char const *argv[])
{int cfd = socket(AF_INET, SOCK_DGRAM, 0);if (cfd == -1){perror("socket error");return -1;}// 端口号快速重用int reuse = 1;if (setsockopt(cfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){perror("setsockopt error");return -1;}// 初始化服务器地址struct sockaddr_in sin; // 对端服务器地址结构体sin.sin_family = AF_INET; // 协议族sin.sin_port = htons(SER_PORT); // 端口号sin.sin_addr.s_addr = inet_addr(SER_IP); // IP地址socklen_t len = sizeof(sin); // 地址长度struct msgType msg;msg.type = 'L';printf("请输入用户名:");fgets(msg.usrName, 20, stdin);msg.usrName[strlen(msg.usrName) - 1] = '\0';char temp[20]; // 将用户名保存起来strcpy(temp, msg.usrName);strcpy(msg.msgText, "");sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, len);// 创建子进程用于收消息pid_t pid = fork();if (pid == -1){perror("fork error");return -1;}if (pid == 0){// 子进程{while (1){bzero(&msg, sizeof(msg));// 接收消息recvfrom(cfd, &msg, sizeof(msg), 0, NULL, NULL);switch (msg.type){case 'L':printf("%s\n", msg.msgText);break;case 'Q':{if (strcmp(temp, msg.usrName) == 0){printf("退出成功\n");exit(EXIT_SUCCESS);}printf("%s\n", msg.msgText);}break;case 'C':printf("[%s]:%s\n", msg.usrName, msg.msgText);break;}}}}else if (pid > 0){// 父进程msg.type = 'C';while (1){sleep(1);// 发送消息fgets(msg.msgText, 1024, stdin);msg.msgText[strlen(msg.msgText) - 1] = '\0';if (strcmp(msg.msgText, "q") == 0 || strcmp(msg.msgText, "Q") == 0){msg.type = 'Q';sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, len);sleep(2);goto end;}sendto(cfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, len);}}end:kill(pid, SIGKILL);wait(NULL);close(cfd);return 0;
}
//服务器主代码
#include "head.h"
#define SER_PORT 6666 // 服务器端口
#define SER_IP "192.168.2.161" // 服务器IP
int main(int argc, char const *argv[])
{// 创建用于通信的服务器套接字文件描述符int sfd = socket(AF_INET, SOCK_DGRAM, 0);if (sfd == -1){perror("socket error");return 1;}// 将端口号快速重启int reuse = 1;if (setsockopt(sfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) == -1){perror("setsockopt error");return 1;}// 绑定服务器套接字struct sockaddr_in sin;sin.sin_family = AF_INET;sin.sin_port = htons(SER_PORT);sin.sin_addr.s_addr = inet_addr(SER_IP);if (bind(sfd, (struct sockaddr *)&sin, sizeof(sin)) == -1){perror("bind error");return 1;}printf("服务器启动成功\n");// 创建链表NodePtr L = list_create();// 接收客户端发送的数据struct sockaddr_in cin;socklen_t len = sizeof(cin);struct msgType msg;bzero(&msg, sizeof(msg));while (1){// 创建子进程pid_t pid = fork();// 接收客户端发送的数据if (pid == 0){while (1){// 接收信息recvfrom(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&cin, &len);switch (msg.type) // 判断信息类型{case 'L':{list_insert(L, msg.usrName, &cin);strcpy(msg.msgText, "欢迎您进入聊天室 开始聊天吧"); // 发送欢迎信息sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&cin, len);strcpy(msg.msgText, "*******欢迎");strcat(msg.msgText, msg.usrName);strcat(msg.msgText, "进入聊天室*******");NodePtr p = L->next;while (p != NULL) // 发送所有人进入聊天室信息(除了刚进来的人 因为已经发了欢迎){if (p->addr.sin_port == cin.sin_port){p = p->next;continue;}if (p == NULL){break;}sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->addr, len);p = p->next;}}break;case 'C':{printf("[%s]:%s\n", msg.usrName, msg.msgText); // 先在终端展示for (NodePtr p = L->next; p != NULL; p = p->next){sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->addr, len);}printf("发送成功\n");}break;case 'Q':{strcpy(msg.msgText, "*********");strcat(msg.msgText, msg.usrName);strcat(msg.msgText, "退出聊天室*********");for (NodePtr p = L->next; p != NULL; p = p->next){sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->addr, len);}sleep(1);list_delete(L, msg.usrName);}break;}}}else if (pid > 0){// 发送数据给客户端while (1){msg.type = 'C';strcpy(msg.usrName, "GM");fgets(msg.msgText, sizeof(msg.msgText), stdin);msg.msgText[strlen(msg.msgText) - 1] = '\0';sendto(sfd, &msg, sizeof(msg), 0, (struct sockaddr *)&sin, len);if (strcmp(msg.msgText, "exit") == 0){goto end;}}}else if (pid < 0){perror("fork error");return 1;}end:kill(pid, SIGKILL);list_destroy(L);close(sfd);wait(NULL);return 0;}
}
//头文件
#include <myhead.h>
#ifndef __HEAD_H__
#define __HEAD_H__
// 定义结构体typedef struct Node
{union{int len; // 节点长度char name[20]; // 节点名称};struct sockaddr_in addr; // 地址信息结构体struct Node *next; // 指向下一个节点
} Node, *NodePtr;struct msgType
{char type; // 消息类型 L:登录,C:聊天,Q;退出char usrName[20]; // 姓名char msgText[1024]; // 消息
};
// 创建链表
NodePtr list_create();
// 申请封装节点函数
NodePtr apply_node(char *name, struct sockaddr_in *addr);
// 链表判空
int list_empty(NodePtr L);
// 链表插入
int list_insert(NodePtr L, char *name, struct sockaddr_in *addr);// 按名字找位置函数
int list_search_name(NodePtr L, char *name);
// 任意位置查找节点
NodePtr list_search_pos(NodePtr L, int pos);
// 任意位置删
int list_delete(NodePtr L, char *name);// 链表销毁
void list_destroy(NodePtr L);#endif//函数文件
#include "head.h"
// 创建链表
NodePtr list_create()
{// 申请头结点NodePtr L = (NodePtr)malloc(sizeof(Node));if (NULL == L){printf("创建失败\n");return NULL;}// 赋值L->len = 0;L->next = NULL;printf("聊天室创建成功\n");return L;
}
// 申请封装节点函数
NodePtr apply_node(char *name, struct sockaddr_in *addr)
{// 申请空间NodePtr p = (NodePtr)malloc(sizeof(Node));if (NULL == p){printf("申请失败\n");return NULL;}// 赋值strcpy(p->name, name);p->addr.sin_family = addr->sin_family;p->addr.sin_port = addr->sin_port;p->addr.sin_addr.s_addr = addr->sin_addr.s_addr;p->next = NULL;return p;
}
// 链表判空
int list_empty(NodePtr L)
{ return L->next == NULL;
}
// 链表插入
int list_insert(NodePtr L, char *name, struct sockaddr_in *addr)
{// 判断逻辑if (NULL == L){return -1;}// 申请节点NodePtr p = apply_node(name, addr);if (NULL == p){return -1;}// 插入逻辑p->next = L->next;L->next = p;printf("*********%s进入聊天室*********\n", p->name);L->len++;return 0;
}
// 按名字找位置函数
int list_search_name(NodePtr L, char *name)
{// 判断逻辑if (NULL == L || list_empty(L)){return -1;}// 遍历逻辑NodePtr q = L->next;for (int index = 1;index<=L->len; index++){if(strcmp(q->name, name) == 0){return index;}q = q->next;}printf("未找到该用户\n");return -1;
}
// 任意位置查找节点
NodePtr list_search_pos(NodePtr L, int pos)
{if (NULL == L || list_empty(L) || pos < 0 || pos > L->len){return NULL;}//查找逻辑//定义指针从头结点出发NodePtr q = L;for (int i = 0; i < pos; i++){q = q->next;}return q; //返回找到的节点
}
// 任意明治删
int list_delete(NodePtr L, char *name)
{// 判断逻辑)if (NULL == L || list_empty(L)){return -1;}// 找到前一个节点int pos = list_search_name(L, name);NodePtr q = list_search_pos(L, pos-1);// 删除逻辑NodePtr p = q->next;q->next = p->next;// 释放空间free(p);p = NULL;printf("*********%s退出聊天室*********\n", name);return 0;
}// 链表销毁
void list_destroy(NodePtr L)
{// 判断逻辑if (NULL == L){return;}// 遍历逻辑while (!list_empty(L)){list_delete(L, L->next->name);}// 释放头结点free(L);L = NULL;
}




