最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

深入理解C++管道編程

 更新時間:2026年02月07日 10:38:08   作者:碼事漫談  
本文主要介紹了深入理解C++管道編程,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧

第一章:管道編程的核心概念

1.1 什么是管道?

管道是UNIX和類UNIX系統(tǒng)中最古老、最基礎的進程間通信(IPC)機制之一。你可以將它想象成現(xiàn)實世界中的水管:數(shù)據(jù)像水流一樣從一個進程"流"向另一個進程。

核心特征

  • 半雙工通信:數(shù)據(jù)只能單向流動(要么從A到B,要么從B到A)
  • 字節(jié)流導向:沒有消息邊界,數(shù)據(jù)是連續(xù)的字節(jié)流
  • 基于文件描述符:使用與文件操作相同的接口
  • 內核緩沖區(qū):數(shù)據(jù)在內核緩沖區(qū)中暫存

1.2 管道的工作原理

讓我們通過一個簡單的比喻來理解管道的工作原理:

想象兩個進程要通過管道通信:

進程A(寫端) → [內核緩沖區(qū)] → 進程B(讀端)

內核緩沖區(qū)的作用

  1. 當進程A寫入數(shù)據(jù)時,數(shù)據(jù)先進入內核緩沖區(qū)
  2. 進程B從緩沖區(qū)讀取數(shù)據(jù)
  3. 如果緩沖區(qū)空,讀操作會阻塞(等待數(shù)據(jù))
  4. 如果緩沖區(qū)滿,寫操作會阻塞(等待空間)

匿名管道的關鍵限制

  • 只能用于有"親緣關系"的進程間通信(通常是父子進程或兄弟進程)
  • 生命周期隨進程結束而結束
  • 無法在無關進程間使用

第二章:入門實踐——創(chuàng)建第一個管道

2.1 理解文件描述符

在深入代碼之前,必須理解文件描述符的概念:

// 每個進程都有這三個標準文件描述符:
// 0 - 標準輸入(stdin)   → 通常從鍵盤讀取
// 1 - 標準輸出(stdout)  → 通常輸出到屏幕
// 2 - 標準錯誤(stderr)  → 錯誤信息輸出

// 當創(chuàng)建管道時,系統(tǒng)會分配兩個新的文件描述符:
// pipefd[0] - 用于讀取的端
// pipefd[1] - 用于寫入的端

2.2 創(chuàng)建第一個管道程序

讓我們從最簡單的例子開始:

#include <iostream>
#include <unistd.h>   // pipe(), fork(), read(), write()
#include <string.h>   // strlen()
#include <sys/wait.h> // wait()

int main() {
    int pipefd[2];  // 管道文件描述符數(shù)組
    char buffer[100];
    
    // 步驟1:創(chuàng)建管道
    // pipe() 返回0表示成功,-1表示失敗
    if (pipe(pipefd) == -1) {
        std::cerr << "管道創(chuàng)建失敗" << std::endl;
        return 1;
    }
    
    // 步驟2:創(chuàng)建子進程
    pid_t pid = fork();
    
    if (pid == -1) {
        std::cerr << "進程創(chuàng)建失敗" << std::endl;
        return 1;
    }
    
    if (pid == 0) {
        // 子進程代碼
        // 關閉不需要的寫端
        close(pipefd[1]);
        
        // 從管道讀取數(shù)據(jù)
        int bytes_read = read(pipefd[0], buffer, sizeof(buffer));
        if (bytes_read > 0) {
            std::cout << "子進程收到: " << buffer << std::endl;
        }
        
        close(pipefd[0]);
        return 0;
    } else {
        // 父進程代碼
        // 關閉不需要的讀端
        close(pipefd[0]);
        
        const char* message = "Hello from parent!";
        
        // 向管道寫入數(shù)據(jù)
        write(pipefd[1], message, strlen(message));
        
        // 關閉寫端,表示數(shù)據(jù)發(fā)送完畢
        close(pipefd[1]);
        
        // 等待子進程結束
        wait(nullptr);
    }
    
    return 0;
}

2.3 關鍵原理分析

為什么需要關閉不用的描述符?

  1. 資源管理:每個進程都有文件描述符限制,及時關閉避免泄漏
  2. 正確終止:讀進程需要知道何時沒有更多數(shù)據(jù)
    • 所有寫端關閉 → 讀端返回0(EOF)
    • 否則讀端會一直等待

