Context是怎么泄露的:Handlers & Inner Classes

Android内存泄漏详解
本文深入探讨了Android中因非静态内部类导致的内存泄漏问题,特别是Handler类的不当使用。文章通过具体示例展示了如何避免这类泄漏,并提出了使用静态内部类及弱引用等解决方案。

先瞅下下面这段代码:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ... 
    }
  }
}

尽管不是那么明显,但这段代码会导致大量内存泄露。Android的Lint工具会给出如下警告:

在Android中,Handler类应该被静态修饰,否则可能会出现内存泄露。

BUT,到底是哪里泄露了?它是怎么发生的?下面让我们根据已有的知识来分析下问题的原因:

1.当Android应用首次启动时,framework会在应用的UI线程创建一个Looper对象。Looper实现了一个简单的消息队列并且一个接一个的处理队列中的消息。应用的所有事件(比如Activity生命周期回调方法,按钮点击等等)都会被当做一个消息对象放入到Looper的消息队列中,然后再被逐一执行。UI线程的Looper存在于整个应用的生命周期内。
2.当在UI线程中创建Handler对象时,它就会和UI线程中Looper的消息队列进行关联。发送到这个消息队列中的消息会持有这个Handler的引用,这样当Looper最终处理这个消息的时候framework就会调用Handler#handleMessage(Message)方法来处理具体的逻辑。
3.在Java中,非静态的内部类或者匿名类会隐式的持有其外部类的引用,而静态的内部类则不会。

那么,到底是哪里发生泄漏了呢?其实泄漏发生的还是比较隐晦的,但是再看看下面这段代码:

public class SampleActivity extends Activity {

