网络编程之服务器模型与UDP编程

一、服务器模型

在网络通信中,通常要求一个服务器连接多个客户端

为了处理多个客户端的请求,通常有多种表现形式

1、循环服务器模型

一个服务器可以连接多个客户端,但同一时间只能连接并处理一个客户的请求

socket()

结构体

bind()

listen()

while(1)

{

        accept();

        while(1)

        {

                recv()

        }

}

2、并发服务器模型 

一个服务器可以同时处理多个客户端请求

2.1 多线程

每有一个客户端连接就去创建一个新的线程用于通信

为什么要使用多线程?-->解决服务器不能同时与多个客户通信

什么时间创建新的子线程?-->在accept之后(因为accept可以循环等待与多个客户建立连接,但无法同时与多个客户通信)

主线程  ---->用于循环等待客户的连接

子线程 ----->用于与客户通信

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <pthread.h>
//  $$ 服务器端 $$  void *handler_thread(void *arg)
{char buf[128] = "";int ret;int acceptfd = *(int *)arg;while (1){/*接收消息*/ret = recv(acceptfd, buf, 128, 0); // 0-->相当于read(acceptfd,buf,128)if (ret < 0){perror("recv err");return NULL;}else if (ret == 0){printf("客户退出\n");break;}else{printf("%s 接收成功\n", buf);memset(buf, 0, sizeof(buf));}}close(acceptfd);pthread_exit(NULL);
}int main(int argc, char const *argv[])
{/*创建流式套接字*/int sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd < 0){perror("socket err");return -1;}else{printf("创建套接字成功\n");}/*指定服务器网络信息 使用的协议族(IPv4-->AF_INET)、IP地址、端口号等*/// 服务器的网络信息通过一个系统定义好的结构体来描述struct sockaddr_in saddr;                     // 定义一个结构体变量saddr.sin_family = AF_INET;                   // 确定协议族-->IPv4saddr.sin_port = htons(atoi(argv[1]));        // 确定使用的端口号saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 确定服务器IP地址/*绑定套接字*/int t1 = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (t1 < 0){perror("bind err");return -1;}else{printf("绑定套接字成功\n");}/*监听*/int t2 = listen(sockfd, 6); // 将默认的主动套接字变为被动套接字if (t2 < 0){printf("listen err");return -1;}else{printf("监听中\n");}pthread_t tid;int acceptfd;// 定义一个结构体变量来存接收到的客户信息struct sockaddr_in caddr;// len是记录客户信息的结构体的大小int len = sizeof(caddr);while (1){/*阻塞等待接收客户端的连接请求,并将连接成功的客户端信息写入到结构体变量caddr中*/acceptfd = accept(sockfd, (struct sockaddr *)&caddr, &len);if (acceptfd < 0){printf("accept err");return -1;}else{printf("等待接收客户端请求\n");}printf("客户IP:%s 端口号:%d \n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));pthread_create(&tid, NULL, handler_thread, &acceptfd);pthread_detach(tid);}/* 关闭套接字 */close(sockfd);return 0;
}

2.1 多进程

每有一个客户端连接就去创建一个新的进程用于通信

为什么要使用多进程?-->解决服务器不能同时与多个客户通信

什么时间创建新的子进程?-->在accept之后(因为accept可以循环等待与多个客户建立连接,但无法同时与多个客户通信)

主进程  ---->用于循环等待客户的连接

子进程 ----->用于与客户通信

2.3 IO多路复用

创建两个fd_set rfds, tempfds

清空表 FD_ZREO(&rfds) ; FD_ZREO(&tempfds);

创建流式套接字socket() 得到sockfd

指明网络信息struct sockaddr_in saddr;

绑定套接字bind()

监听listen()

想要监听的文件描述符放入表中 FD_SET(sockfd,&rfds);FD_SET(0,&rfds);

//设置变量max用来保存文件描述符最大值