管道的阻塞行為

  • 讀阻塞:當管道空且仍有寫端打開時,讀操作會阻塞
  • 寫阻塞:當管道滿(默認64KB),寫操作會阻塞
  • 非阻塞模式:可以通過fcntl()設置O_NONBLOCK

第三章:中級應用——雙向通信與復雜管道

3.1 實現(xiàn)雙向通信

單個管道只能單向通信,要實現(xiàn)雙向通信,我們需要兩個管道:

#include <iostream>
#include <unistd.h>
#include <string>

class BidirectionalPipe {
private:
    int parent_to_child[2];  // 父→子管道
    int child_to_parent[2];  // 子→父管道
    
public:
    BidirectionalPipe() {
        // 創(chuàng)建兩個管道
        if (pipe(parent_to_child) == -1 || pipe(child_to_parent) == -1) {
            throw std::runtime_error("管道創(chuàng)建失敗");
        }
    }
    
    ~BidirectionalPipe() {
        closeAll();
    }
    
    void parentWrite(const std::string& message) {
        write(parent_to_child[1], message.c_str(), message.length());
    }
    
    std::string parentRead() {
        char buffer[256];
        int n = read(child_to_parent[0], buffer, sizeof(buffer)-1);
        if (n > 0) {
            buffer[n] = '\0';
            return std::string(buffer);
        }
        return "";
    }
    
    void childWrite(const std::string& message) {
        write(child_to_parent[1], message.c_str(), message.length());
    }
    
    std::string childRead() {
        char buffer[256];
        int n = read(parent_to_child[0], buffer, sizeof(buffer)-1);
        if (n > 0) {
            buffer[n] = '\0';
            return std::string(buffer);
        }
        return "";
    }
    
    void closeParentSide() {
        close(parent_to_child[1]);  // 關閉父進程的寫端
        close(child_to_parent[0]);  // 關閉父進程的讀端
    }
    
    void closeChildSide() {
        close(parent_to_child[0]);  // 關閉子進程的讀端
        close(child_to_parent[1]);  // 關閉子進程的寫端
    }
    
private:
    void closeAll() {
        close(parent_to_child[0]);
        close(parent_to_child[1]);
        close(child_to_parent[0]);
        close(child_to_parent[1]);
    }
};

3.2 管道鏈的實現(xiàn)

管道鏈是UNIX shell中|操作符的基礎,讓我們實現(xiàn)一個簡單的版本:

#include <vector>
#include <array>

class Pipeline {
private:
    // 存儲多個命令
    std::vector<std::vector<std::string>> commands;
    
public:
    void addCommand(const std::vector<std::string>& cmd) {
        commands.push_back(cmd);
    }
    
    void execute() {
        std::vector<int> prev_pipe_read;  // 前一個管道的讀端
        
        for (size_t i = 0; i < commands.size(); ++i) {
            int pipefd[2];
            
            // 如果不是最后一個命令,創(chuàng)建管道
            if (i < commands.size() - 1) {
                if (pipe(pipefd) == -1) {
                    throw std::runtime_error("管道創(chuàng)建失敗");
                }
            }
            
            pid_t pid = fork();
            
            if (pid == 0) {
                // 子進程代碼
                
                // 設置輸入重定向(從上一個管道讀?。?
                if (!prev_pipe_read.empty()) {
                    dup2(prev_pipe_read[0], STDIN_FILENO);
                    close(prev_pipe_read[0]);
                }
                
                // 設置輸出重定向(寫入下一個管道)
                if (i < commands.size() - 1) {
                    dup2(pipefd[1], STDOUT_FILENO);
                    close(pipefd[0]);
                    close(pipefd[1]);
                }
                
                // 準備exec參數(shù)
                std::vector<char*> args;
                for (const auto& arg : commands[i]) {
                    args.push_back(const_cast<char*>(arg.c_str()));
                }
                args.push_back(nullptr);
                
                // 執(zhí)行命令
                execvp(args[0], args.data());
                
                // exec失敗才執(zhí)行到這里
                exit(1);
            } else {
                // 父進程代碼
                
                // 關閉不再需要的描述符
                if (!prev_pipe_read.empty()) {
                    close(prev_pipe_read[0]);
                }
                
                if (i < commands.size() - 1) {
                    close(pipefd[1]);  // 父進程不需要寫端
                    prev_pipe_read = {pipefd[0]};  // 保存讀端用于下一個進程
                }
            }
        }
        
        // 父進程等待所有子進程
        for (size_t i = 0; i < commands.size(); ++i) {
            wait(nullptr);
        }
    }
};

