Linux 基本语句_16_Udp网络聊天室

代码:

服务端代码:

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>#define N 128
#define L 1
#define C 2
#define Q 3typedef struct{int type;char name[N];char text[N];
}MSG; // 存信息 typedef struct node{struct sockaddr_in addr; // 存ip 和 端口号 struct node *next; // 链表 
}linklist_t;linklist_t *linklist_create(); // 创建链表函数 void do_login(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr); // 某客端上线,将数据发送给其他在线客户端 
void do_chat(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr); // 将用户想要发送的数据广播给其他用户 
void do_quit(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr); // 在链表中删除自己的记录,并将自己退出的信息发送給其他客户端 int main(int argc, const char *argv[]){int sockfd;struct sockaddr_in serveraddr, clientaddr; // 储存信息 socklen_t addrlen = sizeof(serveraddr);if(argc < 3){printf("argc number error\n");return -1;}/* 创建套接字 */ if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ // IPV4、UDP协议、协议标志 printf("socket error\n");return -1;}/* 填充服务器网络信息 */ serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]);serveraddr.sin_port = htons(atoi(argv[2]));if(bind(sockfd, (struct sockaddr *)&serveraddr, addrlen) < 0){ // 套接字与服务器网络信息绑定、(套接字是中转站,bind将ip和端口信息存入中转站)printf("bind error\n");return -1;}MSG msg;pid_t pid;if((pid = fork()) < 0){printf("fork error\n");return -1;}else if(pid == 0){ // 子进程 msg.type = C;strcpy(msg.name, "server");while(1){fgets(msg.text, N, stdin); // 等待控制台输入msg.text[strlen(msg.text) - 1] = '\0';sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, addrlen); // 子进程和父进程绑定在同一个ip地址 和 端口号 子进程能向父进程发送给对方 } }else{ // 父进程负责接收数据并处理 linklist_t *h = linklist_create();while(1){recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&clientaddr, &addrlen); // 从中转站接收数据直到有数据为止 printf("%d -- %s -- %s\n", msg.type, msg.name, msg.text); // 打印接收的数据 switch(msg.type){ // 根据数据的类型做不同操作 case L:do_login(msg, h, sockfd, clientaddr); // 登录广播提醒 break;case C:do_chat(msg, h, sockfd, clientaddr); // 广播聊天 break;case Q:do_quit(msg, h, sockfd, clientaddr); // 广播退出 break;}}   }return 0; 
}linklist_t *linklist_create(){ // 创建链表 linklist_t *h = (linklist_t *)malloc(sizeof(linklist_t)); // 创建一个链表节点,h为链表头部 h->next = NULL; // 整个链表只有一个节点 return h; 
}void do_login(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr){linklist_t *p = h;/* 用户登录信息发送给其他客户 */ sprintf(msg.text, "-------- %s login -------------", msg.name);while(p->next != NULL){sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->next->addr, sizeof(struct sockaddr_in)); // 发送那个给链表中所有对象 p = p->next;}linklist_t *temp = (linklist_t *) malloc(sizeof(linklist_t)); // 创建一个新结点 temp->addr = clientaddr; // 客户端信息存入结点 temp->next = h->next;h->next = temp; // 将temp存入链表末尾 return;
}void do_chat(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr){char buf[N] = {};linklist_t *p = h;/* 将用户信息发送给其他在线的用户 */sprintf(buf, "%s : %s", msg.name, msg.text);strcpy(msg.text, buf); // 将数据存入msg while(p->next != NULL){ // 发送数据 if(memcmp(&clientaddr, &p->next->addr, sizeof(clientaddr)) == 0){ // 数据是自己的就不传输了 p = p->next;} else{ // 其他人就发送 sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->next->addr, sizeof(struct sockaddr_in)); // 发送的数据、长度、发送的位置 p = p->next;}}return; 
}void do_quit(MSG msg, linklist_t *h, int sockfd, struct sockaddr_in clientaddr){linklist_t *p = h;linklist_t *temp;/* 将用户退出的信息发送给其他用户,并将其信息从链表中删除 */sprintf(msg.text, "-------- %s offline --------", msg.name);while(p->next != NULL){if(memcmp(&clientaddr, &p->next->addr, sizeof(clientaddr)) == 0){ // 自己的就不发送 temp = p->next;p->next = temp->next;free(temp); // 释放本结点 temp = NULL; // 指针至为空指针 }else{sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&p->next->addr, sizeof(struct sockaddr_in)); // 不是自己就发送 p = p->next;}}return; 
}

客户端代码:

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <signal.h>
#include <strings.h>#define N 128
#define L 1
#define C 2
#define Q 3typedef struct{int type;char name[N];char text[N];
}MSG; // 存信息 int main(int argc, const char *argv[]){int sockfd;struct sockaddr_in serveraddr, clientaddr; // 储存信息 socklen_t addrlen = sizeof(serveraddr);if(argc < 3){printf("argc number error\n");return -1;}/* 创建套接字 */ if((sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0){ // IPV4、UDP协议、协议标志 printf("socket error\n");return -1;}/* 填充服务器网络信息 */ serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(argv[1]);serveraddr.sin_port = htons(atoi(argv[2]));MSG msg;msg.type = L;printf("please enter your name: ");fgets(msg.name, N, stdin); // 将控制台输入的信息传入name中msg.name[strlen(msg.name) - 1] = '\0'; sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, addrlen); // 将数据发送给服务端 pid_t pid;if((pid = fork()) < 0){printf("fork error\n");return -1;}else if(pid == 0){ // 子进程,发送数据 while(1){fgets(msg.text, N, stdin); // 等待控制台输入msg.text[strlen(msg.text) - 1] = '\0';if(strncmp(msg.text, "quit", 4) == 0){ // 若要退出 msg.type = Q; // 退出广播 sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, addrlen); // 子进程和父进程绑定在同一个ip地址 和 端口号 子进程能向父进程发送给对方close(sockfd);kill(getppid(), SIGKILL); // 退出父进程 return 0; 	} msg.type = C; // 聊天 sendto(sockfd, &msg, sizeof(msg), 0, (struct sockaddr*)&serveraddr, addrlen); // 子进程和父进程绑定在同一个ip地址 和 端口号 子进程能向父进程发送给对方} }else{ // 父进程负责接收数据while(1){recvfrom(sockfd, &msg, sizeof(msg), 0, (struct sockaddr *)&serveraddr, &addrlen); // 从中转站接收数据直到有数据为止 printf("%s\n", msg.text); // 打印接收的数据 }   }return 0; 
}

