Android環(huán)境上用C++使用libcurl實(shí)現(xiàn)4G蜂窩網(wǎng)絡(luò)雙通道的技術(shù)探索

查看圖片
[TOC]

雙通道概述

指設(shè)備連接著WiFi的情況下,同時(shí)打開蜂窩通道,從而實(shí)現(xiàn)雙通道同時(shí)進(jìn)行網(wǎng)絡(luò)請(qǐng)求,提高訪問速度。

graph LR
    client---|socket1| WiFi網(wǎng)卡 -->|socket1|Server
    client---|socket2| 4G蜂窩網(wǎng)絡(luò)網(wǎng)卡 -->|socket2| Server

Android系統(tǒng)下雙通結(jié)合libCurl的方案概述

不同于iOS的權(quán)限簡(jiǎn)潔,能將網(wǎng)卡直接丟給libCurl使用。Android在native層使用4G網(wǎng)卡,需要費(fèi)勁很多。

sequenceDiagram
native ->>+ java: 發(fā)起請(qǐng)求打開雙通

java ->>+ Android OS: 向系統(tǒng)申請(qǐng)4G通道
Note over Android OS : 手機(jī)頂部狀態(tài)欄的4G流量圖標(biāo)常駐
Android OS ->>- java: 持有SocketFactory

java ->>- native:回調(diào)開啟成功

loop 網(wǎng)絡(luò)請(qǐng)求

native -->> native:發(fā)起網(wǎng)絡(luò)請(qǐng)求

native ->>+ java:通知?jiǎng)?chuàng)建socket
java ->>+ Android OS: 創(chuàng)建socket
java ->> java: 使用socket解析域名
Android OS ->>- java:已經(jīng)綁定過目標(biāo)IP的socket

java ->>- native:獲取socket的fd

native ->>+ server: libcurl正常建連
server ->>- native: 完成數(shù)據(jù)傳輸

end

native ->>+ java:關(guān)閉4G通道
java ->>+ Android OS: 主動(dòng)釋放4G通道
Note over Android OS : 手機(jī)頂部狀態(tài)欄上4G流量圖標(biāo)消失
Android OS ->>- java: 釋放成功
java ->>- native:回調(diào)關(guān)閉成功

關(guān)于域名解析服務(wù):

僅使用localHost時(shí):

如果僅使用localHost,那么只需要java層面,用socket綁定域名,不需要額外代碼,就能自動(dòng)的在4G網(wǎng)卡上進(jìn)行域名解析為ip。
查看socket綁定的ip是socket.getRemoteSocketAddress()

使用自有DNS服務(wù)

在指定socket目的IP時(shí),必須主動(dòng)區(qū)分當(dāng)前socket出口的IP類型,不能講IPv4與IPv6混用。如果結(jié)合自己的DNS方案,那就需要把IPv6作為判斷的參數(shù)。這里的樣例代碼,也是僅僅是從已經(jīng)建聯(lián)的socket中取出ip地址,沒有進(jìn)一步復(fù)雜化。

傳遞java上層的socket到native

這里利用到的是ParcelFileDescriptor,在java層獲取socket的描述符,直接丟給libCurl使用

問題概述

  1. 對(duì)比iOS的方案,Android環(huán)境下,libCurl不能直接綁定4G網(wǎng)卡,在Android系統(tǒng)層面,限制了直接使用4G網(wǎng)卡。
  2. Android系統(tǒng)繞不開Java層配合。
  3. Android上層保持4G通道的打開,libCurl仍然不能直接使用4G網(wǎng)卡。
  4. Android系統(tǒng)繞不開Java層創(chuàng)建network(可用這個(gè)創(chuàng)建socket)
  5. 開啟與關(guān)閉雙通,都需要C++層面通知到Java層。

代碼說明

Android雙通的基本使用

