您的位置:首页 > 文旅 > 美景 > 公司网站建设工作室_office制作网页的软件_网络推广的渠道_手机搜索引擎排行榜

公司网站建设工作室_office制作网页的软件_网络推广的渠道_手机搜索引擎排行榜

2025/1/8 15:02:37 来源:https://blog.csdn.net/m0_57832432/article/details/143435878  浏览:    关键词:公司网站建设工作室_office制作网页的软件_网络推广的渠道_手机搜索引擎排行榜
公司网站建设工作室_office制作网页的软件_网络推广的渠道_手机搜索引擎排行榜

项目要求

利用UDP协议,实现一套聊天室软件。服务器端记录客户端的地址,客户端发送消息后,服务器群发给各个客户端软件。

问题思考

  • 客户端会不会知道其它客户端地址?

UDP客户端不会直接互连,所以不会获知其它客户端地址,所有客户端地址存储在服务器端。

  • 有几种消息类型?

登录:服务器存储新的客户端的地址。把某个客户端登录的消息发给其它客户端。

聊天:服务器只需要把某个客户端的聊天消息转发给所有其它客户端。

退出:服务器删除退出客户端的地址,并把退出消息发送给其它客户端。

  • 服务器如何存储客户端的地址?

链表

链表节点结构体:
struct node{struct sockaddr_in addr;struct node *next;
};消息对应的结构体(同一个协议)
typedef struct msg_t
{int type;//L  M  Q  char name[32];//用户名char text[128];//消息正文
}MSG_t;int memcmp(void *s1,void *s2,int size)
功能:比较两个空间内的值是否完全相同
  • 客户端如何同时处理发送和接收?

客户端不仅需要读取服务器消息,而且需要发送消息。读取需要调用recvfrom,发送需要先调用gets,两个都是阻塞函数。所以必须使用多任务来同时处理,可以使用多进程或者多线程来处理。

程序流程图

客户端

伪代码:

 服务器端:

//1.创建服务器流程

//2.创建空链表

//3.循环接受消息

//根据消息类型调用函数

login:

1.将登录信息发送给所有已经登录的客户端(链表,sockfd,msg)

2.将新登录的客户端插入链表(caddr)

quit:

1.将谁退出的信息发送给所有登录的客户端(遍历链表)

2.将退出的客户端信息删除

chat:

将聊天的内容转发给已经登录的客户端

客户端:

1.客户端创建流程

2.登录(输入名字,发送给服务器)login

3.创建子进程(循环接收服务器的信息并打印)chat

4.父进程

终端输入信息并发送给服务器(注意发送的是否为quit,区分正常消息和退出消息)

代码 

head.h 头文件

#ifndef _SEQSTACK_H_
#define _SEQSTACK_H_
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <string.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <dirent.h>
#include <sys/wait.h>
#include <signal.h>
#define N 128
//链表节点结构体,用来存储用户信息
struct node
{struct sockaddr_in addr;struct node *next;
};//消息对应的结构体(同一个协议)
typedef struct msg_t
{char type;       //L  M  Qchar name[32];  //用户名char text[128]; //消息正文
} MSG_t;
// 消息类型
enum msgtype
{L, //登录M, //消息Q  //退出
};
#endif

UDP聊天室客户端 

int main(int argc, char const *argv[])
{MSG_t msg;// 1.创建数据报套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd: %d\n", sockfd);// 2.指定网络信息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]);int len = sizeof(saddr);msg.type = L;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, N, 0, (struct sockaddr *)&saddr, sizeof(saddr));pid_t pid = fork();if (pid < 0){perror("fork err");return -1;}else if (pid == 0){//循环接收消息while (1){//最后两个参数存放:发送消息的人的信息int ret = recvfrom(sockfd, &msg, N, 0, (struct sockaddr *)&saddr, &len);if (ret < 0){perror("recvfrom err");return -1;}else{if (msg.type == M){printf("%s: %s\n", msg.name, msg.text);}else if (msg.type == L){printf("%s\n", msg.text);}else if (msg.type == Q){printf("%s\n", msg.text);}}}}else{printf("------你已登录,开始聊天------\n");while (1){msg.type = M;fgets(msg.text, N, stdin);if (msg.text[strlen(msg.text) - 1] == '\n'){msg.text[strlen(msg.text) - 1] = '\0';}if (strcmp(msg.text, "quit") == 0){msg.type = Q;sendto(sockfd, &msg, N, 0, (struct sockaddr *)&saddr, sizeof(saddr));kill(pid, SIGKILL);wait(NULL);exit(0);}sendto(sockfd, &msg, N, 0, (struct sockaddr *)&saddr, sizeof(saddr));}kill(pid, SIGKILL); wait(NULL);}//5.关闭套接字close(sockfd);return 0;
}

 UDP聊天室服务端

