网络编程_day3

news2024/10/28 10:00:21

 #1024程序员节

#三次握手四次挥手#四次挥手#udp#recvfrom#sendto#服务器模型#客户端模型#Linux IO模型#阻塞式IO#非阻塞IO#设置非阻塞的方式

目录

【0】复习

【1】三次握手四次挥手

四次挥手

 四次挥手既可以由客户端发起,也可以由服务器发起

【2】udp

    1. 通信流程

2. 函数接口

1.recvfrom

2.sendto

服务器模型

客户端模型

【3】Linux IO模型

1. 阻塞式IO:最常见、效率低、不耗费cpu

2. 非阻塞IO:轮询、耗费CPU,可以处理多路IO

设置非阻塞的方式

1. 通过函数自带参数设置

 2. 通过设置文件描述符的属性。把文件描述符的属性设置为非阻塞

3. 信号驱动IO/异步IO:异步通知方式,需要底层驱动的支持(了解)

问题思考


【0】复习

tcp:服务器、客户端

网络模型:OSI:应、表、会、传、网、数、物 TCP/IP:应、传、网、物

TCP:全双工、高可靠,连接

UDP:全双工、无连接、不可靠

wireshark

三次握手:SYN SYN_SEND SYN+ACK SYN_RECV ACK ESTABLISHED

【1】三次握手四次挥手

四次挥手

 四次挥手既可以由客户端发起,也可以由服务器发起

TCP连接终止需四个分节。

类比挂电话的过程:

第一次挥手:我说完了,我要挂了

第二次挥手:好的,我知道了,但是你先别急,等我把话说完

第三次挥手:好了,我说完了,咱们可以挂电话了

第四次挥手:好的,挂了吧

第一次挥手:某个应用进程首先调用close,我们称这一端执行主动关闭。这一端的TCP于是发送一个FIN分节,表示数据发送完毕。

第二次挥手:接收到FIN的另一端执行被动关闭(passive close)。这个FIN由TCP确认。它的接收也作为文件结束符传递给接收端应用进程(放在已排队等候应用进程接收到任何其他数据之后)

第三次挥手:一段时间后,接收到文件结束符的应用进程将调用close关闭它的套接口。这导致它的TCP也发送一个FIN。

第四次挥手:接收到这个FIN的原发送端TCP对它进行确认。

【2】udp

    1. 通信流程

 

服务器----------------------------------------------------------------------------》短信的接收方

  1. 创建数据报套接字(socket)------------------》有手机
  2. 指定网络信息--------------------------------------》有号码
  3. 绑定套接字(bind)------------------------------》绑定手机
  4. 接收、发送消息(recvfrom sendto)-------》收短信
  5. 关闭套接字(close)----------------------------》接收完毕

客户端---------------------------------------------------------------------------》短信的发送方

  1. 创建数据报套接字(socket)------------------》有手机
  2. 指定网络信息--------------------------------------》有对方号码
  3. 接收、发送消息(recvfrom sendto)-------》发短信
  4. 关闭套接字(close)----------------------------》发送完毕

 

2. 函数接口

1.recvfrom

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

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 <sys/types.h> /* See NOTES */
#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>

int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    int ret;
    // 1.创建数据报套接字(socket)------------------》有手机
    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_I
    saddr.sin_port = htons(atoi(argv[1]));
    saddr.sin_addr.s_addr = INADDR_ANY;
    int len = sizeof(caddr);

    // 3.绑定套接字(bind)------------------------------》绑定手机
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind okk\n");
    // 4.接收、发送消息(recvfrom sendto)-------》收短信
    while (1)
    {
        // 最后两个参数存放:发送消息的人的信息
        ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &len);
        if (ret < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        else
        {
            printf("ip:%s port:%d buf:%s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port), buf);
            memset(buf, 0, sizeof(buf));
        }
    }

    // 5.关闭套接字(close)----------------------------》接收完毕
    close(sockfd);
    return 0;
}

客户端模型

#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#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>