在普通的使用場(chǎng)景中,我們不關(guān)注SocketFactory,也不用關(guān)注域名解析,因?yàn)橄到y(tǒng)API返回的connect可以直接進(jìn)行網(wǎng)絡(luò)請(qǐng)求,而內(nèi)部的域名解析細(xì)節(jié)可以完全忽略:network.openConnection(new URL(url));。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
        final String TAG = "NetworkCardInfo";
        TlogUtils.d(TAG, "4g通道,嘗試開啟");
        ConnectivityManager connectivityManager = (ConnectivityManager) getApplication().getSystemService(CONNECTIVITY_SERVICE);

        NetworkRequest request = new NetworkRequest.Builder()
                .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();

        ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
            @Override
            public void onAvailable(Network network) {
                TlogUtils.d(TAG, "4g通道,已經(jīng)開啟");
                // 用network進(jìn)行網(wǎng)絡(luò)請(qǐng)求
                try {
                    HttpURLConnection urlConnection = (HttpURLConnection) network.openConnection(new URL(url));
                    int code = urlConnection.getResponseCode();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        };
        connectivityManager.requestNetwork(request, networkCallback);
    }
}

不成功的方案:libCurl直接綁定4G網(wǎng)卡

精準(zhǔn)的獲取蜂窩網(wǎng)卡名:
Android設(shè)備下,網(wǎng)卡非常多,想獲取到有效的網(wǎng)卡,必須過濾網(wǎng)卡名,同時(shí)必須判斷網(wǎng)卡下是否存在有效的IP:

import java.net.Inet6Address;
import java.net.InetAddress;
import java.net.NetworkInterface;
import java.util.Enumeration;

final class NetworkCardInfo {
    private final static String TAG = "NetworkCardInfo";

    /**
     * 獲取4G網(wǎng)卡名
     */
    public static String getNetworkCardName(boolean isWifi) {
        String networkCardNameKey = isWifi ? "wlan" : "rmnet";
        final String tag = TAG + "," + networkCardNameKey;
        NameAndIp nameAndIp = getNetworkCardInfoOfCellularImpl(networkCardNameKey, tag);

        TlogUtils.i(tag, "獲取到的網(wǎng)卡名:" + nameAndIp);

//        if (isWifi) {
            return nameAndIp.name;
//        } else {
//            return nameAndIp.ip;
//        }
    }

    private static class NameAndIp {
        private String name;
        private String ip;

        public NameAndIp(String name, String ip) {
            this.name = name;
            this.ip = ip;
        }

        @Override
        public String toString() {
            return "NameAndIp{" + "name='" + name + '\'' + ", ip='" + ip + '\'' + '}';
        }
    }

    private static NameAndIp getNetworkCardInfoOfCellularImpl(String networkCardNameKey, String tag) {
        try {
            Enumeration nis = NetworkInterface.getNetworkInterfaces();
            while (nis.hasMoreElements()) {
                NetworkInterface ni = (NetworkInterface) nis.nextElement();
                String name = ni.getName();
                if (name == null) {
                    name = "";
                }
                String nameCompare = name.toLowerCase();
                if (!nameCompare.contains(networkCardNameKey)) {
                    TlogUtils.d(tag, "網(wǎng)卡不符:" + name);
                    continue;
                }
                TlogUtils.d(tag, "網(wǎng)卡可疑:" + name);
                Enumeration<InetAddress> ias = ni.getInetAddresses();
                while (ias.hasMoreElements()) {
                    InetAddress ia = ias.nextElement();
//                    // ipv6
//                    if(ia instanceof Inet6Address) {
//                        continue;
//                    }
                    String ip = ia.getHostAddress();
                    // 局域網(wǎng)與廣域網(wǎng)IP才認(rèn)為合理,否則就認(rèn)為不可用

                    if (ia.isSiteLocalAddress()) {
                        // 判斷出當(dāng)前是否為局域網(wǎng)IP,局域網(wǎng)也是合理的
                        TlogUtils.d(tag, "isSiteLocalAddress:" + ip);
                        return new NameAndIp(name, ip);
                    } else if (ia.isLoopbackAddress()) {
                        TlogUtils.d(tag, "isLoopbackAddress:" + ip);
                    } else if (ia.isAnyLocalAddress()) {
                        TlogUtils.d(tag, "isAnyLocalAddress:" + ip);
                    } else if (ia.isLinkLocalAddress()) {
                        TlogUtils.d(tag, "isLinkLocalAddress:" + ip);
                    } else if (ia.isMulticastAddress()) {
                        TlogUtils.d(tag, "isMulticastAddress:" + ip);
                    } else if (ia.isMCGlobal()) {
                        TlogUtils.d(tag, "isMCGlobal:" + ip);
                    } else if (ia.isMCLinkLocal()) {
                        TlogUtils.d(tag, "isMCLinkLocal:" + ip);
                    } else if (ia.isMCNodeLocal()) {
                        TlogUtils.d(tag, "isMCNodeLocal:" + ip);
                    } else if (ia.isMCOrgLocal()) {
                        TlogUtils.d(tag, "isMCOrgLocal:" + ip);
                    } else if (ia.isMCSiteLocal()) {
                        TlogUtils.d(tag, "isMCSiteLocal:" + ip);
                    } else {
                        // 能走到這里,說明是個(gè)普通的廣域網(wǎng)IP
                        TlogUtils.d(tag, "普通ip:" + ip);
                        return new NameAndIp(name, ip);
                    }
                }
            }
        } catch (Throwable e) {
            e.printStackTrace();
        }
        return null;
    }
}

