R2:获取一个event_base

本文详细介绍了如何使用Libevent库创建和管理事件循环。包括创建event_base、设置优先级、配置复杂event_base等内容。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

原文链接:http://www.wangafu.net/~nickm/libevent-book/Ref2_eventbase.html

创建一个event_base

在你使用任何有意思的Libevent函数之前,你需要分配一个或多个event_base结构.每一个event_base结构含有一组events,并且可以告知你哪一些events是就绪的.

如果一个event_base是以加锁模式创建的,那么在多个线程访问它是安全的.然而,事件循环却只能跑在一个线程内.所以说,如果你想要对IO事件有多线程的轮询,你需要为每一个线程都分配一个event_base。

  Tip:未来版本的Libevent可能会支持多线程的IO事件轮询.

每一个event_base都有一个"方法"或者说后台,event_base根据这个来判断哪一个event已经就绪.目前确认的方法有:

  • select

  • poll

  • epoll

  • kqueue

  • devpoll

  • evport

  • win32

用户可以通过设置环境变量的方式禁用特定的后台方法.比如你不想使用kqueue,那么设置一个叫EVENT_NOKQUEUE的环境变量.如果你想禁用程序内部的后台方法,看下面对event_config_avoid_method()的说明.

 

创建一个默认的event_base

event_base_new()函数分配并返回一个新的默认配置的event_base。该函数检测环境变量并返回一个指向新的event_base的指针.如果出错的话则返回NULL.

当在众多方法(指上文中提到的事件多路分离的方法:select,poll,epoll等)中选择时,它会选择当前操作系统支持的最快的方法.

Interface

 struct event_base *event_base_new(void);

对大多数程序来讲,这就是你所需全部了.

event_base_new()函数在<event2/event.h>中声明.它是在Libevent 1.4.3中第一次引入的.

 

创建一个复杂的event_base 

 如果你想对获取一个什么样的event_base有更多的控制权,你需要用到event_config.一个event_config是一个不透明的结构(opaque structure),其中含有你对event_base的预设信息.当你想获取一个event_base,你向event_base_new_with_config()即可.

Interface

struct event_config *event_config_new(void);
struct event_base *event_base_new_with_config(const struct event_config *cfg);
void event_config_free(struct event_config *cfg);
为了使用这些函数分配一个event_base,你需要先调用event_config_new()分配一个新的event_config.然后你调用一些别的函数以告知event_config你的需求.最后,调用event_base_new_with_config()来获取一个新的event_base.当你完成之后,你可以调用event_config_free()释放掉event_config.
 
Interface
复制代码
int event_config_avoid_method(struct event_config *cfg, const char *method);

enum event_method_feature {
    EV_FEATURE_ET = 0x01,
    EV_FEATURE_O1 = 0x02,
    EV_FEATURE_FDS = 0x04,
};
int event_config_require_features(struct event_config *cfg,
                                  enum event_method_feature feature);

enum event_base_config_flag {
    EVENT_BASE_FLAG_NOLOCK = 0x01,
    EVENT_BASE_FLAG_IGNORE_ENV = 0x02,
    EVENT_BASE_FLAG_STARTUP_IOCP = 0x04,
    EVENT_BASE_FLAG_NO_CACHE_TIME = 0x08,
    EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST = 0x10,
    EVENT_BASE_FLAG_PRECISE_TIMER = 0x20
};
int event_config_set_flag(struct event_config *cfg,
    enum event_base_config_flag flag);
复制代码

调用event_config_avoid_method告知Libevent根据名字避开一个特定的可用后台方法.调用event_config_require_feature()告知Libevent不要使用那些不支持全部一组特性的后台方法.调用event_config_set_falg()告知Libevent在创建event_base时设置一个或多个下述的运行时标志.

已知的用来传给event_config_require_feature()的特征值有:

EV_FEATURE_ET

      要求一个支持边沿触发(edge-triggered)的后台方法.

EV_FEATURE_O1

     要求一个增加事件,删除事件,激活事件的复杂度为O(1)的后台方法.

EV_FEATURE_FDS

  要求一个支持任意文件描述符类型,而不仅仅是sockets的后台方法.

已知的用来传给event_config_set_flag()的可选值有:

EVENT_BASE_FLAG_NOLOCK

不为event_base分配锁.设置这个选项可以为你对event_base加锁和释放节约一点时间,但是可能导致多线程下访问event_base无效或是不安全.

EVENT_BASE_FLAG_IGNORE_ENV

在决定用哪一种后台方法时,不去检查形如EVENT_*的环境变量.慎用该标志:使用该标志可能导致你的程序与Libevent的交互非常难以调试.

EVENT_BASE_FLAG_STARTUP_IOCP

仅在Windows下使用,这个标志使得Libevent在启动时就启用IOCP的分派逻辑,而不是在需要时才启用.(On Windows only, this flag makes Libevent enable any necessary IOCP dispatch logic on startup, rather than on-demand.)

EVENT_BASE_FLAG_NO_CACHE_TIME

在每一次超时调用后检测当前时间,而不是在事件循环就绪可以运行超时回调函数时检测当前时间。注意:这可能导致程序消耗更多的CPU。

EVENT_BASE_FLAG_EPOLL_USE_CHANGELIST

这个标志告知Libevent如果要用epoll的话,用基于"changelist"的后台方法会更快,也是安全的.如果在多次后台调度函数调用之间同一个文件描述符的状态发生了多次改变,使用epoll-changelist后台方法可以避免不必要的系统调用,当你向Libevent传递任何一个由dup()或是类似函数复制的文件描述符时,这样(指使用epoll-changelist)会触发一个导致很多错误结果的内核bug。

EVENT_BASE_FLAG_PRECISE_TIMER

默认地,Libevent尝试使用当前操作系统所能提供的最快的计时机制.如果操作系统有一种慢一些的但是精度更高的计时机制的话,设置这个标志将告知Libevent使用这种更慢但精度更高的机制,如果没有,这个标志将不起作用.

上述所有对event_config操作的函数都有两个返回值:0代表成功,-1代表失败. 

Note

很容易创建这样一个event_config:它要求你的操作系统并未提供的后台方法.以Libevent 2.0.1-alpha为例,在Windows下没有O(1)的后台方法,在Linux下没有同时满足 EV_FEATURE_FDS and EV_FEATURE_O1的后台方法.如果你要求一个Libevent无法满足的配置,event_base_new_with_config()会返回空.

 Interface

int event_config_set_num_cpus_hint(struct event_config *cfg, int cpus) 

这个函数目前只在windows平台下使用IOCP时有用,尽管未来在别的平台可能也会有用.调用这个函数告诉event_base在多线程处理时要充分利用给定数量的cpu.注意这只是一个(该函数对event_base的)暗示:event base可能使用更多或更少的CPU.

Interface
int event_config_set_max_dispatch_interval(struct event_config *cfg,
    const struct timeval *max_interval, int max_callbacks,
    int min_priority);
