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

怎样建设个人手机网站微信营销的模式

怎样建设个人手机网站,微信营销的模式,2024近期新闻,高德为什么没有国外地图网络编程套接字#xff08;二#xff09; 文章目录 1.单执行流的TCP网络程序1.1服务端创建套接字1.2服务端绑定1.3服务端监听1.4服务端获取链接1.5服务端处理请求1.6客户端创建套接字1.7客户端连接服务器1.8客户端发起请求 2.多进程版的TCP网络程序2.1单执行流的弊端2.2多进… 网络编程套接字二 文章目录 1.单执行流的TCP网络程序1.1服务端创建套接字1.2服务端绑定1.3服务端监听1.4服务端获取链接1.5服务端处理请求1.6客户端创建套接字1.7客户端连接服务器1.8客户端发起请求 2.多进程版的TCP网络程序2.1单执行流的弊端2.2多进程版的TCP网络程序 3.多线程版的TCP网络程序3.1多进程版的弊端3.2多线程版的TCP网络程序 4.线程池版的TCP网络程序4.1多线程版的弊端4.2线程池版的TCP网络程序 1.单执行流的TCP网络程序 1.1服务端创建套接字 我们将TCP服务器封装成一个类当我们定义出一个服务器对象后需要马上对服务器进行初始化而初始化TCP服务器要做的第一件事就是创建套接字。 TCP服务器在调用socket函数创建套接字时参数设置如下 协议家族选择AF_INET因为我们要进行的是网络通信。创建套接字时所需的服务类型应该是SOCK_STREAM因为我们编写的是TCP服务器SOCK_STREAM提供的就是一个有序的、可靠的、全双工的、基于连接的流式服务。协议类型默认设置为0即可。 如果创建套接字后获得的文件描述符是小于0的说明套接字创建失败此时也就没必要进行后续操作了直接终止程序即可。 class TcpServer {public:void InitServer(){// 1. 创建流式套接字_sock ::socket(AF_INET, SOCK_STREAM, 0);if (_sock 0){LOG(FATAL, socket error);exit(SOCKET_ERROR);}LOG(DEBUG, socket create success,sockfd is : %d, _sock);}~TcpServer(){if (_sock 0){close(_sock);}}private:int _sock; //套接字 };1.2服务端绑定 套接字创建完成此时的_sock只是一个文件描述符并未与网络进行关联所以我们需要调用bind函数将该套接字绑定对应的协议家族、IP和PORT等信息。 而协议家族、IP和PORT信息存放在struct sockaddr_in这样的结构体中所以我们需要创建一个该结构体并将对应的数据进行填充。 class TcpServer {public:TcpServer(int port) : _sock(-1), _port(port){}void InitServer(){// 1. 创建流式套接字_sock ::socket(AF_INET, SOCK_STREAM, 0);if (_sock 0){LOG(FATAL, socket error);exit(SOCKET_ERROR);}LOG(DEBUG, socket create success,sockfd is : %d, _sock);// 2. 绑定struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型。local是变量用户栈上开辟空间。bzero(local, sizeof(local)); // 将从local开始的sizeof(local)大小的内存区域置零local.sin_family AF_INET; // 设置网络通信方式local.sin_port htons(_port); // port要经过网络传输给对面所有需要从主机序列转换为网络序列local.sin_addr.s_addr INADDR_ANY;int n bind(_sock, (struct sockaddr *)local, sizeof(local));if (n 0){LOG(FATAL, bind error);exit(BIND_ERROR);}LOG(DEBUG, bind success,sockfd is : %d, _sock);}~TcpServer(){if (_sock 0){close(_sock);}}private:int _sock; //监听套接字uint16_t _port; //端口号 };具体细节在网络变成套接字一已经讲解过这里就不重复了。 以上过程TcpServer与UdpServer唯一的区别就在于创建套接字时TcpServer是字节流式SOCK_STREAM而UdpServer是数据报式SOCK_DGRAM。 1.3服务端监听 UDP服务器的初始化操作只有两步第一步就是创建套接字第二步就是绑定。 而TCP服务器是面向连接的客户端在正式向TCP服务器发送数据之前需要先与TCP服务器建立连接然后才能与服务器进行通信。 因此TCP服务器需要时刻注意是否有客户端发来连接请求此时就需要将TCP服务器创建的套接字设置为监听状态。 int listen(int sockfd, int backlog);参数说明 sockfd需要设置为监听状态的套接字对应的文件描述符。backlog全连接队列的最大长度。如果有多个客户端同时发来连接请求此时未被服务器处理的连接就会放入连接队列该参数代表的就是这个全连接队列的最大长度一般不要设置太大设置为5或10即可。 返回值说明 监听成功返回0监听失败返回-1同时错误码会被设置。 const static int gbacklog 5; class TcpServer {public:TcpServer(int port) : _listensockfd(-1), _port(port){}void InitServer(){// 1. 创建流式套接字_listensockfd ::socket(AF_INET, SOCK_STREAM, 0);if (_listensockfd 0){LOG(FATAL, socket error);exit(SOCKET_ERROR);}LOG(DEBUG, socket create success,sockfd is : %d, _listensockfd);// 2. 绑定struct sockaddr_in local; // struct sockaddr_in 系统提供的数据类型。local是变量用户栈上开辟空间。bzero(local, sizeof(local)); // 将从local开始的sizeof(local)大小的内存区域置零local.sin_family AF_INET; // 设置网络通信方式local.sin_port htons(_port); // port要经过网络传输给对面所有需要从主机序列转换为网络序列local.sin_addr.s_addr INADDR_ANY;int n bind(_listensock, (struct sockaddr *)local, sizeof(local));if (n 0){LOG(FATAL, bind error);exit(BIND_ERROR);}LOG(DEBUG, bind success,sockfd is : %d, _listensockfd);// 3. tcp是面向连接的所以通信之前必须先建立连接服务器是被链接的// tcpserver启动未来首先要一直等待客户端的连接listenn listen(_listensockfd, gbacklog);if (n 0){LOG(FATAL, listen error);exit(LISTEN_ERROR);}LOG(DEBUG, listen success,sockfd is : %d, _listensockfd);}~TcpServer(){if (_listensockfd 0){close(_listensockfd);}}private:int _listensockfd; //监听套接字uint16_t _port; //端口号 };初始化TCP服务器时创建的套接字并不是普通的套接字而应该叫做监听套接字。为了表明寓意我们将代码中套接字的名字由_sock改为_listensockfd。 在初始化TCP服务器时只有创建套接字成功、绑定成功、监听成功此时TCP服务器的初始化才算完成。 1.4服务端获取链接 TCP服务器初始化后就可以开始运行了但TCP服务器在与客户端进行网络通信之前服务器需要先获取到客户端的连接请求。 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);参数说明 sockfd特定的监听套接字表示从该监听套接字中获取连接。addr对端网络相关的属性信息包括协议家族、IP地址、端口号等。addrlen调用时传入期望读取的addr结构体的长度返回时代表实际读取到的addr结构体的长度这是一个输入输出型参数。 返回值说明 获取连接成功返回接收到的套接字的文件描述符获取连接失败返回-1同时错误码会被设置。 注意我们发现accept函数返回的也是套接字的文件描述符那么监听套接字和该套接字的区别在哪呢 调用accept函数获取连接时是从监听套接字当中获取的。如果accept函数获取连接成功此时会返回接收到的套接字对应的文件描述符。 监听套接字与accept函数返回的套接字的作用 监听套接字用于获取客户端发来的连接请求。accept函数会不断从监听套接字当中获取新连接。accept函数返回的套接字用于为本次accept获取到的连接提供服务。监听套接字的任务只是不断获取新连接而真正为这些连接提供服务的套接字是accept函数返回的套接字而不是监听套接字。 accept函数获取连接时可能会失败但TCP服务器不会因为获取某个连接失败而退出因此服务端获取连接失败后应该继续获取连接。 void Loop() {_isrunning true;// 4. 不能直接接收数据先获取连接while (_isrunning){struct sockaddr_in peer;socklen_t len sizeof(peer);// accept会阻塞等待直到有客户端连接int sockfd ::accept(_listensockfd, (struct sockaddr *)peer, len);if (sockfd 0){LOG(WARNING, accept error);continue; // 失败了就继续获取就行不需要退出};// 处理请求}_isrunning false; }获取链接后我们就可以拿着该套接字sockfd进行数据传输了。 1.5服务端处理请求 此时为客户端提供服务的不是监听套接字因为监听套接字获取到一个连接后会继续获取下一个请求连接为对应客户端提供服务的套接字实际是accept函数返回的套接字即服务套接字。 为了让通信双方都能看到对应的现象我们这里就实现一个简单的回声TCP服务器服务端在为客户端提供服务时就简单的将客户端发来的数据进行输出并且将客户端发来的数据重新发回给客户端即可。当客户端拿到服务端的响应数据后再将该数据进行打印输出此时就能确保服务端和客户端能够正常通信了。 由于TCP面向字节流所以通信接口我们可以使用read、write或者使用recv、send。 ssize_t read(int fd, void *buf, size_t count);参数说明 fd特定的文件描述符表示从该文件描述符中读取数据。buf数据的存储位置表示将读取到的数据存储到该位置。count数据的个数表示从该文件描述符中读取数据的字节数。 返回值说明 如果返回值大于0则表示本次实际读取到的字节个数。如果返回值等于0则表示读到了文件结尾即client退出关闭连接了。如果返回值小于0则表示读取时遇到了错误。 或者使用recv函数。 ssize_t recv(int sockfd, void *buf, size_t len, int flags);参数说明 sockfd套接字描述符表示要接收数据的套接字。buf指向缓冲区的指针接收到的数据将被存储在这个缓冲区中。len缓冲区的大小即最多可以接收多少字节的数据。flags指定接收操作的行为通常是0但在某些情况下可以指定特殊的行为如非阻塞模式。 返回值说明 成功时recv 返回接收到的字节数。如果连接正常关闭且没有数据可读则返回0。失败时返回-1并设置相应的errno以指示错误原因。 当服务端调用read函数收到客户端的数据后就可以再调用write函数将该数据再响应给客户端。 ssize_t write(int fd, const void *buf, size_t count);参数说明 fd特定的文件描述符表示将数据写入该文件描述符对应的套接字。buf需要写入的数据。count需要写入数据的字节个数。 返回值说明 写入成功返回实际写入的字节数写入失败返回-1同时错误码会被设置。 或者使用send函数。 ssize_t send(int sockfd, const void *buf, size_t len, int flags);参数说明 sockfd套接字描述符表示要发送数据的套接字。buf指向包含要发送数据的缓冲区的指针。len要发送的字节数。flags指定发送操作的行为通常是0但在某些情况下可以指定特殊的行为如非阻塞模式。 返回值说明 成功时send 返回实际发送的字节数。这通常等于请求发送的字节数但在某些情况下如非阻塞套接字且缓冲区已满时它可能小于请求发送的字节数。失败时返回-1并设置相应的错误码errno以指示错误原因。 需要注意的是服务端读取数据是服务套接字中读取的而写入数据的时候也是写入进服务套接字的。也就是说这里为客户端提供服务的套接字既可以读取数据也可以写入数据这就是TCP全双工的通信的体现。 在从服务套接字中读取客户端发来的数据时注意及时关闭服务套接字对应的文件描述符。因为文件描述符本质就是数组的下标因此文件描述符的资源是有限的如果我们一直占用那么可用的文件描述符就会越来越少因此服务完客户端后要及时关闭对应的文件描述符否则会导致文件描述符泄漏。 void Service(int sockfd, InetAddr client) {LOG(DEBUG, get a new link ,info %s:%d,fd:%d, client.Ip(), client.Port(), sockfd);std::string clientaddr [ client.Ip() : std::to_string(client.Port()) ]#;while (true){// tcp连接面向字节流可以使用文件接口readwritechar inbuffer[1024];ssize_t n read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n 0){inbuffer[n] 0;std::cout clientaddr inbuffer std::endl;std::string echo_string [server echo]# ;echo_string inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n 0) // read返回值如果为0表示读到了文件结尾即client退出关闭连接了{LOG(INFO, %s quit, clientaddr.c_str());break;}else{LOG(ERROR, read error);break;}}close(sockfd); // 文件描述符泄露 } void Loop() {_isrunning true;// 4. 不能直接接收数据先获取连接while (_isrunning){struct sockaddr_in peer;socklen_t len sizeof(peer);// accept会阻塞等待直到有客户端连接int sockfd ::accept(_listensockfd, (struct sockaddr *)peer, len);if (sockfd 0){LOG(WARNING, accept error);continue; // 失败了就继续获取就行不需要退出};// version 0 一次只能处理一个请求Service(sockfd, InetAddr(peer));}_isrunning false; }1.6客户端创建套接字 简单些我们这里不对客户端进行封装了。 上篇文章我们提到过客户端是否需要绑定的问题 客户端要不要绑定 答案是肯定的因为网络通信的前提就是需要客户端的IP和PORT服务端的IP和PORT通过他们两个网络中的进程才可以进行通信。但是客户端不能像服务端一样显式的bind设想一个场景淘宝写了一个客户端显示绑定了端口号8080而微信写的客户端也显示绑定的8080端口号那此时就会因为端口冲突导致你只能使用一项服务这很明显是不现实的所以客户端绑定端口的操作由操作系统自动完成就是为了防止客户端端口号冲突一般在首次发送数据的时候绑定。 客户端必须要知道它要连接的服务端的IP地址和端口号因此客户端除了要有自己的套接字之外还需要知道服务端的IP地址和端口号这样客户端才能够通过套接字向指定服务器进行通信。 void Usage(std::string proc) {std::cout Usage:\n\t proc serverip serverport\n std::endl; } // ./tcpclient serverip serverport int main(int argc, char *argv[]) {if (argc ! 3){Usage(argv[0]);exit(1);}std::string serverip argv[1];uint16_t serverport std::stoi(argv[2]);int sockfd socket(AF_INET, SOCK_STREAM, 0);if (sockfd 0){std::cerr socket error std::endl;exit(2);}close(sockfd);return 0; }1.7客户端连接服务器 客户端不需要显式绑定也不需要监听因此当客户端创建完套接字后就可以向服务端发起连接请求。 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);参数说明 sockfd特定的套接字表示通过该套接字发起连接请求。addr对端网络相关的属性信息包括协议家族、IP地址、端口号等。addrlen传入的addr结构体的长度。 返回值说明 连接或绑定成功返回0连接失败返回-1同时错误码会被设置。 需要注意的是客户端不是不需要进行绑定而是不需要我们自己进行绑定操作当客户端向服务端发起连接请求时系统会给客户端随机指定一个端口号进行绑定。因为通信双方都必须要有IP地址和端口号否则无法唯一标识通信双方。也就是说如果connect函数调用成功了客户端本地会随机给该客户端绑定一个端口号发送给对端服务器。 此外调用connect函数向服务端发起连接请求时需要传入服务端对应的网络信息否则connect函数也不知道该客户端到底是要向哪一个服务端发起连接请求。 void Usage(std::string proc) {std::cout Usage:\n\t proc serverip serverport\n std::endl; } // ./tcpclient serverip serverport int main(int argc, char *argv[]) {if (argc ! 3){Usage(argv[0]);exit(1);}std::string serverip argv[1];uint16_t serverport std::stoi(argv[2]);int sockfd socket(AF_INET, SOCK_STREAM, 0);if (sockfd 0){std::cerr socket error std::endl;exit(2);}// 构建目标主机的socket信息struct sockaddr_in server;memset(server, 0, sizeof(server)); // bzeroserver.sin_family AF_INET;server.sin_port htons(serverport);server.sin_addr.s_addr inet_addr(serverip.c_str());int n connect(sockfd, (struct sockaddr *)server, sizeof(server));if (n 0){std::cerr connect error std::endl;exit(3);}close(sockfd);return 0; }1.8客户端发起请求 由于我们实现的是一个简单的回声服务器因此当客户端连接到服务端后客户端就可以向服务端发送数据了这里我们可以让客户端将用户输入的数据发送给服务端发送时调用send函数向套接字当中写入数据即可。 当客户端将数据发送给服务端后由于服务端读取到数据后还会进行回显因此客户端在发送数据后还需要调用recv函数读取服务端的响应数据然后将该响应数据进行打印以确定双方通信无误。 void Usage(std::string proc) {std::cout Usage:\n\t proc serverip serverport\n std::endl; } // ./tcpclient serverip serverport int main(int argc, char *argv[]) {if (argc ! 3){Usage(argv[0]);exit(1);}std::string serverip argv[1];uint16_t serverport std::stoi(argv[2]);int sockfd socket(AF_INET, SOCK_STREAM, 0);if (sockfd 0){std::cerr socket error std::endl;exit(2);}// 构建目标主机的socket信息struct sockaddr_in server;memset(server, 0, sizeof(server)); // bzeroserver.sin_family AF_INET;server.sin_port htons(serverport);server.sin_addr.s_addr inet_addr(serverip.c_str());int n connect(sockfd, (struct sockaddr *)server, sizeof(server));if (n 0){std::cerr connect error std::endl;exit(3);}while (true){std::cout Please Enter# ;std::string outstring;std::getline(std::cin, outstring);ssize_t s send(sockfd, outstring.c_str(), outstring.size(), 0); // writeif (s 0){char inbuffer[1024];ssize_t m recv(sockfd, inbuffer, sizeof(inbuffer) - 1, 0);if (m 0){inbuffer[m] 0;std::cout inbuffer std::endl;}elsebreak;}else{break;}}close(sockfd);return 0; }经过测试服务正常运行。 2.多进程版的TCP网络程序 2.1单执行流的弊端 当我们仅用一个客户端连接服务端时这一个客户端能够正常享受到服务端的服务。 但在这个客户端正在享受服务端的服务时我们让另一个客户端也连接服务器此时虽然在客户端显示连接是成功的但这个客户端发送给服务端的消息既没有在服务端进行打印服务端也没有将该数据回显给该客户端。 只有当第一个客户端退出后服务端才会将第二个客户端发来是数据进行打印并回显该第二个客户端。 通过实验现象可以看到这服务端只有服务完一个客户端后才会服务另一个客户端。因为我们目前所写的是一个单执行流版的服务器这个服务器一次只能为一个客户端提供服务。 当服务端调用accept函数获取到连接后就给该客户端提供服务但在服务端提供服务期间可能会有其他客户端发起连接请求但由于当前服务器是单执行流的只能服务完当前客户端后才能继续服务下一个客户端。 实际上当服务端在给第一个客户端提供服务期间第二个客户端向服务端发起的连接请求时是成功的只不过服务端没有调用accept函数将该连接获取上来罢了。 在底层会为我们维护一个连接队列服务端没有accept的新连接就会放到这个连接队列当中而这个连接队列的最大长度就是通过listen函数的第二个gbacklog来指定的因此服务端虽然没有获取第二个客户端发来的连接请求但是在第二个客户端那里显示是连接成功的。 所以我们需要多执行流即多进程 | 多线程。 2.2多进程版的TCP网络程序 当服务端调用accept函数获取到新连接后不是由当前执行流为该连接提供服务而是当前执行流调用fork函数创建子进程然后让子进程为父进程获取到的连接提供服务。 由于父子进程是两个不同的执行流当父进程调用fork创建出子进程后父进程就可以继续从监听套接字当中获取新连接而不用关心获取上来的连接是否服务完毕。 需要注意的问题 1有关套接字 当创建了子进程后子进程继承父进程的文件描述符表注意这个文件描述符表父子是独立拥有的即父子进程现在都持有监听套接字对应的文件描述符_listensockfd和服务套接字sockfd。 那么此时我们建议 在子进程中关闭监听套接字_listensockfd防止子进程误写_listensockfd。 要求 在父进程中关闭服务套接字sockfd如果服务进程不及时关掉不用的文件描述符最终服务进程中可用的文件描述符就会越来越少。 2有关等待子进程 当父进程创建出子进程后父进程是需要等待子进程退出的否则子进程会变成僵尸进程进而造成内存泄漏。因此服务端创建子进程后需要调用wait或waitpid函数对子进程进行等待。 阻塞式等待与非阻塞式等待 如果服务端采用阻塞的方式等待子进程那么服务端还是需要等待服务完当前客户端才能继续获取下一个连接请求此时服务端仍然是以一种串行的方式为客户端提供服务。如果服务端采用非阻塞的方式等待子进程虽然在子进程为客户端提供服务期间服务端可以继续获取新连接但此时服务端就需要将所有子进程的PID保存下来并且需要不断花费时间检测子进程是否退出。 总之服务端要等待子进程退出无论采用阻塞式等待还是非阻塞式等待都不尽人意。此时我们可以考虑让服务端不等待子进程退出。 不等待子进程退出的方式 捕捉SIGCHLD信号将其处理动作设置为忽略。让父进程创建子进程子进程再创建孙子进程最后让孙子进程为客户端提供服务。 首先是捕捉SIGCHLD信号将其处理动作设置为忽略。 当子进程退出时会给父进程发送SIGCHLD信号如果父进程将SIGCHLD信号进行捕捉并将该信号的处理动作设置为忽略此时父进程就只需专心处理自己的工作不必关心子进程了。 实现方式很简单我们也推荐这样做 void Loop() {signal(SIGCHLD, SIG_IGN); //忽略SIGCHLD信号_isrunning true;// 4. 不能直接接收数据先获取连接while (_isrunning){struct sockaddr_in peer;socklen_t len sizeof(peer);// accept会阻塞等待直到有客户端连接int sockfd ::accept(_listensockfd, (struct sockaddr *)peer, len);if (sockfd 0){LOG(WARNING, accept error);continue; // 失败了就继续获取就行不需要退出};// version 1 多进程版pid_t id fork();if (id 0){// child 关心sockfd不关心listensockfdclose(_listensockfd); // 建议关闭防止子进程误写_listensockfdService(sockfd, InetAddr(peer)); exit(0);}// father 关心listensockfd不关心sockfd因为父进程已经将sockfd交给了子进程close(sockfd);// 必须关闭防止父进程打开过多的文件描述符而不关闭}_isrunning false; }其次是让父进程创建子进程子进程再创建孙子进程最后让孙子进程为客户端提供服务。。 让孙子进程为客户端提供服务 此时我们就不用等待孙子进程退出了。 我先将代码贴出来 void Loop() {signal(SIGCHLD, SIG_IGN); //忽略SIGCHLD信号_isrunning true;// 4. 不能直接接收数据先获取连接while (_isrunning){struct sockaddr_in peer;socklen_t len sizeof(peer);// accept会阻塞等待直到有客户端连接int sockfd ::accept(_listensockfd, (struct sockaddr *)peer, len);if (sockfd 0){LOG(WARNING, accept error);continue; // 失败了就继续获取就行不需要退出};// version 1 采用多进程pid_t id fork();if (id 0){// child 关心sockfd不关心listensockfdclose(_listensockfd); // 建议关闭防止子进程误写_listensockfdif (fork() 0)exit(0);// 子进程直接退出留下孙子进程孤儿被系统领养执行完service自动回收Service(sockfd, InetAddr(peer)); // Service由孙子进程执行exit(0);}// father 关心listensockfd不关心sockfd因为父进程已经将sockfd交给了子进程close(sockfd); // 必须关闭防止父进程打开过多的文件描述符而不关闭waitpid(id, nullptr, 0); // 子进程直接退出所以这里直接瞬间等待成功所以可以继续accept}_isrunning false; }在子进程中创建孙子进程然后让孙子进程执行服务子进程直接退出此时孙子进程就变成了孤儿进程会被系统领养等待的问题也不需要我们考虑了。 3.多线程版的TCP网络程序 3.1多进程版的弊端 多进程版本的TCP网络程序在面对大量请求时也会显得力不从心因为创建进程的成本是很高的创建进程时需要创建该进程对应的进程控制块task_struct、进程地址空间mm_struct、页表等数据结构。 而创建线程的成本比创建进程的成本会小得多因为线程本质是在进程地址空间内运行创建出来的线程会共享该进程的大部分资源因此在实现多执行流的服务器时最好采用多线程进行实现。 3.2多线程版的TCP网络程序 当服务进程调用accept函数获取到一个新连接后就可以直接创建一个线程让该线程为对应客户端提供服务。 当然主线程服务进程创建出新线程后也是需要等待新线程退出的否则也会造成类似于僵尸进程这样的问题。但对于线程来说如果不想让主线程等待新线程退出可以让创建出来的新线程调用pthread_detach函数进行线程分离当这个线程退出时系统会自动回收该线程所对应的资源。此时主线程服务进程就可以继续调用accept函数获取新连接而让新线程去服务对应的客户端。 参数传递的问题 新线程在为客户端提供服务时就是调用Service函数而调用Service函数时是需要传入两个参数的分别是客户端对应的套接字和InetAddr。因此主线程创建新线程时需要给新线程传入两个参数但实际在调用pthread_create函数创建新线程时只能传入一个类型为void*的参数。 所以根据我们之前学习线程创建的知识我们一般是定义一个ThreadData结构体用来存放需要传递给执行函数的参数。 当主线程创建新线程时就可以定义一个ThreadData对象将客户端对应的套接字、InetAddr设计进这个ThreadData对象当中然后将ThreadData对象的地址作为新线程执行例程的参数进行传入。 此时新线程在执行例程当中再将这个void*类型的参数强转为ThreadData*类型然后就能够拿到客户端对应的套接字InetAddr进而调用Service函数为对应客户端提供服务。 注意新线程的执行例程是一个参数为void*返回值为void*的函数。但由于执行例程函数我们放在了类内它隐藏的第一个参数为this指针所以我们需要将线程例程函数HandlerSock设置为静态成员函数即在函数前方加static 修饰但是如果加static修饰后就无法调用类内的Service函数了因为没有this指针了所以我们需要将this指针设为ThreadData的类内成员再通过这个this调用Service或者将Service也设置为静态成员函数。 class ThreadData {public:ThreadData(int fd, InetAddr addr, TcpServer *s) : sockfd(fd), clientaddr(addr), self(s){}public:int sockfd;InetAddr clientaddr;TcpServer *self; };class TcpServer {public:TcpServer(int port) : _port(port), _listensockfd(sockfddefault), _isrunning(false){}void InitServer(){//略}void Service(int sockfd, InetAddr client){LOG(DEBUG, get a new link ,info %s:%d,fd:%d, client.Ip(), client.Port(), sockfd);std::string clientaddr [ client.Ip() : std::to_string(client.Port()) ]#;while (true){// tcp连接面向字节流可以使用文件接口readwritechar inbuffer[1024];ssize_t n read(sockfd, inbuffer, sizeof(inbuffer) - 1);if (n 0){inbuffer[n] 0;std::cout clientaddr inbuffer std::endl;std::string echo_string [server echo]# ;echo_string inbuffer;write(sockfd, echo_string.c_str(), echo_string.size());}else if (n 0) // read返回值如果为0表示读到了文件结尾即client退出关闭连接了{LOG(INFO, %s quit, clientaddr.c_str());break;}else{LOG(ERROR, read error);break;}}close(sockfd); // 文件描述符泄露}static void *HandlerSock(void *args){pthread_detach(pthread_self()); // 线程分离ThreadData *td static_castThreadData *(args);// 需要调用Service函数但是Service函数是类内函数静态成员函数没有this指针无法调用如何解决// 将this指针设为ThreadData的类内成员再通过这个this调用Servicetd-self-Service(td-sockfd, td-clientaddr);delete td;return nullptr;}void Loop(){_isrunning true;// 4. 不能直接接收数据先获取连接while (_isrunning){struct sockaddr_in peer;socklen_t len sizeof(peer);// accept会阻塞等待直到有客户端连接int sockfd ::accept(_listensockfd, (struct sockaddr *)peer, len);if (sockfd 0){LOG(WARNING, accept error);continue; // 失败了就继续获取就行不需要退出};// version 2 采用多线程pthread_t t;ThreadData *td new ThreadData(sockfd, InetAddr(peer), this);pthread_create(t, nullptr, HandlerSock, td);}_isrunning false;}~TcpServer(){//略}private:uint16_t _port;int _listensockfd;bool _isrunning; };4.线程池版的TCP网络程序 4.1多线程版的弊端 每当有新连接到来时服务端的主线程都会重新为该客户端创建为其提供服务的新线程而当服务结束后又会将该新线程销毁。这样做不仅麻烦而且效率低下每当连接到来的时候服务端才创建对应提供服务的线程。 如果有大量的客户端连接请求此时服务端要为每一个客户端创建对应的服务线程。计算机当中的线程越多CPU的压力就越大因为CPU要不断在这些线程之间来回切换此时CPU在调度线程的时候线程和线程之间切换的成本就会变得很高。 此外一旦线程太多每一个线程再次被调度的周期就变长了而线程是为客户端提供服务的线程被调度的周期变长客户端也迟迟得不到应答。 所以为了解决以上问题我们引入线程池线程池的存在就是为了避免处理短时间任务时创建与销毁线程的代价此外线程池还能够保证内核充分利用防止过分调度。 4.2线程池版的TCP网络程序 在线程池设计中我们维护了一个任务队列并利用vector容器管理了多个线程这些线程轮询查看任务队列中是否存在任务并执行任务。 在TcpServer中我们只需要将Service作为任务入队到线程池的任务队列中即可。 所以我们使用bind将Service需要的参数绑定给Service构建一个无参无返回值的函数对象。入队时将该函数对象入队即可。 线程池代码 using namespace ThreadModule;const static int DefaultThreadNum 5;template typename T class ThreadPool {private:void LockQueue(){pthread_mutex_lock(_mutex);}void UnlockQueue(){pthread_mutex_unlock(_mutex);}void ThreadSleep(){pthread_cond_wait(_cond, _mutex);}void ThreadWake(){pthread_cond_signal(_cond);}void ThreadWakeAll(){pthread_cond_broadcast(_cond);}ThreadPool(int threadnum DefaultThreadNum) : _threadnum(threadnum), _waitnum(0), _isrunning(false){pthread_mutex_init(_mutex, nullptr);pthread_cond_init(_cond, nullptr);LOG(INFO, ThreadPool Construct());}void initThreadPool(){for (int num 0; num _threadnum; num){std::string name thread - std::to_string(num 1);// _threads.emplace_back(Print, name)_threads.emplace_back(std::bind(ThreadPool::HandlerTask, this, std::placeholders::_1), name); // 绑定LOG(INFO, Init Thread %s done, name.c_str());}_isrunning true;}void Start(){for (auto thread : _threads){thread.Start();}}// 类的成员方法也可以成为另一个类Thread的回调方法void HandlerTask(std::string name) // 包含this指针{LOG(INFO, Thread %s is running..., name.c_str());while (true){// 1.保证队列安全LockQueue();// 2.队列中不一定有数据while (_task_queue.empty() _isrunning){_waitnum;ThreadSleep();_waitnum--;}// 2.1如果任务队列为空并且线程池已经退出if (_task_queue.empty() !_isrunning){UnlockQueue();break;}// 2.2如果任务队列非空并且线程池未退出// 2.3如果任务队列非空并且线程池已退出 --处理完任务再退出// 3.到这一定有任务处理任务T t _task_queue.front();_task_queue.pop();UnlockQueue();LOG(DEBUG, %s get a task, name.c_str());// 4.处理任务这个任务属于线程私有独占任务所以不放到加锁解锁之间t();// LOG(DEBUG, %s handler a task,result is %s, name.c_str(), t.ResultToString().c_str());}}// 禁止赋值拷贝ThreadPool(const ThreadPoolT ) delete;ThreadPoolT operator(const ThreadPoolT ) delete;public:static ThreadPoolT *GetInstance(){// 只有第一次会创建对象后续都是获取// 双判断的方式可以有效减少获取单例的加锁成本而且保证线程安全if (_instance nullptr){LockGuard lockguard(_lock);if (_instance nullptr){_instance new ThreadPoolT();_instance-initThreadPool();_instance-Start();LOG(DEBUG, 创建线程池实例);return _instance;}}LOG(DEBUG, 获取线程池实例);return _instance;}void Stop(){LockQueue();_isrunning false;ThreadWakeAll();UnlockQueue();}void Wait(){for (auto thread : _threads){thread.Join();LOG(INFO, Thread %s is quit..., thread.name().c_str());}}bool Enqueue(const T t){bool ret false;LockQueue();if (_isrunning){_task_queue.push(t);if (_waitnum 0){ThreadWake();}LOG(DEBUG, enqueue task success);ret true;}UnlockQueue();return ret;}~ThreadPool(){pthread_mutex_destroy(_mutex);pthread_cond_destroy(_cond);}private:int _threadnum;std::vectorThread _threads; // 管理线程std::queueT _task_queue; // 任务队列pthread_mutex_t _mutex;pthread_cond_t _cond;int _waitnum;bool _isrunning;// 添加单例模式static ThreadPoolT *_instance;static pthread_mutex_t _lock; };template typename T ThreadPoolT *ThreadPoolT::_instance nullptr;template typename T pthread_mutex_t ThreadPoolT::_lock PTHREAD_MUTEX_INITIALIZER;TcpServer代码 using task_t std::functionvoid();class TcpServer {public:TcpServer(int port) : _port(port), _listensockfd(sockfddefault), _isrunning(false){}void InitServer(){//略}void Service(int sockfd, InetAddr client){//略}void Loop(){_isrunning true;// 4. 不能直接接收数据先获取连接while (_isrunning){struct sockaddr_in peer;socklen_t len sizeof(peer);// accept会阻塞等待直到有客户端连接int sockfd ::accept(_listensockfd, (struct sockaddr *)peer, len);if (sockfd 0){LOG(WARNING, accept error);continue; // 失败了就继续获取就行不需要退出};// version 3 采用线程池task_t t std::bind(TcpServer::Service, this, sockfd, InetAddr(peer));ThreadPooltask_t::GetInstance()-Enqueue(t);}_isrunning false;}~TcpServer(){//略}private:uint16_t _port;int _listensockfd;bool _isrunning; };选择多线程实现还是线程池实现取决于具体的应用场景和需求。 对于需要处理大量并发连接但每个连接处理时间较短的场景线程池通常是一个更好的选择。而对于连接数较少或每个连接处理时间较长的场景直接使用多线程可能更简单直接。 路漫漫其修远兮吾将上下而求索。 —屈原
http://www.w-s-a.com/news/487053/