讓libcurl綁定網(wǎng)卡:

            curl_easy_setopt(dlcurl->curl, CURLOPT_INTERFACE, "name");

得到錯(cuò)誤日志:

2020-08-26 20:00:40.660 4103-8729/com.phone I/DownloadNative.v2: YKCLog_v2: XNDczNDUwMDIwOA==-2 ->[ |name= |videoRetry=0 |retryTime=2 |reqUrl=http://vali.cp31.ott.cibntv.net/67756D6080932713CFC02204E/03000500005F057E4E8BB780000000B94C56DC-0A70-4635-B35C-3E5A291ECB30-00002.ts?ccode=01010201&duration=2700&expire=18000&psid=9bc52504cc5c194fcd8a64f56679022c428e5&ups_client_netip=6a0b29dc&ups_ts=1598443186&ups_userid=1340295651&utid=XvD8%2BJtNHPkDAIaQEYw6XqaA&vid=XNDczNDUwMDIwOA%3D%3D&sm=1&operate_type=1&dre=u30&si=76&eo=0&dst=1&iv=1&s=bcecd0971b7f4e449eea&type=flvhdv3&bc=2&hotvt=1&rid=20000000163E8ED36726AEE7B2825AE912CC906B02000000&vkey=B9f5937585ebfe0e1f40685ecb4039c79&f=vali |finalPath=http://vali.cp31.ott.cibntv.net/67756D6080932713CFC02204E/03000500005F057E4E8BB780000000B94C56DC-0A70-4635-B35C-3E5A291ECB30-00002.ts?ccode=01010201&duration=2700&expire=18000&psid=9bc52504cc5c194fcd8a64f56679022c428e5&ups_client_netip=6a0b29dc&ups_ts=1598443186&ups_userid=1340295651&utid=XvD8%2BJtNHPkDAIaQEYw6XqaA&vid=XNDczNDUwMDIwOA%3D%3D&sm=1&operate_type=1&dre=u30&si=76&eo=0&dst=1&iv=1&s=bcecd0971b7f4e449eea&type=flvhdv3&bc=2&hotvt=1&rid=20000000163E8ED36726AEE7B2825AE912CC906B02000000&vkey=B9f5937585ebfe0e1f40685ecb4039c79&f=vali |savePath=/storage/emulated/0/Android/data/com.phone/files/offlinedata/XNDczNDUwMDIwOA==/2 |finalIpUrl= |repCode=7 |httpCode=0 |io_error:code=0desc:Success |retryType=0 |proxyUrl= |ipIndex=1 ip=113.142.161.248 isHttpIp1 |segSize=1119916 |curSpeed=-1 |contentLen=-1 |downloadLen=0 |contentType= |fileSize=0 |ioErrorDesc= |redirectUrls= |curlInfo= => Hostname 'vali.cp31.ott.cibntv.net' was found in DNS cache
     =>   Trying 202.108.249.185...
     => TCP_NODELAY set
     => Local Interface rmnet_data0 is ip 10.229.85.126 using address family 2
     => SO_BINDTODEVICE rmnet_data0 failed with errno 1: Operation not permitted; will do regular bind
     => Local port: 0
     => After 5948ms connect time, move on!
     => connect to 202.108.249.185 port 80 failed: Connection timed out
     =>   Trying 202.108.249.186...
     => TCP_NODELAY set
     => Local Interface rmnet_data0 is ip 10.229.85.126 using address family 2
     => SO_BINDTODEVICE rmnet_data0 failed with errno 1: Operation not permitted; will do regular bind
     => Local port: 0
     => After 2915ms connect time, move on!
     => connect to 202.108.249.186 port 80 failed: Connection timed out
     => Failed to connect to vali.cp31.ott.cibntv.net port 80: Connection timed out
     => Closing connection 21
    ]

