当前位置: 首页 > news >正文

直播网站建设模板网站活动怎么做的

直播网站建设模板,网站活动怎么做的,网站的信息架构,ie 常用网站前言对锁的封装整体代码LockGuard - RALLRALLMutex封装 对线程创建的封装整体代码成员函数解释声明 业务处理封装-加减乘除#xff08;可有可无#xff09;整体代码成员函数解释声明 线程池的设计与实现整体代码成员函数解释声明 展示 前言 线程池: 一种线程使用模式。线程过… 前言对锁的封装整体代码LockGuard - RALLRALLMutex封装 对线程创建的封装整体代码成员函数解释声明 业务处理封装-加减乘除可有可无整体代码成员函数解释声明 线程池的设计与实现整体代码成员函数解释声明 展示 前言 线程池: 一种线程使用模式。线程过多会带来调度开销进而影响缓存局部性和整体性能。而线程池维护着多个线程等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用还能防止过分调度。可用线程数量应该取决于可用的并发处理器、处理器内核、内存、网络sockets等的数量。 线程池的应用场景 需要大量的线程来完成任务且完成任务的时间比较短。 WEB服务器完成网页请求这样的任务使用线程池技术是非常合适的。因为单个任务小而任务数量巨大你可以想象一个热门网站的点击次数。 但对于长时间的任务比如一个Telnet连接请求线程池的优点就不明显了。因为Telnet会话时间比线程的创建时间大多了。对性能要求苛刻的应用比如要求服务器迅速响应客户请求。接受突发性的大量请求但不至于使服务器因此产生大量线程的应用。突发性大量客户请求在没有线程池情况下将产生大量线程虽然理论上大部分操作系统线程数目最大值不是问题短时间内产生大量线程可能使内存到达极限出现错误 线程池示例 创建固定数量线程池循环从任务队列中获取任务对象获取到任务对象后执行任务对象中的任务接口 对锁的封装 整体代码 LockGuard.hpp #pragma once #include iostream #include pthread.hclass Mutex { public:Mutex(pthread_mutex_t *lock_p nullptr) : lock_p_(lock_p){}void lock(){if (lock_p_)pthread_mutex_lock(lock_p_);}void unlock(){if (lock_p_)pthread_mutex_unlock(lock_p_);}~Mutex(){}private:pthread_mutex_t *lock_p_; };class LockGuard { public:LockGuard(pthread_mutex_t *mutex) : mutex_(mutex){mutex_.lock(); // 在构造函数中进行加锁}~LockGuard(){mutex_.unlock(); // 在析构函数中进行解锁}private:Mutex mutex_; };LockGuard - RALL 对锁的封装 class LockGuard { public:LockGuard(pthread_mutex_t *mutex) : mutex_(mutex){mutex_.lock(); // 在构造函数中进行加锁}~LockGuard(){mutex_.unlock(); // 在析构函数中进行解锁} private:Mutex mutex_; };定义了一个名为 LockGuard 的类它实现了 RAIIResource Acquisition Is Initialization技术用于自动加锁和解锁互斥量。 具体来说LockGuard 类的构造函数接受一个指向 pthread_mutex_t 类型的指针作为参数并在函数内部将该指针封装成一个 Mutex 对象并进行加锁操作。在 LockGuard 对象创建时该互斥量将被自动加锁确保线程安全。 LockGuard 类的析构函数在对象销毁时自动调用并在函数内部对互斥量进行解锁操作。在 LockGuard 对象销毁时该互斥量将被自动解锁释放锁资源。 通过使用 LockGuard 类可以避免手动加锁和解锁互斥量从而减少代码出错的可能性并确保线程安全。同时使用 RAII 机制可以提高代码的可读性和可维护性使得程序更加健壮和可靠。 RALL RAIIResource Acquisition Is Initialization技术是一种 C 语言的编程技术它的基本思想是在对象的构造函数中获取资源在对象的析构函数中释放资源从而实现资源的自动管理。 RAII 技术通常用于管理资源对象例如内存、文件句柄、互斥锁、临界区等等这些资源需要在使用完成后手动释放否则会导致资源泄露或者资源使用冲突等问题。 使用 RAII 技术可以避免手动管理资源带来的繁琐和容易出错的问题。RAII 技术的关键是利用对象的构造函数和析构函数来自动管理资源当对象超出作用域时其析构函数自动被调用从而释放资源。 在 C 中STL 中的智能指针和标准库中的各种容器例如 vector、map、set等都是使用 RAII 技术实现的它们可以自动管理内存和容器元素的生命周期从而避免了手动管理资源的繁琐和容易出错的问题。 Mutex封装 定义了一个名为 Mutex 的类它封装了 pthread_mutex_t 互斥量并提供了 lock() 和 unlock() 成员函数用于加锁和解锁互斥量 #pragma once #include iostream #include pthread.hclass Mutex { public:Mutex(pthread_mutex_t *lock_p nullptr) : lock_p_(lock_p){}void lock(){if (lock_p_)pthread_mutex_lock(lock_p_);}void unlock(){if (lock_p_)pthread_mutex_unlock(lock_p_);}~Mutex(){}private:pthread_mutex_t *lock_p_; };Mutex 类的构造函数接受一个指向 pthread_mutex_t 类型的指针作为参数并在函数内部将该指针保存到成员变量 lock_p_ 中。如果构造函数没有接收到任何参数则将 lock_p_ 初始化为 nullptr Mutex 类的 lock() 成员函数用于加锁互斥量。它首先检查 lock_p_ 是否为 nullptr如果不是则调用 pthread_mutex_lock() 函数对互斥量进行加锁操作。 Mutex 类的 unlock() 成员函数用于解锁互斥量。它首先检查 lock_p_ 是否为 nullptr如果不是则调用 pthread_mutex_unlock() 函数对互斥量进行解锁操作。 通过使用 Mutex 类可以封装 pthread_mutex_t 互斥量并提供了方便的 lock() 和 unlock() 成员函数用于加锁和解锁互斥量。这使得代码更加简洁和易于管理并确保线程安全。同时Mutex 类的实现也符合 RAII 技术可以自动释放锁资源从而减少代码出错的可能性。 对线程创建的封装 整体代码 Thread.hpp #pragma once#include iostream #include string #include cstring #include cassert #include functional #include pthread.hnamespace ThreadNs {typedef std::functionvoid *(void *) func_t;const int num 1024;class Thread{private:static void *start_routine(void *args) // 类内成员有缺省参数{Thread *_this static_castThread *(args);return _this-callback();}public:Thread(){char namebuffer[num];snprintf(namebuffer, sizeof namebuffer, thread-%d, threadnum);name_ namebuffer;}void start(func_t func, void *args nullptr){func_ func;args_ args;int n pthread_create(tid_, nullptr, start_routine, this); assert(n 0);(void)n;}void join(){int n pthread_join(tid_, nullptr);assert(n 0);(void)n;}std::string threadname(){return name_;}~Thread(){// do nothing}void *callback() { return func_(args_); }private:std::string name_; // 线程名字func_t func_; // 任务处理函数void *args_; // 任务处理函数的参数pthread_t tid_; // 线程的 IDstatic int threadnum; // 计算线程个数用于格式化线程名字为其增加编号};int Thread::threadnum 1; }成员函数解释声明 typedef std::functionvoid *(void *) func_t;定义了一个名为 func_t 的类型别名它是一个函数对象类型用于封装一个可调用对象接受一个指针参数并返回一个指针值 static void *start_routine(void *args) // 类内成员有缺省参数 {Thread *_this static_castThread *(args);return _this-callback(); }在类内创建线程想让线程执行对应的方法需要将方法设置成为static 作用是创建并初始化线程 Thread() {char namebuffer[num];snprintf(namebuffer, sizeof namebuffer, thread-%d, threadnum);name_ namebuffer; }构造函数Thread()它的作用是创建并初始化线程 定义了一个字符数组 namebuffer用来存储线程的名称。然后使用 snprintf 函数将线程的名称格式化成字符串并将该字符串存储在 namebuffer 中。其中threadnum 是一个静态变量用来记录已经创建的线程数量每次创建线程时都会将 threadnum 加 1从而保证每个线程都有一个唯一的名称 启动一个新的线程并开始执行指定的任务处理函数 void start(func_t func, void *args nullptr) {func_ func;args_ args;// 调用 pthread_create() 函数创建一个新的线程并将线程 ID 保存到成员变量 tid_ 中int n pthread_create(tid_, nullptr, start_routine, this); // TODOassert(n 0);(void)n; }定义了 Thread 类的成员函数 start()它的作用是启动一个新的线程并开始执行指定的任务处理函数。一个任务处理函数指针 func 和一个可选的参数 args表示任务处理函数的参数。 等待线程执行结束并回收线程资源。 void join() {int n pthread_join(tid_, nullptr);assert(n 0);(void)n; }业务处理封装-加减乘除可有可无 整体代码 Task.hpp #pragma once#include iostream #include string #include cstdio #include functionalclass Task {using func_t std::functionint(int, int, char); public:Task(){}Task(int x, int y, char op, func_t func): _x(x), _y(y), _op(op), _callback(func){}std::string operator()(){int result _callback(_x, _y, _op);char buffer[1024];snprintf(buffer, sizeof buffer, %d %c %d %d, _x, _op, _y, result);return buffer;}std::string toTaskString(){char buffer[1024];snprintf(buffer, sizeof buffer, %d %c %d ?, _x, _op, _y);return buffer;}private:int _x;int _y;char _op;func_t _callback; };const std::string oper -*/%;int mymath(int x, int y, char op) {int result 0;switch (op){case :result x y;break;case -:result x - y;break;case *:result x * y;break;case /:{if (y 0){std::cerr div zero error! std::endl;result -1;}elseresult x / y;}break;case %:{if (y 0){std::cerr mod zero error! std::endl;result -1;}elseresult x % y;}break;default:// do nothingbreak;}return result; }成员函数解释声明 using func_t std::functionint(int, int, char);一个函数对象类型用于封装一个可调用对象接受两个 int 类型参数和一个 char 类型参数并返回一个 int 类型值。 具体来说func_t 是一个 std::function 模板类的实例化类型它接受一个函数类型作为模板参数其中该函数类型接受两个 int 类型参数和一个 char 类型参数分别表示运算符两侧的操作数和运算符本身并返回一个 int 类型值表示运算结果。 因此func_t 类型可以用于封装一个可调用对象该对象接受两个 int 类型参数和一个 char 类型参数并返回一个 int 类型值类似于 C 语言中的函数指针类型。 在本例中func_t 类型被用作 Task 类的成员变量 _callback 的类型它用于封装一个计算加减乘除的回调函数该函数接受两个 int 类型参数和一个 char 类型参数并返回一个 int 类型值。在 Task 类的成员函数中可以通过调用成员变量 _callback 来调用回调函数并传递参数。 它的作用是执行一个回调函数并将结果以字符串的形式返回。 std::string operator()() {int result _callback(_x, _y, _op);char buffer[1024];snprintf(buffer, sizeof buffer, %d %c %d %d, _x, _op, _y, result);return buffer; }定义了一个函数调用运算符的重载函数 operator() 调用成员变量 _callback 所指向的回调函数传递成员变量 _x、_y、_op 作为参数并将返回值存储在 result 变量中。使用 snprintf 函数将计算结果格式化为一个字符串并将其存储在 buffer 数组中。最后它将 buffer 数组转换为一个 std::string 对象并返回该对象。 作用是将任务的信息格式化成字符串并返回。 std::string toTaskString() {char buffer[1024];snprintf(buffer, sizeof buffer, %d %c %d ?, _x, _op, _y);return buffer; }使用了 C 标准库中的 sprintf 函数将 _x、_op 、_y 这三个成员变量格式化成字符串并将结果存储在字符数组 buffer 中。其中_x 、_y 分别表示任务要操作的两个数_op 表示操作符例如加号、减号等。然后通过 std::string 类型的构造函数将字符数组 buffer 转换成字符串并将该字符串作为函数的返回值。 线程池的设计与实现 整体代码 ThreadPool.hpp #pragma once #include Thread.hpp #include LockGuard.hpp #include vector #include queue #include mutex #include pthread.h #include unistd.husing namespace ThreadNs; const int gnum 3;template class T class ThreadPool;template class T class ThreadData { public:ThreadPoolT *threadpool;std::string name;public:ThreadData(ThreadPoolT *tp, const std::string n) : threadpool(tp), name(n){} }; template class T class ThreadPool { private:static void *handlerTask(void *args){ThreadDataT *td (ThreadDataT *)args;while (true){T t;{LockGuard lockguard(td-threadpool-mutex()); while (td-threadpool-isQueueEmpty()){td-threadpool-threadWait();}t td-threadpool-pop(); }std::cout td-name 获取了一个任务 t.toTaskString() 并处理完成结果是 t() std::endl;}delete td; // 删除new出的对象return nullptr;}ThreadPool(const int num gnum) : _num(num){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_cond, nullptr);for (int i 0; i _num; i){_threads.push_back(new Thread());}}void operator(const ThreadPool ) delete;ThreadPool(const ThreadPool ) delete;public:void lockQueue() { pthread_mutex_lock(_mutex); }void unlockQueue() { pthread_mutex_unlock(_mutex); }bool isQueueEmpty() { return _task_queue.empty(); }void threadWait() { pthread_cond_wait(_cond, _mutex); }T pop(){T t _task_queue.front();_task_queue.pop();return t;}pthread_mutex_t *mutex(){return _mutex;}public:void run(){for (const auto t : _threads){ThreadDataT *td new ThreadDataT(this, t-threadname());t-start(handlerTask, td);std::cout t-threadname() start ... std::endl;}}void push(const T in){LockGuard lockguard(_mutex); _task_queue.push(in); pthread_cond_signal(_cond);}// 析构函数~ThreadPool(){pthread_mutex_destroy(_mutex); pthread_cond_destroy(_cond); for (const auto t : _threads) delete t;}static ThreadPoolT *getInstance(){if (nullptr tp){_singlock.lock();if (nullptr tp){tp new ThreadPoolT();}_singlock.unlock();}return tp;} private:int _num;std::vectorThread * _threads; // 存放创建的线程实例的指针std::queueT _task_queue; // 存储待执行的任务。它是一个先进先出FIFO的队列pthread_mutex_t _mutex; // pthread 库中的互斥锁类型用于保护任务队列的访问pthread_cond_t _cond; // pthread 库中的条件变量类型用于线程之间的同步和通信。// 在任务队列为空时线程需要等待条件变量的信号以便在有新的任务加入时立即处理。// 而当任务加入队列时需要发送条件变量的信号以通知等待的线程有新的任务可以处理了。// tp 是一个指向 ThreadPool 类的指针它是静态变量只有一个实例用于在整个程序中共享线程池的实例。// 由于线程池是一个全局的资源需要确保所有的线程都共享同一个实例避免资源浪费和线程安全问题。static ThreadPoolT *tp; // 指向 ThreadPool 类的指针 tp// 由于线程池是一个单例模式需要确保只有一个实例被创建避免资源浪费和线程安全问题。因此当多个线程同时访问时// 需要使用互斥锁对其进行保护避免多个线程同时创建线程池实例static std::mutex _singlock; // std::mutex 类型的互斥锁 _singlock。语言层面 };// tp 是一个指向 ThreadPool 类对象的指针初始值为 nullptr。 // 它被用于实现 ThreadPool 类的单例模式确保每个程序只有一个 ThreadPool 类对象。 template class T ThreadPoolT *ThreadPoolT::tp nullptr;// _singlock 是一个互斥量用于保护对静态成员变量 tp 的访问。 // 它被用于实现线程安全的单例模式避免多个线程同时创建 ThreadPool 类对象导致资源浪费和线程安全问题。 template class T std::mutex ThreadPoolT::_singlock;成员函数解释声明 template class T class ThreadData { public:ThreadPoolT *threadpool;std::string name; public:ThreadData(ThreadPoolT *tp, const std::string n) : threadpool(tp), name(n){} };定义了一个名为ThreadData 的模板类用于封装线程池和线程之间的关系保存了线程池的指针和线程的名称。 一个指向线程池的指针 threadpool一个表示线程名称的字符串 name。它们分别用于保存线程池的指针和线程的名称。 接受两个参数分别是线程池的指针 tp 和线程的名称 n。构造函数将这两个参数保存到 ThreadData 对象的成员变量中以便在后续的线程处理函数中使用。 通过创建一个 ThreadDataT 类型的对象并将线程池的指针和线程的名称传递给其构造函数可以封装线程池和线程之间的关系并确保线程池的正确运行。在启动线程时会将该对象的指针传递给线程并在处理任务时使用其中的线程池指针。这样可以简化线程池的实现提高线程池的可维护性和可扩展性。 作为线程的入口函数循环从任务队列中获取任务并执行。 static void *handlerTask(void *args){ThreadDataT *td (ThreadDataT *)args;while (true){//定义一个类型为 T 的变量 t用来存储从任务队列中获取到的任务。T t;{LockGuard lockguard(td-threadpool-mutex()); while (td-threadpool-isQueueEmpty()){td-threadpool-threadWait();}t td-threadpool-pop(); }// 格式化输出信息std::cout td-name 获取了一个任务 t.toTaskString() 并处理完成结果是 t() std::endl;}delete td; // 删除new出的对象return nullptr;}LockGuard lockguard(td-threadpool-mutex()); 这段代码使用了 RAIIResource Acquisition Is Initialization技术通过定义一个局部变量 LockGuard 对象 lockguard实现了自动获取和释放互斥锁的操作。LockGuard 是一个 RAII类模板它的构造函数会在对象创建时获取互斥锁析构函数会在对象销毁时释放互斥锁。这样在函数执行过程中只需要将需要保护的代码块放在 LockGuard 对象的作用域内就可以保证在退出作用域时自动释放互斥锁避免了手动释放锁的繁琐操作和可能的遗漏。 在这段代码中LockGuard 对象 lockguard 的构造函数被调用时传入了一个指向互斥锁的指针 td-threadpool-mutex()表示获取该互斥锁。当 lockguard 对象的作用域结束时析构函数自动释放该互斥锁这样就保证了在访问任务队列时的线程安全。 while (td-threadpool-isQueueEmpty()) 从任务队列中获取任务并执行它的核心是一个循环不断尝试从任务队列中获取任务直到获取到任务为止。 td-threadpool-threadWait(); 将当前线程挂起等待有新的任务加入队列或者线程被停止。如果队列不为空则执行下一步操作。 t td-threadpool-pop(); 从任务队列中取出一个任务并将其赋值给变量 t。这个 pop 方法的本质是将任务从公共队列中拿到当前线程自己独立的栈中以避免多个线程同时访问同一个任务对象的线程安全问题。 ThreadPool(const int num gnum) : _num(num) {pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_cond, nullptr);for (int i 0; i _num; i){_threads.push_back(new Thread());} }ThreadPool构造函数它的作用是创建并初始化线程池。接受一个 int 类型的参数num用来指定线程池中线程的数量。如果没有传入 num就使用全局变量 gnum 的默认值。然后构造函数初始化了线程池中的互斥量和条件变量以及线程池的大小。 使用 pthread_mutex_init 函数初始化互斥量 _mutex使用 pthread_cond_init 函数初始化条件变量 _cond这两个函数都是 POSIX 线程库中的函数用来创建和初始化互斥量和条件变量。 使用一个 for 循环创建线程池中的线程。每次循环创建一个 Thread 类的实例并将其指针添加到线程池的 _threads 向量中。这个 _threads 向量是用来存储线程池中所有线程的指针的它的大小就是线程池的大小即 num。 void operator(const ThreadPool ) delete; ThreadPool(const ThreadPool ) delete;定义了 ThreadPool 类的拷贝赋值运算符和拷贝构造函数并将它们声明为 delete表示禁止进行拷贝构造和拷贝赋值操作。 因为线程池是一个资源管理类它包含了多个线程、任务队列、互斥锁、条件变量等资源这些资源的管理和释放都需要仔细考虑。如果允许进行拷贝构造和拷贝赋值操作就会导致多个对象共享同一份资源可能会引起资源泄露、线程安全问题等等。 为了避免这种情况通常会将拷贝构造函数和拷贝赋值运算符声明为 delete表示禁止进行拷贝操作。这样就可以确保每个线程池都拥有自己独立的资源避免了资源共享带来的问题。需要注意的是这段代码中使用了 C11 中的新特性即使用 “ delete” 显式声明某个函数为删除函数。这种方式可以在编译期间检查是否存在拷贝构造和拷贝赋值操作避免了在运行时出现错误。 void lockQueue() { pthread_mutex_lock(_mutex); }使用 pthread_mutex_lock 函数对任务队列的互斥锁进行加锁操作以保证在多个线程同时访问时不会出现冲突。 void unlockQueue() { pthread_mutex_unlock(_mutex); }使用 pthread_mutex_unlock 函数对任务队列的互斥锁进行解锁操作以释放资源并让其它线程获得访问权限。 bool isQueueEmpty() { return _task_queue.empty(); }用于判断任务队列是否为空它返回一个 bool 类型的值如果任务队列为空则返回 true否则返回 false。 void threadWait() { pthread_cond_wait(_cond, _mutex); }使用 pthread_cond_wait函数将线程放入等待状态并等待条件变量的信号。该函数将在 _cond 条件变量上等待并同时释放 _mutex 互斥锁以便其它线程可以获得访问权限。当条件变量的信号发生时该函数将重新获得 _mutex 互斥锁并继续执行后续的任务处理操作。 将队列中的任务弹出并返回弹出值 T pop() {T t _task_queue.front();_task_queue.pop();return t; }返回一个指向线程池的互斥锁的指针。 pthread_mutex_t *mutex() {return _mutex; }返回互斥锁的指针可以使得其它函数可以方便地对互斥锁进行加锁和解锁操作以保证线程安全。同时通过返回指针的方式也避免了多余的复制操作和内存开销。 启动线程池中的所有线程并开始处理任务。 void run() {for (const auto t : _threads){ThreadDataT *td new ThreadDataT(this, t-threadname());t-start(handlerTask, td);std::cout t-threadname() start ... std::endl;} }run() 函数遍历线程池中的所有线程并为每个线程创建一个 ThreadDataT 类型的对象 td。ThreadDataT 类型是一个模板类用于封装线程池和线程的关系保存了线程池的指针和线程的名称 定义了 ThreadPool 类的成员函数 push()它的作用是向线程池中添加一个任务。 void push(const T in) {LockGuard lockguard(_mutex); // 使用 LockGuard 对象对互斥量进行加锁 RALL设计_task_queue.push(in); // 进队列push// 使用 pthread_cond_signal() 函数发送一个条件信号以通知等待在条件变量 _cond 上的线程有新的任务可以处理。pthread_cond_signal(_cond); }作用是获取 ThreadPool 类的单例对象。 static ThreadPoolT *getInstance() {if (nullptr tp){_singlock.lock();if (nullptr tp){tp new ThreadPoolT();}_singlock.unlock();}return tp; }getInstance()函数首先检查静态成员变量 tp 是否为空指针。如果 tp 不为空直接返回 tp指向的对象。否则它使用单例模式的方式创建一个新的 ThreadPool 对象并将其赋值给 tp。在创建对象时使用了双重检查锁定的方式来确保线程安全。最后getInstance() 函数返回 tp 指向的对象。 调用 getInstance() 函数可以获取 ThreadPool 类的单例对象。这使得线程池可以全局共享并确保线程池的唯一性。通过使用单例模式可以简化线程池的实现提高代码的可维护性和可扩展性。 展示 main.cc #include ThreadPool.hpp #include Task.hpp #include memory #include unistd.hint main() {// 调用了 ThreadPoolTask 类的静态成员函数 getInstance() 和成员函数 run()// 用于获取 ThreadPool 类的单例对象并运行线程池// getInstance() 函数返回一个指向 ThreadPoolTask 类对象的指针// 该对象是全局唯一的并使用了双重检查锁定的方式确保线程安全。然后调用该对象的成员函数 run()用于启动线程池的运行ThreadPoolTask::getInstance()-run();int x, y;char op;while (1){std::cout 请输入数据1# ;std::cin x;std::cout 请输入数据2# ;std::cin y;std::cout 请输入你要进行的运算#;std::cin op;Task t(x, y, op, mymath);ThreadPoolTask::getInstance()-push(t);sleep(1);} }如有错误或者不清楚的地方欢迎私信或者评论指出
http://www.w-s-a.com/news/510503/