while(1){

tempfds=rfds

//使用select轮询监听tempfds

select(max+1,&tempfds,......)

//判断是哪个文件描述符发生了变化,并做出相应处理

 if(FD_ISSTE(sockfd,&tempfds);)

{

        acceptfd=accept();

        if(acceptfd>max)

        max=acceptfd;

        FD_SET(acceptfd,&rfds);

}

for(int i=0;i<max;i++)

{

        if(FD_ISSET(acceptfd))

{

        //通信

        if(出错)

        {

return -1

        }

        else if(有客户退出)

        {

                close(i);

                FD_CLR(i,&rfds);

                while(FD_ISSET(max,&rfds)==0) //看是否需要更新max

                max--;

}

else

{

收发消息

}

}

}

}

3、UDP

3.1通信流程

                       

    

3.2 函数接口

(1) 接收信息recvfrom()

recvfrom最后连两个参数accept最后两个参数起到了相同的作用--->获取消息发送方的信息

#include <sys/types.h>
#include <sys/socket.h>ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);
功能:接收数据
参数:sockfd:套接字描述符buf:接收缓存区的首地址len:接收缓存区的大小flags:0src_addr:发送端的网络信息结构体的指针addrlen:发送端的网络信息结构体的大小的指针
返回值:成功接收的字节个数失败:-10:客户端退出

(2) 发送信息 sendto()

sendto的最后连两个参数与connect最后两个参数传参一样---->确定消息要发给谁

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:0src_addr:接收端的网络信息结构体的指针addrlen:接收端的网络信息结构体的大小
返回值: 成功发送的字节个数失败:-1

普通通信:

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>/*消息发送方(无bind)*/int main(int argc, char const *argv[])
{/*创建数据报套接字*/int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}else{printf("创建套接字成功\n");}/*指定服务器网络信息 使用的协议族(IPv4-->AF_INET)、IP地址、端口号等*/// 服务器的网络信息通过一个系统定义好的结构体来描述struct sockaddr_in saddr;                     // 定义一个结构体变量saddr.sin_family = AF_INET;                   // 确定协议族-->IPv4saddr.sin_port = htons(atoi(argv[1]));        // 确定使用的端口号saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 确定服务器IP地址char buf[128]="";while (1){fgets(buf,sizeof(buf),stdin);if(buf[strlen(buf)-1]=='\n')buf[strlen(buf)-1]='\0';if (strcmp(buf,"quit")==0){break;}sendto(sockfd, buf, 128, 0,(struct sockaddr *)&saddr,sizeof(saddr)); // 0-->相当于read(acceptfd,buf,128)}return 0;
}
#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>/*消息接收方(有bind)*/int main(int argc, char const *argv[])
{/*创建数据报套接字*/int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}else{printf("创建套接字成功\n");}/*指定服务器网络信息 使用的协议族(IPv4-->AF_INET)、IP地址、端口号等*/// 服务器的网络信息通过一个系统定义好的结构体来描述struct sockaddr_in saddr;                     // 定义一个结构体变量saddr.sin_family = AF_INET;                   // 确定协议族-->IPv4saddr.sin_port = htons(atoi(argv[1]));        // 确定使用的端口号saddr.sin_addr.s_addr = inet_addr("0.0.0.0"); // 确定服务器IP地址/*绑定套接字*/int t1 = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (t1 < 0){perror("bind err");return -1;}else{printf("绑定套接字成功\n");}struct sockaddr_in caddr;int len=sizeof(caddr);char buf[128]="";int ret;while (1){ret = recvfrom(sockfd, buf, 128, 0,(struct sockaddr *)&caddr,&len); // 0-->相当于read(acceptfd,buf,128)if (ret < 0){perror("recv err");return -1;}else{printf("IP为:%s,端口号为:%d的客户发来了:%s \n", inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),buf);memset(buf, 0, sizeof(buf));}}return 0;
}

4、广播与组播

广播:

前面介绍的数据包发送方式只有一个接受方,称为单播

如果同时发给局域网中的所有主机,称为广播

只有用户数据报(使用UDP协议)套接字才能广播

一般被设计成局域网搜索协议

● 广播地址:局域网中主机号最大的一个

注意要在同一网段下!!!

缺点:

广播方式发给所有的主机,过多的广播会大量的占用网络带宽,造成广播风暴,影响正常的通信

广播风暴: 网络长时间被大量的广播数据包所占用,使正常的点对点通信无法正常进行,其外在表现为网络速度奇慢无比,甚至导致网络瘫痪