效果:

在这里插入图片描述
总体效果是客户上线状态、退出状态、发送的消息都能通过广播,将信息发送给所有在线客户端,服务端能接收并显示所有客户端发送的消息,且也具备广播能力

原理:

服务端:

服务端创建了一个链表,这个链表中的每个节点是专用于储存客户端的ip地址和端口号等一系列信息,目的是方便遍历实现广播功能

服务端的创建了父子进程,子进程专门接收控制台发送的数据,并转发给父进程,父进程将数据广播给所有客户端,朴素的讲子进程接收控制台数据,父进程接收客户端信息并广播

客户端:

客户端也是创建父子进程,父进程负责接收服务器转发的数据,并打印。子进程负责发送,从本控制台获取的信息并发送给服务端通过服务器广播给其他客户端

服务器本质就是中转站,负责接收客户端信息状态并广播

拓展:

套接字:

套接字可以理解为网络通信的中转站,将通信双方的ip地址和端口号等相关信息存入套接字,以便通信双方能通过ip地址和端口号找到对应接收端。

服务端和客户端都创建套接字的原因:

在一个典型的客户端-服务器模型中,服务器和客户端通过套接字建立通信。一般情况下,服务器会先创建一个套接字并绑定到一个特定的 IP 地址和端口上,然后等待客户端连接。
在客户端与服务器建立连接时,客户端会创建一个新的套接字,并尝试连接到服务器的套接字地址。如果连接成功,服务器会接受这个连接并为客户端创建一个新的套接字,该套接字将用于与这个特定客户端之间的通信。
这样,服务器会保持一个主套接字用于监听客户端的连接请求,并为每个连接创建一个新的套接字来处理与特定客户端之间的通信。这些连接的套接字通常是独立的,即服务器和每个客户端之间都有一个独立的套接字,用于他们之间的通信。

UDP连接方式:

在 UDP 协议中,客户端并不需要显式地调用 bind() 来绑定一个端口。通常情况下,在客户端发送数据时,系统会自动分配一个临时的端口号,并在发送数据时使用这个端口号。这个临时端口号通常在发送后被释放,因此客户端不需要显式地绑定一个端口。
客户端在发送数据时,使用 sendto() 或者 sendmsg() 等函数向目标服务器发送数据报。在发送时,指定目标服务器的 IP 地址和端口号即可,而不需要调用 bind() 来指定客户端的本地端口。UDP是无连接的,因此客户端不需要事先建立连接,只需要在发送数据时指定目标地址和端口即可。
相反,服务器通常会先调用 bind() 来绑定一个固定的端口号,以便监听客户端发送来的数据。服务器需要绑定一个固定端口号来等待客户端的连接请求或者接收数据报。
总之,在UDP中,客户端通常不需要显式地调用 bind() 来绑定端口,它可以自动分配一个临时的端口来发送数据。服务器端则需要绑定一个固定的端口号来等待客户端的连接或接收数据。

