Linux 网络编程(UDP模型,libevent库使用)

news2025/1/31 3:04:53

1.ctags的使用

安装命令:sudo apt install exuberant-ctags

要使用ctags需要在当前目录生成 tags 文件,可以组织目录内所有.c间函数调用关系

生成方法:1.在项目目录下输入命令:ctags ./* -R

2.在任意一个文件内使用 ctrl+p

一些快捷命令:

命令

作用

ctrl + ]

光标放置于调用函数上,跳转至函数定义位置

ctrl + t

返回到此前跳转位置

ctrl + o

在文件左边列出文件列表,再次 ctrl+o关闭

F4

在文件右边列出函数列表,再次 F4 关闭

2.UDP通信的实现

1.使用到的函数

1.recvfrom函数:包含在头文件<sys/types.h>和<sys/socket.h>

函数原型:

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
    作用:接受信息,涵盖了客户端或者服务器的信息
    参数解释:
        参数sockfd:服务器socket
        参数buf:缓冲区
        参数len:缓冲区大小
        参数src_addr:对端的结构体对象
        参数addrlen:结构体大小
    返回值:成功返回接受的数据字符个数,失败返回-1,返回0,表示对端关闭

2.sendto函数:包含在头文件<sys/types.h>和<sys/socket.h>

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
    作用:发送信息,涵盖了客户端或者服务器的信息
    参数解释:
        参数sockfd:服务器socket
        参数buf:缓冲区
        参数len:缓冲区大小
        参数src_addr:目标结构体对象
        参数addrlen:结构体大小
    返回值:成功返回写出的数据字符个数,失败返回-1,返回0,表示对端关闭

3.fgets函数:包含在头文件<stdio.h>

函数原型:

char *fgets(char *s, int size, FILE *stream); 
    作用:从stream流中读取size个字符存储到字符指针变量s所指向的内存空间
    参数解释:
        参数s:表示存储字符的空间地址
        参数size:读取字符串的长度
        参数stream:表示从何种流中读取,可以是标准输入流stdin,也可以是文件流
UDP客户端实现代码
#include<stdio.h>
#include<string.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<unistd.h>

int main()
{
    int cfd = socket(AF_INET, SOCK_DGRAM, 0);
    struct sockaddr_in client_addr;
    client_addr.sin_family = AF_INET;
    client_addr.sin_port = htons(12345);
    inet_pton(AF_INET, "127.0.0.1", &client_addr.sin_addr.s_addr);
    char buf[1024] = {0};
    while(fgets(buf, 1024, stdin) != NULL)
    {
        //参数5使用的是服务器的socket信息
        int n = sendto(cfd, buf, strlen(buf), 0, (struct sockaddr*)&client_addr,sizeof(client_addr)); 
        memset(buf, 0, 1024);
        recvfrom(cfd, buf, 1024, 0, NULL, 0);
        printf("服务器发来消息:%s", buf);
    }
    
    close(cfd);
    return 0;
}
UDP服务器实现代码
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<ctype.h>

int main()
{
    int lfd = socket(AF_INET, SOCK_DGRAM, 0);//在这里使用sock_dgram
    struct sockaddr_in sev_addr;
    bzero(&sev_addr, sizeof(sev_addr));
    sev_addr.sin_family = AF_INET;
    sev_addr.sin_port = htons(12345);
    sev_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    bind(lfd, (struct sockaddr*)&sev_addr, sizeof(sev_addr));
    printf("wait connecting.....\n");
    struct sockaddr_in client_addr;
    char buf[1024] = {0};
    int i;
    while(1)
    {
        int client_len = sizeof(client_addr);
        //接受客户端的同时,参数5可以获取到客户端的socket信息
        int n = recvfrom(lfd, buf, 1024, 0, (struct sockaddr*)&client_addr, &client_len);
        printf("客户端发来消息:%s",buf); 
        for(i = 0;i < n;i++);
        {
            buf[i] = toupper(buf[i]);
        }
        //给获取到的客户端发送消息
        sendto(lfd, buf, n, 0, (struct sockaddr*)&client_addr, sizeof(client_addr));
    }
    close(lfd);
    return 0;
}    

3.本地套接字

概念:进程间通信的一种方式是使用UNIX套接字,人们在使用这种方式时往往用的不是网络套接字,而是一种称为本地套接字的方式,就是创建一个伪文件访问绑定结构体成员中的path作为一个通信的介质

1.使用到的函数以及结构体

1.socket_un结构体

结构体原型:

struct sockaddr_un
        {
            sa_family_t  sun_family;    //地址结构体类型,只能是AF_LOCAL或AF_UNIX
            char         sun_path[108];    //本地文件的路径通常放在/tmp下
        }
    在sockaddr_un需要使用strcpy()函数把伪文件名赋值给岑有sun_path

2.offset函数:包含在头文件<stddef.h>

函数原型:

size_t offsetof(type, member);
    作用:计算结构体成员在内存中的偏移量
    参数解释:
            参数type:结构体名称
            参数member:是结构体的内部成员
        返回值:返回成员在结构体中的偏移量

3.unlink函数:包含在头文件<unistd.h>

函数原型:

int unlink(const char *pathname);
    作用:从文件系统中删除一个名称。如果名称是文件的最后一个连接,并且没有其它进程将文件打开
,名称对应的文件会实际被删除
        参数解释:需要解除连接的文件名
本地套接字通信的客户端代码实现
#include<stdio.h>
#include<unistd.h>
#include<ctype.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<arpa/inet.h>
#include<sys/un.h>
#include<stddef.h>

#define CLIENT_FILE "client.socket"
#define SERVER_FILE "serv.socket"

int main()
{
    int cfd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un client_addr;
    bzero(&client_addr, sizeof(client_addr));
    client_addr.sun_family = AF_UNIX;
    strcpy(client_addr.sun_path, CLIENT_FILE);
    //因为本地套接字不会自动绑定socket端口号和ip地址信息,所以需要使用bind函数进行绑定
    int len = offsetof(struct sockaddr_un, sun_path) + strlen(client_addr.sun_path);
    unlink(CLIENT_FILE);
    bind(cfd, (struct sockaddr*)&client_addr, len);    //需要手动绑定数据信息,在本地套接字是不会自动绑定结构体信息的
    
    

    struct sockaddr_un serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, SERVER_FILE);
    len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path);
    //因为是需要连接的是服务器,所以需要传服务器的文件套接字
    connect(cfd, (struct sockaddr*)&serv_addr, len);

    char buf[1024] = {0};
    while(fgets(buf, 1024, stdin) != NULL)
    {
        write(cfd, buf, strlen(buf));
        int ret = read(cfd, buf, 1024);
        if(ret == 0 || ret == -1)
        {
            printf("error\n");
            break;
        }
        printf("服务器%s发来消息:%s\n", serv_addr.sun_path, buf);
    }
    close(cfd);
    return 0;
}
    

本地通信的套接字服务器代码实现
#include<stdio.h>
#include<unistd.h>
#include<stddef.h>
#include<string.h>
#include<ctype.h>
#include<arpa/inet.h>
#include<sys/un.h>

#define SERV_FILE "serv.socket"

int main()
{
    int lfd = socket(AF_UNIX, SOCK_STREAM, 0);
    struct sockaddr_un serv_addr;
    bzero(&serv_addr, sizeof(serv_addr));
    serv_addr.sun_family = AF_UNIX;
    strcpy(serv_addr.sun_path, SERV_FILE);//把文件路径复制到结构体成员
    //因为结构体sockaddr_un具有首部2字节长度,所以需要使用该函数计算path的实际长度    
    int len = offsetof(struct sockaddr_un, sun_path) + strlen(serv_addr.sun_path);//求取文件名字的实际长度
    
    unlink(SERV_FILE);//如果该文件socket存在则先删除
    bind(lfd, (struct sockaddr*)&serv_addr, len);//绑定给socket
    
    listen(lfd, 20);

    struct sockaddr_un client_addr;//给accept函数使用,用来绑定客户端的信息    
    char buf[1024] = {0};    

    printf("Accept....\n");
    while(1)
    {
        int client_len = sizeof(client_addr);//作为accept函数的第三个参数
        //参数2是客户端本地套接字信息,但是和服务器的一样,参数三获取到的是client_addr.sun_path的实际长度
        int cfd = accept(lfd, (struct sockaddr*)&client_addr, (socklen_t*)&client_len);
        
        len -= offsetof(struct sockaddr_un, sun_path);/* 得到文件名的长度 */
               client_addr.sun_path[len] = '\0';                 /* 确保打印时,没有乱码出现 */

            printf("client bind filename %s\n", client_addr.sun_path);
        
        int size = 0;
        int i = 0;
        while((size = read(cfd, buf, 1024)) > 0)
        {
            for(i = 0;i < size;i ++)
            {
                buf[i] = toupper(buf[i]);
            }
            printf("客户端%s的消息:%s\n", client_addr.sun_path, buf);
            write(cfd, buf, size);
        }
        close(cfd);
    }


    close(lfd);    
    return 0;
}