补充:

int setsockopt(int sockfd,int level,int optname,void *optval,socklen_t optlen)
功能:获得/设置套接字属性
参数:sockfd:套接字描述符level:协议层optname:选项名optval:选项值optlen:选项值大小
返回值:     成功 0                  失败-1

set:设置 sock:套接字 option:属性 选项

socket属性(int类型,允许则为非0不允许0

流程:

接收者

1.  创建套接字(socket)

2.  指定网络信息

3.  绑定套接字(bind)

4.  接收消息(recvfrom)

5.  关闭套接字(close)

发送者

1.  创建套接字(socket)

2.  由于原本的套接字不支持广播,所以要给套接字设置广播属性

3.  指定网络(服务器)信息

4.  发送消息(sendto)

5.  关闭套接字(close)

#include <stdio.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>/*UDP广播*/int main(int argc, char const *argv[])
{/*创建数据报套接字*/int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}else{printf("创建套接字成功\n");}//为套接字设置广播属性int optval = 1;setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST, &optval, sizeof(optval));//指定网络信息struct sockaddr_in saddr;                            // 定义一个结构体变量saddr.sin_family = AF_INET;                          // 确定协议族-->IPv4saddr.sin_port = htons(atoi(argv[1]));               // 确定使用的端口号saddr.sin_addr.s_addr = inet_addr("192.168.50.255"); // IP使用该网段广播地址//通信char buf[128] = "";while (1){fgets(buf, sizeof(buf), stdin);if (buf[strlen(buf) - 1] == '\n')buf[strlen(buf) - 1] = '\0';if (strcmp(buf, "quit") == 0){break;}sendto(sockfd, buf, 128, 0, (struct sockaddr *)&saddr, sizeof(saddr)); }return 0;
}

组播:

又名多播   

播是一个人发送后,只有加入到多播组的人接收数据。

多播方式既可以发给多个主机,又能避免像广播那样带来过多的负载(每台主机要到传输层才能判断广播包是否要处理)

多播地址:D类IP:224.0.0.1~239.255.255.254

流程

接收者

1.  创建套接字(socket)

2.  置多播属性,将自己的IP加入到多播组中

3.  指定网络信息

4.  绑定套接字(bind)

5.  接收消息(recvfrom)

6.  关闭套接字(close)

发送者

1.  创建套接字(socket)

2.  指定网络(服务器)信息

3.  发送消息(sendto)

4.  关闭套接字(close)

#include <stdio.h>
#include <sys/types.h> 
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>/*消息接收方(有bind)*/int main(int argc, char const *argv[])
{/*创建数据报套接字*/int sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (sockfd < 0){perror("socket err");return -1;}else{printf("创建套接字成功\n");}//设置多播属性,将自己加入多播组(绑定自己的ip和组播ip)struct ip_mreq mreq;mreq.imr_multiaddr.s_addr=inet_addr(argv[1]);//组播ipmreq.imr_interface.s_addr=INADDR_ANY;//自己的ipsetsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));/*指定服务器网络信息 使用的协议族(IPv4-->AF_INET)、IP地址、端口号等*/// 服务器的网络信息通过一个系统定义好的结构体来描述struct sockaddr_in saddr;                     // 定义一个结构体变量saddr.sin_family = AF_INET;                   // 确定协议族-->IPv4saddr.sin_port = htons(atoi(argv[2]));        // 确定使用的端口号saddr.sin_addr.s_addr = INADDR_ANY;     // 服务器地址/*绑定套接字*/int t1 = bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr));if (t1 < 0){perror("bind err");return -1;}else{printf("绑定套接字成功\n");}struct sockaddr_in caddr;int len=sizeof(caddr);char buf[128]="";int ret;while (1){ret = recvfrom(sockfd, buf, 128, 0,(struct sockaddr *)&caddr,&len); if (ret < 0){perror("recv err");return -1;}else{printf("IP为:%s,端口号为:%d的客户发来了:%s \n", inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),buf);memset(buf, 0, sizeof(buf));}}return 0;
}

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

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

相关文章

open3D:三维点云处理