這個(gè)方案,在iOS系統(tǒng)上,其實(shí)是可以正常。但是Android系統(tǒng)存在的權(quán)限管理問題,導(dǎo)致C++的libcurl報(bào)錯(cuò)。

我們查看curl的源碼介紹,并沒有找到有效的解決方案

#ifdef SO_BINDTODEVICE
      /* I am not sure any other OSs than Linux that provide this feature,
       * and at the least I cannot test. --Ben
       *
       * This feature allows one to tightly bind the local socket to a
       * particular interface.  This will force even requests to other
       * local interfaces to go out the external interface.
       *
       *
       * Only bind to the interface when specified as interface, not just
       * as a hostname or ip address.
       *
       * interface might be a VRF, eg: vrf-blue, which means it cannot be
       * converted to an IP address and would fail Curl_if2ip. Simply try
       * to use it straight away.
       */
      if(setsockopt(sockfd, SOL_SOCKET, SO_BINDTODEVICE,
                    dev, (curl_socklen_t)strlen(dev) + 1) == 0) {
        /* This is typically "errno 1, error: Operation not permitted" if
         * you're not running as root or another suitable privileged
         * user.
         * If it succeeds it means the parameter was a valid interface and
         * not an IP address. Return immediately.
         */
        return CURLE_OK;
      }
#endif

透?jìng)鱯ocket方案:

java層申請(qǐng)打開蜂窩通道,創(chuàng)建一個(gè)socket,將socket透?jìng)鹘oC++


    void openDoubleChanel() {
        TlogUtils.d(TAG, "4g通道,嘗試開啟");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            final ConnectivityManager connectivityManager = (ConnectivityManager) getApplication().getSystemService(CONNECTIVITY_SERVICE);

            NetworkRequest request = new NetworkRequest.Builder()
                    .addTransportType(NetworkCapabilities.TRANSPORT_CELLULAR)
                    .addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET).build();

            connectivityManager.requestNetwork(request, new NetworkCallbackImpl(connectivityManager));
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    public static class NetworkCallbackImpl extends ConnectivityManager.NetworkCallback {
        final ConnectivityManager connectivityManager;

        public NetworkCallbackImpl(ConnectivityManager connectivityManager) {
            this.connectivityManager = connectivityManager;
        }

        @Override
        public void onAvailable(Network network) {
            TlogUtils.d(TAG, "4g通道,已經(jīng)開啟");
            // 不要測(cè)百度,百度不支持IPv6,但是可以測(cè)m.baidu.com
            String url = "http://valipl.cp31.ott.cibntv.net/6975030867335716092826D8B/03000300005F56527AB06EB7961451D178FC12-4CD4-44A7-A033-FAE171DF26A0.m3u8?ccode=01010101&duration=5374&expire=18000&psid=851c388bd1f8fc9a0aa11888f95b2854434af&ups_client_netip=2f580582&ups_ts=1599658512&ups_userid=1340295651&utid=XvD8%2BJtNHPkDAIaQEYw6XqaA&vid=XNDgwODE0NDc0MA&vkey=Bfcc9d9a459df2c6a97c2df3e223e65df&sm=1&operate_type=1&dre=u33&si=75&eo=0&dst=1&iv=1&s=aedc187a7603482190d5&type=3gphdv3&bc=2&rid=20000000DB5093085DD73D72365407F5E57F9FBE02000000";//hostEt.getText().toString();
            try {
                URL url1 = new URL(url);
                int port = url1.getPort();
                if (port <= -1) {
                    port = url1.getDefaultPort();
                }

                SocketFactory socketFactory = network.getSocketFactory();

                //java 層創(chuàng)建好socket并綁好
                final Socket socket = socketFactory.createSocket(url1.getHost(), port);
                InetSocketAddress socketAddress = (InetSocketAddress) socket.getRemoteSocketAddress();
                Log.e(TAG, "java層解析到的ip: " + socketAddress);
                final ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.fromSocket(socket);
                // 開啟C++
                IYKCache.testSocket(parcelFileDescriptor.getFd(), url, new SocketOptionImpl(connectivityManager, this, socket, parcelFileDescriptor));
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }

    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    private static class SocketOptionImpl implements SocketOption {
        private final ConnectivityManager connectivityManager;
        private final NetworkCallbackImpl networkCallback;
        private final Socket socket;
        private final ParcelFileDescriptor parcelFileDescriptor;

        public SocketOptionImpl(ConnectivityManager connectivityManager, NetworkCallbackImpl networkCallback, Socket socket, ParcelFileDescriptor parcelFileDescriptor) {
            this.connectivityManager = connectivityManager;
            this.networkCallback = networkCallback;
            this.socket = socket;
            this.parcelFileDescriptor = parcelFileDescriptor;
        }

        // 關(guān)閉socket
        @Override
        public void closeSocket() {
            try {
                socket.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
            parcelFileDescriptor.detachFd();
            try {
                parcelFileDescriptor.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }

        // 關(guān)閉雙通
        @Override
        public void closeDoubleChanel() {
            connectivityManager.unregisterNetworkCallback(networkCallback);
        }
    }

對(duì)應(yīng)的C++層發(fā)起請(qǐng)求,請(qǐng)求結(jié)束后關(guān)閉socket,關(guān)閉4G通道


#include <string>

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <curl/curl.h>

#ifdef WIN32
#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#define close closesocket
#else

#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>

#endif


std::string resStr;

static size_t write_data(void *ptr, size_t size, size_t nmemb, void *stream) {
    resStr += std::string((char *) ptr, size * nmemb);
    return size * nmemb;
}

static curl_socket_t
opensocket(void *clientp, curlsocktype purpose, struct curl_sockaddr *address) {
    curl_socket_t sockfd;
    (void) purpose;
    (void) address;
    sockfd = *(curl_socket_t *) clientp;
    /* the actual externally set socket is passed in via the OPENSOCKETDATA
       option */

    flash_util::log::d("雙通道", std::string("opensocket"));
    return sockfd;
}

static int closecb(void *clientp, curl_socket_t item) {
    (void) clientp;
    printf("libcurl wants to close %d now\n", (int) item);
    return 0;
}

std::string errStr;

static int
dl_curl_debug_cb(CURL *handle, curl_infotype type, char *data, size_t size, void *userp) {
    errStr += std::string((char *) data, size);
    return 0;
}


static int sockopt_callback(void *clientp, curl_socket_t curlfd,
                            curlsocktype purpose) {
    (void) clientp;
    (void) curlfd;
    (void) purpose;
    /* This return code was added in libcurl 7.21.5 */
    return CURL_SOCKOPT_ALREADY_CONNECTED;
}
extern "C"
JNIEXPORT void JNICALL
Java_com_flash_downloader_jni_FlashDownloaderJni_testSocket(JNIEnv *env, jclass clazz,
                                                                  jint sockfd, 
                                                                  jstring urlJ,
                                                                  jobject socket_optionJ) {
    CURL *curl = curl_easy_init();

    const std::string &url = parseJStringAndDeleteRef(env, urlJ);
    curl_easy_setopt(curl, CURLOPT_URL, url.c_str());

    /* no progress meter please */
    curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 1L);

    /* send all data to this function  */
    curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_data);

    /* call this function to get a socket */
    curl_easy_setopt(curl, CURLOPT_OPENSOCKETFUNCTION, opensocket);
    curl_easy_setopt(curl, CURLOPT_OPENSOCKETDATA, &sockfd);

    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
    curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L);

    curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1);

    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1L);
    curl_easy_setopt(curl, CURLOPT_DEBUGFUNCTION, dl_curl_debug_cb);
//    curl_easy_setopt(curl, CURLOPT_DEBUGDATA, dlcurl);

    /* call this function to close sockets */
    curl_easy_setopt(curl, CURLOPT_CLOSESOCKETFUNCTION, closecb);
    curl_easy_setopt(curl, CURLOPT_CLOSESOCKETDATA, &sockfd);

    /* call this function to set options for the socket */
    curl_easy_setopt(curl, CURLOPT_SOCKOPTFUNCTION, sockopt_callback);

