目录
一,简述
开发工具: Linux平台GCC交叉编译环境
网络聊天室
- 登录:服务器存储新的客户端的地址。把某个客户端登录的消息发给其它客户端。
- 聊天:服务器只需要把某个客户端的聊天消息转发给所有其它客户端。
- 退出:服务器删除退出客户端的地址,并把退出消息发送给其它客户端。
二,项目要求
利用UDP协议,实现一套聊天室软件。服务器端记录客户端的地址,客户端发送消息后,服务器群发给各个客户端软件。
三,程序流程图
客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程。
服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程。
服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存。
数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据。
对登录聊天室的用户,需要保存用户信息,将登录的客户端信息新建节点插入链表。
服务器端:
客户端:
四,相关知识点
通信流程:
udp流程:(类似发短信)
server:
创建数据报套接字(socket(,SOCK_DGRAM,))----->有手机
绑定网络信息(bind())---------------------->绑定号码(发短信知道发给谁)
接收信息(recvfrom())--------------------->接收短信
关闭套接字(close())----------------------->接收完毕
client:
创建数据报套接字(socket())----------------------->有手机
指定服务器的网络信息------------------------------>有对方号码
发送信息(sendto())---------------------------->发送短信
关闭套接字(close())--------------------------->发送完
函数接口:
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t*addrlen);
功能:接收数据
参数:
sockfd:套接字描述符
buf:接收缓存区的首地址
len:接收缓存区的大小
flags:0
src_addr:发送端的网络信息结构体的指针
addrlen:发送端的网络信息结构体的大小的指针
返回值:
成功接收的字节个数
失败:-1
0:客户端退出
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen);
功能:发送数据
参数:
sockfd:套接字描述符
buf:发送缓存区的首地址
len:发送缓存区的大小
flags:0
src_addr:接收端的网络信息结构体的指针
addrlen:接收端的网络信息结构体的大小
返回值:
成功发送的字节个数
失败:-1
五,代码实现
客户端:
int main(int argc, char const *argv[])
{
//创建数据报
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(atoi(argv[2]));
saddr.sin_addr.s_addr = inet_addr(argv[1]);
//1.登录
MSG_t msg;
msg.type = login;
printf("please input your name:");
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, sizeof(saddr));
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, NULL, NULL) < 0)
{
perror("recvfrom err.");
return -1;
}
printf("%s said %s\n", msg.name, msg.text);
}
}
else
{
while (1)
{
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)
{
msg.type = quit;
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&saddr, sizeof(saddr));
break;
}
else
{
msg.type = chat;
}
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&saddr, sizeof(saddr));
}
kill(pid, SIGKILL);
wait(NULL);
}
close(sockfd);
return 0;
}
服务器:
主程序
int main(int argc, char const *argv[])
{
int sockfd = socket(AF_INET, SOCK_DGRAM, 0);
if (sockfd < 0)
{
perror("socket err.");
return -1;
}
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(caddr);
if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
{
perror("bind err.");
return -1;
}
MSG_t msg;
pid_t pid = fork();
if (pid < 0)
{
perror("fork err.");
return -1;
}
else if (pid == 0)
{
//创建一个空的链表
list_t *p = createList();
while (1)
{
if (recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&caddr, &len) < 0)
{
perror("recvfrom err.");
return -1;
}
switch (msg.type)
{
case login:
server_login(sockfd, p, msg, caddr);
break;
case chat:
server_chat(sockfd, p, msg, caddr);
break;
case quit:
server_quit(sockfd, p, msg, caddr);
break;
}
}
}else{
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, sizeof(saddr));
}
}
close(sockfd);
return 0;
}
函数——server_quit
//1.将谁退出消息转发给所有登录的客户端
//2.将推出客户端信息从链表中清除
void server_quit(int sockfd, list_t *p, MSG_t msg, struct sockaddr_in caddr)
{
list_t *pdel = NULL;
sprintf(msg.text, "%s quit", msg.name);
while (p->next != NULL)
{
if (memcmp(&(p->next->addr), &caddr, sizeof(caddr)) == 0)
{
pdel = p->next;
p->next = pdel->next;
free(pdel);
pdel = NULL;
}
else
{
p = p->next;
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&(p->addr), sizeof(p->addr));
}
}
}
函数——server_chat
//chat:
//1.将聊天转发除自己之外的所有客户端(sockfd,p,msg,caddr)
void server_chat(int sockfd, list_t *p, MSG_t msg, struct sockaddr_in caddr)
{
//遍历链表转发消息
while (p->next != NULL)
{
p = p->next;
if (memcmp(&(p->addr), &caddr, sizeof(caddr)) == 0)
continue;
sendto(sockfd, &msg, sizeof(msg), 0,
(struct sockaddr *)&(p->addr), sizeof(p->addr));
}
}
函数——server_login
//login:
//1.将谁登录转发所有已经登录的客户端 (链表、sockfd,msg)
//2.将新登录的客户端信息新建节点插入链表 (caddr)
void server_login(int sockfd, list_t *p, MSG_t msg, struct sockaddr_in caddr)
{
sprintf(msg.text, "%s login", msg.name);
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;
pnew->next = NULL;
//链接到链表最后
p->next = pnew;
}
函数——链表
//创建一个空的有头单向链表
//创建一个空的有头单向链表
list_t *createList(void)
{
list_t *p = (list_t *)malloc(sizeof(list_t));
if (NULL == p)
{
perror("malloc err.");
return NULL;
}
//初始化节点
p->next = NULL;
return p;
}