open3d 点云数据处理 爆肝5万字❤️Open3D 点云数据处理基础&#xff08;Python版&#xff09;_python 点云 焊缝-CSDN博客 如何用NumPy读取和保存点云数据 - 知乎 读取并可视化点云 np.loadtxt 从txt中读取点集&#xff0c;并open3d显示单个点云 txt内容&#xff1a;每行皆…

使用联邦多轨迹图神经网络(GNNs)结合稀缺数据预测婴儿脑连接|文献速递-深度学习医疗AI最新文献

Title 题目 Predicting infant brain connectivity with federated multi-trajectory GNNs using scarce data 使用联邦多轨迹图神经网络&#xff08;GNNs&#xff09;结合稀缺数据预测婴儿脑连接 01 文献速递介绍 多模态影像下的婴儿脑连接演化预测&#xff1a;联邦学习与…

[特殊字符] 深入理解 Linux 内核进程管理:架构、核心函数与调度机制

Linux 内核作为一个多任务操作系统&#xff0c;其进程管理子系统是核心组成部分之一。无论是用户应用的运行、驱动行为的触发&#xff0c;还是系统调度决策&#xff0c;几乎所有操作都离不开进程的创建、调度与销毁。本文将从进程的概念出发&#xff0c;深入探讨 Linux 内核中进…

第16节 Node.js 文件系统

Node.js 提供一组类似 UNIX&#xff08;POSIX&#xff09;标准的文件操作API。 Node 导入文件系统模块(fs)语法如下所示&#xff1a; var fs require("fs") 异步和同步 Node.js 文件系统&#xff08;fs 模块&#xff09;模块中的方法均有异步和同步版本&#xff…

《探秘局域网广播:网络世界的 “大喇叭”》

揭开局域网广播的神秘面纱 在当今数字化时代,网络已成为人们生活和工作中不可或缺的一部分。从日常的网页浏览、社交媒体互动,到企业级的数据传输、云计算应用,网络通信无处不在。在这个庞大而复杂的网络世界里,数据如同信息流在各个节点之间穿梭,而局域网广播则是其中一种…

基于Ubuntu22.04安装SVN服务器之仓库迁移

基于Ubuntu22.04安装SVN服务器之仓库迁移 第一步: 停止svn服务器 第一步: 停止svn服务器 1&#xff09;建议迁移的时候先把SN服务器停掉&#xff0c;以免操作失败。 svnserve -d -r /usr/svn第二步&#xff1a;dump出svn代码库 1&#xff09;通过dump出旧的svn服务器上的代码…

Unity UI 性能优化终极指南 — Image篇

&#x1f3af; Unity UI 性能优化终极指南 — Image篇 &#x1f9e9; Image 是什么&#xff1f; Image 是UGUI中最常用的基本绘制组件支持显示 Sprite&#xff0c;可以用于背景、按钮图标、装饰等是UI性能瓶颈的头号来源之一&#xff0c;直接影响Draw Call和Overdraw &#x1…

「Java基本语法」代码格式与注释规范

Java代码的基本格式 Java代码的规范格式是编写和维护Java程序的基础&#xff0c;其中包括类定义、方法定义、代码缩进、大括号位置等。 1&#xff0e;核心规则 每个Java文件必须包含一个公共类&#xff08;public class&#xff09;&#xff0c;且Java源文件的文件名必须和这…

2025年AI编程工具推荐

目录 &#x1f451; **一、全能型AI开发环境&#xff08;IDE&#xff09;**&#x1f6e0;️ **二、AI代码助手与插件**&#x1f3af; **三、垂直领域工具**&#x1f1e8;&#x1f1f3; **四、国产工具精选**&#x1f52e; **五、创新前沿工具**⚖️ **选型建议** 2025年&#x…

【工具使用】STM32CubeMX-FreeRTOS操作系统-信号标志、互斥锁、信号量篇

一、概述 无论是新手还是大佬&#xff0c;基于STM32单片机的开发&#xff0c;使用STM32CubeMX都是可以极大提升开发效率的&#xff0c;并且其界面化的开发&#xff0c;也大大降低了新手对STM32单片机的开发门槛。     本文主要讲述STM32芯片FreeRTOS信号标志、互斥锁和信号…

ArrayList和LinkedList(深入源码加扩展)