4.事件异步通信模型

在这个模型中需要使用到libevent库,下载网址:www.libevent.org,下载tar安装包并且上传到linux服务器

1.如图,是上传的安装包

2.使用如下命令进行解包

tar -zxvf libevent-2.1.10-stable.tar.gx

生成如下文件

3.进入该文件

4.执行如下命令

1. ./configure
2. make
3. sudo make install

这样就可以使用libevent库了

使用libevent创建服务器的过程

1.使用socket创建套接字

2.设置端口复用->setsockopt()

3.绑定IP地址->bind

4.设置监听->listen

5.创建地基->event_base_new()函数

作用:event_base_new()函数分配并且返回一个新的具有默认设置的event_base。函数会检测环境变量,返回一个到event_base的指针。

函数原型:

struct  event_base  * event_base_new(void );

    返回值:如果发生错误,则返回NULL

6.创建服务器socket对应的事件->event_new函数

作用:分配并初始化一个新的event结构体,准备被添加

函数原型:

struct event*event_new(struct event_base*base, evutil_socket_t fd, short whar, 
                            event_callback_fn cb, void *arg);
    参数解释:
        参数base:event_base_new的返回值
        参数 fd:绑定到event上的 文件描述符
        参数what:对应的事件(r w e)
            EV_READ    一次 读事件
            EV_WRTIE    一次 写事件
            EV_PERSIST    持续触发,结合 event_base_dispatch函数使用,生效
        参数cb:一旦事件满足监听条件,触发该事件的处理函数,就是回调函数
        参数arg:回调的函数的参数
    返回值:成功创建的 event
    
    回调函数的规范书写:
typedef void(*event_callback_fn)(evutil_socket_t fd, short, void*)

7.把服务器socket对应的事件放上event_base地基上->event_add函数

作用:该函数将event_new创建出来的事件挂上地基,实现监听

函数原型:

int event_add(struct event* ev, const struct timeval *tv)
    参数解释:
        参数ev:event_new的返回值
        参数tv:表示超时时间
            NULL:表示没有超时,一直等待,直到有事件触发,才会调用回调函数
            非0值:如果超过指定的时间,即使没有事件触发也会调用回调函数    

8.进入时间循环->event_base_dispatch

作用:事件调度循环,此循环将运行event,直到不再有挂起或活动的事件,或者直到有东西调用event_base_loopbreak()或event_ base_loopexit()

函数原型:

int event_base_dispatch(struct event_base *);
    参数:event_base_new创建的返回值

事件处理调用函数:
int event_base_loop(struct event_base *, int);
    作用:等待事件变为活动状态,然后运行它们的回调。这是比event_base_dispatch更灵活版本。
默认情况下,此循环将运行事件库,直到不再存在挂起或活动事件,
或者直到调用event_base_loopbreak()或event_ base_loopexit()
    参数解释:
        参数1:event_base_new的返回值
        参数2:可以设置重写函数的参数
    返回值:如果成功,则返回0;如果发生错误,则返回1;
            如果由于没有挂起或活动的事件而退出,则返回2

9.释放资源->event_base_free函数和event_free函数

作用:event_base_free释放地基的资源,event_free函数释放事件资源

使用libevent实现的服务器代码
#include<stdio.h>
#include<stdlib.h>
#include<arpa/inet.h>
#include<unistd.h>
#include<string.h>
#include<event.h>

struct event* connev;

//回调函数的原型:typedef void (*event_callback_fn)(evutil_socket_t fd, short events, void* arg)
//读数据回调函数
void readcb(evutil_socket_t fd, short events, void* arg)
{
    int n ;
    char buf[1024];
    memset(buf, 0x00, sizeof(buf));
    n = read(fd, buf, sizeof(buf));
    printf("客户端发来消息:%s\n", buf);
    if(n <= 0)
    {
        close(fd);
        //将对应的客户端事件从地基上删除
        event_del(connev);
    }
    else
        write(fd, buf, n);
}

//连接回调函数
void conncb(evutil_socket_t fd, short events, void* arg)
{
    struct event_base* base = (struct event_base*)arg;
    //接受客户端连接
    int cfd = accept(fd, NULL, NULL);
    if(cfd > 0)
    {    
        //创建客户端对应的事件并设置回调函数readcb
        //参数3是事件信号,参数4是回调函数
        connev = event_new(base, cfd, EV_READ|EV_PERSIST, readcb, NULL);
        if(connev == NULL)
        {
            //退出循环
            event_base_loopexit(base, NULL);
            printf("connev error\n");
            exit(1);
        }
        //将客户端对应的事件放到地基上
        event_add(connev, NULL);
    }
}