// 使用示例
int main() {
    Pipeline pipeline;
    
    // 模擬: ls -l | grep ".cpp" | wc -l
    pipeline.addCommand({"ls", "-l"});
    pipeline.addCommand({"grep", "\\.cpp"});
    pipeline.addCommand({"wc", "-l"});
    
    pipeline.execute();
    
    return 0;
}

3.3 命名管道(FIFO)的深入理解

命名管道與匿名管道的區(qū)別

特性匿名管道命名管道(FIFO)
持久性進程結束即消失文件系統(tǒng)中有實體文件
進程關系必須有親緣關系任意進程都可訪問
創(chuàng)建方式pipe()系統(tǒng)調用mkfifo()函數(shù)
訪問控制基于文件描述符繼承基于文件權限

創(chuàng)建和使用命名管道

#include <iostream>
#include <fcntl.h>
#include <sys/stat.h>
#include <unistd.h>

class NamedPipe {
private:
    std::string path;
    int fd;
    
public:
    NamedPipe(const std::string& pipePath) : path(pipePath) {
        // 創(chuàng)建命名管道(如果不存在)
        if (mkfifo(path.c_str(), 0666) == -1) {
            // 如果已存在,忽略EEXIST錯誤
            if (errno != EEXIST) {
                throw std::runtime_error("無法創(chuàng)建命名管道");
            }
        }
    }
    
    // 作為讀取者打開
    void openForReading(bool nonblock = false) {
        int flags = O_RDONLY;
        if (nonblock) flags |= O_NONBLOCK;
        
        fd = open(path.c_str(), flags);
        if (fd == -1) {
            throw std::runtime_error("無法打開命名管道進行讀取");
        }
    }
    
    // 作為寫入者打開
    void openForWriting(bool nonblock = false) {
        int flags = O_WRONLY;
        if (nonblock) flags |= O_NONBLOCK;
        
        fd = open(path.c_str(), flags);
        if (fd == -1) {
            throw std::runtime_error("無法打開命名管道進行寫入");
        }
    }
    
    // 讀取數(shù)據(jù)
    std::string readData(size_t max_size = 1024) {
        char buffer[max_size];
        ssize_t bytes = read(fd, buffer, max_size - 1);
        if (bytes > 0) {
            buffer[bytes] = '\0';
            return std::string(buffer);
        }
        return "";
    }
    
    // 寫入數(shù)據(jù)
    void writeData(const std::string& data) {
        write(fd, data.c_str(), data.length());
    }
    
    ~NamedPipe() {
        if (fd != -1) {
            close(fd);
        }
        // 可以選擇是否刪除管道文件
        // unlink(path.c_str());
    }
};

第四章:高級主題——性能與并發(fā)

4.1 非阻塞管道操作

非阻塞管道在某些場景下非常有用,比如同時監(jiān)控多個管道:

#include <fcntl.h>

class NonBlockingPipe {
private:
    int pipefd[2];
    
public:
    NonBlockingPipe() {
        if (pipe(pipefd) == -1) {
            throw std::runtime_error("管道創(chuàng)建失敗");
        }
        
        // 設置為非阻塞模式
        setNonBlocking(pipefd[0]);
        setNonBlocking(pipefd[1]);
    }
    
private:
    void setNonBlocking(int fd) {
        int flags = fcntl(fd, F_GETFL, 0);
        if (flags == -1) {
            throw std::runtime_error("獲取文件狀態(tài)失敗");
        }
        
        if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) {
            throw std::runtime_error("設置非阻塞模式失敗");
        }
    }
    
public:
    // 非阻塞讀取
    bool tryRead(std::string& result) {
        char buffer[1024];
        ssize_t bytes = read(pipefd[0], buffer, sizeof(buffer) - 1);
        
        if (bytes > 0) {
            buffer[bytes] = '\0';
            result = buffer;
            return true;
        } else if (bytes == -1 && errno == EAGAIN) {
            // 沒有數(shù)據(jù)可讀(非阻塞模式)
            return false;
        }
        
        return false;  // 錯誤或EOF
    }
};

