一、聊天框项目
主要思想是:实现并发
1.采用了网络编程的UDP和串口、数据结构中的链表、IO的父子进程
2.主要功能是:登录,聊天,退出
聊天显示,加入群聊成功,聊天信息,退出提示,串口的复用,父子进程,链表等知识;
可以实现的方式有很多,IO多路复用比较难理解,进程线程
二、项目的流程
服务器:
客户端:
三、代码部分
服务器部分:
//服务器
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <string.h>
#include "head.h"
list_t *createEmptyListLink();
void radio_add(int sockfd, list_t *p, struct sockaddr_in caddr);
void users_chatting(int sockfd, list_t *p, struct sockaddr_in caddr);
void users_exit(int sockfd, list_t *p, struct sockaddr_in caddr);
MSG_t msg;
char buf[128];
int main(int argc, char const *argv[])
{
if (argc != 2)
{
perror("please input <port>");
return -1;
}
//1.服务器的创建UDP的创建
// 2.创建一个套节字文件
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
printf("socket success\n");
//3.绑定套节字
struct sockaddr_in saddr, caddr;
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);
int p = 1;
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &p, sizeof(p));
if (bind(sockfd, (struct sockaddr *)&saddr, len) < 0)
{
perror("bind err");
return -1;
}
printf("connet success\n");
pid_t pid = fork();
// 创建父子进程
if (pid < 0)
{
perror("fork err");
return -1;
}
else if (pid == 0) //子进程
{
while (1)
{
msg.type = chat;
strcpy(msg.name,"server");
fgets(msg.text, sizeof(msg.text), stdin);
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
}
}
else //父进程
{
//定义链表
list_t *p = createEmptyListLink();
while (1)
{
//链接客户端
ssize_t ret = recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&caddr, &len);
if (ret < 0)
{
perror("recvfrom err");
return -1;
}
if (msg.type == login)
{
radio_add(sockfd, p, caddr);
printf("ip:%s port:%d id: %s\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), msg.name);
}
if (msg.type == chat)
{
users_chatting(sockfd, p, caddr);
}
if (msg.type == quit)
{
users_exit(sockfd, p, caddr);
}
}
}
close(sockfd);
return 0;
}
//创建链表
list_t *createEmptyListLink()
{
list_t *p = (list_t *)malloc(sizeof(list_t));
if (p == NULL)
{
perror("malloc err");
return NULL;
}
p->next = NULL;
return p;
}
//用户登录
void radio_add(int sockfd, list_t *p, struct sockaddr_in caddr)
{
//拼接信息
strcpy(msg.text,"login");
//遍历向其他人发送消息
while (p->next != NULL)
{
p = p->next;
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
//创建一个新的节点
list_t *pnew = (list_t *)malloc(sizeof(list_t));
//给新节点赋值
pnew->addr = caddr;
//将ptail尾节点的指针域指向新节点的地址
pnew->next = NULL;
//移动尾指针到新节点
p->next = pnew;
printf("end success\n");
}
//聊天
void users_chatting(int sockfd, list_t *p, struct sockaddr_in caddr)
{
while (p->next != NULL)
{
p = p->next;
if (memcmp(&(p->addr), &caddr, sizeof(caddr)) != 0) //使用将存储区里面得内容进行比较
{
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
}
}
//退出
void users_exit(int sockfd, list_t *p, struct sockaddr_in caddr)
{
list_t *pdel = NULL;
// sprintf(msg.text, "%s:exit", msg.name);
while (p->next != NULL)
{
//发送退出信息给所有客户端
if (memcmp(&(p->next->addr), &caddr, sizeof(caddr)) != 0)
{
p = p->next;
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&(p->addr), sizeof(p->addr));
}
else //删除链表的节点
{
pdel = p->next;
p->next = pdel->next;
free(pdel);
pdel = NULL;
}
}
printf("del success\n");
}
客户端部分
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <stdlib.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include "head.h"
MSG_t msg;
int sockfd;
socklen_t len;
struct sockaddr_in saddr, caddr;
void handler(int sig)
{
msg.type = quit;
strcpy(msg.text,"quit");
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
exit(0);
}
int main(int argc, char const *argv[])
{
if (argc < 3)
{
perror("please input <ip> <port>");
return -1;
}
//创建套节字UDP的套节字
sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err");
return -1;
}
//创建结构体来存放要连接服务器的ip+port
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
len = sizeof(saddr);
//获取登录的信息//将信息放到协议里
//执行一次
msg.type = login;
printf("plese input id:");
fgets(msg.name, sizeof(msg.name), stdin);
if (msg.name[strlen(msg.name) - 1] == '\n')
msg.name[strlen(msg.name) - 1] = '\0';
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)
{
//接收数据,服务器的数据
ssize_t ret = recvfrom(sockfd, &msg, sizeof(msg), 0, NULL, NULL);
if (ret < 0)
{
perror("recvfrom err");
return -1;
}
//打数据
printf("%s:%s\n",msg.name,msg.text);
}
}
else
{
signal(SIGINT,handler);
while(1)
{
//用来发送数据
//终端输入数据
ssize_t ss = read(0, msg.text, sizeof(msg.text));
//清除换行
if (msg.text[strlen(msg.text) - 1] == '\n')
msg.text[strlen(msg.text) - 1] = '\0';
//将数据发送到服务器
if (!strncmp(msg.text, "quit\n", 4))
{
msg.type = quit;
sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len);
break;
}
else
{
//将标志改为chat
msg.type = chat;
if (sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&saddr, len) < 0)
{
perror("sendto err");
return -1;
}
}
}
waitpid(-1, NULL, WNOHANG); //非阻塞
exit(0);
}
close(sockfd);
return 0;
}
头文件
#ifndef __HEAD_H__
#define __HEAD_H__
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
//类型
enum type_t
{
login,
chat,
quit,
};
//定义消息传递的协议
typedef struct msg_t
{
int type;
char name[32];
char text[128];
} MSG_t;
//链表节点结构体
typedef struct node_t
{
struct sockaddr_in addr;
struct node_t *next;
}list_t;
#endif
四、总结
本代码思路方便理解,当然还有很多的思路和功能可以添加,思路是活的,综合使用,逻辑跟方式可以就行,感谢你的观看!