#include "head.h"
void login(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd);
void chat(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd);
void quit(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd);
struct node *linklist_create();int main(int argc, char const *argv[])
{MSG_t msg;// 1.创建数据报套接字int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}printf("sockfd: %d\n", sockfd);// 2.指定网络信息struct sockaddr_in saddr, caddr;saddr.sin_family = AF_INET;saddr.sin_port = htons(atoi(argv[1]));saddr.sin_addr.s_addr = INADDR_ANY;int len = sizeof(caddr);//3.绑定套接字if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0){perror("bind err");return -1;}printf("bind ok\n");//创建空的有头单向链表struct node *p = linklist_create();//循环接收消息while (1){int ret = recvfrom(sockfd, &msg, N, 0, (struct sockaddr *)&caddr, &len);if (ret < 0){perror("recvfrom err");return -1;}else{if (msg.type == L){login(p, msg, caddr, sockfd);printf("%s信息保存成功\n", msg.name);}else if (msg.type == M){chat(p, msg, caddr, sockfd);}else if (msg.type == Q){quit(p, msg, caddr, sockfd);printf("%s信息已删除\n", msg.name);}}}//5.关闭套接字close(sockfd);return 0;
}
//创建头结点函数
struct node *linklist_create()
{struct node *p = (struct node *)malloc(sizeof(struct node));if (NULL == p){perror("p malloc lost\n");return NULL;}p->next = NULL;return p;
}//登录函数
void login(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd)
{给其他用户发送该用户已登录信息sprintf(msg.text, "%s login", msg.name);while (p->next != NULL){p = p->next;sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));}//创建新节点用来保存用户信息struct node *ptail = NULL;ptail = p;struct node *pnew = (struct node *)malloc(sizeof(struct node));if (NULL == pnew){perror("pnew malloc err\n");return;}pnew->addr = caddr;pnew->next = NULL;ptail->next = pnew;ptail = pnew;return;
}
聊天函数
void chat(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd)
{while (p->next != NULL){p = p->next;if (memcmp(&p->addr, &caddr, sizeof(caddr)) != 0){sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));}}return;
}
退出函数
void quit(struct node *p, MSG_t msg, struct sockaddr_in caddr, int sockfd)
{sprintf(msg.text, "%s: 退出", msg.name);while (p->next != NULL){p = p->next;if (memcmp(&p->addr, &caddr, sizeof(caddr)) != 0){sendto(sockfd, &msg, N, 0, (struct sockaddr *)&p->addr, sizeof(p->addr));}}struct node *q = p->next;while (q != NULL){if (memcmp(&p->addr, &caddr, sizeof(caddr)) == 0){p->next = q->next;free(q);q = NULL;break;}else{p = p->next;q = q->next;}}return;
}

问题 

一:第一个问题就是在用sendto,recvfrom函数在进行收发时我传的参数是结构体里的成员变量,在输入名字时我传的是sendto(sockfd,&msg.name....),在传消息时我传的参数sendto(sockfd,&msg.text.)

导致服务器接收不到已经改变了的msg.type消息类型了,所以会出现一些问题。我们应该传入&msg整个结构体,里面包含着消息类型,消息正文,名字,我就像一个协议一样在服务器与客户端之间收发。

二:第二个问题就是我在服务器写到判断消息类型的逻辑错误,我把先判断写在前面了,然后再接收,导致出现2次登录。正确的逻辑应该是先接收客户端发来的协议以及数据,然后再判断。

三:第三个问题就是我在客户端判断客户端退出了,然后没有在while循环外加wait(NULL)回收子进程资源,会导致子进程变成僵尸进程,然后我又在子进程while循环外加入了exit(0),然后父进程写了wait(NULL),虽然会回收,但是也把其他客户端也给退出回收了,因为其他客户端在子进程会收到Q类型的消息类型,也会退出while循环然后退出被父进程回收。最后我又被老师指导下只在父进程加了个kill(pid,SIGKILL)和wait(NULL)。只要我父进程收到quit退出消息直接向子进程发起终止信号强制终止进程。

至此呢,在我解决了这三个问题之后呢,我的项目就完成了,也对整个项目有了更深入的认识。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com