相关文章:

  • 廊坊网站建设佛山厂商wordpress神主题
  • 成县建设局网站中国建筑有几个工程局
  • 网站打不开被拦截怎么办单页面网站制作
  • 关于协会网站建设的建议设计公司名字参考
  • 怎样申请做p2p融资网站页面设计时最好使用一种颜色
  • 一般做网站上传的图片大小网站软件设计
  • 用来网站备案注册什么公司好wordpress怎么搜索中文主题
  • 网站开发 打标签深圳软件公司排名
  • 邯郸的网站建设电子网站怎么做的
  • 中国企业信用网四川游戏seo整站优化
  • 下载站推广wordpress扩展字段
  • 网站建设这个工作怎么样免费电子版个人简历模板
  • 移动网站设计与制作网站开发接私活
  • 视频制作素材网站wordpress mysql 被删
  • 静态网站 模板公司一般都用什么邮箱
  • 做网站效果图是用ps还是ai泰安人才网最新招聘信息2022年
  • 免费建站网站一级大录像不卡在线看网页郑州网站关键
  • 做网站 然后百度推广哈尔滨建筑网
  • 章丘营销型网站建设网站测评必须做
  • 营销者网站怎么把网站黑了
  • 律师事务所手机网站校园网站设计
  • 网站案例展示分类网站响应速度优化
  • 风景网站的制作网站ip地址查询域名
  • 怎样看网站是谁做的马鞍山什么房产网站做的好
  • 西安推荐企业网站制作平台软装设计方案ppt
  • 网站静态页模板专业网站设计开发公司
  • 手机免费在线搭建网站短网址生成防红
  • 天津网站设计网站制作如何新建wordpress
  • 山东省建设备案网站审批国际新闻最新消息10条简短
  • 成都市建设网扬尘监控网站短域名转换