相关文章:

  • 医院网站建设网站网站开发工资高嘛
  • 个人网站备案内容写什么做网站是不是涉及很多语言职
  • 怎么用手机做一个网站门户网站建设工作的自查报告
  • 网站搭建怎么收费浙江建设集团网站
  • 建网站怎么赚钱免费引流软件下载
  • 自建网站服务器备案做基础销量的网站
  • 淘宝 网站建设 发货音乐网站首页设计
  • 丽水做网站杭州建电商网站多少钱
  • 建设网站能解决什么问题wordpress 模板 中文
  • 平台型网站建设预算表友情链接中有个网站域名过期了会影响
  • 漯河网站开发运营seo是什么意思
  • 网站建设的征求意见稿iis 网站 红
  • 网站搭建教室企业网站开发实训心得
  • 阿克苏建设网站佛山app定制
  • 做淘宝网站要求与想法大型网站建设推荐
  • 在百度做网站赚钱吗跨境电商网站开发
  • 酒店网站建设策划方案南昌网站建设南昌
  • 临沂罗庄做网站房产cms
  • 五合一网站做优化好用吗网站设计的专业流程
  • 毕业设计论文网站开发需要多少网站seo建设方案
  • h5页面用什么做杭州优化外包哪里好
  • 许昌网站建设百姓国货app下载
  • 什么是建站装修公司做宣传在哪个网站
  • 阿里云虚拟主机多个网站吗大庆油田建设集团网站
  • 坂田公司做网站公司有网站域名后如何建网站
  • 自媒体网站程序淘宝网站维护
  • 凡科网站建设网站wordpress 七牛oss
  • 搬瓦工的主机可以用来做网站吗分类信息网站开发需求方案
  • 上海高端网站开发站霸网络国际网站建设的目的
  • 程序员招聘求职的网站做网站加入广告联盟