  private final Handler mLeakyHandler = new Handler() {
    @Override
    public void handleMessage(Message msg) {
      // ...
    }
  }

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mLeakyHandler.postDelayed(new Runnable() {
      @Override
      public void run() { /* ... */ }
    }, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

当activity被finish的时候,延迟发送的消息仍然会存活在UI线程的消息队列中,直到10分钟后它被处理掉。这个消息持有activity的Handler的引用,Handler又隐式的持有它的外部类(这里就是SampleActivity)的引用。这个引用会一直存在直到这个消息被处理,所以垃圾回收机制就没法回收这个activity,内存泄露就发生了。需要注意的是:15行的匿名Runnable子类也会导致内存泄露。非静态的匿名类会隐式的持有外部类的引用,所以context会被泄露掉。

解决这个问题也很简单:在新的类文件中实现Handler的子类或者使用static修饰内部类。静态的内部类不会持有外部类的引用,所以activity不会被泄露。如果你要在Handler内调用外部activity类的方法的话,可以让Handler持有外部activity类的弱引用,这样也不会有泄露activity的风险。关于匿名类造成的泄露问题,我们可以用static修饰这个匿名类对象解决这个问题,因为静态的匿名类也不会持有它外部类的引用。

public class SampleActivity extends Activity {

  /**
   * Instances of static inner classes do not hold an implicit
   * reference to their outer class.
   */
  private static class MyHandler extends Handler {
    private final WeakReference<SampleActivity> mActivity;

    public MyHandler(SampleActivity activity) {
      mActivity = new WeakReference<SampleActivity>(activity);
    }

    @Override
    public void handleMessage(Message msg) {
      SampleActivity activity = mActivity.get();
      if (activity != null) {
        // ...
      }
    }
  }

  private final MyHandler mHandler = new MyHandler(this);

  /**
   * Instances of anonymous classes do not hold an implicit
   * reference to their outer class when they are "static".
   */
  private static final Runnable sRunnable = new Runnable() {
      @Override
      public void run() { /* ... */ }
  };

  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    // Post a message and delay its execution for 10 minutes.
    mHandler.postDelayed(sRunnable, 1000 * 60 * 10);

    // Go back to the previous Activity.
    finish();
  }
}

静态和非静态内部类的区别是非常微妙的,但这个区别是每个Android开发者应该清楚的。那么底线是什么?如果要实例化一个超出activity生命周期的内部类对象,避免使用非静态的内部类。建议使用静态内部类并且在内部类中持有外部类的弱引用。

MultiValueDictKeyError at /auth/users/ &#39;password&#39; Request Method: POST Request URL: http://127.0.0.1:8000/auth/users/ Django Version: 4.2.21 Exception Type: MultiValueDictKeyError Exception Value: &#39;password&#39; Exception Location: C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\django\utils\datastructures.py, line 86, in __getitem__ Raised during: account.views.UserView Python Executable: C:\Users\高琨\AppData\Local\Programs\Python\Python39\python.exe Python Version: 3.9.11 Python Path: [&#39;C:\\Users\\高琨\\Desktop\\backend&#39;, &#39;C:\\Users\\高琨\\AppData\\Local\\Programs\\Python\\Python39\\python39.zip&#39;, &#39;C:\\Users\\高琨\\AppData\\Local\\Programs\\Python\\Python39\\DLLs&#39;, &#39;C:\\Users\\高琨\\AppData\\Local\\Programs\\Python\\Python39\\lib&#39;, &#39;C:\\Users\\高琨\\AppData\\Local\\Programs\\Python\\Python39&#39;, &#39;C:\\Users\\高琨\\AppData\\Local\\Programs\\Python\\Python39\\lib\\site-packages&#39;] Server time: Tue, 24 Jun 2025 10:29:49 +0800 Traceback Switch to copy-and-paste view C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\django\utils\datastructures.py, line 84, in __getitem__ list_ = super().__getitem__(key) &hellip; Local vars During handling of the above exception (&#39;password&#39;), another exception occurred: C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\django\core\handlers\exception.py, line 55, in inner response = get_response(request) &hellip; Local vars C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\django\core\handlers\base.py, line 197, in _get_response response = wrapped_callback(request, *callback_args, **callback_kwargs) &hellip; Local vars C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\django\views\decorators\csrf.py, line 56, in wrapper_view return view_func(*args, **kwargs) &hellip; Local vars C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\rest_framework\viewsets.py, line 124, in view return self.dispatch(request, *args, **kwargs) &hellip; Local vars C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\rest_framework\views.py, line 509, in dispatch response = self.handle_exception(exc) &hellip; Local vars C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\rest_framework\views.py, line 469, in handle_exception self.raise_uncaught_exception(exc) &hellip; Local vars C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\rest_framework\views.py, line 480, in raise_uncaught_exception raise exc &hellip; Local vars C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\rest_framework\views.py, line 506, in dispatch response = handler(request, *args, **kwargs) &hellip; Local vars C:\Users\高琨\Desktop\backend\account\views.py, line 31, in login password = request.data[&#39;password&#39;] &hellip; Local vars C:\Users\高琨\AppData\Local\Programs\Python\Python39\lib\site-packages\django\utils\datastructures.py, line 86, in __getitem__ raise MultiValueDictKeyError(key) &hellip; Local vars Request information USER AnonymousUser GET No GET data POST Variable Value csrfmiddlewaretoken &#39;DWg42eYqodHAyGvU8iq7SW01Agw6SWsrGqn2RNRtENkZgyesArpAfiWcxFAkniwR&#39; last_login &#39;&#39; first_name &#39;琨&#39; last_name &#39;高&#39; date_joined &#39;&#39; email &#39;3537676579@qq.com&#39; desc &#39;啊大苏打&#39; mobile &#39;13698551543&#39; gender &#39;&#39; FILES Variable Value avatar &lt;InMemoryUploadedFile: 屏幕截图 2025-05-16 181140.png (image/png)&gt; COOKIES Variable Value csrftoken &#39;********************&#39; META Variable Value ALLUSERSPROFILE &#39;C:\\ProgramData&#39; APPDATA &#39;C:\\Users\\高琨\\AppData\\Roaming&#39; CHROME_CRASHPAD_PIPE_NAME &#39;\\\\.\\pipe\\crashpad_2004_IXNEYUSYGSDNGSXV&#39; COLORTERM &#39;truecolor&#39; COMMONPROGRAMFILES &#39;C:\\Program Files\\Common Files&#39; COMMONPROGRAMFILES(X86) &#39;C:\\Program Files (x86)\\Common Files&#39; COMMONPROGRAMW6432 &#39;C:\\Program Files\\Common Files&#39; COMPUTERNAME &#39;LAPTOP-SUN5G18K&#39; COMSPEC &#39;C:\\WINDOWS\\system32\\cmd.exe&#39; CONTENT_LENGTH &#39;75110&#39; CONTENT_TYPE &#39;multipart/form-data; boundary=----WebKitFormBoundaryJGK9BG3i7H6CHnvK&#39; CSRF_COOKIE &#39;dEh8ZJ3dqKNzS2TICj9Dxw6l7zeoFweA&#39; DJANGO_SETTINGS_MODULE &#39;core.settings&#39; DRIVERDATA &#39;C:\\Windows\\System32\\Drivers\\DriverData&#39; EFC_9700_1262719628 &#39;1&#39; EFC_9700_1592913036 &#39;1&#39; EFC_9700_2283032206 &#39;1&#39; EFC_9700_2775293581 &#39;1&#39; EFC_9700_3789132940 &#39;1&#39; FPS_BROWSER_APP_PROFILE_STRING &#39;Internet Explorer&#39; FPS_BROWSER_USER_PROFILE_STRING &#39;Default&#39; GATEWAY_INTERFACE &#39;CGI/1.1&#39; HOMEDRIVE &#39;C:&#39; HOMEPATH &#39;\\Users\\高琨&#39; HTTP_ACCEPT &#39;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&#39; HTTP_ACCEPT_ENCODING &#39;gzip, deflate, br, zstd&#39; HTTP_ACCEPT_LANGUAGE &#39;zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6&#39; HTTP_CACHE_CONTROL &#39;max-age=0&#39; HTTP_CONNECTION &#39;keep-alive&#39; HTTP_COOKIE &#39;********************&#39; HTTP_HOST &#39;127.0.0.1:8000&#39; HTTP_ORIGIN &#39;http://127.0.0.1:8000&#39; HTTP_REFERER &#39;http://127.0.0.1:8000/auth/users/&#39; HTTP_SEC_CH_UA &#39;&quot;Microsoft Edge&quot;;v=&quot;137&quot;, &quot;Chromium&quot;;v=&quot;137&quot;, &quot;Not/A)Brand&quot;;v=&quot;24&quot;&#39; HTTP_SEC_CH_UA_MOBILE &#39;?0&#39; HTTP_SEC_CH_UA_PLATFORM &#39;&quot;Windows&quot;&#39; HTTP_SEC_FETCH_DEST &#39;document&#39; HTTP_SEC_FETCH_MODE &#39;navigate&#39; HTTP_SEC_FETCH_SITE &#39;same-origin&#39; HTTP_SEC_FETCH_USER &#39;?1&#39; HTTP_UPGRADE_INSECURE_REQUESTS &#39;1&#39; HTTP_USER_AGENT (&#39;Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like &#39; &#39;Gecko) Chrome/137.0.0.0 Safari/537.36 Edg/137.0.0.0&#39;) LANG &#39;zh_CN.UTF-8&#39; LOCALAPPDATA &#39;C:\\Users\\高琨\\AppData\\Local&#39; LOGONSERVER &#39;\\\\LAPTOP-SUN5G18K&#39; NPM_HOME &#39;C:\\Program Files\\nodejs\\node_global&#39; NUMBER_OF_PROCESSORS &#39;16&#39; NVM_HOME &#39;E:\\nvm&#39; NVM_SYMLINK &#39;C:\\nvm4w\\nodejs&#39; ONEDRIVE &#39;C:\\Users\\高琨\\OneDrive&#39; ORIGINAL_XDG_CURRENT_DESKTOP &#39;undefined&#39; OS &#39;Windows_NT&#39; PATH (&#39;E:\\bin\\;C:\\Windows\\system32;C:\\Windows;C:\\Windows\\System32\\Wbem;C:\\Windows\\System32\\WindowsPowerShell\\v1.0\\;C:\\Windows\\System32\\OpenSSH\\;C:\\Windows\\system32\\config\\systemprofile\\AppData\\Local\\Microsoft\\WindowsApps;C:\\windows\\system32\\HWAudioDriver\\;C:\\WINDOWS\\system32;C:\\WINDOWS;C:\\WINDOWS\\System32\\Wbem;C:\\WINDOWS\\System32\\WindowsPowerShell\\v1.0\\;C:\\WINDOWS\\System32\\OpenSSH\\;E:\\nvm;C:\\nvm4w\\nodejs;C:\\Program &#39; &#39;Files\\dotnet\\;C:\\Program Files\\nodejs\\;C:\\Program &#39; &#39;Files\\nodejs\\node_global;C:\\Windows\\System32\\node_modules\\yarn\\bin;D:\\yarn_global\\bin;F:\\;C:\\Program &#39; &#39;Files &#39; &#39;(x86)\\Tencent\\微信web开发者工具\\dll;C:\\Users\\高琨\\AppData\\Local\\Microsoft\\WindowsApps\\python3.exe;D:\\phpstudy_pro\\Extensions\\php\\php7.3.4nts;D:\\phpstudy_pro\\Extensions\\composer2.5.8;C:\\Program &#39; &#39;Files (x86)\\NetSarang\\Xftp &#39; &#39;8\\;C:\\Users\\高琨\\AppData\\Local\\Programs\\Python\\Python39\\Scripts\\;C:\\Users\\高琨\\AppData\\Local\\Programs\\Python\\Python39\\;C:\\Users\\高琨\\AppData\\Local\\Programs\\Python\\Launcher\\;C:\\Users\\高琨\\AppData\\Local\\Microsoft\\WindowsApps;C:\\Users\\高琨\\AppData\\Local\\Programs\\Microsoft &#39; &#39;VS Code\\bin;E:\\nvm;C:\\nvm4w\\nodejs;C:\\Users\\高琨\\AppData\\Roaming\\npm&#39;) PATHEXT &#39;.COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC;.CPL&#39; PATH_INFO &#39;/auth/users/&#39; PROCESSOR_ARCHITECTURE &#39;AMD64&#39; PROCESSOR_IDENTIFIER &#39;Intel64 Family 6 Model 154 Stepping 3, GenuineIntel&#39; PROCESSOR_LEVEL &#39;6&#39; PROCESSOR_REVISION &#39;9a03&#39; PROGRAMDATA &#39;C:\\ProgramData&#39; PROGRAMFILES &#39;C:\\Program Files&#39; PROGRAMFILES(X86) &#39;C:\\Program Files (x86)&#39; PROGRAMW6432 &#39;C:\\Program Files&#39; PSMODULEPATH (&#39;C:\\Users\\高琨\\Documents\\WindowsPowerShell\\Modules;C:\\Program &#39; &#39;Files\\WindowsPowerShell\\Modules;C:\\WINDOWS\\system32\\WindowsPowerShell\\v1.0\\Modules&#39;) PUBLIC &#39;C:\\Users\\Public&#39; QUERY_STRING &#39;&#39; REMOTE_ADDR &#39;127.0.0.1&#39; REMOTE_HOST &#39;&#39; REQUEST_METHOD &#39;POST&#39; RUN_MAIN &#39;true&#39; SCRIPT_NAME &#39;&#39; SERVER_NAME &#39;127.0.0.1&#39; SERVER_PORT &#39;8000&#39; SERVER_PROTOCOL &#39;HTTP/1.1&#39; SERVER_SOFTWARE &#39;WSGIServer/0.2&#39; SESSIONNAME &#39;Console&#39; SYSTEMDRIVE &#39;C:&#39; SYSTEMROOT &#39;C:\\WINDOWS&#39; TEMP &#39;C:\\Users\\高琨\\AppData\\Local\\Temp&#39; TERM_PROGRAM &#39;vscode&#39; TERM_PROGRAM_VERSION &#39;1.101.1&#39; TMP &#39;C:\\Users\\高琨\\AppData\\Local\\Temp&#39; USERDOMAIN &#39;LAPTOP-SUN5G18K&#39; USERDOMAIN_ROAMINGPROFILE &#39;LAPTOP-SUN5G18K&#39; USERNAME &#39;高琨&#39; USERPROFILE &#39;C:\\Users\\高琨&#39; VSCODE_INJECTION &#39;1&#39; VSCODE_NONCE &#39;71bb0211-da42-4580-bb3d-e761a3af6e04&#39; VSCODE_STABLE &#39;1&#39; WINDIR &#39;C:\\WINDOWS&#39; ZES_ENABLE_SYSMAN &#39;1&#39; wsgi.errors &lt;_io.TextIOWrapper name=&#39;&lt;stderr&gt;&#39; mode=&#39;w&#39; encoding=&#39;utf-8&#39;&gt; wsgi.file_wrapper &lt;class &#39;wsgiref.util.FileWrapper&#39;&gt; wsgi.input &lt;django.core.handlers.wsgi.LimitedStream object at 0x000001EBE9AE46D0&gt; wsgi.multiprocess False wsgi.multithread True wsgi.run_once False wsgi.url_scheme &#39;http&#39; wsgi.version (1, 0) Settings Using settings module core.settings Setting Value ABSOLUTE_URL_OVERRIDES {} ADMINS [] ALLOWED_HOSTS [&#39;*&#39;] APPEND_SLASH True AUTHENTICATION_BACKENDS [&#39;django.contrib.auth.backends.ModelBackend&#39;] AUTH_PASSWORD_VALIDATORS &#39;********************&#39; AUTH_USER_MODEL &#39;account.User&#39; BASE_DIR WindowsPath(&#39;C:/Users/高琨/Desktop/backend&#39;) CACHES {&#39;default&#39;: {&#39;BACKEND&#39;: &#39;django.core.cache.backends.locmem.LocMemCache&#39;}} CACHE_MIDDLEWARE_ALIAS &#39;default&#39; CACHE_MIDDLEWARE_KEY_PREFIX &#39;********************&#39; CACHE_MIDDLEWARE_SECONDS 600 CORS_ALLOW_ALL_ORIGINS True CSRF_COOKIE_AGE 31449600 CSRF_COOKIE_DOMAIN None CSRF_COOKIE_HTTPONLY False CSRF_COOKIE_MASKED False CSRF_COOKIE_NAME &#39;csrftoken&#39; CSRF_COOKIE_PATH &#39;/&#39; CSRF_COOKIE_SAMESITE &#39;Lax&#39; CSRF_COOKIE_SECURE False CSRF_FAILURE_VIEW &#39;django.views.csrf.csrf_failure&#39; CSRF_HEADER_NAME &#39;HTTP_X_CSRFTOKEN&#39; CSRF_TRUSTED_ORIGINS [] CSRF_USE_SESSIONS False DATABASES {&#39;default&#39;: {&#39;ATOMIC_REQUESTS&#39;: False, &#39;AUTOCOMMIT&#39;: True, &#39;CONN_HEALTH_CHECKS&#39;: False, &#39;CONN_MAX_AGE&#39;: 0, &#39;ENGINE&#39;: &#39;django.db.backends.sqlite3&#39;, &#39;HOST&#39;: &#39;&#39;, &#39;NAME&#39;: WindowsPath(&#39;C:/Users/高琨/Desktop/backend/db.sqlite3&#39;), &#39;OPTIONS&#39;: {}, &#39;PASSWORD&#39;: &#39;********************&#39;, &#39;PORT&#39;: &#39;&#39;, &#39;TEST&#39;: {&#39;CHARSET&#39;: None, &#39;COLLATION&#39;: None, &#39;MIGRATE&#39;: True, &#39;MIRROR&#39;: None, &#39;NAME&#39;: None}, &#39;TIME_ZONE&#39;: None, &#39;USER&#39;: &#39;&#39;}} DATABASE_ROUTERS [] DATA_UPLOAD_MAX_MEMORY_SIZE 2621440 DATA_UPLOAD_MAX_NUMBER_FIELDS 1000 DATA_UPLOAD_MAX_NUMBER_FILES 100 DATETIME_FORMAT &#39;N j, Y, P&#39; DATETIME_INPUT_FORMATS [&#39;%Y-%m-%d %H:%M:%S&#39;, &#39;%Y-%m-%d %H:%M:%S.%f&#39;, &#39;%Y-%m-%d %H:%M&#39;, &#39;%m/%d/%Y %H:%M:%S&#39;, &#39;%m/%d/%Y %H:%M:%S.%f&#39;, &#39;%m/%d/%Y %H:%M&#39;, &#39;%m/%d/%y %H:%M:%S&#39;, &#39;%m/%d/%y %H:%M:%S.%f&#39;, &#39;%m/%d/%y %H:%M&#39;] DATE_FORMAT &#39;N j, Y&#39; DATE_INPUT_FORMATS [&#39;%Y-%m-%d&#39;, &#39;%m/%d/%Y&#39;, &#39;%m/%d/%y&#39;, &#39;%b %d %Y&#39;, &#39;%b %d, %Y&#39;, &#39;%d %b %Y&#39;, &#39;%d %b, %Y&#39;, &#39;%B %d %Y&#39;, &#39;%B %d, %Y&#39;, &#39;%d %B %Y&#39;, &#39;%d %B, %Y&#39;] DEBUG True DEBUG_PROPAGATE_EXCEPTIONS False DECIMAL_SEPARATOR &#39;.&#39; DEFAULT_AUTO_FIELD &#39;django.db.models.BigAutoField&#39; DEFAULT_CHARSET &#39;utf-8&#39; DEFAULT_EXCEPTION_REPORTER &#39;django.views.debug.ExceptionReporter&#39; DEFAULT_EXCEPTION_REPORTER_FILTER &#39;django.views.debug.SafeExceptionReporterFilter&#39; DEFAULT_FILE_STORAGE &#39;django.core.files.storage.FileSystemStorage&#39; DEFAULT_FROM_EMAIL &#39;webmaster@localhost&#39; DEFAULT_INDEX_TABLESPACE &#39;&#39; DEFAULT_TABLESPACE &#39;&#39; DISALLOWED_USER_AGENTS [] EMAIL_BACKEND &#39;django.core.mail.backends.smtp.EmailBackend&#39; EMAIL_HOST &#39;localhost&#39; EMAIL_HOST_PASSWORD &#39;********************&#39; EMAIL_HOST_USER &#39;&#39; EMAIL_PORT 25 EMAIL_SSL_CERTFILE None EMAIL_SSL_KEYFILE &#39;********************&#39; EMAIL_SUBJECT_PREFIX &#39;[Django] &#39; EMAIL_TIMEOUT None EMAIL_USE_LOCALTIME False EMAIL_USE_SSL False EMAIL_USE_TLS False FILE_UPLOAD_DIRECTORY_PERMISSIONS None FILE_UPLOAD_HANDLERS [&#39;django.core.files.uploadhandler.MemoryFileUploadHandler&#39;, &#39;django.core.files.uploadhandler.TemporaryFileUploadHandler&#39;] FILE_UPLOAD_MAX_MEMORY_SIZE 2621440 FILE_UPLOAD_PERMISSIONS 420 FILE_UPLOAD_TEMP_DIR None FIRST_DAY_OF_WEEK 0 FIXTURE_DIRS [] FORCE_SCRIPT_NAME None FORMAT_MODULE_PATH None FORM_RENDERER &#39;django.forms.renderers.DjangoTemplates&#39; IGNORABLE_404_URLS [] INSTALLED_APPS [&#39;django.contrib.admin&#39;, &#39;django.contrib.auth&#39;, &#39;django.contrib.contenttypes&#39;, &#39;django.contrib.sessions&#39;, &#39;django.contrib.messages&#39;, &#39;django.contrib.staticfiles&#39;, &#39;rest_framework&#39;, &#39;corsheaders&#39;, &#39;account&#39;] INTERNAL_IPS [] LANGUAGES [(&#39;af&#39;, &#39;Afrikaans&#39;), (&#39;ar&#39;, &#39;Arabic&#39;), (&#39;ar-dz&#39;, &#39;Algerian Arabic&#39;), (&#39;ast&#39;, &#39;Asturian&#39;), (&#39;az&#39;, &#39;Azerbaijani&#39;), (&#39;bg&#39;, &#39;Bulgarian&#39;), (&#39;be&#39;, &#39;Belarusian&#39;), (&#39;bn&#39;, &#39;Bengali&#39;), (&#39;br&#39;, &#39;Breton&#39;), (&#39;bs&#39;, &#39;Bosnian&#39;), (&#39;ca&#39;, &#39;Catalan&#39;), (&#39;ckb&#39;, &#39;Central Kurdish (Sorani)&#39;), (&#39;cs&#39;, &#39;Czech&#39;), (&#39;cy&#39;, &#39;Welsh&#39;), (&#39;da&#39;, &#39;Danish&#39;), (&#39;de&#39;, &#39;German&#39;), (&#39;dsb&#39;, &#39;Lower Sorbian&#39;), (&#39;el&#39;, &#39;Greek&#39;), (&#39;en&#39;, &#39;English&#39;), (&#39;en-au&#39;, &#39;Australian English&#39;), (&#39;en-gb&#39;, &#39;British English&#39;), (&#39;eo&#39;, &#39;Esperanto&#39;), (&#39;es&#39;, &#39;Spanish&#39;), (&#39;es-ar&#39;, &#39;Argentinian Spanish&#39;), (&#39;es-co&#39;, &#39;Colombian Spanish&#39;), (&#39;es-mx&#39;, &#39;Mexican Spanish&#39;), (&#39;es-ni&#39;, &#39;Nicaraguan Spanish&#39;), (&#39;es-ve&#39;, &#39;Venezuelan Spanish&#39;), (&#39;et&#39;, &#39;Estonian&#39;), (&#39;eu&#39;, &#39;Basque&#39;), (&#39;fa&#39;, &#39;Persian&#39;), (&#39;fi&#39;, &#39;Finnish&#39;), (&#39;fr&#39;, &#39;French&#39;), (&#39;fy&#39;, &#39;Frisian&#39;), (&#39;ga&#39;, &#39;Irish&#39;), (&#39;gd&#39;, &#39;Scottish Gaelic&#39;), (&#39;gl&#39;, &#39;Galician&#39;), (&#39;he&#39;, &#39;Hebrew&#39;), (&#39;hi&#39;, &#39;Hindi&#39;), (&#39;hr&#39;, &#39;Croatian&#39;), (&#39;hsb&#39;, &#39;Upper Sorbian&#39;), (&#39;hu&#39;, &#39;Hungarian&#39;), (&#39;hy&#39;, &#39;Armenian&#39;), (&#39;ia&#39;, &#39;Interlingua&#39;), (&#39;id&#39;, &#39;Indonesian&#39;), (&#39;ig&#39;, &#39;Igbo&#39;), (&#39;io&#39;, &#39;Ido&#39;), (&#39;is&#39;, &#39;Icelandic&#39;), (&#39;it&#39;, &#39;Italian&#39;), (&#39;ja&#39;, &#39;Japanese&#39;), (&#39;ka&#39;, &#39;Georgian&#39;), (&#39;kab&#39;, &#39;Kabyle&#39;), (&#39;kk&#39;, &#39;Kazakh&#39;), (&#39;km&#39;, &#39;Khmer&#39;), (&#39;kn&#39;, &#39;Kannada&#39;), (&#39;ko&#39;, &#39;Korean&#39;), (&#39;ky&#39;, &#39;Kyrgyz&#39;), (&#39;lb&#39;, &#39;Luxembourgish&#39;), (&#39;lt&#39;, &#39;Lithuanian&#39;), (&#39;lv&#39;, &#39;Latvian&#39;), (&#39;mk&#39;, &#39;Macedonian&#39;), (&#39;ml&#39;, &#39;Malayalam&#39;), (&#39;mn&#39;, &#39;Mongolian&#39;), (&#39;mr&#39;, &#39;Marathi&#39;), (&#39;ms&#39;, &#39;Malay&#39;), (&#39;my&#39;, &#39;Burmese&#39;), (&#39;nb&#39;, &#39;Norwegian Bokm&aring;l&#39;), (&#39;ne&#39;, &#39;Nepali&#39;), (&#39;nl&#39;, &#39;Dutch&#39;), (&#39;nn&#39;, &#39;Norwegian Nynorsk&#39;), (&#39;os&#39;, &#39;Ossetic&#39;), (&#39;pa&#39;, &#39;Punjabi&#39;), (&#39;pl&#39;, &#39;Polish&#39;), (&#39;pt&#39;, &#39;Portuguese&#39;), (&#39;pt-br&#39;, &#39;Brazilian Portuguese&#39;), (&#39;ro&#39;, &#39;Romanian&#39;), (&#39;ru&#39;, &#39;Russian&#39;), (&#39;sk&#39;, &#39;Slovak&#39;), (&#39;sl&#39;, &#39;Slovenian&#39;), (&#39;sq&#39;, &#39;Albanian&#39;), (&#39;sr&#39;, &#39;Serbian&#39;), (&#39;sr-latn&#39;, &#39;Serbian Latin&#39;), (&#39;sv&#39;, &#39;Swedish&#39;), (&#39;sw&#39;, &#39;Swahili&#39;), (&#39;ta&#39;, &#39;Tamil&#39;), (&#39;te&#39;, &#39;Telugu&#39;), (&#39;tg&#39;, &#39;Tajik&#39;), (&#39;th&#39;, &#39;Thai&#39;), (&#39;tk&#39;, &#39;Turkmen&#39;), (&#39;tr&#39;, &#39;Turkish&#39;), (&#39;tt&#39;, &#39;Tatar&#39;), (&#39;udm&#39;, &#39;Udmurt&#39;), (&#39;uk&#39;, &#39;Ukrainian&#39;), (&#39;ur&#39;, &#39;Urdu&#39;), (&#39;uz&#39;, &#39;Uzbek&#39;), (&#39;vi&#39;, &#39;Vietnamese&#39;), (&#39;zh-hans&#39;, &#39;Simplified Chinese&#39;), (&#39;zh-hant&#39;, &#39;Traditional Chinese&#39;)] LANGUAGES_BIDI [&#39;he&#39;, &#39;ar&#39;, &#39;ar-dz&#39;, &#39;ckb&#39;, &#39;fa&#39;, &#39;ur&#39;] LANGUAGE_CODE &#39;zh-hans&#39; LANGUAGE_COOKIE_AGE None LANGUAGE_COOKIE_DOMAIN None LANGUAGE_COOKIE_HTTPONLY False LANGUAGE_COOKIE_NAME &#39;django_language&#39; LANGUAGE_COOKIE_PATH &#39;/&#39; LANGUAGE_COOKIE_SAMESITE None LANGUAGE_COOKIE_SECURE False LOCALE_PATHS [] LOGGING {} LOGGING_CONFIG &#39;logging.config.dictConfig&#39; LOGIN_REDIRECT_URL &#39;/accounts/profile/&#39; LOGIN_URL &#39;/accounts/login/&#39; LOGOUT_REDIRECT_URL None MANAGERS [] MEDIA_ROOT &#39;C:/Users/高琨/Desktop/backend/media&#39; MEDIA_URL &#39;/media/&#39; MESSAGE_STORAGE &#39;django.contrib.messages.storage.fallback.FallbackStorage&#39; MIDDLEWARE [&#39;corsheaders.middleware.CorsMiddleware&#39;, &#39;django.middleware.security.SecurityMiddleware&#39;, &#39;django.contrib.sessions.middleware.SessionMiddleware&#39;, &#39;django.middleware.common.CommonMiddleware&#39;, &#39;django.middleware.csrf.CsrfViewMiddleware&#39;, &#39;django.contrib.auth.middleware.AuthenticationMiddleware&#39;, &#39;django.contrib.messages.middleware.MessageMiddleware&#39;, &#39;django.middleware.clickjacking.XFrameOptionsMiddleware&#39;] MIGRATION_MODULES {} MONTH_DAY_FORMAT &#39;F j&#39; NUMBER_GROUPING 0 PASSWORD_HASHERS &#39;********************&#39; PASSWORD_RESET_TIMEOUT &#39;********************&#39; PREPEND_WWW False REST_FRAMEWORK {&#39;DEFAULT_AUTHENTICATION_CLASSES&#39;: (&#39;rest_framework_simplejwt.authentication.JWTAuthentication&#39;,), &#39;DEFAULT_PAGINATION_CLASS&#39;: &#39;rest_framework.pagination.PageNumberPagination&#39;, &#39;DEFAULT_PERMISSION_CLASSES&#39;: &#39;rest_framework.permissions.IsAuthenticated&#39;, &#39;NON_FIELD_ERRORS_KEY&#39;: &#39;********************&#39;, &#39;PAGE_SIZE&#39;: 3} ROOT_URLCONF &#39;core.urls&#39; SECRET_KEY &#39;********************&#39; SECRET_KEY_FALLBACKS &#39;********************&#39; SECURE_CONTENT_TYPE_NOSNIFF True SECURE_CROSS_ORIGIN_OPENER_POLICY &#39;same-origin&#39; SECURE_HSTS_INCLUDE_SUBDOMAINS False SECURE_HSTS_PRELOAD False SECURE_HSTS_SECONDS 0 SECURE_PROXY_SSL_HEADER None SECURE_REDIRECT_EXEMPT [] SECURE_REFERRER_POLICY &#39;same-origin&#39; SECURE_SSL_HOST None SECURE_SSL_REDIRECT False SERVER_EMAIL &#39;root@localhost&#39; SESSION_CACHE_ALIAS &#39;default&#39; SESSION_COOKIE_AGE 1209600 SESSION_COOKIE_DOMAIN None SESSION_COOKIE_HTTPONLY True SESSION_COOKIE_NAME &#39;sessionid&#39; SESSION_COOKIE_PATH &#39;/&#39; SESSION_COOKIE_SAMESITE &#39;Lax&#39; SESSION_COOKIE_SECURE False SESSION_ENGINE &#39;django.contrib.sessions.backends.db&#39; SESSION_EXPIRE_AT_BROWSER_CLOSE False SESSION_FILE_PATH None SESSION_SAVE_EVERY_REQUEST False SESSION_SERIALIZER &#39;django.contrib.sessions.serializers.JSONSerializer&#39; SETTINGS_MODULE &#39;core.settings&#39; SHORT_DATETIME_FORMAT &#39;m/d/Y P&#39; SHORT_DATE_FORMAT &#39;m/d/Y&#39; SIGNING_BACKEND &#39;django.core.signing.TimestampSigner&#39; SILENCED_SYSTEM_CHECKS [] SIMPLE_JWT {&#39;ACCESS_TOKEN_LIFETIME&#39;: &#39;********************&#39;, &#39;AUTH_HEADER_TYPES&#39;: (&#39;Bearer&#39;,), &#39;REFRESH_TOKEN_LIFETIME&#39;: &#39;********************&#39;, &#39;SIGNING_KEY&#39;: &#39;********************&#39;} STATICFILES_DIRS [] STATICFILES_FINDERS [&#39;django.contrib.staticfiles.finders.FileSystemFinder&#39;, &#39;django.contrib.staticfiles.finders.AppDirectoriesFinder&#39;] STATICFILES_STORAGE &#39;django.contrib.staticfiles.storage.StaticFilesStorage&#39; STATIC_ROOT &#39;C:\\Users\\高琨\\Desktop\\backend\\static&#39; STATIC_URL &#39;/static/&#39; STORAGES {&#39;default&#39;: {&#39;BACKEND&#39;: &#39;django.core.files.storage.FileSystemStorage&#39;}, &#39;staticfiles&#39;: {&#39;BACKEND&#39;: &#39;django.contrib.staticfiles.storage.StaticFilesStorage&#39;}} TEMPLATES [{&#39;APP_DIRS&#39;: True, &#39;BACKEND&#39;: &#39;django.template.backends.django.DjangoTemplates&#39;, &#39;DIRS&#39;: [], &#39;OPTIONS&#39;: {&#39;context_processors&#39;: [&#39;django.template.context_processors.debug&#39;, &#39;django.template.context_processors.request&#39;, &#39;django.contrib.auth.context_processors.auth&#39;, &#39;django.contrib.messages.context_processors.messages&#39;]}}] TEST_NON_SERIALIZED_APPS [] TEST_RUNNER &#39;django.test.runner.DiscoverRunner&#39; THOUSAND_SEPARATOR &#39;,&#39; TIME_FORMAT &#39;P&#39; TIME_INPUT_FORMATS [&#39;%H:%M:%S&#39;, &#39;%H:%M:%S.%f&#39;, &#39;%H:%M&#39;] TIME_ZONE &#39;Asia/Shanghai&#39; USE_DEPRECATED_PYTZ False USE_I18N True USE_L10N True USE_THOUSAND_SEPARATOR False USE_TZ False USE_X_FORWARDED_HOST False USE_X_FORWARDED_PORT False WSGI_APPLICATION &#39;core.wsgi.application&#39; X_FRAME_OPTIONS &#39;DENY&#39; YEAR_MONTH_FORMAT &#39;F Y&#39;
06-25
namespace beast = boost::beast; // from &lt;boost/beast.hpp&gt; namespace http = beast::http; // from &lt;boost/beast/http.hpp&gt; namespace net = boost::asio; // from &lt;boost/asio.hpp&gt; using tcp = boost::asio::ip::tcp; // from &lt;boost/asio/ip/tcp.hpp&gt; using TypeCallbackClientOnDone = std::function&lt;void(const char*, const std::string&amp;, const char*)&gt;; // Performs an HTTP GET and prints the response class ClientSession : public std::enable_shared_from_this&lt;ClientSession&gt; { public: // Objects are constructed with a strand to // ensure that handlers do not execute concurrently. explicit ClientSession(net::io_context&amp; ioc, std::shared_ptr&lt;beast::tcp_stream&gt; stream, const std::string&amp; host, int port); // Start the asynchronous operation void Run(int timeout, char const* target, const char* contentType, const std::string&amp; content, const TypeCallbackClientOnDone&amp; cb); // 拿走其内的Stream std::shared_ptr&lt;beast::tcp_stream&gt; TakeStream(); private: void DoRun(int timeout, const std::string&amp; target, const std::string&amp; contentType, const std::string&amp; content, const TypeCallbackClientOnDone&amp; cb); void OnConnect(beast::error_code* ecPtr, int timeout); void OnWrite(beast::error_code ec, std::size_t bytes_transferred, int timeout); void OnRead(beast::error_code ec, std::size_t bytes_transferred, int timeout); void OnFail(const std::string&amp; errMsg, char const* what); void ReportFail(const std::string&amp; errMsg, char const* what); void CloseSocketAndConnect(int timeout); private: net::io_context&amp; ioc_; std::shared_ptr&lt;beast::tcp_stream&gt; stream_; std::string host_; int port_{ 0 }; //用于底层连接复用,是否失败时重连 bool reconnect_{ true }; beast::flat_buffer buffer_; // (Must persist between reads) http::request&lt;http::string_body&gt; request_; http::response&lt;http::string_body&gt; res_; TypeCallbackClientOnDone funcOnDone_; }; 改用strand序列化
07-24
&lt;think&gt;为了将`ClientSession`改为使用`strand`序列化所有异步操作,我们需要确保所有异步操作都通过`strand`分发执行。这样,即使`io_context`在多线程环境下运行,同一个`ClientSession`实例的异步操作也不会并发执行,从而保证线程安全。 ### 修改步骤: 1. **添加strand成员**:在`ClientSession`类中添加一个`net::strand`成员,用于序列化所有异步操作。 2. **修改构造函数**:初始化`strand_`成员。 3. **修改所有异步操作**:确保每个异步操作的开始(包括回调)都通过`strand_`分发。可以使用`net::dispatch`(立即执行)或`net::post`(排队执行)来将操作提交到`strand`中。另外,在异步操作的完成处理函数中,使用`bind_executor`绑定到`strand_`,确保回调在`strand_`中执行。 4. **调整`Run`方法**:将实际执行操作的`DoRun`方法通过`strand_`分发。 5. **调整`TakeStream`方法**:由于`TakeStream`可能被外部调用,我们也需要确保它在`strand`中执行,或者考虑其线程安全性(这里可能不需要,因为通常是在操作完成后调用,但为了安全,我们可以加锁或通过`strand`分发)。不过,根据需求,如果外部调用者能保证在正确的时机调用,可以暂时不修改,但为了线程安全,最好也通过`strand`分发。 ### 代码修改: ```cpp #include &lt;boost/beast/core.hpp&gt; #include &lt;boost/beast/http.hpp&gt; #include &lt;boost/asio.hpp&gt; namespace beast = boost::beast; namespace http = beast::http; namespace net = boost::asio; using tcp = net::ip::tcp; using TypeCallbackClientOnDone = std::function&lt;void(const char*, const std::string&amp;, const char*)&gt;; class ClientSession : public std::enable_shared_from_this&lt;ClientSession&gt; { public: explicit ClientSession(net::io_context&amp; ioc, std::shared_ptr&lt;beast::tcp_stream&gt; stream, const std::string&amp; host, int port) : ioc_(ioc) , strand_(net::make_strand(ioc)) // 创建strand , stream_(stream) , host_(host) , port_(port) { } void Run(int timeout, char const* target, const char* contentType, const std::string&amp; content, const TypeCallbackClientOnDone&amp; cb) { // 通过strand分发执行,确保线程安全 net::post(strand_, [self = shared_from_this(), timeout, target = std::string(target), contentType = std::string(contentType), content, cb]() { self-&gt;DoRun(timeout, target, contentType, content, cb); }); } // 注意:TakeStream可能需要考虑线程安全,如果外部调用者不确定,可以也通过strand分发 std::shared_ptr&lt;beast::tcp_stream&gt; TakeStream() { // 由于TakeStream可能被外部调用,我们通过strand确保安全 // 使用promise和future来同步获取结果 auto promise = std::make_shared&lt;std::promise&lt;std::shared_ptr&lt;beast::tcp_stream&gt;&gt;&gt;(); auto future = promise-&gt;get_future(); net::post(strand_, [self = shared_from_this(), promise]() { promise-&gt;set_value(self-&gt;stream_); // 注意:TakeStream后,当前session不再持有stream self-&gt;stream_.reset(); }); return future.get(); } private: void DoRun(int timeout, const std::string&amp; target, const std::string&amp; contentType, const std::string&amp; content, const TypeCallbackClientOnDone&amp; cb) { // 保存回调 funcOnDone_ = cb; // 设置请求 request_.version(11); // HTTP/1.1 request_.method(http::verb::post); request_.target(target); request_.set(http::field::host, host_); request_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); request_.set(http::field::content_type, contentType); request_.body() = content; request_.prepare_payload(); // 设置超时 stream_-&gt;expires_after(std::chrono::milliseconds(timeout)); // 如果stream已经连接,直接进行写操作;否则需要先连接 if (stream_-&gt;socket().is_open()) { DoWrite(timeout); } else { // 异步连接 auto const address = net::ip::make_address(host_); stream_-&gt;async_connect( tcp::endpoint{ address, static_cast&lt;unsigned short&gt;(port_) }, beast::bind_front_handler( &amp;ClientSession::OnConnect, shared_from_this(), timeout ) ); } } void OnConnect(beast::error_code ec, int timeout) { if (ec) { return ReportFail(ec.message(), &quot;connect&quot;); } DoWrite(timeout); } void DoWrite(int timeout) { // 发送HTTP请求 http::async_write( *stream_, request_, beast::bind_front_handler( &amp;ClientSession::OnWrite, shared_from_this(), timeout ) ); } void OnWrite(beast::error_code ec, std::size_t, int timeout) { if (ec) { return ReportFail(ec.message(), &quot;write&quot;); } // 读取响应 http::async_read( *stream_, buffer_, res_, beast::bind_front_handler( &amp;ClientSession::OnRead, shared_from_this(), timeout ) ); } void OnRead(beast::error_code ec, std::size_t, int timeout) { if (ec) { return ReportFail(ec.message(), &quot;read&quot;); } // 调用回调,传递响应结果 if (funcOnDone_) { funcOnDone_(res_.body().c_str(), &quot;&quot;, nullptr); } // 注意:这里不关闭socket,以便复用 // 重置buffer和响应对象,以便下次使用 buffer_.consume(buffer_.size()); res_ = {}; } void ReportFail(const std::string&amp; errMsg, char const* what) { if (funcOnDone_) { funcOnDone_(nullptr, errMsg, what); } // 失败后关闭socket CloseSocket(); } void CloseSocket() { beast::error_code ec; stream_-&gt;socket().shutdown(tcp::socket::shutdown_both, ec); stream_-&gt;close(); buffer_.consume(buffer_.size()); } private: net::io_context&amp; ioc_; net::strand&lt;net::io_context::executor_type&gt; strand_; // 添加strand成员 std::shared_ptr&lt;beast::tcp_stream&gt; stream_; std::string host_; int port_; beast::flat_buffer buffer_; http::request&lt;http::string_body&gt; request_; http::response&lt;http::string_body&gt; res_; TypeCallbackClientOnDone funcOnDone_; }; ``` ### 关键点解释: 1. **添加strand**:使用`net::make_strand(ioc)`创建strand,它关联到`io_context`的执行器。 2. **通过strand分发操作**:所有公共接口(如`Run`和`TakeStream`)都通过`net::post`将实际工作分发到`strand`中,确保操作在`strand`中顺序执行。 3. **异步操作回调绑定到strand**:在发起异步操作(如`async_connect`、`async_write`、`async_read`)时,使用`beast::bind_front_handler`绑定成员函数,这些回调函数会在异步操作完成时被调用。由于我们在`strand`中启动这些操作,并且这些操作的回调也是通过`strand`分发(因为我们在`strand`中启动操作,所以回调默认会在同一个`strand`中执行),因此保证了整个操作链都在同一个`strand`中执行,不会并发。 4. **TakeStream的线程安全**:在`TakeStream`中,我们通过`net::post`将获取`stream_`的操作放到`strand`中,并使用`promise`和`future`来等待结果。这样外部调用`TakeStream`的线程会阻塞直到`strand`中执行完该操作,从而保证线程安全。 ### 注意事项: - 使用`strand`后,同一个`ClientSession`实例的所有异步操作(包括回调)都会在同一个`strand`中顺序执行,因此不会出现并发访问成员变量的情况。 - 在`TakeStream`中,我们使用了阻塞等待(`future.get()`),这可能会导致调用线程阻塞。如果不想阻塞,可以设计一个异步版本的`TakeStream`,通过回调返回结果。 - 在`Run`方法中,我们将参数复制到lambda中,确保在分发到`strand`时参数仍然有效。 ### 相关问题: &sect;&sect;相关问题&sect;&sect;: 1. 在Boost.Asio中,strand和io_context的关系是什么? 2. 使用strand时,如何避免死锁或竞争条件? 3. 在异步操作链中,如何确保对象的生命周期? 4. Boost.Asio中的`bind_executor`和`bind_front_handler`有什么区别和联系?&lt;/think&gt;### 使用 Strand 序列化改造 ClientSession 以下是使用 `boost::asio::strand` 重构 `ClientSession` 的完整实现,确保所有异步操作线程安全: ```cpp #include &lt;boost/beast/core.hpp&gt; #include &lt;boost/beast/http.hpp&gt; #include &lt;boost/asio.hpp&gt; #include &lt;memory&gt; namespace beast = boost::beast; namespace http = beast::http; namespace net = boost::asio; using tcp = net::ip::tcp; using TypeCallbackClientOnDone = std::function&lt;void(const char*, const std::string&amp;, const char*)&gt;; class ClientSession : public std::enable_shared_from_this&lt;ClientSession&gt; { public: explicit ClientSession(net::io_context&amp; ioc, std::shared_ptr&lt;beast::tcp_stream&gt; stream, const std::string&amp; host, int port) : ioc_(ioc) , strand_(net::make_strand(ioc)) // 关键:创建strand , stream_(std::move(stream)) , host_(host) , port_(port) {} void Run(int timeout, char const* target, const char* contentType, const std::string&amp; content, const TypeCallbackClientOnDone&amp; cb) { // 通过strand分发操作 net::post( strand_, [self = shared_from_this(), timeout, target = std::string(target), contentType = std::string(contentType), content, cb]() { self-&gt;DoRun(timeout, target, contentType, content, cb); } ); } std::shared_ptr&lt;beast::tcp_stream&gt; TakeStream() { // 使用promise/future确保线程安全 auto promise = std::make_shared&lt;std::promise&lt;std::shared_ptr&lt;beast::tcp_stream&gt;&gt;&gt;(); auto future = promise-&gt;get_future(); net::post( strand_, [self = shared_from_this(), promise]() { promise-&gt;set_value(self-&gt;stream_); self-&gt;stream_.reset(); // 转移所有权后重置 } ); return future.get(); } private: void DoRun(int timeout, const std::string&amp; target, const std::string&amp; contentType, const std::string&amp; content, const TypeCallbackClientOnDone&amp; cb) { funcOnDone_ = cb; request_.version(11); // HTTP/1.1 request_.method(http::verb::post); request_.target(target); request_.set(http::field::host, host_); request_.set(http::field::user_agent, BOOST_BEAST_VERSION_STRING); request_.set(http::field::content_type, contentType); request_.body() = content; request_.prepare_payload(); // 设置超时 stream_-&gt;expires_after(std::chrono::milliseconds(timeout)); if (stream_-&gt;socket().is_open()) { DoWrite(timeout); } else { auto const address = net::ip::make_address(host_); // 异步连接(绑定到strand) stream_-&gt;async_connect( tcp::endpoint{address, static_cast&lt;unsigned short&gt;(port_)}, net::bind_executor( strand_, beast::bind_front_handler( &amp;ClientSession::OnConnect, shared_from_this(), timeout ) ) ); } } void OnConnect(beast::error_code ec, int timeout) { if (ec) { return ReportFail(ec.message(), &quot;connect&quot;); } DoWrite(timeout); } void DoWrite(int timeout) { // 使用strand绑定写操作 http::async_write( *stream_, request_, net::bind_executor( strand_, beast::bind_front_handler( &amp;ClientSession::OnWrite, shared_from_this(), timeout ) ) ); } void OnWrite(beast::error_code ec, std::size_t, int timeout) { if (ec) { return ReportFail(ec.message(), &quot;write&quot;); } // 使用strand绑定读操作 http::async_read( *stream_, buffer_, res_, net::bind_executor( strand_, beast::bind_front_handler( &amp;ClientSession::OnRead, shared_from_this(), timeout ) ) ); } void OnRead(beast::error_code ec, std::size_t, int timeout) { if (ec) { return ReportFail(ec.message(), &quot;read&quot;); } // 在strand中执行回调 if (funcOnDone_) { funcOnDone_(res_.body().c_str(), &quot;&quot;, nullptr); } // 清理状态 buffer_.consume(buffer_.size()); res_ = {}; } void ReportFail(const std::string&amp; errMsg, char const* what) { // 确保回调在strand中执行 net::post( strand_, [self = shared_from_this(), errMsg, what]() { if (self-&gt;funcOnDone_) { self-&gt;funcOnDone_(nullptr, errMsg, what); } self-&gt;CloseSocket(); } ); } void CloseSocket() { beast::error_code ec; stream_-&gt;cancel(); stream_-&gt;socket().shutdown(tcp::socket::shutdown_both, ec); stream_-&gt;close(ec); buffer_.consume(buffer_.size()); } private: net::io_context&amp; ioc_; net::strand&lt;net::io_context::executor_type&gt; strand_; // 核心同步机制 std::shared_ptr&lt;beast::tcp_stream&gt; stream_; std::string host_; int port_; beast::flat_buffer buffer_; http::request&lt;http::string_body&gt; request_; http::response&lt;http::string_body&gt; res_; TypeCallbackClientOnDone funcOnDone_; }; ``` ### 关键改造点详解 1. **Strand 集成** ```cpp net::strand&lt;net::io_context::executor_type&gt; strand_; ``` - 使用 `net::make_strand(ioc)` 创建与 io_context 关联的 strand - 所有操作通过 `net::post(strand_, ...)` 分发 2. **异步操作绑定** ```cpp http::async_read( *stream_, buffer_, res_, net::bind_executor( // 关键绑定 strand_, beast::bind_front_handler(...) ) ); ``` - `net::bind_executor` 确保回调在 strand 上下文中执行 - `beast::bind_front_handler` 适配成员函数 3. **线程安全状态管理** ```cpp net::post(strand_, [self = shared_from_this(), ...]() { // 所有状态访问都在strand内 self-&gt;request_.body() = content; }); ``` - 使用 `shared_from_this()` 确保对象生命周期 - 所有成员变量访问都在 strand 上下文中 4. **安全回调执行** ```cpp net::post(strand_, [self, errMsg, what]() { if (self-&gt;funcOnDone_) { self-&gt;funcOnDone_(...); } }); ``` - 用户回调在 strand 中执行,避免并发问题 - 防止回调执行期间对象被销毁 ### Strand 工作流程 ```mermaid sequenceDiagram participant Caller as 外部调用者 participant Strand as Strand队列 participant Socket as Socket操作 participant Callback as 用户回调 Caller-&gt;&gt;Strand: post(Run) Strand-&gt;&gt;Socket: async_connect Socket--&gt;&gt;Strand: OnConnect Strand-&gt;&gt;Socket: async_write Socket--&gt;&gt;Strand: OnWrite Strand-&gt;&gt;Socket: async_read Socket--&gt;&gt;Strand: OnRead Strand-&gt;&gt;Callback: 安全执行funcOnDone_ ``` ### 性能优化技巧 1. **移动语义优化** ```cpp Run(..., const std::string&amp; content, ...) { net::post(strand_, [self, content = std::move(content)]() {...}); } ``` 2. **Strand 局部化** ```cpp // 为不同资源使用独立strand net::strand read_strand_ = net::make_strand(ioc_); net::strand write_strand_ = net::make_strand(ioc_); ``` 3. **批处理操作** ```cpp void BatchRequests(const std::vector&lt;Request&gt;&amp; requests) { net::post(strand_, [self, requests]() { for (auto&amp; req : requests) { self-&gt;request_queue_.push(req); } if (self-&gt;request_queue_.size() == requests.size()) { self-&gt;ProcessBatch(); } }); } ``` 4. **动态超时调整** ```cpp void OnRead(..., int timeout) { // 根据响应大小调整后续超时 int new_timeout = CalculateTimeout(res_.body().size()); stream_-&gt;expires_after(std::chrono::milliseconds(new_timeout)); } ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值