区别:

TCP是面向连接的协议,它在通信之前需要建立连接,并确保数据传输的可靠性。它提供数据的可靠性保证、流量控制和拥塞控制。
UDP是无连接的协议,不需要在发送数据之前建立连接。它不保证数据的可靠性,也不提供类似TCP的可靠性保证机制。

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

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

相关文章

探索 Coinbase 二层链 Base 的潜力与风险

作者&#xff1a;lesleyfootprint.network 在不断变化的加密货币领域&#xff0c;Coinbase 已经确立了自己领先中心化交易所&#xff08;CEX&#xff09;的地位。然而&#xff0c;Coinbase 坚信去中心化是创造一个开放、全球范围内对每个人都可访问的加密经济的关键&#xff0…

从0到1:志愿者小程序开发心得

背景调研 志愿者服务小程序可以使志愿服务更加有序和高效&#xff0c;方便志愿者参与&#xff0c;也方便组织方管理和组织志愿服务活动;志愿者可以方便的在手机上报名&#xff0c;查看记录&#xff0c;获取积分&#xff0c;了解积分排行&#xff1b; 同时提供积分商城模块&…

用23种设计模式打造一个cocos creator的游戏框架----(十九)备忘录模式

1、模式标准 模式名称&#xff1a;备忘录模式 模式分类&#xff1a;行为型 模式意图&#xff1a;在不破坏封装性的前提下捕获一个对象的内部状态&#xff0c;并在对象之外保存这个状态。这样以后就可以将对象恢复到原先保存的状态 结构图&#xff1a; 适用于&#xff1a; …

Freemarker基本语法与案例讲解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《SpringBoot》。&#x1f3af;&#x1f3af; &…

Azure Machine Learning - 提示工程简介

OpenAI的GPT-3、GPT-3.5和GPT-4模型基于用户输入的文本提示工作。有效的提示构造是使用这些模型的关键技能&#xff0c;涉及到配置模型权重以执行特定任务。这不仅是技术操作&#xff0c;更像是一种艺术&#xff0c;需要经验和直觉。本文旨在介绍适用于所有GPT模型的提示概念和…

网络(十)ACL和NAT

前言 网络管理在生产环境和生活中&#xff0c;如何实现拒绝不希望的访问连接&#xff0c;同时又要允许正常的访问连接&#xff1f;当下公网地址消耗殆尽&#xff0c;且公网IP地址费用昂贵&#xff0c;企业访问Internet全部使用公网IP地址不够现实&#xff0c;如何让私网地址也…

前端面试(5)

1、移动端适配 1.1、设置meta缩放比例&#xff0c;将设备窗口调整为设计图大小。 <!DOCTYPE html> <html lang"en"> <head><meta charset"UTF-8"><meta name"viewport" content"widthdevice-width,initial-sc…

基于ssm的简单学校课程管理系统的设计与实现(源码+调试)

项目描述 临近学期结束&#xff0c;还是毕业设计&#xff0c;你还在做java程序网络编程&#xff0c;期末作业&#xff0c;老师的作业要求觉得大了吗?不知道毕业设计该怎么办?网页功能的数量是否太多?没有合适的类型或系统?等等。今天给大家介绍一篇基于ssm的简单学校课程管…

C++之程序生成

一、C的发展史 截止到2023年12月&#xff0c;C已经更新了很多版本&#xff0c;并在每个版本中修复了bug和添加了新的特性&#xff0c;ISO C委员会每三年会对C进行一次更新&#xff1a; C98&#xff1a;于1998年发布&#xff0c;是最早的国际标准化版本。它包含了面向对象编程…

可替代LM5145,5.5V-100V Vin同步降压控制器_SCT82A30

SCT82A30是一款100V电压模式控制同步降压控制器&#xff0c;具有线路前馈。40ns受控高压侧MOSFET的最小导通时间支持高转换比&#xff0c;实现从48V输入到低压轨的直接降压转换&#xff0c;降低了系统复杂性和解决方案成本。如果需要&#xff0c;在低至6V的输入电压下降期间&am…

GMP原理与调度

GMP是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统,区别于操作系统调度OS线程 Golang"调度器"的由来 单进程时代不需要调度器 我们知道,一切的软件都是跑在操作系统上,真正用来干活(计算)的是CPU,早期的操作系统每个程序就是一个进程,直到一个…

Yapi详细安装过程(亲测可用)

1. 前置条件 1、Git 2、NodeJs&#xff08;7.6&#xff09; 3、Mongodb&#xff08;2.6&#xff09; 2. NodeJs的安装 1、获取资源 curl -sL https://rpm.nodesource.com/setup_8.x | bash - 2、安装NodeJS yum install -y nodejs 3、查看NodeJs和Npm node -v npm -v…