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

news2025/7/26 8:15:22

一、服务器模型

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

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

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;                   // 确定协议族-->IPv4
    saddr.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:0
	src_addr:发送端的网络信息结构体的指针
	addrlen:发送端的网络信息结构体的大小的指针
返回值:
	成功接收的字节个数
	失败:-1
	0:客户端退出

(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:0
	src_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;                   // 确定协议族-->IPv4
    saddr.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;                   // 确定协议族-->IPv4
    saddr.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;                          // 确定协议族-->IPv4
    saddr.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]);//组播ip
    mreq.imr_interface.s_addr=INADDR_ANY;//自己的ip
    setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

    
     /*指定服务器网络信息 使用的协议族(IPv4-->AF_INET)、IP地址、端口号等*/

    // 服务器的网络信息通过一个系统定义好的结构体来描述
    struct sockaddr_in saddr;                     // 定义一个结构体变量
    saddr.sin_family = AF_INET;                   // 确定协议族-->IPv4
    saddr.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.coloradmin.cn/o/2404448.html

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

相关文章

Transformer-BiLSTM、Transformer、CNN-BiLSTM、BiLSTM、CNN五模型时序预测

Transformer-BiLSTM、Transformer、CNN-BiLSTM、BiLSTM、CNN五模型时序预测 目录 Transformer-BiLSTM、Transformer、CNN-BiLSTM、BiLSTM、CNN五模型时序预测预测效果基本介绍程序设计参考资料 预测效果 基本介绍 Transformer-BiLSTM、Transformer、CNN-BiLSTM、BiLSTM、CNN五…

阿里云服务器安装nginx并配置前端资源路径(前后端部署到一台服务器并成功访问)

​​​运行以下命令&#xff0c;安装Nginx相关依赖。 yum install -y gcc-c yum install -y pcre pcre-devel yum install -y zlib zlib-devel yum install -y openssl openssl-devel 运行wget命令下载Nginx 1.21.6。 您可以通过Nginx开源社区直接获取对应版本的安装包URL&…

C++11新增重要标准(下)

前言 一&#xff0c;forward&#xff08;完美转发&#xff09; 二&#xff0c;可变参数模板 三&#xff0c;emplace系列接口 四&#xff0c;新增类功能 五&#xff0c;default与delete 六&#xff0c;lambda表达式 七&#xff0c;包装器 八&#xff0c;bind 在C11中新增…

【第六篇】 SpringBoot的日志基础操作

简介 日志系统在软件开发中至关重要&#xff0c;用于调试代码、记录运行信息及错误堆栈。本篇文章不仅详细介绍了日志对象的创建及快速使用&#xff0c;还说明了日志持久化的两种配置方式和滚动日志的设置。实际开发需根据场景选择合适的日志级别和存储策略。文章内容若存在错误…

Pluto论文阅读笔记

主要还是参考了这一篇论文笔记&#xff1a;https://zhuanlan.zhihu.com/p/18319150220 Pluto主要有三个创新点&#xff1a; 横向纵向用lane的query来做将轨迹投回栅格化地图&#xff0c;计算碰撞loss对数据进行正增强和负增强&#xff0c;让正增强的结果也无增强的结果相近&a…

matlab 2024a ​工具箱Aerospsce Toolbox报错​

Matlab R2024a中Aerospsce Toolbox报错 警告&#xff1a;Aerospace Toolbox and Aerospace Blockset licenses are required in ‘built-in/Spacecraft Dynamics’ 找到安装路径\MATLAB\R2024a\licenses文件夹license_****_R2024a.lic 里面工具箱名称出错&#xff0c;手动修改…

使用有限计算实现视频生成模型的高效训练

大家读完觉得有帮助记得关注和点赞&#xff01;&#xff01;&#xff01; 抽象 视频生成的最新进展需要越来越高效的训练配方&#xff0c;以减轻不断上升的计算成本。在本报告中&#xff0c;我们介绍了 ContentV&#xff0c;这是一种 8B 参数文本到视频模型&#xff0c;在 256 …

Server2003 B-1 Windows操作系统渗透

任务环境说明&#xff1a; 服务器场景&#xff1a;Server2003&#xff08;开放链接&#xff09; 服务器场景操作系统&#xff1a;Windows7 1.通过本地PC中渗透测试平台Kali对服务器场景Windows进行系统服务及版本扫描渗透测试&#xff0c;并将该操作显示结果中Telnet服务对应的…

一次Oracle的非正常关闭