int main()
{
    //创建socket
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    //设置端口号复用
    int opt = 1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
    //创建socket信息
    struct sockaddr_in serv_addr;
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(8888);
    serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    bind(lfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
    //监听
    listen(lfd, 128);
    
    //创建地基
    struct event_base* base = event_base_new();
    if(base == NULL)
    {
        printf("event_base create error\n");
        return -1;
    }
    //创建监听文件描述符对应的事件
    struct event* ev = event_new(base, lfd, EV_READ|EV_PERSIST, conncb, base);
    if(ev == NULL)
    {
        printf("event_new error\n");
        return -1;
    }
    
    //将新的事件放到base地基上
    event_add(ev, NULL);
    //进入事件循环等待
    event_base_dispatch(base);
    //释放资源
    event_base_free(base);//释放地基资源
    event_free(ev);//释放事件资源
    
    close(lfd);
    return 0;
}

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

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

相关文章

python入门项目06:批量处理文件

文章目录前言一、理论知识1.1 OS模块1.2 XML的解析二、使用步骤1.引入库2.创建新文件夹3文件操作4 修改文件总结前言 本文要完成的是对于较多XML文档的自动修改&#xff0c;这部分往往在大量的图像标注的修改中会使用到&#xff0c;同时也不要局限于本文中所提到的使用场景。 …

springbboot随笔

无效的源发行版问题 改springboot版本 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http:…

高校竞赛信息管理系统

摘要随着当今社会的发展&#xff0c;时代的进步&#xff0c;各行各业也在发生着变化&#xff0c;比如高校竞赛信息管理这一方面&#xff0c;利用网络已经逐步进入人们的生活。传统的高校竞赛信息管理&#xff0c;都是学生去学校查看竞赛信息然后再进行报名&#xff0c;这种传统…

linux面试高级篇

题目目录1.虚拟机常用有几种网络模式&#xff1f;请简述其工作原理或你个人的理解&#xff1f;2. Dockerfile中最常见的指令是什么&#xff1f;3.docker网络模式有哪些&#xff1f;4.Kubernetes有哪些核心组件这些组件负责什么工作&#xff1f;5. Pod是什么&#xff1f;6.描述一…

H264编码原理

1.什么是音视频&#xff1f; 比如我们下载的mp4格式&#xff08;还有rmvb、avi&#xff09;的电影 2.什么是h264&#xff1f; 对摄像头采集的每一帧视频需要进行编码&#xff0c;由于视频中存在空间和时间的冗余&#xff0c; 需要用算法来去除这些冗余。H264是专门去除这些冗…

SQL基础语句小结

&#x1f34e;道阻且长&#xff0c;行则将至。&#x1f353; 目录 一、SQL概述 1.简介 2.格式语法 3.SQL分类 二、DDL操作数据库 1.创建数据库 2.查询与使用 3.删除数据库 三、DDL:操作表 (1)数据类型 (2)创建表 (3)查询当前数据库的表 (4)删除表 (5)修改表 四、DML…

小鹏汽车「错失」智能化好牌

本周&#xff0c;作为小鹏品牌销售主力的P7正式迎来改款发布。3月6日&#xff0c;小鹏P7i迎来首发&#xff0c;作为P7的改款车型&#xff0c;在整车电子架构、三电系统、智能化硬件配置上都进行了全面升级。 基于和G9相同的双Orin-X计算平台两个盲区激光雷达&#xff0c;P7i实现…

RocketMQ基础篇(一)

目录一、发送消息类型1、同步消息2、异步消息3、单向消息4、顺序消费5、延迟消费二、消费模式1、集群模式2、广播模式3、消费模式扩展4、如何配置三、其他用法1、事务消息2、过滤消息1&#xff09;Tag过滤2&#xff09;SQL方式过滤源码放到了GitHub仓库上&#xff0c;地址 http…

HyperLPR3车牌识别-Android-SDK光速部署与使用

简介HyperLPR在2023年初已经更新到了v3的版本&#xff0c;该版本与先前的版本一样都是用于识别中文车牌的开源图像算法项目&#xff0c;最新的版本的源码可从github中提取&#xff1a;https://github.com/szad670401/HyperLPRHyperLPR-Android-SDK for JitPackHyperLPR3的官方源…

Prim算法和Kruskal算法到底哪个好?

Prim和Kruskal有啥区别&#xff1f;到底哪个好&#xff1f; 今天做了一道最小生成树的题&#xff0c;发现了一点猫腻&#xff01; 题目在这里 &#xff1a; 《修路问题1》 文章目录Prim和Kruskal有啥区别&#xff1f;到底哪个好&#xff1f;先说结论PrimKruskal修路问题1——…

不好!有敌情,遭到XSS攻击【网络安全篇】

XSS&#xff1a;当一个目标的站点&#xff0c;被我们用户去访问&#xff0c;在渲染HTMl的过程中&#xff0c;出现了没有预期到的脚本指令&#xff0c;然后就会执行攻击者用各种方法注入并执行的恶意脚本&#xff0c;这个时候就会产生XSS。 涉及方&#xff1a; 用户&#xff0…

Linux端安装MySQL并实现远程连接Navicat

文章目录Linux端安装MySQL&#xff08;centos版本&#xff09;Linux端安装MySQL&#xff08;centos版本&#xff09; 1、先将MySQL需要的四个rpm安装包上传上去&#xff0c;这里可以使用Xftp软件或者是通过window端使用ftp文件传输方式上传到Linux端&#xff0c;这里选择Xftp来…

基于JavaWeb学生选课系统开发与设计(附源码资料)

文章目录1. 适用人群2. 你将收获3.项目简介4.技术实现5.运行部分截图5.1.管理员模块5.2.教师模块5.3.学生模块1. 适用人群 本课程主要是针对计算机专业相关正在做毕业设计或者是需要实战项目的Java开发学习者。 2. 你将收获 提供&#xff1a;项目源码、项目文档、数据库脚本…

远程办公18年,把一个开源工具变成了价值 75亿美元的跨国企业

把自己的兴趣做成了一份事业&#xff0c;把一个开源工具发展成为一家价值75亿美元的跨国企业&#xff0c;而且还是那种员工做梦都想进入的公司&#xff0c;真正实现了功成名就&#xff0c;这或许是所有程序员的梦想吧。 先来看看这家公司的福利&#xff1a; 员工拥有没有限制的…

git快速入门(1)

1 git的下载与安装1&#xff09;下载git安装包下载路径&#xff1a;https://git-scm.com/我的操作系统是window&#xff0c;64位的&#xff0c;我下载的Git-2.33.0-64-bit.exe&#xff0c;从官网下载或者从网址下载链接&#xff1a;链接地址&#xff1a;https://pan.baidu.com/…

【MySQL】P8 多表查询(2) - 连接查询 联合查询

连接查询以及联合查询多表查询概述连接查询内连接隐式内连接显式内连接外连接左外连接右外连接自连接联合查询多表查询概述 建表语句见上一篇博文&#xff1a;https://blog.csdn.net/weixin_43098506/article/details/129402302 e.g.e.g.e.g. select * from emp, dept where e…

深入分析@Configuration源码

文章目录一、源码时序图1. 注册ConfigurationClassPostProcessor流程源码时序图2. 注册ConfigurationAnnotationConfig流程源码时序图3. 实例化流程源码时序图二、源码解析1. 注册ConfigurationClassPostProcessor流程源码解析&#xff08;1&#xff09;运行案例程序启动类Conf…

Python安装、断点调试

一、安装Python方法 1.1 在Microsoft Store微软商店中搜索Python安装&#xff08;推荐&#xff09; 或直接在cmd中Python运行 已经安装了就显示版本号&#xff0c; 如果没有安装过&#xff0c;会直接跳到微软商店 1.2 在python官网中找最新版下载安装 二、VSCODE中运行与断点…

容易混淆的嵌入式(Embedded)术语

因为做嵌入式开发工作虽然跳不出电子行业&#xff0c;但还是能接触到跨度较大的不同行当&#xff0c;身处不同的圈子。诸如医疗&#xff0c;银行&#xff0c;车载&#xff0c;工业&#xff1b;亦或者手机&#xff0c;PC&#xff0c;专用芯片&#xff1b;甚至可能横跨系统开发、…

Vue常见的事件修饰符

前言 vue一共给我们准备了6个事件修饰符&#xff0c;前三个比较常用&#xff0c;后三个少见&#xff0c;这里着重讲下前三个 1.prevent:阻止默认事件(常用) 2. stop:阻止事件冒泡(常用) 3. once:事件只触发一次(常用) 4.captrue:使用事件的捕捉模式(不常用) 5.self:只有event…