//    curl_easy_setopt(curl, CURLOPT_VERBOSE, 1);
    flash_util::log::d("雙通道", "begin");
    resStr = "";
    errStr = "";
    CURLcode resCode = curl_easy_perform(curl);
    char *ip;
    curl_easy_getinfo(curl, CURLINFO_PRIMARY_IP, &(ip));


    close(sockfd);
    //回調(diào)java層關(guān)閉fd引用
    env->CallVoidMethod(socket_optionJ, env->GetMethodID(env->GetObjectClass(socket_optionJ), "closeSocket", "()V"));
    //回調(diào)java層關(guān)閉雙通
    env->CallVoidMethod(socket_optionJ, env->GetMethodID(env->GetObjectClass(socket_optionJ), "closeDoubleChanel", "()V"));

    curl_easy_cleanup(curl);

    flash_util::log::d("雙通道",
                       "code:" + std::to_string(resCode) + " ip:" + ip + "\nresStr:" + resStr + "\nerrStr:" + errStr);
}

成功發(fā)起請(qǐng)求,且IP為IPv6類型,當(dāng)前設(shè)備僅蜂窩網(wǎng)才有IPv6,所以說明流量走了蜂窩網(wǎng)。

2020-09-10 12:06:51.377 31409-31409/com.phone D/YKDownload.雙通道: 4g通道,嘗試開啟
2020-09-10 12:06:52.085 31409-31557/com.phone D/YKDownload.雙通道: 4g通道,已經(jīng)開啟
2020-09-10 12:06:52.471 31409-31557/com.phone E/雙通道: java層解析到的ip: valipl.cp31.ott.cibntv.net/2408:871a:1001:51ff::ff53:80
2020-09-10 12:06:53.282 31409-31557/com.phone D/DownloadNative.雙通道: begin
2020-09-10 12:06:53.349 31409-31557/com.phone D/DownloadNative.雙通道: opensocket
2020-09-10 12:06:53.400 31409-31557/com.phone D/DownloadNative.雙通道: code:0 ip:2408:871a:1001:51ff::ff53
    resStr:<html>
    <head><title>403 Forbidden</title></head>
    <body bgcolor="white">
    <center><h1>403 Forbidden</h1></center>
    <hr><center>openresty</center>
    </body>
    </html>
    
    errStr:  Trying 101.227.24.225...
    TCP_NODELAY set
    Connected to valipl.cp31.ott.cibntv.net (2408:871a:1001:51ff::ff53) port 80 (#0)
    GET /6975030867335716092826D8B/03000300005F56527AB06EB7961451D178FC12-4CD4-44A7-A033-FAE171DF26A0.m3u8?ccode=01010101&duration=5374&expire=18000&psid=851c388bd1f8fc9a0aa11888f95b2854434af&ups_client_netip=2f580582&ups_ts=1599658512&ups_userid=1340295651&utid=XvD8%2BJtNHPkDAIaQEYw6XqaA&vid=XNDgwODE0NDc0MA&vkey=Bfcc9d9a459df2c6a97c2df3e223e65df&sm=1&operate_type=1&dre=u33&si=75&eo=0&dst=1&iv=1&s=aedc187a7603482190d5&type=3gphdv3&bc=2&rid=20000000DB5093085DD73D72365407F5E57F9FBE02000000 HTTP/1.1
    Host: valipl.cp31.ott.cibntv.net
    Accept: */*
    
    HTTP/1.1 403 Forbidden
    Date: Thu, 10 Sep 2020 04:06:52 GMT
    Content-Type: text/html
    Content-Length: 166
    Connection: keep-alive
    anti-detail-code: 403611
    Access-Control-Allow-Origin: *
    Access-Control-Expose-Headers: Content-Length
    cloud_type: aliyun
    Server: Tengine
    
    <html>
    <head><title>403 Forbidden</title></head>
    <body bgcolor="white">
    <center><h1>403 Forbidden</h1></center>
    <hr><center>openresty</center>
    </body>
    </html>
    Curl_http_done: called premature == 0
    Connection #0 to host valipl.cp31.ott.cibntv.net left intact

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

推薦閱讀更多精彩內(nèi)容