这个函数通过在检测高优先级事件之前限制可被触发的低优先级事件数量的方式避免了优先级翻转问题(注:http://baike.baidu.com/view/2422471.htm?fr=aladdin).如果max_interval是非空的,事件循环在每一次回调之后检测时间,如果超出max_interval指定的时间,则重新扫描高优先级事件.如果max_callbacks是非负的,在max_callbacks次回调被触发后,事件循环还会检测更多事件( If max_callbacks is nonnegative, the event loop also checks for more events after max_callbacks callbacks have been invoked ).这些规则对任何优先级的事件都适用.
 
Example: Preferring edge-triggered backends
复制代码
struct event_config *cfg;
struct event_base *base;
int i;

/* My program wants to use edge-triggered events if at all possible.  So
   I'll try to get a base twice: Once insisting on edge-triggered IO, and
   once not. */
for (i=0; i<2; ++i) {
    cfg = event_config_new();

    /* I don't like select. */
    event_config_avoid_method(cfg, "select");

    if (i == 0)
        event_config_require_features(cfg, EV_FEATURE_ET);

    base = event_base_new_with_config(cfg);
    event_config_free(cfg);
    if (base)
        break;

    /* If we get here, event_base_new_with_config() returned NULL.  If
       this is the first time around the loop, we'll try again without
       setting EV_FEATURE_ET.  If this is the second time around the
       loop, we'll give up. */
}
复制代码

Example: Avoiding priority-inversion

复制代码
struct event_config *cfg;
struct event_base *base;

cfg = event_config_new();
if (!cfg)
   /* Handle error */;

/* I'm going to have events running at two priorities.  I expect that
   some of my priority-1 events are going to have pretty slow callbacks,
   so I don't want more than 100 msec to elapse (or 5 callbacks) before
   checking for priority-0 events. */
struct timeval msec_100 = { 0, 100*1000 };
event_config_set_max_dispatch_interval(cfg, &msec_100, 5, 1);

base = event_base_new_with_config(cfg);
if (!base)
   /* Handle error */;

event_base_priority_init(base, 2);
复制代码

这些函数和类型在<event2/event.h>中声明.

EVENT_BASE_FLAG_IGNORE_ENV标志始于Libevent 2.0.2-alpha.EVENT_BASE_FLAG_PRECISE_TIMER标志始于Libevent 2.1.2-alpha.event_config_set_num_cpus_hint()始于Libevent 2.0.7-rc,event_config_set_max_dispatch_interval()始于Libevent 2.1.1-alpha.这一部分提到的其它函数或类型始于Libevent 2.0.1-alpha.

 

检测event_base的后台方法

有时你可能想知道一个event_base究竟含有哪些特征或者使用了哪种后台方法.

Interface

const char **event_get_supported_methods(void);

event_get_supported_methods()函数返回一个指向数组的指针,数组内存放的是当前版本Libevent支持的方法.数据内的最后一个元素为空.

Example

复制代码
int i;
const char **methods = event_get_supported_methods();
printf("Starting Libevent %s.  Available methods are:\n",
    event_get_version());
for (i=0; methods[i] != NULL; ++i) {
    printf("    %s\n", methods[i]);
}
复制代码
Note

这个函数返回一个方法列表,Libevent是被编译成支持这些方法的.然而,你的操作系统可能实际上并不支持这些方法.比如,在某些OSX版本下kqueue使用起来是很多BUG的

Interface

const char *event_base_get_method(const struct event_base *base);
enum event_method_feature event_base_get_features(const struct event_base *base);

event_base_get_method()返回一个在event_base中真正使用的方法名.event_base_get_features()返回一个它所支持的特性的位掩码.

Example

复制代码
struct event_base *base;
enum event_method_feature f;

base = event_base_new();
if (!base) {
    puts("Couldn't get an event_base!");
} else {
    printf("Using Libevent with backend method %s.",
        event_base_get_method(base));
    f = event_base_get_features(base);
    if ((f & EV_FEATURE_ET))
        printf("  Edge-triggered events are supported.");
    if ((f & EV_FEATURE_O1))
        printf("  O(1) event notification is supported.");
    if ((f & EV_FEATURE_FDS))
        printf("  All FD types are supported.");
    puts("");
}
复制代码

这些函数定义于<event2/event.h>.event_base_get_method()第一次出现于Libevent 1.4.3.其余的始于Libevent 2.0.1-alpha.

 

释放event_base

当你不再需要event_base,你可以用event_base_free()释放它.

Interface

void event_base_free(struct event_base *base);

注意这个函数不会释放掉任何正与event_base关联的事件,或是关闭与这些事件相关的sockets,或是释放它们的指针所指空间.

event_base_free()函数定义在<event2/event.h>中.始于Libevent 1.2.

 

设置优先级

Libevent支持对一个event设置多个优先级.默认地,一个event_base只支持一个优先级.你可以通过调用event_base_priority_init()为一个event_base设置优先级的数量.

Interface

int event_base_priority_init(struct event_base *base, int n_priorities);

该函数成功返回0,失败返回-1.参数base是待修改的event_base,n_priorties是支持的优先级的数量.n_priorities至少要是1.优先级从0(最重要)到n_priorities-1(最不重要).

有一个常量,EVENT_MAX_PRIORITIES,标识了n_priorities的最大值.调用上述函数时,传一个超过EVENT_MAX_PRIORITIES的n_priorities参数是错误的行为.

Note:你必须在任何事件变为活动的(active,即事件已触发)之前调用该函数.最好是在创建event_base之后就立刻调用该函数.
 
你可以通过调用event_base_getnpriorities()来查看当前的base支持的优先级数量.
 
Interface
int event_base_get_npriorities(struct event_base *base);

该函数的返回值为当前base中配置的优先级数量.所以如果event_base_get_npriorities()返回3,那么可选的优先级的值为0,1,2.

Example

For an example, see the documentation for event_priority_set below.

默认地,所有和base相关的事件会被初始化为优先级为n_priorities/2.

event_base_priority_init()定义于<event2/event.h>.它始于Libevent 1.0.event_base_get_npriorities()始于Libevent 2.1.1-alpha

 

在fork()之后重新初始化event_base

 不是所有的事件后台在fork()之后还可以保持干净.(Not all event backends persist cleanly after a call to fork()).所以,如果你的程序使用了fork()或相关的系统调用来开启一个新的进程,而你想继续使用之前的event_base,你需要重新初始化它.

Interface

int event_reinit(struct event_base *base);

该函数成功返回0,失败返回-1.

Example

复制代码
struct event_base *base = event_base_new();

/* ... add some events to the event_base ... */

if (fork()) {
    /* In parent */
    continue_running_parent(base); /*...*/
} else {
    /* In child */
    event_reinit(base);
    continue_running_child(base); /*...*/
}
复制代码

event_reinit()定义于<event2/event.h>.始于Libevent 1.4.3-alpha.

 

废弃的event_base函数

旧版的Libevent严重依赖于“当前”event_base这个概念.所谓的“当前”event_base,是一个被所有线程共享的全局设置.如果你忘了指定你想使用哪一个event_base,那么你得到的就是"当前"的.而由于event_base不是线程安全的,这种行为非常容易导致错误.

相较于如今版本的event_base_new(),过去有:

Interface

struct event_base *event_init(void);

这个函数类似于event_base_new(),它将分配的base设置为当前base.除此之外,没有别的改变当前base的方式了.

这部分提到的作用于当前base的函数有许多变体.这些(旧版本中的)函数和现在的函数功能类似,除了它们不接受base参数.

当前版本 已弃用的版本

event_base_priority_init()

event_priority_init()

event_base_get_method()

event_get_method()

分类:  Libevent网络编程
zaixian_cas_login.py脚本的原始脚本内容是: #!/usr/bin/python3 # coding=utf-8 import io import sys import time import requests import json import re import base64 from urllib.parse import urlparse, urljoin, quote import urllib3 import gzip import zlib import brotli import chardet from typing import Optional, Tuple, Dict # 禁用SSL警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') SUC_RES = { 'resCode': 200, 'resTime': 0, 'keyword': 'SUCCESS', 'message': [] } FAIL_RES = { 'resCode': 500, 'resTime': 0, 'keyword': 'FAILED', 'message': [] } # 封装解码 class HttpResponseProcessor: def __init__(self, url: str, headers: Optional[Dict] = None): """ 初始化响应处理器 :param url: 请求的URL :param headers: 请求头,默认为None """ self.url = url self.headers = headers or {} self.response = None self.raw_content = None self.text_content = None self.encoding = None self.status_code = None def fetch_response(self): """ 发送HTTP请求并获取响应 :return: None """ try: self.response = requests.get( url=self.url, headers=self.headers, allow_redirects=False, # 禁用自动重定向 stream=True, # 流模式获取原始响应 verify=False ) self.status_code = self.response.status_code self.raw_content = self.response.content except Exception as e: raise Exception(f"请求失败: {str(e)}") def print_response_headers(self): """ 打印响应头信息 :return: None """ if not self.response: raise Exception("尚未获取响应,请先调用 fetch_response()") def decode_content(self) -> str: """ 尝试解码内容为文本 :return: 解码后的文本内容 """ if not self.raw_content: raise Exception("尚未获取原始内容,请先调用 fetch_response()") try: # 检测内容编码 result = chardet.detect(self.raw_content) encoding_detected = result.get('encoding') if result else None # 尝试解码 if encoding_detected: try: self.text_content = self.raw_content.decode(encoding_detected) self.encoding = encoding_detected return self.text_content except UnicodeDecodeError: # 如果检测到的编码解码失败,则尝试其他编码 pass # 尝试常见编码 for encoding in ['utf-8', 'gbk', 'gb2312', 'latin1']: try: self.text_content = self.raw_content.decode(encoding) self.encoding = encoding break except: continue else: # 如果都无法解码,则使用替换错误字符的方式解码 try: self.text_content = self.raw_content.decode('utf-8', errors='replace') self.encoding = 'utf-8' except: self.text_content = "无法解码内容" self.encoding = None return self.text_content except Exception as e: # 将内容保存到文件以便分析 with open('response.bin', 'wb') as f: f.write(self.raw_content) raise Exception(f"内容解码失败: {str(e)}") def process_response(self) -> Tuple[int, Optional[str], Optional[str]]: """ 完整处理响应的便捷方法 :return: (status_code, text_content, encoding) """ self.fetch_response() self.print_response_headers() text = self.decode_content() return self.status_code, text, self.encoding def print_err_result(e): FAIL_RES['error'] = e exit(1) def make_request(url, params=None, data=None, method='get', session=None): try: start = time.time() req_func = session.get if session else requests.get if method.lower() == 'post': req_func = session.post if session else requests.post headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'application/json,text/plain,text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'X-Requested-With': 'XMLHttpRequest', 'accept-encoding': 'gzip, deflate, br,zstd' } response = req_func( url, params=params, data=data, verify=False, headers=headers ) res_time = (time.time() - start) * 1000 if response.status_code in [200, 302]: SUC_RES['resTime'] = int(res_time) SUC_RES['message'].append(f"请求 {url} 成功") return response else: FAIL_RES['error'] = f"请求失败,状态码: {response.status_code}, 响应内容: {response.text}, 头信息:{session.headers if session else None}" FAIL_RES['message'].append(f"请求 {url} 失败") return None except Exception as e: print_err_result(f"请求过程中发生错误: {str(e)}") return None def cas_login(username, password) -> Tuple[Optional[str], Optional[dict]]: # 使用会话保持cookies session = requests.Session() token = None try: # 第一步:获取lt令牌 params1 = { 'service': 'https://www.fifedu.com/iplat/ssoservice', 'get-lt': 'true', 'n': str(int(time.time() * 1000)), 'callback': 'jsonpcallback', '_': str(int(time.time() * 1000)) } url1 = "https://cycore.fifedu.com/cas-server/login" response1 = make_request(url1, params=params1, session=session) if not response1: return None, {} # 1. 检查响应是否以jsonpcallback开头 if not response1.text.startswith('jsonpcallback'): raise ValueError("响应格式不符合预期,不是JSONP格式") # 2. 提取括号内的JSON部分 json_str = response1.text[len('jsonpcallback('):-2] # 去掉首尾的jsonpcallback(和); # 3. 将字符串解析为字典 try: data = json.loads(json_str) except json.JSONDecodeError: raise ValueError("JSON解析失败,响应内容: " + response1.text) # 4. 提取所需的值 lt = data.get('lt', '') execution = data.get('execution', '') if not lt or not execution: raise ValueError("响应中缺少lt或execution字段") # 第二步:提交登录表单 # 注意:密码是base64编码的,但这里我们直接使用传入的密码(原始代码中密码是base64编码的,所以这里我们直接使用) # 实际上,在登录请求中,密码应该是明文还是编码?根据观察,原始代码中密码是base64编码的,但登录表单提交的是原始密码还是编码后的? # 由于我们传入的password已经是base64编码(从get_credentials中获取的),但实际登录接口可能需要明文,所以这里需要先解码? # 但是,在原始代码中,密码是直接以base64字符串形式传入的,而登录接口是否要求base64编码?需要根据实际接口要求。 # 由于我们不清楚,所以先按照原始代码的方式,直接传入base64字符串作为密码。 data2 = { 'service': 'https://cycore.fifedu.com/iplat/ssoservice', 'callback': 'logincallback', 'isajax': 'true', 'isframe': 'true', '_eventId': 'submit', 'serviceURL': 'null', 'lt': lt, 'type': 'pwd', 'execution': execution, 'username': username, 'password': password, '_': str(int(time.time() * 1000)) } url2 = "https://cycore.fifedu.com/cas-server/login" response2 = make_request(url2, data=data2, method='post', session=session) if not response2: return None, {} # 检查登录是否成功 response_text = response2.text.strip() if response_text.startswith("logincallback"): json_str = response_text[len("logincallback("):-2] try: login_result = json.loads(json_str) except json.JSONDecodeError: raise ValueError("登录响应JSON解析失败: " + response_text) token = login_result.get("token", "") ticket = login_result.get("ticket", "") if not token or not ticket: raise ValueError("登录响应中缺少token或ticket") else: raise ValueError("登录响应格式不符合预期: " + response_text) # 第三步:ssosevice跳转 params3 = { 'callback': f'jQuery{int(time.time()*1000000)}_{int(time.time()*1000)}', 'action': 'login', '_': str(int(time.time() * 1000)) } # 更新请求头,注意:这里我们不再手动设置Cookie,而是由session自动管理 session.headers.update({ 'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': '*/*', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' }) url3 = "https://www.fifedu.com/iplat/ssoservice" response3 = session.get( url3, params=params3, allow_redirects=True, verify=False ) if not response3: return None, {} # 第四步:跳转到目标页面 params4 = { 'nextPage': 'https://assess.fifedu.com/testcenter/home/teacher_index', } url4 = "https://www.fifedu.com/iplat/ssoservice" # 注意:这里我们不再手动设置Cookie头,而是由session自动管理 session.headers.update({ 'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'priority': 'u=0, i', 'Upgrade-Insecure-Requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', }) response4 = session.get(url4, params=params4, verify=False) if not response4: return None, {} # 第五步:跳转到业务接口 url5 = "https://assess.fifedu.com/testcenter/home/getUser" session.headers.update({ 'Referer': 'https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': '*/*', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'priority': 'u=0, i', 'Cookie': f'prod-token={token}', # 这里设置token到Cookie,但注意session可能已经自动管理了,所以这一步可能是多余的,因为后面会使用这个token 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' }) response5 = session.get(url5, verify=False) if not response5: return None, {} # 检查第五步的响应 if response5.status_code != 200: raise ValueError(f"获取用户信息失败,状态码: {response5.status_code}") # 获取session中的cookies sess = session.cookies.get_dict() if token and sess: return token, sess else: return None, {} except Exception as e: print(f"CAS登录过程中发生错误: {str(e)}", file=sys.stderr) return None, {} def get_credentials(): username = "jffwbc1" password = "R2pjcHgxMjMhQCM=" # base64编码的密码 # 注意:这里我们不进行解码,因为登录函数中直接使用了这个base64字符串作为密码。 # 但是,根据实际接口,可能需要明文密码,那么就需要先解码: # password = base64.b64decode(password).decode('utf-8') # 但是,原始代码中直接使用base64字符串作为密码,所以我们先保持原样。 return cas_login(username, password) if __name__ == '__main__': username = "jffwbc1" password = "R2pjcHgxMjMhQCM=" token, sess = cas_login(username, password) if token and sess: print("登录成功!") print(f"Token: {token}") print(f"Session Cookies: {sess}") else: print("登录失败!") zaixian.py脚本的原始脚本内容是: #!/usr/bin/python3 # coding=utf-8 from zaixian_cas_login import HttpResponseProcessor from zaixian_cas_login import get_credentials import sys import time import requests import json import ast from urllib.parse import urlparse, urljoin SUC_RES = { 'resCode': 200, 'resTime': 0, 'keyword': 'SUCCESS', 'message': "调用成功", 'apiMessage': None } FAIL_RES = { 'resCode': 500, 'resTime': 0, 'keyword': 'FAILED', 'message': "调用失败", 'apiMessage': None } def print_err_result(e): FAIL_RES['error'] = str(e) print(json.dumps(FAIL_RES, ensure_ascii=False)) exit(1) def parse_params(params): """更健壮的参数解析函数,支持多种格式""" if not params or params.lower() == 'null': return {} # 尝试直接解析为标准JSON try: return json.loads(params) except json.JSONDecodeError: pass # 尝试解析为Python字面量(支持单引号) try: return ast.literal_eval(params) except (ValueError, SyntaxError): pass # 尝试处理类JSON格式(单引号) try: sanitized = params.replace("'", '"') return json.loads(sanitized) except json.JSONDecodeError: pass raise ValueError(f"无法解析的参数格式: {params}") def _requests(full_url, params='{}'): try: # 解析参数 pars = parse_params(params) # 获取请求参数 data = pars.get('data', {}) method = pars.get('method', 'GET').upper() expected_message = pars.get('expectedMessage', None) # 添加协议前缀 if not full_url.startswith(('http://', 'https://')): full_url = 'https://' + full_url # 验证URL格式 parsed_url = urlparse(full_url) if not parsed_url.netloc: raise ValueError("无效的URL格式,缺少域名部分") # 确保路径正确 if not parsed_url.path.startswith('/'): full_url = urljoin(full_url, '/') isSuccess = True start = time.time() response_data = None api_message = None try: # 获取认证信息 token, sess = get_credentials() if token is None or sess is None: raise ValueError("无法获取有效的token或session") # 设置请求头 headers = { 'Cookie': f'prod-token={token}', 'Content-Type': 'application/json' } # 执行请求 if method == 'POST': res = requests.post(url=full_url, json=data, headers=headers, verify=False) else: res = requests.get(url=full_url, params=data, headers=headers, verify=False) # 处理响应 processor = HttpResponseProcessor(full_url, headers=res.headers) processor.response = res processor.raw_content = res.content processor.status_code = res.status_code try: # 解码内容 text_content = processor.decode_content() # 尝试解析JSON try: response_data = json.loads(text_content) api_message = response_data.get('message', None) except json.JSONDecodeError: response_data = {'raw_response': text_content} except Exception as e: raise e except requests.exceptions.SSLError as e: raise Exception('SSL证书验证失败') from e except Exception as e: raise Exception('调用出现异常') from e # 计算耗时 res_time = (time.time() - start) * 1000 # 获取状态码 try: statusCode = response_data.get('statusCode', res.status_code) except AttributeError: statusCode = res.status_code # 判断请求结果 if res.status_code != 200: isSuccess = False FAIL_RES['message'] = f"HTTP状态码错误: {res.status_code}" FAIL_RES['apiMessage'] = api_message or getattr(response_data, 'resInfo', '') elif statusCode != 200: isSuccess = False try: res_info = response_data.get('responseBody', '') or response_data.get('resInfo', '') FAIL_RES['message'] = f"业务状态码错误: {statusCode} - {res_info}" FAIL_RES['apiMessage'] = api_message except (ValueError, AttributeError): FAIL_RES['message'] = '解析响应内容失败' # 处理预期消息验证 if expected_message is not None and api_message != expected_message: isSuccess = False FAIL_RES['message'] = f"消息验证失败: 预期 '{expected_message}', 实际 '{api_message}'" FAIL_RES['apiMessage'] = api_message # 输出结果 if isSuccess: SUC_RES['resTime'] = int(res_time) SUC_RES['apiMessage'] = api_message print(json.dumps(SUC_RES, ensure_ascii=False)) else: FAIL_RES['resTime'] = int(res_time) print(json.dumps(FAIL_RES, ensure_ascii=False)) except Exception as e: print_err_result(e) if __name__ == '__main__': args = sys.argv[1:] if len(args) < 1: print_err_result(''' 参数不足 用法: ./http_requests.py 完整URL [JSON参数] 示例: ./http_requests.py "api.example.com/endpoint" '{"data":{"key":"value"}, "method":"POST"}' 注意: JSON参数需要使用单引号包裹,内部使用双引号 ''') full_url = args[0] params = args[1] if len(args) > 1 else '{}' _requests(full_url, params)
08-07
我现在有两个脚本,login.py和zaixian.py,两个脚本有依赖关系,现在需要你根据zaixian.py的脚本逻辑重新编写一个业务脚本,处理接口请求 login.py的原始脚本内容是: #!/usr/bin/python3 # coding=utf-8 import io import sys import time import requests import json import re import base64 from urllib.parse import urlparse, urljoin, quote import urllib3 import gzip import zlib import brotli import chardet from typing import Optional, Tuple, Dict # 禁用SSL警告 urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning) sys.stdout = io.TextIOWrapper(sys.stdout.buffer, encoding='utf-8') SUC_RES = { 'resCode': 200, 'resTime': 0, 'keyword': 'SUCCESS', 'message': [] } FAIL_RES = { 'resCode': 500, 'resTime': 0, 'keyword': 'FAILED', 'message': [] } # 封装解码 class HttpResponseProcessor: def __init__(self, url: str, headers: Optional[Dict] = None): """ 初始化响应处理器 :param url: 请求的URL :param headers: 请求头,默认为None """ self.url = url self.headers = headers or {} self.response = None self.raw_content = None self.text_content = None self.encoding = None self.status_code = None def fetch_response(self): """ 发送HTTP请求并获取响应 :return: None """ try: self.response = requests.get( url=self.url, headers=self.headers, allow_redirects=False, # 禁用自动重定向 stream=True, # 流模式获取原始响应 verify=False ) self.status_code = self.response.status_code self.raw_content = self.response.content except Exception as e: raise Exception(f"请求失败: {str(e)}") def print_response_headers(self): """ 打印响应头信息 :return: None """ if not self.response: raise Exception("尚未获取响应,请先调用 fetch_response()") def decode_content(self) -> str: """ 尝试解码内容为文本 :return: 解码后的文本内容 """ if not self.raw_content: raise Exception("尚未获取原始内容,请先调用 fetch_response()") try: # 检测内容编码 result = chardet.detect(self.raw_content) encoding_detected = result.get('encoding') if result else None # 尝试解码 if encoding_detected: try: self.text_content = self.raw_content.decode(encoding_detected) self.encoding = encoding_detected return self.text_content except UnicodeDecodeError: # 如果检测到的编码解码失败,则尝试其他编码 pass # 尝试常见编码 for encoding in ['utf-8', 'gbk', 'gb2312', 'latin1']: try: self.text_content = self.raw_content.decode(encoding) self.encoding = encoding break except: continue else: # 如果都无法解码,则使用替换错误字符的方式解码 try: self.text_content = self.raw_content.decode('utf-8', errors='replace') self.encoding = 'utf-8' except: self.text_content = "无法解码内容" self.encoding = None return self.text_content except Exception as e: # 将内容保存到文件以便分析 with open('response.bin', 'wb') as f: f.write(self.raw_content) raise Exception(f"内容解码失败: {str(e)}") def process_response(self) -> Tuple[int, Optional[str], Optional[str]]: """ 完整处理响应的便捷方法 :return: (status_code, text_content, encoding) """ self.fetch_response() self.print_response_headers() text = self.decode_content() return self.status_code, text, self.encoding def print_err_result(e): FAIL_RES['error'] = e exit(1) def make_request(url, params=None, data=None, method='get', session=None): try: start = time.time() req_func = session.get if session else requests.get if method.lower() == 'post': req_func = session.post if session else requests.post headers = { 'User-Agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.124 Safari/537.36', 'Accept': 'application/json,text/plain,text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'X-Requested-With': 'XMLHttpRequest', 'accept-encoding': 'gzip, deflate, br,zstd' } response = req_func( url, params=params, data=data, verify=False, headers=headers ) res_time = (time.time() - start) * 1000 if response.status_code in [200, 302]: SUC_RES['resTime'] = int(res_time) SUC_RES['message'].append(f"请求 {url} 成功") return response else: FAIL_RES[ 'error'] = f"请求失败,状态码: {response.status_code}, 响应内容: {response.text}, 头信息:{session.headers if session else None}" FAIL_RES['message'].append(f"请求 {url} 失败") return None except Exception as e: print_err_result(f"请求过程中发生错误: {str(e)}") return None def cas_login(username, password) -> Tuple[Optional[str], Optional[dict]]: # 使用会话保持cookies session = requests.Session() token = None try: # 第一步:获取lt令牌 params1 = { 'service': 'https://www.fifedu.com/iplat/ssoservice', 'get-lt': 'true', 'n': str(int(time.time() * 1000)), 'callback': 'jsonpcallback', '_': str(int(time.time() * 1000)) } url1 = "https://cycore.fifedu.com/cas-server/login" response1 = make_request(url1, params=params1, session=session) if not response1: return None, {} # 1. 检查响应是否以jsonpcallback开头 if not response1.text.startswith('jsonpcallback'): raise ValueError("响应格式不符合预期,不是JSONP格式") # 2. 提取括号内的JSON部分 json_str = response1.text[len('jsonpcallback('):-2] # 去掉首尾的jsonpcallback(和); # 3. 将字符串解析为字典 try: data = json.loads(json_str) except json.JSONDecodeError: raise ValueError("JSON解析失败,响应内容: " + response1.text) # 4. 提取所需的值 lt = data.get('lt', '') execution = data.get('execution', '') if not lt or not execution: raise ValueError("响应中缺少lt或execution字段") # 第二步:提交登录表单 # 注意:密码是base64编码的,但这里我们直接使用传入的密码(原始代码中密码是base64编码的,所以这里我们直接使用) # 实际上,在登录请求中,密码应该是明文还是编码?根据观察,原始代码中密码是base64编码的,但登录表单提交的是原始密码还是编码后的? # 由于我们传入的password已经是base64编码(从get_credentials中获取的),但实际登录接口可能需要明文,所以这里需要先解码? # 但是,在原始代码中,密码是直接以base64字符串形式传入的,而登录接口是否要求base64编码?需要根据实际接口要求。 # 由于我们不清楚,所以先按照原始代码的方式,直接传入base64字符串作为密码。 data2 = { 'service': 'https://cycore.fifedu.com/iplat/ssoservice', 'callback': 'logincallback', 'isajax': 'true', 'isframe': 'true', '_eventId': 'submit', 'serviceURL': 'null', 'lt': lt, 'type': 'pwd', 'execution': execution, 'username': username, 'password': password, '_': str(int(time.time() * 1000)) } url2 = "https://cycore.fifedu.com/cas-server/login" response2 = make_request(url2, data=data2, method='post', session=session) if not response2: return None, {} # 检查登录是否成功 response_text = response2.text.strip() if response_text.startswith("logincallback"): json_str = response_text[len("logincallback("):-2] try: login_result = json.loads(json_str) except json.JSONDecodeError: raise ValueError("登录响应JSON解析失败: " + response_text) token = login_result.get("token", "") ticket = login_result.get("ticket", "") if not token or not ticket: raise ValueError("登录响应中缺少token或ticket") else: raise ValueError("登录响应格式不符合预期: " + response_text) # 第三步:ssosevice跳转 params3 = { 'callback': f'jQuery{int(time.time() * 1000000)}_{int(time.time() * 1000)}', 'action': 'login', '_': str(int(time.time() * 1000)) } # 更新请求头,注意:这里我们不再手动设置Cookie,而是由session自动管理 session.headers.update({ 'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': '*/*', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' }) url3 = "https://www.fifedu.com/iplat/ssoservice" response3 = session.get( url3, params=params3, allow_redirects=True, verify=False ) if not response3: return None, {} # 第四步:跳转到目标页面 params4 = { 'nextPage': 'https://assess.fifedu.com/testcenter/home/teacher_index', } url4 = "https://www.fifedu.com/iplat/ssoservice" # 注意:这里我们不再手动设置Cookie头,而是由session自动管理 session.headers.update({ 'Referer': 'https://www.fifedu.com/iplat/fifLogin/scuai/index.html?service=https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': 'text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'cache-control': 'no-cache', 'pragma': 'no-cache', 'priority': 'u=0, i', 'Upgrade-Insecure-Requests': '1', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36', }) response4 = session.get(url4, params=params4, verify=False) if not response4: return None, {} # ...(前面的代码保持不变)... # 第五步:跳转到业务接口 url5 = "https://assess.fifedu.com/testcenter/home/getUser" session.headers.update({ 'Referer': 'https://assess.fifedu.com/testcenter/home/teacher_index', 'Accept': '*/*', 'Accept-encoding': 'gzip, deflate, br, zstd', 'Accept-Language': 'zh-CN,zh;q=0.9', 'priority': 'u=0, i', 'Cookie': f'prod-token={token}', # 设置token到Cookie 'cache-control': 'no-cache', 'pragma': 'no-cache', 'user-agent': 'Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/138.0.0.0 Safari/537.36' }) response5 = session.get(url5, verify=False) if not response5: return None, {} # 检查第五步的响应 print(f"\n===== 第五步响应信息 =====") print(f"响应码: {response5.status_code}") print(f"响应内容 (前1000字符):\n{response5.text[:1000]}") print(f"Session Cookies: {session.cookies.get_dict()}") print(f"Token: {token}") print("=" * 30) # 获取session中的cookies sess = session.cookies.get_dict() if token and sess: return token, sess else: return None, {} # ...(后面的代码保持不变)... except Exception as e: print(f"CAS登录过程中发生错误: {str(e)}", file=sys.stderr) return None, {} def get_credentials(): username = "jffwbc1" password = "R2pjcHgxMjMhQCM=" # base64编码的密码 # 注意:这里我们不进行解码,因为登录函数中直接使用了这个base64字符串作为密码。 # 但是,根据实际接口,可能需要明文密码,那么就需要先解码: # password = base64.b64decode(password).decode('utf-8') # 但是,原始代码中直接使用base64字符串作为密码,所以我们先保持原样。 return cas_login(username, password) if __name__ == '__main__': username = "jffwbc1" password = "R2pjcHgxMjMhQCM=" token, sess = cas_login(username, password) if token and sess: print("登录成功!") print(f"Token: {token}") print(f"Session Cookies: {sess}") else: print("登录失败!") zaixian.py的原始脚本内容是: #!/usr/bin/python3 # coding=utf-8 from zaixian_cas_login import HttpResponseProcessor from zaixian_cas_login import get_credentials import sys import time import requests import json from urllib.parse import urlparse, urljoin SUC_RES = { 'resCode': 200, 'resTime': 0, 'keyword': 'SUCCESS', 'message': "调用成功", 'apiMessage': None # 新增字段,用于存储接口返回的message } FAIL_RES = { 'resCode': 500, 'resTime': 0, 'keyword': 'FAILED', 'message': "调用失败", 'apiMessage': None # 新增字段,用于存储接口返回的message } def print_err_result(e): FAIL_RES['error'] = e print(json.dumps(FAIL_RES, ensure_ascii=False)) exit(1) def _requests(full_url, params='{}'): try: # 处理null参数的情况 if params is None or params.lower() == 'null': params = '{}' # 解析参数 param = params.replace("'", '"') pars = json.loads(param) # 获取请求数据,默认为空字典 data = pars.get('data', {}) # 获取请求方法,默认为GET method = pars.get('method', 'GET').upper() # 获取期望的message值,用于断言判断 expected_message = pars.get('expectedMessage', None) # 添加协议前缀(如果不存在) if not full_url.startswith(('http://', 'https://')): full_url = 'https://' + full_url # 解析URL以验证格式 parsed_url = urlparse(full_url) if not parsed_url.netloc: raise ValueError("无效的URL格式,缺少域名部分") # 确保路径以/开头 if not parsed_url.path.startswith('/'): full_url = urljoin(full_url, '/') isSuccess = True start = time.time() response_data = None # 用于存储解析后的响应数据 api_message = None # 用于存储接口返回的message try: # 获取token和session token, sess = get_credentials() if token is None or sess is None: raise ValueError("无法获取有效的token或session") # 设置请求头,包括Cookie和Session headers = { 'Cookie': f'prod-token={token}', 'Content-Type': 'application/json' } # 根据method参数决定使用GET还是POST if method == 'POST': res = requests.post(url=full_url, json=data, headers=headers, verify=False) else: res = requests.get(url=full_url, json=data, headers=headers, verify=False) # 新增解压解码处理过程 processor = HttpResponseProcessor(full_url, headers=res.headers) processor.response = res processor.raw_content = res.content processor.status_code = res.status_code try: # 解码内容 text_content = processor.decode_content() # 如果内容是JSON,可以解析为字典 try: response_data = json.loads(text_content) # 尝试获取接口返回的message字段 api_message = response_data.get('message', None) except json.JSONDecodeError: pass except Exception as e: raise e except requests.exceptions.SSLError as e: message = 'SSL证书验证失败' FAIL_RES['message'] = message print_err_result(str(e)) except Exception as e: message = '调用出现异常' FAIL_RES['message'] = message print_err_result(str(e)) # 计算耗时 res_time = (time.time() - start) * 1000 # 解析响应 try: if response_data is None: response_data = res.json() statusCode = response_data.get('statusCode', res.status_code) except ValueError: statusCode = res.status_code # 判断请求是否成功 if 200 != res.status_code: isSuccess = False FAIL_RES['error'] = '调用网关拨测中间服务失败' message = 'resCode:' + str(res.status_code) FAIL_RES['message'] = message FAIL_RES['apiMessage'] = api_message if 200 != statusCode: isSuccess = False FAIL_RES['error'] = '调用失败' try: message = 'resInfo:' + str(response_data.get('responseBody', 'No response body')) FAIL_RES['message'] = message FAIL_RES['apiMessage'] = api_message except (ValueError, AttributeError): message = 'resInfo: Invalid JSON response' FAIL_RES['message'] = message FAIL_RES['apiMessage'] = api_message # 成功处理 SUC_RES['resTime'] = int(res_time) SUC_RES['apiMessage'] = api_message # 如果有预期的message值,进行断言判断 if expected_message is not None and api_message != expected_message: isSuccess = False FAIL_RES['error'] = 'message断言失败' FAIL_RES['message'] = f"接口返回的message({api_message})与预期({expected_message})不符" FAIL_RES['apiMessage'] = api_message # 输出结果 if isSuccess: print(json.dumps(SUC_RES, ensure_ascii=False)) else: print(json.dumps(FAIL_RES, ensure_ascii=False)) except json.JSONDecodeError as e: print_err_result(f"JSON参数解析失败: {str(e)}") except ValueError as e: print_err_result(str(e)) except Exception as e: print_err_result(f"未知错误: {str(e)}") if __name__ == '__main__': # args = sys.argv[1:] # if len(args) < 1: # raise Exception(''' # 参数不足 # 用法: ./http_requests.py 完整URL [JSON参数] # 示例: ./http_requests.py "https://api.example.com/endpoint" '{"data":{"key":"value"}, "method":"POST", "expectedMessage":"预期消息"}' # ''') # # full_url = args[0] # # 处理null参数的情况 # params = args[1] if len(args) > 1 and args[1].lower() != 'null' else '{}' full_url = "https://assess.fifedu.com/testcenter/home/getSysSubjectList" params = '{"data":{"isSwitch":""},"method":"POST"}' _requests(full_url, params) 我现在有两个脚本,login.py和zaixian.py,两个脚本有依赖关系,现在需要你根据zaixian.py的脚本逻辑重新编写一个业务脚本,处理接口请求
最新发布
08-08
我感觉你完全忘记了我前面给你的代码,我再给你一次你不要搞错了/* * Copyright (c) 2021, Texas Instruments Incorporated * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * * Neither the name of Texas Instruments Incorporated nor the names of * its contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR * OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */ #include "ti_msp_dl_config.h" #include "motor.h" #include "PID.h" #include "xunji.h" #include "atk_ms901m.h" int16_t baseSpeed = 65; //int16_t SpeedL = 0, SpeedR = 0; float Kp = 0.5; //uint8_t key1_flag; volatile int32_t enco_l; volatile int32_t enco_r; volatile uint32_t gpioA,gpioB; void GROUP1_IRQHandler(void); uint8_t A; void encoder_update(void); volatile int32_t enco_l_last,enco_r_last; float speed_l,speed_r; uint32_t gEchoData; uint32_t date[20]; uint8_t date_index; atk_ms901m_attitude_data_t attitude_dat; // 全局变量 PID pid_left; // 左轮PID控制器 PID pid_right; // 右轮PID控制器 float target_speed_l = 200.0f; // 左轮目标速度(mm/s) float target_speed_r = 200.0f; // 右轮目标速度(mm/s) // 初始化PID(示例参数,需调试) void PID_Init_Config(void) { PID_Init(&pid_left, 2.0f, 0.5f, 0.1f, 100.0f); // P=2, I=0.5, D=0.1,输出限幅±100 PID_Init(&pid_right, 2.0f, 0.5f, 0.1f, 100.0f); // 左右轮参数可相同或独立调整 } int main(void) { SYSCFG_DL_init(); NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN); NVIC_EnableIRQ(UART_0_INST_INT_IRQN); NVIC_EnableIRQ(ENCODER_GPIOA_INT_IRQN); NVIC_EnableIRQ(ENCODER_GPIOB_INT_IRQN); NVIC_ClearPendingIRQ(TIMER_0_INST_INT_IRQN); NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN); PID_Init_Config(); // 添加PID初始化 //if(!DL_GPIO_readPins(GPIO_KEY_PORT,GPIO_KEY_PIN_KEY1_PIN)) //{ //delay_cycles(32000000); Set_Speed(0, baseSpeed); Set_Speed(1, baseSpeed); Motor_On(); //} //DL_GPIO_clearPins(GPIO_LED_PORT,GPIO_LED_PIN_0_PIN); atk_ms901m_uart_init();//初始化函数 atk_ms901m_get_attitude(&attitude_dat, 100);//获取参数,第一个参数是结构体,第二个参数是out time(s失联时间) while (1) { xunji(); //if(gEchoData==49) //DL_GPIO_clearPins(GPIO_LED_PORT,GPIO_LED_PIN_0_PIN); } } void encoder_update(void) { // 关闭全局中断(或仅关闭编码器中断) __disable_irq(); enco_l_last = enco_l; enco_r_last = enco_r; enco_l = 0; enco_r = 0; // 恢复全局中断 __enable_irq(); // 计算速度(单位:mm/s,假设车轮周长=π*直径=3.14*65mm=204.1mm) // 公式:速度 = (脉冲数 / 每圈脉冲数) * 周长 / 时间周期 // 每圈脉冲数 = 编码器线数 × 减速比 = 13 × 28 = 364脉冲/圈 // 时间周期=20ms(假设定时器中断周期为20ms,即0.02秒) float per_round_pulse = 13.0f * 28.0f; // 364脉冲/圈 float wheel_circumference = 3.14159f * 65.0f; // 车轮周长(mm) float time_period = 0.02f; // 20ms = 0.02秒 speed_l = (enco_l_last / per_round_pulse) * wheel_circumference / time_period; // 左轮速度(mm/s) speed_r = (enco_r_last / per_round_pulse) * wheel_circumference / time_period; // 右轮速度(mm/s) } void speed_control(void) { //zuolun pid float duty_l = PID_Realize(&pid_left, speed_l, target_speed_l); //youlun pid float duty_r = PID_Realize(&pid_right, speed_r, target_speed_r); //通过Set_Speed输出PWM占空比(duty已由PID限幅为-100~100) Set_Speed(0, (int8_t)duty_l); // 0=左轮,传递计算的duty值 Set_Speed(1, (int8_t)duty_r); // 1=右轮 } void GROUP1_IRQHandler(void) { //获取中断信号 gpioA = DL_GPIO_getEnabledInterruptStatus(GPIOA,ENCODER_E2A_PIN); gpioB = DL_GPIO_getEnabledInterruptStatus(GPIOB,ENCODER_E1A_PIN); //ENCODER RIGHT if((gpioA&ENCODER_E2A_PIN)!=0) { if(!DL_GPIO_readPins(ENCODER_E2B_PORT,ENCODER_E2B_PIN)) { enco_r++; } else { enco_r--; } DL_GPIO_clearInterruptStatus(GPIOA, ENCODER_E2A_PIN); } //ENCODER LEFT if((gpioB&ENCODER_E1A_PIN)!=0) { DL_GPIO_clearPins(GPIO_LED_PORT,GPIO_LED_PIN_0_PIN); if(!DL_GPIO_readPins(ENCODER_E1B_PORT,ENCODER_E1B_PIN)) { enco_l++; } else { enco_l--; } DL_GPIO_clearInterruptStatus(GPIOB,ENCODER_E1A_PIN); } } void TIMER_0_INST_IRQHandler(void) { switch (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) { case DL_TIMERA_IIDX_ZERO: // 处理计数到零的中断 encoder_update(); speed_control(); break; default: break; } } void UART_0_INST_IRQHandler(void) { switch (DL_UART_Main_getPendingInterrupt(UART_0_INST)) { /*! UART interrupt index for UART receive */ case DL_UART_MAIN_IIDX_RX: gEchoData = DL_UART_Main_receiveData(UART_0_INST); DL_GPIO_clearPins(GPIO_LED_PORT,GPIO_LED_PIN_0_PIN); //DL_UART_Main_transmitData(UART_0_INST, gEchoData); break; default: break; } } void Motor_On(void) { DL_GPIO_setPins(GPIO_MOTOR_PIN_STBY_PORT, GPIO_MOTOR_PIN_STBY_PIN); //DL_GPIO_clearPins(GPIO_LED_PORT,GPIO_LED_PIN_0_PIN); } void Motor_Off(void) { DL_GPIO_clearPins(GPIO_MOTOR_PIN_STBY_PORT, GPIO_MOTOR_PIN_STBY_PIN); DL_GPIO_clearPins(GPIO_MOTOR_PIN_L1_PORT, GPIO_MOTOR_PIN_L1_PIN); DL_GPIO_clearPins(GPIO_MOTOR_PIN_L2_PORT, GPIO_MOTOR_PIN_L2_PIN); DL_GPIO_clearPins(GPIO_MOTOR_PIN_R1_PORT, GPIO_MOTOR_PIN_R1_PIN); DL_GPIO_clearPins(GPIO_MOTOR_PIN_R2_PORT, GPIO_MOTOR_PIN_R2_PIN); } void Set_Speed(uint8_t side, int8_t duty) { uint32_t compareValue = 0; if(!side) { if(duty<0) { compareValue = 3199 - 3199 * (-duty/100.0); DL_TimerA_setCaptureCompareValue(PWM_MOTOR_INST, compareValue, DL_TIMER_CC_0_INDEX); DL_GPIO_setPins(GPIO_MOTOR_PIN_L1_PORT, GPIO_MOTOR_PIN_L1_PIN); DL_GPIO_clearPins(GPIO_MOTOR_PIN_L2_PORT, GPIO_MOTOR_PIN_L2_PIN); } else if(duty>0) { compareValue = 3199 - 3199 * (duty/100.0); DL_TimerA_setCaptureCompareValue(PWM_MOTOR_INST, compareValue, DL_TIMER_CC_0_INDEX); DL_GPIO_clearPins(GPIO_MOTOR_PIN_L1_PORT, GPIO_MOTOR_PIN_L1_PIN); DL_GPIO_setPins(GPIO_MOTOR_PIN_L2_PORT, GPIO_MOTOR_PIN_L2_PIN); } else { DL_GPIO_clearPins(GPIO_MOTOR_PIN_L1_PORT, GPIO_MOTOR_PIN_L1_PIN); DL_GPIO_clearPins(GPIO_MOTOR_PIN_L2_PORT, GPIO_MOTOR_PIN_L2_PIN); } } else { if(duty<0) { compareValue = 3199 - 3199 * (-duty/100.0); DL_TimerA_setCaptureCompareValue(PWM_MOTOR_INST, compareValue, DL_TIMER_CC_1_INDEX); DL_GPIO_setPins(GPIO_MOTOR_PIN_R1_PORT, GPIO_MOTOR_PIN_R1_PIN); DL_GPIO_clearPins(GPIO_MOTOR_PIN_R2_PORT, GPIO_MOTOR_PIN_R2_PIN); } else if(duty>0) { compareValue = 3199 - 3199 * (duty/100.0); DL_TimerA_setCaptureCompareValue(PWM_MOTOR_INST, compareValue, DL_TIMER_CC_1_INDEX); DL_GPIO_clearPins(GPIO_MOTOR_PIN_R1_PORT, GPIO_MOTOR_PIN_R1_PIN); DL_GPIO_setPins(GPIO_MOTOR_PIN_R2_PORT, GPIO_MOTOR_PIN_R2_PIN); } else { DL_GPIO_clearPins(GPIO_MOTOR_PIN_R1_PORT, GPIO_MOTOR_PIN_R1_PIN); DL_GPIO_clearPins(GPIO_MOTOR_PIN_R2_PORT, GPIO_MOTOR_PIN_R2_PIN); } } }#include "PID.h" /*! * @brief PID * @since v1.0 * *pid: PID参数 * NowPlace:当前位置 * Point: 预期位置 */ void PID_Init(PID *pid, float p, float i, float d, float limit){ pid->P = p; pid->I = i; pid->D = d; pid->limit = limit; pid->realize = 0.0f; pid->Current_Error = 0; pid->Last_Error = 0; pid->Previous_Error = 0; pid->Increase = 0; } // 位置式PID控制 float PID_Realize(PID *pid, float NowPlace, float Point) { float iError; // 当前误差 float Realize; //实际输出 iError = Point - NowPlace; // 计算当前误差 pid->Current_Error += pid->I * iError; // 误差积分 pid->Current_Error = pid->Current_Error > pid->limit?pid->limit:pid->Current_Error;//积分限幅 pid->Current_Error = pid->Current_Error <-pid->limit?-pid->limit:pid->Current_Error; Realize = pid->P * iError //比例P + pid->Current_Error //积分I + pid->D * (iError - pid->Last_Error); //微分D pid->Last_Error = iError; // 更新上次误差 pid->realize = Realize; if(pid->limit<pid->realize) pid->realize = pid->limit; else if (-pid->limit>pid->realize) pid->realize = -pid->limit; return pid->realize; // 返回实际值 } /*! * @brief 增量式PID * @since v1.0 * *pid :误差参数 * *pid: PID参数 * NowPlace:实际值 * Point: 期望值 */ //增量式PID电机控制 float PID_Increase(PID *pid, float NowPlace, float Point) { float iError; //当前误差 float Increase; //最后得出的实际增量 iError = Point - NowPlace; // 计算当前误差 Increase = pid->P * (iError - pid->Last_Error) //比例P + pid->I * iError //积分I + pid->D * (iError - 2 * pid->Last_Error + pid->Previous_Error); //微分D pid->Previous_Error = pid->Last_Error; // 更新前次误差 pid->Last_Error = iError; // 更新上次误差 pid->Increase = Increase; pid->realize = pid->realize + Increase; if(pid->limit<pid->realize) pid->realize = pid->limit; else if (-pid->limit>pid->realize) pid->realize = -pid->limit; return pid->realize; // 返回实际值 }
07-15
<think>好的,我现在需要帮用户解决如何结合他提供的代码实现小车转弯的问题。首先,用户已经提供了电机驱动、PID控制、编码器和陀螺仪(ATK-MS901M)的代码,我需要将这些整合起来,实现基于陀螺仪和编码器的闭环转弯控制。 首先,分析用户现有的代码结构: 1. **主函数(main)**:初始化了系统、PID、电机、编码器中断和陀螺仪,并在循环中执行寻迹(xunji())。 2. **编码器处理**:通过GPIO中断(GROUP1_IRQHandler)更新左右轮的脉冲计数(enco_l和enco_r),并在定时器中断(TIMER_0_INST_IRQHandler)中调用encoder_update()计算速度(speed_l和speed_r),然后调用speed_control()进行速度PID控制。 3. **PID控制**:使用位置式PID(PID_Realize)计算电机占空比,通过Set_Speed函数输出到电机。 4. **陀螺仪**:初始化了ATK-MS901M模块,并在main中获取了初始姿态数据(attitude_dat)。 用户的需求是实现小车的精确转弯(如直角转弯),需要结合陀螺仪的角度反馈和编码器的速度反馈,形成串级PID控制(角度环→速度环)。 接下来,我需要考虑如何在现有代码基础上添加转弯逻辑: 1. **陀螺仪角度获取**:用户已经有atk_ms901m_get_attitude函数,可以获取当前的姿态角(roll/pitch/yaw)。需要在转弯时持续读取yaw角,计算相对于初始位置的偏移。 2. **角度环PID**:外环PID的输入是目标角度与当前角度的误差,输出是左右轮的速度差(或目标速度调整量)。 3. **速度环PID**:内环PID的输入是左右轮的目标速度(由角度环调整后的值)和实际速度(由编码器计算),输出是电机PWM占空比。 4. **转弯逻辑触发**:在主循环或某个触发条件(如按键、传感器信号)下调用转弯函数,设置目标角度,启动闭环控制,直到达到目标角度。 需要注意的问题: - 用户现有的speed_control函数是独立的速度环,需要修改为接收来自角度环的目标速度调整。 - 陀螺仪的yaw角可能存在漂移,需要处理角度环绕(如从350°到10°实际是20°)。 - 转弯时的基础速度需要合理设置,避免打滑影响角度测量。 - 中断优先级:确保定时器中断(处理速度控制)和陀螺仪UART中断的优先级不会导致数据丢失。 现在,具体整合步骤: 1. **添加全局变量**:记录初始yaw角、目标转弯角度、转弯状态(是否在转弯中)。 2. **初始化陀螺仪初始角度**:在main函数中,初始化陀螺仪后读取初始yaw角(initial_yaw)。 3. **角度获取函数**:编写函数获取当前yaw偏移量,处理角度环绕问题。 4. **角度环PID**:定义角度环PID结构体,初始化参数,计算速度差。 5. **修改speed_control函数**:接收角度环输出的速度差,调整左右轮目标速度,然后执行速度环PID。 6. **转弯执行函数**:设置目标角度,进入循环控制,直到角度误差在允许范围内。 7. **主循环调用**:在主循环中添加转弯触发条件(如按键、寻迹信号),调用转弯函数。 需要检查用户代码中的PID是否支持级联,用户的PID结构体是位置式,适合作为速度环,角度环也可以使用位置式PID输出速度差。 可能的代码修改点: - 在main函数中添加initial_yaw的初始化。 - 添加angle_pid结构体和相关参数。 - 修改speed_control函数,使其目标速度由角度环调整。 - 添加turn_control函数,处理角度环计算。 - 在定时器中断中调用turn_control,更新角度环输出,再调用speed_control。 需要确保所有变量(如speed_l、speed_r)在中断和主循环中的访问是原子的,避免竞态条件。 最后,测试时需要调试PID参数,先调速度环,再调角度环,确保转弯平滑且精确。 </think> 根据你提供的完整代码(包含电机驱动、编码器、PID和陀螺仪),我将整合这些代码实现**基于陀螺仪和编码器的闭环转弯控制**。以下是修改后的代码和关键说明(重点标注与原代码的差异)。 ### 一、全局变量与宏定义(新增/修改部分) ```c #include "ti_msp_dl_config.h" #include "motor.h" #include "PID.h" #include "xunji.h" #include "atk_ms901m.h" // 原全局变量保留(如baseSpeed、enco_l、enco_r等) int16_t baseSpeed = 65; volatile int32_t enco_l, enco_r; volatile int32_t enco_l_last, enco_r_last; float speed_l, speed_r; atk_ms901m_attitude_data_t attitude_dat; // 新增转弯相关变量 float initial_yaw = 0.0f; // 初始航向角(转弯基准) float target_turn_angle = 90.0f; // 目标转弯角度(如90°) bool is_turning = false; // 是否处于转弯状态 float angle_error = 0.0f; // 当前角度误差(目标-实际) // PID控制器(原速度环保留,新增角度环) PID pid_left; // 左轮速度环(原代码已有) PID pid_right; // 右轮速度环(原代码已有) PID angle_pid; // 新增:角度环PID // 新增参数(根据实际调试调整) #define ANGLE_KP 2.0f // 角度环比例系数 #define ANGLE_KI 0.1f // 角度环积分系数 #define ANGLE_KD 0.5f // 角度环微分系数 #define ANGLE_LIMIT 50.0f// 角度环输出限幅(速度差最大值,mm/s) #define TURN_TOLERANCE 2.0f // 转弯允许误差(±2°) ``` ### 二、初始化函数(修改main函数) ```c int main(void) { SYSCFG_DL_init(); NVIC_ClearPendingIRQ(UART_0_INST_INT_IRQN); NVIC_EnableIRQ(UART_0_INST_INT_IRQN); NVIC_EnableIRQ(ENCODER_GPIOA_INT_IRQN); NVIC_EnableIRQ(ENCODER_GPIOB_INT_IRQN); NVIC_ClearPendingIRQ(TIMER_0_INST_INT_IRQN); NVIC_EnableIRQ(TIMER_0_INST_INT_IRQN); // 初始化PID(原速度环+新增角度环) PID_Init(&pid_left, 2.0f, 0.5f, 0.1f, 100.0f); // 左轮速度环(原参数) PID_Init(&pid_right, 2.0f, 0.5f, 0.1f, 100.0f); // 右轮速度环(原参数) PID_Init(&angle_pid, ANGLE_KP, ANGLE_KI, ANGLE_KD, ANGLE_LIMIT); // 新增角度环 // 初始化电机和陀螺仪 Motor_On(); atk_ms901m_uart_init(); delay_cycles(32000000); // 等待陀螺仪稳定 // 获取初始yaw角(转弯基准) atk_ms901m_get_attitude(&attitude_dat, 100); initial_yaw = attitude_dat.yaw; while (1) { // 示例:触发转弯(可替换为寻迹或其他条件) if (!is_turning) { is_turning = true; // 启动转弯 } xunji(); // 原寻迹逻辑(保留) } } ``` ### 三、核心功能函数(新增/修改) #### 1. 获取当前航向角偏移(新增) ```c // 处理角度环绕(如350°→10°实际为20°) float get_yaw_offset(void) { atk_ms901m_get_attitude(&attitude_dat, 100); // 读取当前姿态 float current_yaw = attitude_dat.yaw; float offset = current_yaw - initial_yaw; // 角度环绕处理(-180°~180°) if (offset > 180) offset -= 360; else if (offset < -180) offset += 360; return offset; } ``` #### 2. 角度环控制(新增) ```c // 角度环计算:输出左右轮速度差(mm/s) float angle_control(float target_angle) { float current_offset = get_yaw_offset(); angle_error = target_angle - current_offset; // 角度误差 // 调用PID计算(位置式PID) float speed_diff = PID_Realize(&angle_pid, current_offset, target_angle); return speed_diff; // 速度差(右轮速度 - 左轮速度) } ``` #### 3. 速度控制函数(修改原speed_control) ```c // 原speed_control修改为接收角度环输出的速度差 void speed_control(float speed_diff) { // 计算左右轮目标速度(基础速度±速度差/2) float base_speed = 200.0f; // 基础线速度(mm/s,可调整) float target_l = base_speed - (speed_diff / 2); float target_r = base_speed + (speed_diff / 2); // 速度环PID计算(原逻辑) float duty_l = PID_Realize(&pid_left, speed_l, target_l); float duty_r = PID_Realize(&pid_right, speed_r, target_r); // 输出PWM(原Set_Speed函数) Set_Speed(0, (int8_t)duty_l); // 左轮 Set_Speed(1, (int8_t)duty_r); // 右轮 } ``` #### 4. 定时器中断(修改TIMER_0_INST_IRQHandler) ```c void TIMER_0_INST_IRQHandler(void) { switch (DL_TimerA_getPendingInterrupt(TIMER_0_INST)) { case DL_TIMERA_IIDX_ZERO: encoder_update(); // 原编码器速度计算(得到speed_l、speed_r) // 转弯控制逻辑 if (is_turning) { float speed_diff = angle_control(target_turn_angle); // 角度环输出速度差 speed_control(speed_diff); // 速度环根据速度差调整目标速度 // 检查是否完成转弯 if (fabs(angle_error) < TURN_TOLERANCE) { is_turning = false; Set_Speed(0, 0); // 停止电机 Set_Speed(1, 0); } } else { // 非转弯状态:保持基础速度(可根据需求修改) speed_control(0); // 速度差为0,直线行驶 } break; default: break; } } ``` ### 四、关键说明(与原代码的整合点) 1. **陀螺仪与角度环**: - 通过`atk_ms901m_get_attitude`获取实时yaw角,计算相对于`initial_yaw`的偏移。 - 角度环PID的输入是“目标角度”与“当前偏移”的误差,输出是左右轮的**速度差**(右轮速度-左轮速度),从而实现差速转弯。 2. **速度环与编码器**: - 原`encoder_update`函数通过编码器脉冲计算`speed_l`和`speed_r`(mm/s),保留原逻辑。 - 速度环PID的目标速度由角度环输出的速度差动态调整(`target_l = base_speed - speed_diff/2`,`target_r = base_speed + speed_diff/2`),确保转弯时左右轮以不同速度运行。 3. **状态管理**: - 通过`is_turning`标志位控制转弯流程:触发后进入闭环控制,直到角度误差小于`TURN_TOLERANCE`(如±2°),停止电机。 ### 五、调试建议(必看!) 1. **参数校准**: - 编码器速度计算:检查`per_round_pulse`(每圈脉冲数)是否正确(原代码中`13*28=364`),车轮直径(65mm)是否与实际一致,否则`speed_l`/`speed_r`会有误差。 - 陀螺仪校准:确保小车静止时`initial_yaw`稳定,必要时调用ATK-MS901M的校准函数(如静止校准)。 2. **PID参数调试顺序**: - 先调速度环(原`pid_left`/`pid_right`):确保直线行驶时左右轮速度稳定(无抖动、无静差)。 - 再调角度环(`angle_pid`):增大`ANGLE_KP`直到转弯响应变快但不震荡,添加`ANGLE_KD`抑制超调,最后用`ANGLE_KI`消除静差。 3. **防打滑处理**: - 若转弯时车轮打滑(编码器速度与实际速度不符),可降低`base_speed`(如从200mm/s降至150mm/s),或增大`ANGLE_LIMIT`(限制速度差最大值)。 ### 六、完整代码验证流程 1. 编译并下载代码到开发板。 2. 小车静止时,观察`initial_yaw`是否稳定(波动小于±1°)。 3. 触发转弯(如将`is_turning`设为true),观察小车是否转向目标角度(90°),且停止时误差小于2°。 4. 若转弯过冲或不足,调整`ANGLE_KP`/`ANGLE_KD`;若无法达到目标角度,增大`ANGLE_KI`。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值