数据库自己会关闭吗&#xff1f; 从现象来说Oracle MySQL Redis等都会出现进程意外停止的情况。而这些停止都是非人为正常关闭或者暴力关闭&#xff08;abort或者kill 进程&#xff09; 一次测试环境的非关闭 一般遇到这种情况先看一下错误日志吧。 2025-06-01T06:26:06.35…

YOLO11解决方案之分析

概述 Ultralytics提供了一系列的解决方案&#xff0c;利用YOLO11解决现实世界的问题&#xff0c;包括物体计数、模糊处理、热力图、安防系统、速度估计、物体追踪等多个方面的应用。 Ultralytics提供了三种基本的数据可视化类型&#xff1a;折线图&#xff08;面积图&#xf…

yolov11与双目测距结合,实现目标的识别和定位测距(onnx版本)

一、yolov11双目测距基本流程 yolov11 双目测距的大致流程就是&#xff1a; 双目标定 --> 立体校正&#xff08;含消除畸变&#xff09; --> 立体匹配 --> 视差计算 --> 深度计算(3D坐标)计算 --> 目标检测 --> 目标距离计算及可视化 下面将分别阐述每…

基于51单片机和8X8点阵屏、独立按键的填充消除类小游戏

目录 系列文章目录前言一、效果展示二、原理分析三、各模块代码1、8X8点阵屏2、独立按键3、定时器04、定时器1 四、主函数总结 系列文章目录 前言 使用的是普中A2开发板。 【单片机】STC89C52RC 【频率】12T11.0592MHz 【外设】8X8点阵屏、独立按键 效果查看/操作演示&#x…

物联网技术发展与应用研究分析

文章目录 引言一、物联网的基本架构&#xff08;一&#xff09;感知层&#xff08;二&#xff09;网络层&#xff08;三&#xff09;平台层&#xff08;四&#xff09;应用层 二、物联网的关键技术&#xff08;一&#xff09;传感器技术&#xff08;二&#xff09;通信技术&…

金融系统渗透测试

金融系统渗透测试是保障金融机构网络安全的核心环节&#xff0c;它的核心目标是通过模拟攻击手段主动发现系统漏洞&#xff0c;防范数据泄露、资金盗取等重大风险。 一、金融系统渗透测试的核心框架 合规性驱动 需严格遵循《网络安全法》《数据安全法》及金融行业监管要求&am…

9.进程间通信

1.简介 为啥要有进程间通信&#xff1f; 如果未来进程之间要协同呢&#xff1f;一个进程要把自己的数据交给另一个进程&#xff01;进程是具有独立性的&#xff0c;所以把一个进程的数据交给另一个进程----基本不可能&#xff01;必须通信起来&#xff0c;就必须要有另一个人…

React 基础入门笔记

一、JSX语法规则 1. 定义虚拟DOM时&#xff0c;不要写引号 2.标签中混入JS表达式时要用 {} &#xff08;1&#xff09;.JS表达式与JS语句&#xff08;代码&#xff09;的区别 &#xff08;2&#xff09;.使用案例 3.样式的类名指定不要用class&#xff0c;要用className 4.内…

压测软件-Jmeter

1 下载和安装 1.1 检查运行环境 Jmeter需要运行在java环境&#xff08;JRE 或 JDK&#xff09;中 在window的"命令提示窗"查看安装的java版本: java -version 1.2 下载Jmeter 从Apache官网下载Jmeter安装包 1.3 解压和运行 解压后,进入bin文件夹,双击jmeter.bat即可…

NLP学习路线图(三十):微调策略

在自然语言处理领域,预训练语言模型(如BERT、GPT、T5)已成为基础设施。但如何让这些“通才”模型蜕变为特定任务的“专家”?微调策略正是关键所在。本文将深入剖析七种核心微调技术及其演进逻辑。 一、基础概念:为什么需要微调? 预训练模型在海量语料上学习了通用语言表…

leetcode刷题日记——1.组合总和

解答&#xff1a; class Solution { public:void dfs(vector<int>& candidates, int target, vector<vector<int>>& ans, vector<int>& combine, int idx) {if(idxcandidates.size()){//遍历完的边界return;}if(target0){//找完了能组成和…

关于单片机的基础知识(一)

成长路上不孤单&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a;&#x1f60a; 【14后&#x1f60a;///计算机爱好者&#x1f60a;///持续分享所学&#x1f60a;///如有需要欢迎收藏转发///&#x1f60a;】 今日分享关于单片机基础知识的相关内容&#xf…