int main(int argc, char const *argv[])
{
    char buf[128] = {0};
    int ret;
    // 1.创建数据报套接字(socket)------------------》有手机
    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.绑定套接字(bind)------------------------------》绑定手机
    if (bind(sockfd, (struct sockaddr *)&saddr, sizeof(saddr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("bind okk\n");
    // 4.接收、发送消息(recvfrom sendto)-------》收短信
    while (1)
    {
        // 最后两个参数存放:发送消息的人的信息
        ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&caddr, &len);
        if (ret < 0)
        {
            perror("recvfrom err");
            return -1;
        }
        else
        {
            printf("ip:%s port:%d buf:%s\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port), buf);
            memset(buf, 0, sizeof(buf));
        }
    }

    // 5.关闭套接字(close)----------------------------》接收完毕
    close(sockfd);
    return 0;
}

注意:

1、对于TCP是先运行服务器,客户端才能运行。

2、对于UDP来说,服务器和客户端运行顺序没有先后,因为是无连接,所以服务器和客户端谁先开始,没有关系,

3、一个服务器可以同时连接多个客户端。想知道是哪个客户端登录,可以在服务器代码里面打印IP和端口号。

4、UDP,客户端当使用send的时候,上面需要加connect,这个connect不是代表连接的作用,而是指定客户端即将要发送给谁数据。这样就不需要使用sendto而用send就可以。

5、在TCP里面,也可以使用recvfrom和sendto,使用的时候将后面的两个参数都写为NULL就OK。

 

【3】Linux IO模型

4种:阻塞IO、非阻塞IO、信号驱动IO(异步IO)、IO多路复用

场景假设一

假设妈妈有一个孩子,孩子在房间里睡觉,妈妈需要及时获知孩子是否醒了,如何做?

  1. 一直看着他,一直在一个房间呆着:不累,但是不能处理其他的事情
  2. 时不时的进房间看看:累,但是可以处理其他事情
  3. 睡觉,听孩子哭不哭:互不耽误

1. 阻塞式IO:最常见、效率低、不耗费cpu

阻塞I/O 模式是最普遍使用的I/O 模式,大部分程序使用的都是阻塞模式的I/O 。

缺省情况下(及系统默认状态),套接字建立后所处于的模式就是阻塞I/O 模式。

学习的读写函数在调用过程中会发生阻塞相关函数如下:

•读操作中的read、recv、recvfrom

读阻塞--》需要读缓冲区中有数据可读,读阻塞解除

•写操作中的write、send

写阻塞--》阻塞情况比较少,主要发生在写入的缓冲区的大小小于要写入的数据量的情况下,写操作不进行任何拷贝工作,将发生阻塞,一旦缓冲区有足够的空间,内核将唤醒进程,将数据从用户缓冲区拷贝到相应的发送数据缓冲区。

注意:sendto没有写阻塞

1)无sendto函数的原因:

sendto不是阻塞函数,本身udp通信不是面向链接的,udp无发送缓冲区,即sendto没有发送缓冲区,send是有发送缓存区的,即sendto不是阻塞函数。

2)UDP不用等待确认,没有实际的发送缓冲区,所以UDP协议中不存在缓冲区满的情况,在UDP套接字上进行写操作永远不会阻塞。

•其他操作:accept、connect

 

udp丢包

 

tcp粘包

 

tcp拆包

 

TCP粘包、拆包发生原因:

发生TCP粘包或拆包有很多原因,常见的几点:

1、要发送的数据大于TCP发送缓冲区剩余空间大小,将会发生拆包

2、待发送数据大于MSS(传输层的最大报文长度),将进行拆包(到网络层拆包 - id ipflags )。

3、要发送的数据小于TCP发送缓冲区的大小,TCP将多次写入缓冲区的数据一次发送出去,将会发生粘包

4、接收数据端的应用层没有及时读取接收缓冲区中的数据,将发生粘包

粘包解决办法:

解决问题的关键在于如何给每个数据包添加边界信息,常用的方法有如下:

1、发送端给每个数据包添加包首部,首部中应该至少包含数据包的长度,这样接收端在接收到数据后,通过读取包首部的长度字段,便知道每一个数据包的实际长度了。

2、发送端将每个数据包封装为固定长度,这样接收端每次从接收缓冲区中读取固定长度的数据就自然而然的把每个数据包拆分开来。

3、可以在数据包之间设置边界,如添加特殊符号,这样,接收端通过这个边界就可以将不同的数据包拆分开。

4、延时发送

tcp粘包与udp丢包的原因 - 清风软件测试开发 - 博客园

2. 非阻塞IO:轮询、耗费CPU,可以处理多路IO

•当我们将一个套接字设置为非阻塞模式,我们相当于告诉了系统内核:“当我请求的I/O 操作不能够马上完成,你想让我的进程进行休眠等待的时候,不要这么做,请马上返回一个错误给我。”

•当一个应用程序使用了非阻塞模式的套接字,它需要使用一个循环来不停地测试是否一个文件描述符有数据可读(称做polling)。

•应用程序不停的polling 内核来检查是否I/O操作已经就绪。这将是一个极浪费CPU 资源的操作。

•这种模式使用中不普遍。

设置非阻塞的方式

1. 通过函数自带参数设置

 2. 通过设置文件描述符的属性。把文件描述符的属性设置为非阻塞

int fcntl(int fd, int cmd, ... /* arg */ );
功能:设置文件描述符属性
参数:
   fd:文件描述符
   cmd:设置方式 - 功能选择
        F_GETFL  获取文件描述符的状态信息     第三个参数化忽略
        F_SETFL  设置文件描述符的状态信息     通过第三个参数设置
        O_NONBLOCK  非阻塞
        O_ASYNC     异步
        O_SYNC      同步
  arg:设置的值  in
返回值:
      特殊选择返回特殊值 - F_GETFL  返回的状态值(int)
        其他:成功0  失败-1,更新errno
        
使用:0为例
  0-原本:阻塞、读权限  修改或添加非阻塞
  int flags=fcntl(0,F_GETFL);//1.获取文件描述符原有的属性信息
  flags = flags | O_NONBLOCK;//2.修改添加权限
  fcntl(0,F_SETFL,flags);    //3.将修改好的权限设置回去

3. 信号驱动IO/异步IO:异步通知方式,需要底层驱动的支持(了解)

异步通知:异步通知是一种非阻塞的通知机制,发送方发送通知后不需要等待接收方的响应或确认。通知发送后,发送方可以继续执行其他操作,而无需等待接收方处理通知。

1. 通过信号方式,当内核检测到设备数据后,会主动给应用发送信号SIGIO。

2. 应用程序收到信号后做异步处理即可。

应用程序需要把自己的进程号告诉内核,并打开异步通知机制。

//1.设置将文件描述符和进程号提交给内核驱动
//一旦fd有事件响应, 则内核驱动会给进程号发送一个SIGIO的信号
   fcntl(fd,F_SETOWN,getpid());

//2.设置异步通知
    int flags;
    flags = fcntl(fd, F_GETFL); //获取原属性
    flags |= O_ASYNC;       //给flags设置异步   O_ASUNC 通知
    fcntl(fd, F_SETFL, flags);  //修改的属性设置进去,此时fd属于异步
    
//3.signal捕捉SIGIO信号 --- SIGIO:内核通知会进程有新的IO信号可用
//一旦内核给进程发送sigio信号,则执行handler
    signal(SIGIO,handler);

 

阻塞IO(Blocking IO)

非阻塞IO(Non-blocking IO)

信号驱动IO(Signal-driven IO)

同步性

同步

非同步

异步

描述

调用IO操作的线程会被阻塞,直到操作完成

调用IO操作时,如果不能立即完成操作,会立即返回,线程可以继续执行其他操作

当IO操作可以进行时,内核会发送信号通知进程

特点

最常见、效率低、不耗费cpu,

轮询、耗费CPU,可以处理多路IO,效率高

异步通知方式,需要底层驱动的支持

适应场景

小规模IO操作,对性能要求不高

高并发网络服务器,减少线程阻塞时间

实时性要求高的应用,避免轮询开销

 

问题思考

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

不知道,服务器完成,服务器存储连接的客户端信息---》链表

有几种消息类型?

登录

聊天

退出

客户端如何同时处理发送和接收?

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

链表节点结构体:
struct node{
	struct sockaddr_in addr;//data   memcmp
	struct node *next;
};

消息对应的结构体(同一个协议)
typedef struct msg_t
{
    int type;//'L' C  Q    enum un{login,chat,quit};
    char name[32];//用户名 
    char text[128];//消息正文
}MSG_t;

int memcmp(void *s1,void *s2,int size);//对比

程序流程图

服务器

客户端

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

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

相关文章

[OpenCV] 数字图像处理 C++ 学习——17轮廓发现详细讲解+附完整代码

文章目录 前言1.理论基础1.1轮廓发现1.2轮廓发现步骤1.3相关API(1)轮廓发现(find contour)(2)轮廓绘制(draw contour) 2.代码实现2.1图像预处理2.2轮廓发现2.3绘制轮廓2.4Trackbar 调整阈值 3.完整代码 前言 轮廓发现是图像处理中一个重要的操作&#xff0c;可以帮助找到图像中…

基于SSM+微信小程序的快递的管理系统(快递1)

&#x1f449;文末查看项目功能视频演示获取源码sql脚本视频导入教程视频 1、项目介绍 基于SSM微信小程序的快递管理平台&#xff0c;有管理员&#xff0c;用户&#xff0c;配送员&#xff0c; 1、管理员实现了个人中心&#xff0c;用户管理&#xff0c;配送员管理&#xff0…

IDM下载器 (Internet Download Manager) v6.42.2 中文免激活绿色版

Internet Download Manager (IDM下载器) 是一款先进的下载工具,可以提升您的下载速度高达5倍,支持续传&#xff0c;IDM可以让用户自动下载某些类型的文件&#xff0c;它可将文件划分为多个下载点以更快速度下载&#xff0c;并列出最近的下载&#xff0c;方便访问文件。相对于其…

如何通过sip信令以及抓包文件分析媒体发到哪个地方

前言 问题描述&#xff1a;A的媒体没转发到B&#xff0c;B只能听到回铃音&#xff0c;没有A的说话声音&#xff0c;并且fs这边按正常的信令发送了. 分析流程 分析早期媒体发送到哪一个IP 10.19.0.1发送了一个请求给10.19.0.157这个IP&#xff0c;然而这里的SDP媒体地址&am…

Leetcode 二叉树的右视图

好的&#xff0c;我来用中文详细解释这段代码的算法思想。 问题描述 题目要求给定一个二叉树的根节点&#xff0c;从树的右侧看过去&#xff0c;按从上到下的顺序返回看到的节点值。即&#xff0c;我们需要找到每一层的最右侧节点并将其加入结果中。 算法思想 这道题可以通…

【gRPC】什么是RPC——介绍一下RPC

说起RPC&#xff0c;博主使用CPP手搓了一个RPC项目&#xff0c;RPC简单来说&#xff0c;就是远程过程调用&#xff1a;我们一般在本地传入数据进行执行函数&#xff0c;然后返回一个结果&#xff1b;当我们使用RPC之后&#xff0c;我们可以将函数的执行过程放到另外一个服务器上…

go语言中的Scanf()输入函数

Scanf() 第一种情况 package mainimport "fmt"func main() {var a intfor {fmt.Println("请输入一个整数:")fmt.Scanf("%d", &a)fmt.Println("----------------", a)} }运行结果&#xff1a; 解释&#xff1a; 出现这种现象是因…

Docker下载途径

Docker不是Linux自带的&#xff0c;需要我们自己安装 官网&#xff1a;https://www.docker.com/ 安装步骤&#xff1a;https://docs.docker.com/engine/install/centos/ Docker Hub官网(镜像仓库)&#xff1a;https://hub.docker.com/ 在线安装docker 先卸载旧的docker s…

C/C++小宇宙代码

系列目录 序号直达链接1C/C爱心代码2C/C跳动的爱心3C/C李峋同款跳动的爱心代码4C/C满屏飘字表白代码5C/C大雪纷飞代码6C/C烟花代码7C/C黑客帝国同款字母雨8C/C樱花树代码9C/C奥特曼代码10C/C圣诞树代码11C/C俄罗斯方块12C/C贪吃蛇13C/C小宇宙代码 目录 系列目录 写在前面 …

LVGL显示图片2——显示GIF图片,图片尺寸注意,图片太大无法显示

1进入图像转换网页中 https://lvgl.io/tools/imageconverter 2选择图片 3设置生成的格式 4点击生成 5复制文件&#xff0c;配置环境 6编写代码&#xff08;&#xff09; //显示一张动图 void demo_gif(){lv_obj_t* screenlv_scr_act();//声名对象LV_IMG_DECLARE(GIF_1);//创…

【数据集】NCEP辐射数据-用于计算漫射天窗比(diffuse skylight ration)

【数据集】NCEP辐射数据-用于计算漫射天窗比(diffuse skylight ration) 漫射天窗比(diffuse skylight ration)地表反射率计算漫射天窗比计算NCEP辐射数据数据下载参考漫射天窗比(diffuse skylight ration) 基于NCEP辐射数据利用Python代码计算漫射天窗比(diffuse skyli…

【实用知识】Spring Boot 优雅捕捉异常的几种姿势

&#x1f449;博主介绍&#xff1a; 博主从事应用安全和大数据领域&#xff0c;有8年研发经验&#xff0c;5年面试官经验&#xff0c;Java技术专家&#xff0c;WEB架构师&#xff0c;阿里云专家博主&#xff0c;华为云云享专家&#xff0c;51CTO 专家博主 ⛪️ 个人社区&#x…

ATom:加州理工学院化学电离质谱仪(CIT-CIMS)的现场数据,V2版

目录 简介 摘要 代码 引用 网址推荐 知识星球 机器学习 ATom: In Situ Data from Caltech Chemical Ionization Mass Spectrometer (CIT-CIMS), V2 ATom&#xff1a;加州理工学院化学电离质谱仪&#xff08;CIT-CIMS&#xff09;的现场数据&#xff0c;V2版 简介 该数…

Centos7.9安装MySQL(二进制)

安装包 https://downloads.mysql.com/archives/community/ mysql-8.0.39-linux-glibc2.17-x86_64.tar.xz 1.卸载MariaDB 查看 rpm -qa|grep mariadb卸载 可能名称不一样&#xff0c;记得替换 rpm -e --nodeps mariadb-libs-5.5.68-1.el7.x86_64rpm -qa|grep mariadb 执行…

leetcode228:汇总取件

步骤1&#xff1a;定义题目的计算问题性质 题目要求从一个无重复元素且有序的整数数组 nums 中&#xff0c;找出所有恰好覆盖数组中所有数字的最小区间范围列表。这意味着每个数字都必须被某个区间包含&#xff0c;且没有多余的数字在区间内。 输入&#xff1a;一个有序整数数…

Github 2024-10-28 开源项目周报 Top15

根据Github Trendings的统计,本周(2024-10-28统计)共有15个项目上榜。根据开发语言中项目的数量,汇总情况如下: 开发语言项目数量Python项目6JavaScript项目4TypeScript项目4Jupyter Notebook项目2C++项目1C#项目1Svelte: 一种新的Web应用程序构建方法 创建周期:2899 天开发…

软件分享丨ImTip输入状态

ImTip软件介绍 ImTip是一款实用的输入法状态跟踪提示工具&#xff0c;官网为https://imtip.aardio.com。 它具有诸多优势&#xff0c;体积小巧仅696KB且免费开源&#xff0c;是单文件绿色软件&#xff0c;无外部依赖&#xff0c;能在多种流行桌面操作系统上流畅运行。其核心…

软硬件开发面试问题大汇总篇——针对非常规八股问题的提问与应答(代码规范与生态管理)

软硬件开发&#xff0c;对于编码规范、生态管理等等综合问题的考察尤为重要。 阐述下环形缓冲区的用途 环形缓冲区&#xff08;Ring Buffer&#xff09;是一种固定大小的数据结构&#xff0c;常用于实现数据的流式传输或临时存储。在环形缓冲区中&#xff0c;当到达缓冲区的末尾…

react18中的合成事件与浏览器中的原生事件

React 通过将事件 normalize 以让他们在不同浏览器中拥有一致的属性。 合成事件 SyntheticEvent 实例将被传递给你的事件处理函数&#xff0c;它是浏览器的原生事件的跨浏览器包装器。除兼容所有浏览器外&#xff0c;它还拥有和浏览器原生事件相同的接口&#xff0c;包括 stopP…

Atlas800昇腾服务器(型号:3000)—SwinTransformer等NPU推理【图像分类】(九)

服务器配置如下&#xff1a; CPU/NPU&#xff1a;鲲鹏 CPU&#xff08;ARM64&#xff09;A300I pro推理卡 系统&#xff1a;Kylin V10 SP1【下载链接】【安装链接】 驱动与固件版本版本&#xff1a; Ascend-hdk-310p-npu-driver_23.0.1_linux-aarch64.run【下载链接】 Ascend-…