4.2 使用select實現(xiàn)多路復用

當需要同時監(jiān)控多個管道時,select是一個非常有效的工具:

#include <sys/select.h>
#include <vector>

class PipeMonitor {
private:
    std::vector<int> read_fds;  // 需要監(jiān)控的讀描述符
    
public:
    void addPipe(int read_fd) {
        read_fds.push_back(read_fd);
    }
    
    // 監(jiān)控所有管道,返回有數(shù)據(jù)可讀的管道列表
    std::vector<int> monitor(int timeout_sec = 0) {
        fd_set read_set;
        FD_ZERO(&read_set);
        
        int max_fd = 0;
        for (int fd : read_fds) {
            FD_SET(fd, &read_set);
            if (fd > max_fd) max_fd = fd;
        }
        
        struct timeval timeout;
        timeout.tv_sec = timeout_sec;
        timeout.tv_usec = 0;
        
        // 使用select等待數(shù)據(jù)
        int ready = select(max_fd + 1, &read_set, nullptr, nullptr, 
                          timeout_sec >= 0 ? &timeout : nullptr);
        
        std::vector<int> ready_fds;
        if (ready > 0) {
            for (int fd : read_fds) {
                if (FD_ISSET(fd, &read_set)) {
                    ready_fds.push_back(fd);
                }
            }
        }
        
        return ready_fds;
    }
};

4.3 零拷貝技術:splice()

Linux提供了高級的系統(tǒng)調用來優(yōu)化管道性能,避免不必要的數(shù)據(jù)拷貝:

#include <fcntl.h>

class HighPerformancePipe {
private:
    int pipefd[2];
    
public:
    HighPerformancePipe() {
        if (pipe(pipefd) == -1) {
            throw std::runtime_error("管道創(chuàng)建失敗");
        }
    }
    
    // 使用splice實現(xiàn)零拷貝數(shù)據(jù)傳輸
    // 將數(shù)據(jù)從一個文件描述符直接移動到管道
    ssize_t transferFrom(int source_fd, size_t len) {
        // splice從source_fd讀取數(shù)據(jù),直接寫入管道
        // 避免了用戶空間的內存拷貝
        return splice(source_fd, nullptr,        // 源文件描述符
                     pipefd[1], nullptr,         // 目標管道寫端
                     len,                        // 傳輸長度
                     SPLICE_F_MOVE | SPLICE_F_MORE);
    }
    
    // 將數(shù)據(jù)從管道直接傳輸?shù)侥繕宋募枋龇?
    ssize_t transferTo(int dest_fd, size_t len) {
        return splice(pipefd[0], nullptr,        // 源管道讀端
                     dest_fd, nullptr,          // 目標文件描述符
                     len,
                     SPLICE_F_MOVE | SPLICE_F_MORE);
    }
};

第五章:最佳實踐與錯誤處理

5.1 RAII包裝器

為了避免資源泄漏,使用RAII(資源獲取即初始化)模式管理管道:

#include <memory>

class PipeRAII {
private:
    int pipefd[2];
    bool valid;
    
public:
    PipeRAII() : valid(false) {
        if (pipe(pipefd) == 0) {
            valid = true;
        }
    }
    
    ~PipeRAII() {
        if (valid) {
            close(pipefd[0]);
            close(pipefd[1]);
        }
    }
    
    // 刪除拷貝構造函數(shù)和賦值運算符
    PipeRAII(const PipeRAII&) = delete;
    PipeRAII& operator=(const PipeRAII&) = delete;
    
    // 允許移動語義
    PipeRAII(PipeRAII&& other) noexcept 
        : pipefd{other.pipefd[0], other.pipefd[1]}, 
          valid(other.valid) {
        other.valid = false;
    }
    
    int readEnd() const { return valid ? pipefd[0] : -1; }
    int writeEnd() const { return valid ? pipefd[1] : -1; }
    
    explicit operator bool() const { return valid; }
};

