基于TCP的网络聊天室实现(C语言)

基于TCP的网络聊天室实现(C语言)

  • 一、网络聊天室的功能
  • 二、网络聊天室的结果展示
  • 三、实现思路及流程
  • 四、代码及说明
    • 1.LinkList.h
    • 2.LinkList.c
    • 3.client.c
    • 4.server.c


一、网络聊天室的功能

有新用户登录,其他在线的用户可以收到登录信息
有用户发送群聊消息,其他在线的用户可以收到群聊信息
有用户退出,其他在线的用户可以收到退出信息
服务器可以发送系统信息


二、网络聊天室的结果展示

在这里插入图片描述1.已经加入群聊的用户可以看到新加入群聊的用户
2.用户退出或者断线,其他用户也可以看到
3.server端可以发送系统消息给所有在聊天室的用户

三、实现思路及流程

客户端登录之后,为了实现一边发送数据一边接收数据,可以使用多进程或者多线程。
服务器既可以发送系统信息,又可以接收客户端信息并处理,可以使用多进程或者多线程。
服务器需要给多个用户发送数据,所以需要保存每一个用户的信息,使用链表来保存。
数据传输的时候要定义结构体,结构体中包含操作码、用户名以及数据。
在这里插入图片描述

四、代码及说明

对登录聊天室的用户,需要保存用户信息,本文用链式存储来存放用户信息,因此需要用到链队列,动态分配空间。链式队列的便利之处就在于往队列中插入用户信息的时候,不用想数组那样可能需要大量移动数据。

1.LinkList.h

代码如下:

#ifndef __LINKLIST_H__
#define __LINKLIST_H__#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <pthread.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/select.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>#define ERRLOG(msg)                                         \do {                                                    \printf("%s:%s:%d\n", __FILE__, __func__, __LINE__); \perror(msg);                                        \exit(-1);                                           \} while (0)#define N 128#define LEN 128
#define NUM_USR 64#define datatype int//自定义结构体,用来保存所有连接的客户的IP地址、端口号以及acceptfd文件描述符这三个参数
typedef struct info {struct sockaddr_in clientaddr;int acceptfd;char name[16];char named;struct info* next;char sayHi;
} usr_info_t;usr_info_t* info_head;usr_info_t* LinkListNodeCreate(void);
int LinkListInsertHead(usr_info_t* head, usr_info_t* node);
usr_info_t* LinkListSearchUsrByAcceptfd(usr_info_t* h, int acceptfd);#endif

2.LinkList.c

代码如下:

#include "LinkList.h"
#include <string.h>
/**功能:创建单链表*参数:*	@:无*返回值:成功返回单链表节点的首地址,失败返回NULL*注:创建链表节点,将指针域指向NULL,并将节点内容清零*/
usr_info_t* LinkListNodeCreate(void)
{usr_info_t* h;h = (usr_info_t*)malloc(sizeof(*h));if (h == NULL) {printf("alloc memory error\n");return NULL;}memset(h, 0, sizeof(*h));h->next = NULL;return h;
}/**功能:单链表按照头插法插入数据*参数:*	@head: 用户信息链表头节点的首地址*   @node: 要插入的节点的地址*返回值:成功返回0*/
int LinkListInsertHead(usr_info_t* head, usr_info_t* node)
{// 1.将node插入到head中即可node->next = head->next;head->next = node;// 2.成功返回0return 0;
}/**功能:通过Acceptfd查询用户数据*参数:*	@h:用户信息链表头指针*  @acceptfd:accept函数产生的文件描述符*返回值:成功返回0,失败返回-1*/
usr_info_t* LinkListSearchUsrByAcceptfd(usr_info_t* h, int acceptfd)
{while (h->next != NULL) {if (h->next->acceptfd == acceptfd) {return h->next;}h = h->next;}printf("用户不存在,查询失败\n");
}

3.client.c

代码如下:

#include <arpa/inet.h>
#include <fcntl.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <unistd.h>#define ERRLOG(msg)                                         \do {                                                    \printf("%s:%s:%d\n", __FILE__, __func__, __LINE__); \perror(msg);                                        \exit(-1);                                           \} while (0)void recycle()
{wait(NULL);
}void tuichu()
{exit(0);
}int main(int argc, const char* argv[])
{//参数合理性检查if (3 != argc) {printf("Usage : %s <IP> <port>\n", argv[0]);exit(-1);}//创建套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd) {ERRLOG("socket error");}//填充服务器网络信息结构体struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(argv[2]));serveraddr.sin_addr.s_addr = inet_addr(argv[1]);socklen_t serveraddr_len = sizeof(serveraddr);//与服务器建立连接if (-1 == connect(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {ERRLOG("connect error");}printf("与服务器连接成功..\n");char buff[128] = { 0 };int num = 0;//等待子进程退出信号SIGCHLD,回收子进程资源signal(SIGCHLD, recycle);//需要分出来一个子进程,专门用于接收消息,父进程用于发送消息pid_t pid = fork();int named = 0;while (1) {if (pid == 0) { //子进程,接收消息signal(SIGINT, tuichu); //子进程捕获到SIGINT信号就退出//接收应答消息memset(buff, 0, sizeof(buff));if (-1 == (num = recv(sockfd, buff, 128, 0))) {ERRLOG("recv ersockfdror");}printf("%s\n", buff);//如果收到自己的退出消息,就结束子进程} else { //父进程,发送消息if (named == 0) {printf("请输入群聊名称:\n");named = 1;} else if (named == 1) {printf("请再次输入群聊名称:\n");named = 2;}fgets(buff, 128, stdin);if (strlen(buff) != 0) {buff[strlen(buff) - 1] = '\0';}//如果用户输入quit就退出if (!strcmp(buff, "quit")) {//如果退出了,最后也要发送数据"quit"if (-1 == send(sockfd, buff, 128, 0)) {ERRLOG("send error");}char buf[22] = { 0 };sprintf(buf, "kill %d", pid);system(buf);wait(NULL);break;}//发送数据if (-1 == send(sockfd, buff, 128, 0)) {ERRLOG("send error");}}}//关闭套接字close(sockfd);return 0;
}

4.server.c

代码如下:

#include "LinkList.h"int max_fd = 0;
int acceptfd = 0;
int ret = 0;
int i = 0;
int nbytes = 0;
char buff[N] = { 0 };
int loop = 0;
char send_buf[128] = { 0 };
//创建要监视的文件描述集合
fd_set readfds; //母本
fd_set readfds_temp; //给select擦除用的void* sendThread(void* arg)
{char sys_send_buf[256] = { 0 };while (1) {memset(send_buf, 0, sizeof(send_buf));scanf("%s", send_buf);/*for (loop = 4; loop < max_fd + 1 && ret != 0; loop++)这样写是不对的,因为,和主线程共享ret,主线程中的ret每次结束都是会减到0的,所以如果这么写,一次循环也不会进入!!!!*/memset(sys_send_buf, 0, sizeof(sys_send_buf));sprintf(sys_send_buf, "[系统消息]:%s\n", send_buf);printf("%s\n", sys_send_buf);for (loop = 4; loop < max_fd + 1; loop++) {if (FD_ISSET(loop, &readfds)) {if (-1 == send(loop, sys_send_buf, N, 0)) {ERRLOG("send error");}}}}
}int main(int argc, const char* argv[])
{//参数合理性检查if (3 != argc) {printf("Usage : %s <IP> <port>\n", argv[0]);exit(-1);}//创建套接字 流式套接字int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (-1 == sockfd) {ERRLOG("socket error");}//填充网络信息结构体struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_addr.s_addr = inet_addr(argv[1]);serveraddr.sin_port = htons(atoi(argv[2]));socklen_t serveraddr_len = sizeof(serveraddr);//绑定if (-1 == bind(sockfd, (struct sockaddr*)&serveraddr, serveraddr_len)) {ERRLOG("bind error");}//将套接字设置成被动监听状态if (-1 == listen(sockfd, 5)) {ERRLOG("listen error");}//清空集合FD_ZERO(&readfds);FD_ZERO(&readfds_temp);//将sockfd添加到集合中FD_SET(sockfd, &readfds);//更新最大文件描述符max_fd = max_fd > sockfd ? max_fd : sockfd;socklen_t clientaddr_len = sizeof(struct sockaddr_in);usr_info_t* usr_info_head = LinkListNodeCreate();usr_info_t* usr_info_node;usr_info_t* usr = NULL;pthread_t tid;int err_code;if (0 != (err_code = pthread_create(&tid, NULL, sendThread, NULL))) {printf("pthread_create error %s\n", strerror(err_code));exit(-1);}while (1) {readfds_temp = readfds;if (-1 == (ret = select(max_fd + 1, &readfds_temp, NULL, NULL, NULL))) {ERRLOG("select error");}//遍历集合 看哪个文件描述符就绪了for (i = 3; i < max_fd + 1 && ret != 0; i++) {if (FD_ISSET(i, &readfds_temp)) {if (sockfd == i) {//说明有新客户端连接了,创建一个新节点保存客户信息usr_info_node = LinkListNodeCreate();if (-1 == (acceptfd = accept(sockfd, (struct sockaddr*)&usr_info_node->clientaddr, &clientaddr_len))) {ERRLOG("accept error");}usr_info_node->acceptfd = acceptfd;LinkListInsertHead(usr_info_head, usr_info_node); //用户信息入队printf("客户端[%d]号连接到服务器..\n", usr_info_node->acceptfd);printf("客户端[%d]已连接\n", ntohs(usr_info_node->clientaddr.sin_port));memset(send_buf, 0, sizeof(send_buf));//将新客户端的acceptfd加入到集合FD_SET(acceptfd, &readfds);//更新最大文件描述符max_fd = max_fd > acceptfd ? max_fd : acceptfd;} else {//说明有客户端发来数据了usr = LinkListSearchUsrByAcceptfd(usr_info_head, i); //先根据acceptfd找一下用户信息if (-1 == (nbytes = recv(i, buff, N, 0))) {ERRLOG("recv error");} else if (0 == nbytes) {printf("客户端[%d]断开连接..\n", i);memset(send_buf, 0, sizeof(send_buf));sprintf(send_buf, "用户[%s]断开连接..", usr->name);for (loop = 4; loop < max_fd + 1 && ret != 0; loop++) {if (FD_ISSET(loop, &readfds) && loop != i) {if (-1 == send(loop, send_buf, N, 0)) {ERRLOG("send error");}}}//将该客户端的文件描述符在集合中删除FD_CLR(i, &readfds);close(i);continue;}if (!strncmp(buff, "quit", 4)) {printf("客户端[%d]退出了..\n", i);memset(send_buf, 0, sizeof(send_buf));sprintf(send_buf, "用户[%s]退出了..", usr->name);for (loop = 4; loop < max_fd + 1 && ret != 0; loop++) {if (FD_ISSET(loop, &readfds) && loop != i) {if (-1 == send(loop, send_buf, N, 0)) {ERRLOG("send error");}}}//将该客户端的文件描述符在集合中删除FD_CLR(i, &readfds);close(i);continue;}//第一次发过来的是用户的名字,所以,应该保存一下if (usr->named != 9) {strcpy(usr->name, buff);usr->named = 9;continue;}printf("客户端[%d]发来数据[%s]..\n", i, buff);//组装应答if (usr->sayHi == 7) {memset(send_buf, 0, sizeof(send_buf));sprintf(send_buf, "用户[%s]:%s\n", usr->name, buff);} else { //只有第一次才会对所有已经加入群聊的用户显示**加入群聊memset(send_buf, 0, sizeof(send_buf));sprintf(send_buf, "用户[%s]加入群聊..\n", usr->name);usr->sayHi = 7;}//如果用户的acceptfd还在readfds里,就接收本次用户发送的消息.注意不能从3开始,3是sockfd,给sockfd发消息,会出错!!!!for (loop = 4; loop < max_fd + 1 && ret != 0; loop++) {if (FD_ISSET(loop, &readfds) && loop != i) {if (-1 == send(loop, send_buf, N, 0)) {ERRLOG("send error");}}}ret--;}}}}close(sockfd);return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/30990.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

CobaltStrike(钓鱼攻击工具)

一、介绍 1、CobaltStrike是一款渗透测试软件&#xff0c;分为客户端与服务端&#xff0c;可以进行团队分布式操作&#xff0c;服务端:1个&#xff0c;客户端:N个&#xff0c;被业界人称为CS神器。 2、CobaltStrike集成了端口转发、服务扫描&#xff0c;自动化溢出&#xff0c;…

Discord 私信钓鱼手法分析

事件背景 5 月 16 日凌晨&#xff0c;当我在寻找家人的时候&#xff0c;从项目官网的邀请链接加入了官方的 Discord 服务器。在我加入服务器后立刻就有一个"机器人"(Captcha.bot)发来私信要我进行人机验证。这一切看起来相当的合理。我也点击了这个验证链接进行查看。…

154.网络安全渗透测试—[Cobalt Strike系列]—[钓鱼攻击/鱼叉钓鱼]

我认为&#xff0c;无论是学习安全还是从事安全的人多多少少都有些许的情怀和使命感&#xff01;&#xff01;&#xff01; 文章目录 一、钓鱼攻击和鱼叉钓鱼简介1、钓鱼攻击简介2、钓鱼攻击模块&#xff1a;6个3、鱼叉钓鱼简介4、鱼叉钓鱼示例&#xff1a;邮件钓鱼 二、钓鱼攻…

个人年终述职报告PPT怎么做?

适用于职场工作汇报、述职报告、岗位竞聘的PPT模板 这套微粒体风格的述职报告PPT模板采用了立体的几何图形设计&#xff0c;以白色、橙色、深蓝色为主&#xff0c;整体设计简约大气高端。其中还结合了时间轴、流程图、脑图等PPT素材&#xff0c;可以更直观展现工作述职报告的内…

计算机机房防雷接地标准,机房防雷接地规范与防雷接地方式,你知道吗?

雷电的描述 雷电是由天空中云层间的相互高速运动、剧烈磨擦&#xff0c;使高端云层和低端云层带上相反电荷。此时&#xff0c;低端云层在其下面的大地上也感应出大量的异种电荷&#xff0c;形成一个极大的电容&#xff0c;当其场强达到一定强度时&#xff0c;就会产生对地放电&…

地凯模块化机房防雷接地防雷工程设计方案

智能微模块的防雷接地系统由防雷方案和接地方案组成。 防雷方案&#xff1a;智能微模块主要有以下防雷工程方案。 SPD&#xff08;surge protection device&#xff09;浪涌保护器的安装符合以下要求&#xff1a;SPD 安装在被保护设备 的前端&#xff0c;SPD 的连接导线应尽可…

防雷工程中防雷等级的意义

在现代社会中&#xff0c;各种电子设备和通信系统已经成为我们生活中不可或缺的一部分。然而&#xff0c;雷击是这些设备和系统的一个常见问题&#xff0c;不仅会导致设备损坏&#xff0c;还可能对人们的生命财产造成威胁。因此&#xff0c;防雷措施变得尤为重要。 为了保护设…

防雷接地的施工工艺与防雷施工方案

雷电是自然界的一种强大而危险的自然现象&#xff0c;经常造成重大财产损失和人员伤亡。为了保护建筑物和人员免受雷电的危害&#xff0c;防雷接地系统的设计和施工至关重要。本文将介绍防雷接地的施工工艺和防雷施工方案&#xff0c;强调专业和符合国家标准的方法&#xff0c;…

防雷知识:什么是雷电浪涌

浪涌是突然发生并超过典型工作电压的过电压。一般来说&#xff0c;浪涌是电路中短暂的电流、电压或功率波。今天我们就来科普一下什么是雷电浪涌。 什么是浪涌&#xff1f; 浪涌&#xff0c;顾名思义&#xff0c;是一种突然发生并超过典型工作电压的过电压。一般来说&#xf…

单位、家庭建筑物电气、电子设备防雷举措

前 言 在现实的学习、工作、生活中&#xff0c;有时会面对自然灾害、重特大事故、环境公害及人为破坏等突发事件&#xff0c;为了控制事故的发展&#xff0c;就不得不需要事先制定应急预案。那要怎么制定科学的应急预案呢﹖下面是小编为大家整理的单位、住宅建筑物、电子电气防…

科学防雷接地和雷电防护方案

说到防雷&#xff0c;可能不少人首先会想到避雷针&#xff0c;而“避雷针”这一概念&#xff0c;很容易让大家对防雷的概念造成误解。 误解1: 避雷针是用来“避雷”的。 其实&#xff0c;避雷针的学名叫“接闪器”&#xff0c;不是用来“避开雷击”的&#xff0c;而是用来“迎…

通信基站防雷接地方案

由于各基站的环境和建设方式不同&#xff0c;所以对基站防雷接地不能一概而论&#xff0c;应根据具体情况采取防雷与接地措施&#xff0c; 因地制宜实施防雷接地工程&#xff0c;将基站接地系统按照均压等电位的原理进行设计和改造&#xff0c;即通信设备的工作地、保护地、防雷…

智能云防雷,信号浪涌保护器防雷接地方案

1 现代防雷的重点是信息化设备 信息技术设备是集计算机技术和微电子技术于一身的高科技技术产品&#xff0c;由大规模芯片电路组成&#xff0c;信号电压低&#xff0c;抗雷击电磁脉冲(LEMP)的能力很差&#xff0c;在闪电强磁场环境下的易损性较高。雷电已成为信息技术应用中的一…

做好防雷检测的重要意义和作用

防雷检测是一项非常重要的工作&#xff0c;它可以保障人们的生命财产安全&#xff0c;并维护国家的安全稳定。地凯科技将从防雷的重要性、防雷检测的行业应用和防雷行业国标三个方面来阐述防雷检测的重要性。 一、防雷的重要性 随着科技的不断发展&#xff0c;人们的生活和工作…

同为(TOWE)远程智能防雷预警监测——交直流遥信防雷配电柜

当前&#xff0c;社会各领域中各类先进的电子仪器广泛分布于每一个角落&#xff0c;由于高精尖电子设备的高度集成化&#xff0c;其耐压水平普遍较低&#xff0c;导致雷电流、浪涌侵入设备的风险越来越高&#xff0c;故需要在重要设备前端加装浪涌保护器&#xff08;SPD&#x…

教你学会塔罗免费占卜十八招

教你学会塔罗免费占卜十八招 塔罗牌占卜抽到不好的命运怎么办&#xff1f;#塔罗牌#塔罗牌占卜 hello&#xff0c;大家好&#xff0c;这是我的第一期文章。今天主要讲一讲在塔罗牌占卜中&#xff0c;有的时候我们会抽到不好的命运&#xff0c;不好的牌艺&#xff0c;我们该如何…

全新算命付费测算网站源码 星座运势塔罗牌牛年运程宝宝起名姻缘等

介绍&#xff1a; PHP开发的 网盘下载地址&#xff1a; http://kekewangLuo.net/BFhpbGNNy1P 图片&#xff1a;

最新修复PHP塔罗牌风水占卜源码 对接支付

介绍&#xff1a; 测试环境&#xff1a;Nginx 1.18.0-MySQL 5.6.49-PHP-5.6 大家跟着这个环境配置就行 1.创建站点&#xff0c;上传源码至根目录&#xff0c;解压 2.创建数据库&#xff0c;填入数据库配置信息&#xff0c;上传&#xff0c;导入 数据库 3.修改数据库连接信息 …

2023最新测算系统源码/有风水起名/算姻缘/易经周易/占卜开运等等

正文: 支持微信/支付宝H5和电脑端扫码支付、手机端可以调起微信/支付宝应用支付 支持后台设置价格、后台设置支付接口信息 支持各种手机浏览器、微信公众绑定、微信内&#xff0c;等各种设置的使用和调起支付能力 支持分销代理功能&#xff0c;可设下线、用户自主注册、也可…

新版付费测算源码 星座运势 塔罗牌 牛年运程 宝宝起名 月老姻缘 起名算命程序第三版支持易支付

新版付费测算源码 星座运势 塔罗牌 牛年运程 宝宝起名 月老姻缘 起名算命程序第三版支持对接易支付&#xff0c;这是一款很不错的测算源码小白已经测试过了是能正常运营的&#xff0c;首页有三套模板原版的有点BUG我已经修复好了搭建的教程我也写在源码里面的&#xff0c;感兴趣…