ArrayList 和 LinkedList 是 Java 集合框架中两种常用的列表实现,它们在底层数据结构、性能特点和适用场景上有显著的区别。以下是它们的详细对比以及 ArrayList 的扩容机制。 1. ArrayList 和 LinkedList 的底层区别 (1) 底层数据结构 ArrayList: 基于动态数组(Dynamic Ar…

浅谈 React Suspense

React Suspense 是 React 中用于处理异步操作的功能。它可以让你"等待"某些操作&#xff0c;如数据获取或组件加载完成&#xff0c;然后再渲染组件。Suspense 的核心理念是让组件在准备好之前显示一个备用的 UI&#xff0c;例如加载指示器&#xff0c;从而提高用户体…

机器学习的数学基础:线性模型

线性模型 线性模型的基本形式为&#xff1a; f ( x ) ω T x b f\left(\boldsymbol{x}\right)\boldsymbol{\omega}^\text{T}\boldsymbol{x}b f(x)ωTxb 回归问题 利用最小二乘法&#xff0c;得到 ω \boldsymbol{\omega} ω和 b b b的参数估计$ \boldsymbol{\hat{\omega}}…

Linux【4】------RK3568启动和引导顺序

引导顺序 RK3568 的启动流程如下&#xff1a; 加电后&#xff0c;芯片首先执行 BootROM 中的代码&#xff1b; BootROM 会尝试从配置好的外部设备&#xff08;如 NOR/NAND/eMMC/SD 卡&#xff09;加载启动程序&#xff1b; 如果这些设备都没有有效的启动代码&#xff0c;Bo…

Deepseek/cherry studio中的Latex公式复制到word中

需要将Deepseek/cherry studio中公式复制到word中&#xff0c;但是deepseek输出Latex公式&#xff0c;比如以下Latex代码段&#xff0c;需要通过Mathtype翻译才能在word中编辑。 $$\begin{aligned}H_1(k1) & H_1(k) \frac{1}{A_1} \left( Q_1 u_1(k) Q_{i1} - Q_2 u_2(k…

关于iview组件中使用 table , 绑定序号分页后序号从1开始的解决方案

问题描述&#xff1a;iview使用table 中type: "index",分页之后 &#xff0c;索引还是从1开始&#xff0c;试过绑定后台返回数据的id, 这种方法可行&#xff0c;就是后台返回数据的每个页面id都不完全是按照从1开始的升序&#xff0c;因此百度了下&#xff0c;找到了…

【机器学习】支持向量机实验报告——基于SVM进行分类预测

目录 一、实验题目描述 二、实验步骤 三、Python代码实现基于SVM进行分类预测 四、我的收获 五、我的感受 一、实验题目描述 实验题目&#xff1a;基于SVM进行分类预测 实验要求&#xff1a;通过给定数据&#xff0c;使用支持向量机算法&#xff08;SVM&#xff09;实现分…

前端开发面试题总结-JavaScript篇(二)

文章目录 其他高频问题15、JS的数据类型有哪些16、如何判断数组类型&#xff1f;17、解释 this 的指向规则18、跨域问题及解决方案19、宏任务与微任务的区别是什么&#xff1f;列举常见的宏任务和微任务。20、为什么微任务的优先级高于宏任务&#xff1f;设计目的是什么&#x…

硬件电路设计-开关电源设计

硬件电路设计-开关电源 电容选取设置输出电压电感的选取PCB布局典型电路 这里以杰华特的JW5359M 开关电源为例&#xff0c;介绍各个部分的功能电路。 当EN引脚电压低于0.4V时&#xff0c;整个稳压器关闭&#xff0c;稳压器消耗的电源电流降至1μΑ以下 电容选取 1.C1和C25构成…

phosphobot开源程序是控制您的 SO-100 和 SO-101 机器人并训练 VLA AI 机器人开源模型

​一、软件介绍 文末提供程序和源码下载 phosphobot开源程序是控制您的 SO-100 和 SO-101 机器人并训练 VLA AI 机器人开源模型。 二、Overview 概述 &#x1f579;️ Control your robot with the keyboard, a leader arm, a Meta Quest headset or via API &#x1f579;️…