// 使用智能指針管理
class SafePipeManager {
private:
    std::unique_ptr<PipeRAII> pipe;
    
public:
    SafePipeManager() : pipe(std::make_unique<PipeRAII>()) {
        if (!*pipe) {
            throw std::runtime_error("管道創(chuàng)建失敗");
        }
    }
    
    void sendData(const std::string& data) {
        if (pipe) {
            write(pipe->writeEnd(), data.c_str(), data.length());
        }
    }
};

5.2 常見錯誤與處理

class RobustPipe {
private:
    int pipefd[2];
    
    // 安全讀取函數(shù)
    ssize_t safeRead(void* buf, size_t count) {
        ssize_t bytes_read;
        do {
            bytes_read = read(pipefd[0], buf, count);
        } while (bytes_read == -1 && errno == EINTR);  // 處理信號中斷
        
        return bytes_read;
    }
    
    // 安全寫入函數(shù)
    ssize_t safeWrite(const void* buf, size_t count) {
        ssize_t bytes_written;
        size_t total_written = 0;
        const char* ptr = static_cast<const char*>(buf);
        
        while (total_written < count) {
            do {
                bytes_written = write(pipefd[1], ptr + total_written, 
                                     count - total_written);
            } while (bytes_written == -1 && errno == EINTR);
            
            if (bytes_written == -1) {
                // 處理真正的錯誤
                if (errno == EPIPE) {
                    std::cerr << "管道斷裂:讀端已關閉" << std::endl;
                }
                return -1;
            }
            
            total_written += bytes_written;
        }
        
        return total_written;
    }
    
public:
    RobustPipe() {
        if (pipe(pipefd) == -1) {
            // 檢查具體錯誤
            switch (errno) {
                case EMFILE:
                    throw std::runtime_error("進程文件描述符耗盡");
                case ENFILE:
                    throw std::runtime_error("系統(tǒng)文件描述符耗盡");
                default:
                    throw std::runtime_error("未知管道創(chuàng)建錯誤");
            }
        }
        
        // 設置管道緩沖區(qū)大?。蛇x)
        int size = 65536;  // 64KB
        fcntl(pipefd[0], F_SETPIPE_SZ, size);
    }
};

第六章:實戰(zhàn)應用案例

6.1 日志收集系統(tǒng)

#include <thread>
#include <queue>
#include <mutex>
#include <condition_variable>

class LogCollector {
private:
    int log_pipe[2];
    std::queue<std::string> log_queue;
    std::mutex queue_mutex;
    std::condition_variable queue_cv;
    std::thread worker_thread;
    bool running;
    
    void worker() {
        char buffer[4096];
        
        while (running) {
            ssize_t bytes = read(log_pipe[0], buffer, sizeof(buffer) - 1);
            
            if (bytes > 0) {
                buffer[bytes] = '\0';
                std::string log_entry(buffer);
                
                {
                    std::lock_guard<std::mutex> lock(queue_mutex);
                    log_queue.push(log_entry);
                }
                queue_cv.notify_one();
            }
        }
    }
    
public:
    LogCollector() : running(true) {
        if (pipe(log_pipe) == -1) {
            throw std::runtime_error("日志管道創(chuàng)建失敗");
        }
        
        worker_thread = std::thread(&LogCollector::worker, this);
    }
    
    ~LogCollector() {
        running = false;
        close(log_pipe[1]);  // 關閉寫端,使讀端退出
        if (worker_thread.joinable()) {
            worker_thread.join();
        }
        close(log_pipe[0]);
    }
    
    // 寫入日志
    void log(const std::string& message) {
        write(log_pipe[1], message.c_str(), message.length());
    }
    
    // 獲取日志(線程安全)
    std::string getLog() {
        std::unique_lock<std::mutex> lock(queue_mutex);
        queue_cv.wait(lock, [this] { return !log_queue.empty(); });
        
        std::string log = log_queue.front();
        log_queue.pop();
        return log;
    }
};

總結

管道編程是C++系統(tǒng)編程的重要部分,掌握它需要:

  1. 理解基本原理:文件描述符、緩沖區(qū)、阻塞行為
  2. 掌握核心API:pipe(), fork(), dup2(), read(), write()
  3. 學會高級技術:非阻塞IO、多路復用、零拷貝
  4. 遵循最佳實踐:RAII管理、錯誤處理、資源清理

