通信协议
#ifndef __HEAD_H__
#define __HEAD_H__
#include
#include
#include
//类型
enum type_t
{
login,
chat,
quit,
};
//定义消息传递的协议
typedef struct mag_t
{
int type;//类型
char name[32];//ip地址
char text[128];//发送内容
} MSG_t;
//链表节点结构体
typedef struct node_t
{
struct sockaddr_in addr;//ip地址
struct node_t *next;//链表下一个地址
}list_t;
#endif
服务器端
/*服务器创建代码 */
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#include
#include
#include
#include
#include
#include
#include "head.h"
MSG_t msg;
list_t *createList(); //创建头节点
int loginclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd); //首次登录,加入链表
int quitclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd); //客户端退出,删除链表中对应内容
void chatclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd); //遍历发送
int main(int argc, char const *argv[])
{
if (argc < 2)
{
printf("plase input \n");
return -1;
}
//1.创建套接字,用于链接
int sockfd;
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
//2.绑定 ip+port 填充结构体
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[1]));
saddr.sin_addr.s_addr = inet_addr("0.0.0.0");
socklen_t len = sizeof(saddr); //结构体大小
//bind绑定ip和端口
if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
{
perror("bind err");
return -1;
}
printf("udp聊天室已创建,等待加入\n");
list_t *p = createList(); //创建有头单向链表表头
pid_t pid = fork(); //创建一个子进程
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子进程,发送消息
{
//发送信息
while (1)
{
fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中
if (msg.text[strlen(msg.text)] == '\0')
{
msg.text[strlen(msg.text) - 1] = '\0';
}
strncpy(msg.name, "server", 6);
msg.type = chat;
list_t *temp = p;
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, sizeof(saddr));
}
}
else //父进程,接收消息
{
while (1)
{
//接收信息
if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
{
perror("recvfrom err");
return -1;
}
// printf("client ip:%s ,port:%d buf:%s\n", inet_ntoa(saddr.sin_addr), ntohs(saddr.sin_port),buf);
switch (msg.type)
{
case login:
loginclient(p, saddr, msg, sockfd);
break;
case chat:
chatclient(p, saddr, msg, sockfd);
break;
case quit:
quitclient(p, saddr, msg, sockfd);
break;
}
}
char buff[32];
sprintf(buff, "kill -9 %d", pid);
system(buff); // 向子进程发送结束进程的信
kill(getpid(), SIGINT);
waitpid(pid, NULL, 0);
exit(0);
}
close(sockfd);
return 0;
}
//创建一个空的有头单项链表
list_t *createList()
{
//1.创建节点,作为连表的头节点。
list_t *h = (list_t *)malloc(sizeof(list_t));
if (NULL == h)
{
perror("createList malloc err");
return NULL;
}
//2.初始化
h->next = NULL;
return h;
}
//向链表中插入数据,传入的参数:头节点,结构体(链表数据域),数据包,套接字描述符
int loginclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd)
{
list_t *temp = p; //定义一个新的指针保存头节点,防止头节点改变.
sprintf(msg.text, "%s 已登录.", msg.name); //将登录成员名格式化输入到发送内容中
while (temp->next != NULL)
{
temp = temp->next;
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&temp->addr, sizeof(temp->addr));
//接收方保存在链表中,通过遍历链表访问
}
printf("成员:%s ,内容:%s\n", msg.name, msg.text);
list_t *pnew = NULL;
//创建一个新节点,保存插入数据
pnew = (list_t *)malloc(sizeof(list_t));
if (pnew == NULL) //判断开辟成功
{
perror("loginclient pnew malloc err");
return -1;
}
pnew->addr = addr; //将传入的结构体保存到新节点中
pnew->next = NULL;
//将新节点插入到链表,先连后面,再连前面。前插法.
pnew->next = p->next;
p->next = pnew;
return 0;
}
//删除内容
int quitclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd)
{
list_t *pdel = NULL; //用于指向被删除的节点
sprintf(msg.text, "%s 已退出.", msg.name); //将登录成员名格式化输入到发送内容中
while (p->next != NULL)
{
if (memcmp(&(p->next->addr), &addr, sizeof(addr)) != 0)
{
p = p->next;
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
else
{
pdel = p->next; //(1)将pdel指向被删除节点
p->next = pdel->next; //(2)跨过被删除节点
free(pdel); //(3)释放被删除节点
pdel = NULL;
}
}
printf("成员:%s ,内容:%s\n", msg.name, msg.text);
return 0;
}
//遍历转发成员消息
void chatclient(list_t *p, struct sockaddr_in addr, MSG_t msg, int sockfd)
{
list_t *temp = p;
while (temp->next != NULL)
{
temp = temp->next;
if (memcmp(&(temp->addr), &addr, sizeof(addr)) != 0)
{
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&temp->addr, sizeof(temp->addr));
}
}
printf("成员:%s ,内容:%s\n", msg.name, msg.text);
}
客户端
/*客户端创建代码 */
#include
#include /* See NOTES */
#include
#include
#include /* superset of previous */
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "head.h"
int sockfd;
char names[32];
struct sockaddr_in saddr;
MSG_t msg; //消息包
void handler(int sig) // 信号处理函数 处理Ctrl C
{
if (sig == SIGINT)
{
msg.type = quit;
strcpy(msg.text, "quit");
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, sizeof(saddr));
close(sockfd); // 关闭套接字
wait(NULL);
exit(0); // 退出程序
}
}
int main(int argc, char const *argv[])
{
if (argc < 3)
{
printf("plase input ");
return -1;
}
//1.创建套接字,用于链接
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("sockfd:%d\n", sockfd);
//2.填充结构体
saddr.sin_family = AF_INET; //协议族
saddr.sin_port = htons(atoi(argv[2])); //端口
saddr.sin_addr.s_addr = inet_addr(argv[1]); //IP
//登录-----只发送一次,发送协议结构体
socklen_t len = sizeof(saddr); //结构体大小
msg.type = login;
//输入客户端ID
printf("请输入您的ID:\n");
fgets(names, sizeof(names), stdin);
if (names[strlen(names) - 1] == '\n')
{
names[strlen(names) - 1] = '\0';
}
strncpy(msg.name, names, 9); //客户端昵称
strncpy(msg.text, "login", 6);
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len); //发送信号
pid_t pid = fork(); //创建父子进程
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子进程接收消息
{
while (1)
{
//接受信息
if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, &len) < 0)
{
perror("recvfrom err");
return -1;
}
printf("成员:%s 内容:%s\n", msg.name, msg.text);
}
}
else //父进程发送消息
{
while (1)
{
// 捕捉键盘输入 Ctrl C
signal(SIGINT, handler);
//发送信息
memset(msg.text, 0, sizeof(msg.text)); //清空数组内容
fgets(msg.text, sizeof(msg.text), stdin); //从终端获取内容存放到数组中
if (msg.text[strlen(msg.text) - 1] == '\n')
{
msg.text[strlen(msg.text) - 1] = '\0';
}
if (strncmp(msg.text, "quit", 4) == 0) //输入quit退出客户端
{
msg.type = quit; //退出状态
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
char buff[32];
sprintf(buff, "kill -9 %d", pid);
system(buff); // 从终端向子进程发送结束进程的信号
// 防止kill触发SIGINT信号导致二次发送quit信息
waitpid(pid, NULL, 0); //等待子进程结束回收资源
exit(0);
}
msg.type = chat; //交互状态
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len); //发送信号
}
}
close(sockfd);
return 0;
}