管道不僅是一種技術,更是一種設計哲學——它鼓勵我們創(chuàng)建模塊化、可組合的程序,這正是UNIX哲學的核心理念之一。

到此這篇關于深入理解C++管道編程的文章就介紹到這了,更多相關C++管道編程內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • 一文詳細講解C++精妙的哈希算法

    一文詳細講解C++精妙的哈希算法

    這篇文章主要介紹了C++精妙的哈希算法的相關資料,哈希結構通過哈希函數(shù)將關鍵碼映射到表中的特定位置,以提高搜索效率,理想的哈希函數(shù)應保證一致性、哈希值均勻分布、高計算效率與最小化沖突,文中通過代碼介紹的非常詳細,需要的朋友可以參考下
    2024-10-10
  • C++ Boost Archive超詳細講解

    C++ Boost Archive超詳細講解

    Boost是為C++語言標準庫提供擴展的一些C++程序庫的總稱。Boost庫是一個可移植、提供源代碼的C++庫,作為標準庫的后備,是C++標準化進程的開發(fā)引擎之一,是為C++語言標準庫提供擴展的一些C++程序庫的總稱
    2022-12-12
  • C++實現(xiàn)顯示MP3文件信息的方法

    C++實現(xiàn)顯示MP3文件信息的方法

    這篇文章主要介紹了C++實現(xiàn)顯示MP3文件信息的方法,可實現(xiàn)顯示如作者、專輯等(libZPlay)信息的功能,需要的朋友可以參考下
    2015-06-06
  • Opencv實現(xiàn)傅里葉變換

    Opencv實現(xiàn)傅里葉變換

    這篇文章主要為大家詳細介紹了Opencv實現(xiàn)傅里葉變換的相關資料,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-04-04
  • C程序中唯一序列號的生成實例詳解

    C程序中唯一序列號的生成實例詳解

    這篇文章主要介紹了C程序中唯一序列號的生成實例詳解的相關資料,需要的朋友可以參考下
    2017-06-06
  • C語言詳解判斷相同樹案例分析

    C語言詳解判斷相同樹案例分析

    這篇文章主要介紹了用C語言檢查兩棵樹是否相同,本篇文章通過簡要的案例,講解了該項技術的了解與使用,以下就是詳細內容,需要的朋友可以參考下
    2022-04-04
  • C++內存泄漏的檢測與實現(xiàn)詳細流程

    C++內存泄漏的檢測與實現(xiàn)詳細流程

    內存泄漏(memory leak) 是指由于疏忽或錯誤造成了程序未能釋放掉不再使用的內存的情況。內存泄漏并非指內存在物理上的消失,而是應用程序分配某段內存后,由于設計錯誤,失去了對該段內存的控制,因而造成了內存的浪費
    2022-08-08
  • C++ 設置透明背景圖片

    C++ 設置透明背景圖片

    這篇文章主要介紹了C++ 設置透明背景圖片的相關資料,需要的朋友可以參考下
    2015-06-06
  • 使用VS2019編譯CEF2623項目的libcef_dll_wrapper.lib的方法

    使用VS2019編譯CEF2623項目的libcef_dll_wrapper.lib的方法

    這篇文章主要介紹了使用VS2019編譯CEF2623項目的libcef_dll_wrapper.lib的方法,本文給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2020-04-04
  • C++設計模式之觀察者模式

    C++設計模式之觀察者模式

    這篇文章主要介紹了C++設計模式之觀察者模式,本文講解了什么是觀察者模式、觀察者模式的UML類圖、觀察者模式的使用場合等內容,需要的朋友可以參考下
    2014-10-10

最新評論

新安县| 泊头市| 武宣县| 攀枝花市| 义乌市| 杨浦区| 茶陵县| 蓝田县| 襄垣县| 镇江市| 宣城市| 石台县| 南城县| 获嘉县| 常宁市| 通河县| 都安| 柳江县| 神农架林区| 乌拉特前旗| 方山县| 萨迦县| 从化市| 大埔区| 宾阳县| 新闻| 安化县| 丰原市| 余干县| 巫溪县| 成安县| 漳州市| 淳化县| 弋阳县| 扶绥县| 宾川县| 忻州市| 肇东市| 上蔡县| 麻阳| 太仆寺旗|