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

没网站怎么做cpa企业网站 php 下载

没网站怎么做cpa,企业网站 php 下载,网站被k有什么表现,销售类网站开发架构目录 简单的TCP网络程序服务端创建套接字服务端绑定服务端监听服务端获取连接服务端处理请求单执行流服务器的弊端 多进程版TCP网络程序捕捉SIGCHLD信号让孙子进程提供服务多线程版的TCP网络程序客户端创建套接字客户端链接服务器客户端发起请求 线程池版的TCP网络程序 简单的T… 目录 简单的TCP网络程序服务端创建套接字服务端绑定服务端监听服务端获取连接服务端处理请求单执行流服务器的弊端 多进程版TCP网络程序捕捉SIGCHLD信号让孙子进程提供服务多线程版的TCP网络程序客户端创建套接字客户端链接服务器客户端发起请求 线程池版的TCP网络程序 简单的TCP网络程序 我们将TCP服务器封装成一个类 class TcpServer { public:TcpServer(uint16_t port, std::string ip ) : _port(port), _ip(ip), listensock(-1){}~TcpServer(){}private:uint16_t _port;std::string _ip;int listensock; };服务端创建套接字 首先我们要做的就是初始化服务器而初始化服务器最先做的就是要创建套接字TCP服务器在调用socket函数创建套接字时参数设置如下 协议家族选择AF_INET因为我们要进行的是网络通信。创建套接字时所需的服务类型应该是SOCK_STREAM因为我们编写的是TCP服务器SOCK_STREAM提供的就是一个有序的、可靠的、全双工的、基于连接的流式服务。协议类型默认设置为0即可。 如果创建套接字后获得的文件描述符是小于0的说明套接字创建失败此时也就没必要进行后续操作了直接终止程序即可。 void Serverinit() {// 1.创建socketlistensock socket(AF_INET, SOCK_STREAM, 0);if (listensock 0){logMessage(FATAL, create socket error:%d:%s, errno, strerror(errno));exit(1);}logMessage(NORMAL, create socket sucess:%d, listensock); }服务端绑定 套接字创建完毕后我们实际只是在系统层面上打开了一个文件该文件还没有与网络关联起来因此创建完套接字后我们还需要调用bind函数进行绑定操作。 TCP服务端绑定与UDP基本一致 void Serverinit() {// 1.创建socketlistensock socket(AF_INET, SOCK_STREAM, 0);if (listensock 0){logMessage(FATAL, create socket error:%d:%s, errno, strerror(errno));exit(1);}logMessage(NORMAL, create socket sucess:%d, listensock);// 2. bindstruct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(_port);local.sin_addr.s_addr _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if (bind(listensock, (struct sockaddr *)local, sizeof(local)) 0){logMessage(FATAL, bond error:%d:%s, errno, strerror(errno));exit(2);} }服务端监听 UDP服务器的初始化操作只有两步第一步就是创建套接字第二步就是绑定。而TCP服务器是面向连接的客户端在正式向TCP服务器发送数据之前需要先与TCP服务器建立连接然后才能与服务器进行通信。 因此TCP服务器需要时刻注意是否有客户端发来连接请求此时就需要将TCP服务器创建的套接字设置为监听状态。 listen函数 设置套接字为监听状态的函数叫做listen该函数的函数原型如下 int listen(int sockfd, int backlog); 参数说明 sockfd需要设置为监听状态的套接字对应的文件描述符。backlog全连接队列的最大长度。如果有多个客户端同时发来连接请求此时未被服务器处理的连接就会放入连接队列该参数代表的就是这个全连接队列的最大长度一般不要设置太大设置为5或20即可。 返回值说明 监听成功返回0监听失败返回-1同时错误码会被设置。 服务器监听 TCP服务器在创建完套接字和绑定后需要再进一步将套接字设置为监听状态监听是否有新的连接到来。如果监听失败也没必要进行后续操作了因为监听失败也就意味着TCP服务器无法接收客户端发来的连接请求因此监听失败我们直接终止程序即可。 void Serverinit() {// 1.创建socketlistensock socket(AF_INET, SOCK_STREAM, 0);if (listensock 0){logMessage(FATAL, create socket error:%d:%s, errno, strerror(errno));exit(1);}logMessage(NORMAL, create socket sucess:%d, listensock);// 2. bindstruct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(_port);local.sin_addr.s_addr _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if (bind(listensock, (struct sockaddr *)local, sizeof(local)) 0){logMessage(FATAL, bond error:%d:%s, errno, strerror(errno));exit(2);}// 3. 建立连接if (listen(listensock, gbacklog) 0){logMessage(FATAL, listen error:%d:%s, errno, strerror(errno));exit(3);}logMessage(NORMAL, init server sucess); }服务端获取连接 TCP服务器初始化后就可以开始运行了但TCP服务器在与客户端进行网络通信之前服务器需要先获取到客户端的连接请求。 accept函数 获取连接的函数叫做accept该函数的函数原型如下 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 参数说明 sockfd特定的监听套接字表示从该监听套接字中获取连接。addr对端网络相关的属性信息包括协议家族、IP地址、端口号等。addrlen调用时传入期望读取的addr结构体的长度返回时代表实际读取到的addr结构体的长度这是一个输入输出型参数。 返回值说明 获取连接成功返回接收到的套接字的文件描述符获取连接失败返回-1同时错误码会被设置。 accept函数返回值 调用accept函数过程中我们是从监听套接字listensock中获取连接的此时就会返回接收到套接字对应的文件描述符。accept函数中的监听套接字是为了让我们不断来获取连接的而它返回的套接字才是真正为我们为您提供服务的。 void start(){while (true){// sleep(1);// 4. 获取连接struct sockaddr_in src;socklen_t len sizeof(src);int serversock accept(listensock, (struct sockaddr *)src, len);if (serversock 0){logMessage(ERROR, accept error:%d:%s, errno, strerror(errno));continue;}}}服务端处理请求 此时服务端获取连接已经成功了此时就需要对获取的连接进行处理我们的客户端所接收的并不是监听套接字而是accept函数返回的套接字因为监听套接字一个连接完成就会继续连接下一个。 我们在这儿实现一个简单的回声服务器服务端将客户端发出的数据进行打印并将数据重新发回客户端客户端拿到此数据以后在进行回显这样就完成了服务端与客户端之间的通信。 read函数 TCP服务器读取数据的函数叫做read该函数的函在这里插入代码片数原型如下 ssize_t read(int fd, void *buf, size_t count); 参数说明 fd特定的文件描述符表示从该文件描述符中读取数据。buf数据的存储位置表示将读取到的数据存储到该位置。count数据的个数表示从该文件描述符中读取数据的字节数。 返回值说明 如果返回值大于0则表示本次实际读取到的字节个数。如果返回值等于0则表示对端已经把连接关闭了。如果返回值小于0则表示读取时遇到了错误。 注意 read返回值为0表示对端连接关闭这就和我们学习管道通信一样 写端不写读端一直在读此时读端就会就会被挂起读端不读写端一直写此时写端写满数据后就会被挂起写端写完数据后就关闭读端就会将管道数据读到0后挂起读端进程直接关闭此时写端进程就会被操作系统杀死因为读端不会进行读取。 此时的客户端就对应写端如果客户端将连接关闭了那么此时服务端将套接字当中的信息读完后就会读取到0因此如果服务端调用read函数后得到的返回值为0此时服务端就不必再为该客户端提供服务了。 write函数 TCP服务器写入数据的函数叫做write该函数的函数原型如下 ssize_t write(int fd, const void *buf, size_t count); 参数说明 fd特定的文件描述符表示将数据写入该文件描述符对应的套接字。buf需要写入的数据。count需要写入数据的字节个数。 返回值说明 写入成功返回实际写入的字节数写入失败返回-1同时错误码会被设置。 当服务端调用read函数收到客户端的数据后就可以再调用write函数将该数据再响应给客户端。 服务端读取数据是从accept函数返回的套接字当中读取的写入数据也是向accept函数返回的套接字当中写入的所以为客户端提供的套接字是既可以读取数据也可以写入数据的这就是TCP全双工的体现。 在此我们还需注意的是从服务套接字中读取客户端发来的数据时如果调用read函数后得到的返回值为0或者读取出错了此时就应该直接将服务套接字对应的文件描述符关闭。因为文件描述符本质就是数组的下标因此文件描述符的资源是有限的如果我们一直占用那么可用的文件描述符就会越来越少因此服务完客户端后要及时关闭对应的文件描述符否则会导致文件描述符泄漏。 static void service(int sock, const std::string clientip, const uint16_t clientport) {char buffer[1024];while (true){ssize_t s read(sock, buffer, sizeof(buffer) - 1);if (s 0) // 将发过来的数据当做字符串{buffer[s] 0;std::cout clientip : clientport # buffer std::endl;}else if (s 0){logMessage(NORMAL, %s:%d shutdown, me too!!!, clientip.c_str(), clientport);break;}else{logMessage(errno, read socket error:%d %s, errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));}close(sock); } void start() {while (true){// sleep(1);// 4. 获取连接struct sockaddr_in src;socklen_t len sizeof(src);int serversock accept(listensock, (struct sockaddr *)src, len);if (serversock 0){logMessage(ERROR, accept error:%d:%s, errno, strerror(errno));continue;}// 获取连接成功uint16_t client_port htons(src.sin_port);std::string client_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link success, sereversock:%d | %s | %d, serversock, client_ip.c_str(), client_port);// 开始通信//(1) 单进程循环一次只处理一个客户端处理完成以后在处理下一个service(serversock, client_ip, client_port);} }此时尽管我们并没有编写客户端代码但我们可以使用telnet命令远程登录到该服务器因为telnet底层实际采用的就是TCP协议。 此时我们就可以看见客户端输入数据以后服务端就将数据接收到并打印出来而且服务端还将数据发送回客户端并进行打印。 我们还会发现为该连接提供服务的套接字对应的文件描述符就是4。因为0、1、2是默认打开的其分别对应标准输入流、标准输出流和标准错误流而3号文件描述符在初始化服务器时分配给了监听套接字因此当第一个客户端发起连接请求时为该客户端提供服务的套接字对应的文件描述符就是4。 单执行流服务器的弊端 上面我们使用一个客户端链接服务器时服务端与客户端之间可以正常通信可是当我们在打开一个客户端以后此时虽然在客户端显示连接是成功的但这个客户端发送给服务端的消息既没有在服务端进行打印服务端也没有将该数据回显给该客户端。 只有当第一个客户端退出后服务端才会将第二个客户端发来是数据进行打印并回显该第二个客户端。 通过上述实验现象就可以发现对于单执行流服务器来说我们必须是一个客户端退出后才会运行另一个客户端而一个客户端正在运行而另一个客户端连接成功的原因就在于底层我们实际上会维护一个链接队列服务端没有accept的新连接就会放到这个连接队列当中当服务端在给第一个客户端提供服务期间第二个客户端向服务端发起的连接请求时是成功的只不过服务端没有调用accept函数将该连接获取上来罢了而这个连接队列的最大长度就是通过listen函数的第二个参数来指定的因此服务端虽然没有获取第二个客户端发来的连接请求但是在第二个客户端那里显示是连接成功的。 由于单执行流的弊端我们就需要听过多进程或者是多线程来解决。 多进程版TCP网络程序 我们可以将当前的单执行流服务器改为多进程版的服务器当服务端调用accept函数获取到新连接后不是由当前执行流为该连接提供服务而是当前执行流调用fork函数创建子进程然后让子进程为父进程获取到的连接提供服务。 由于父子进程是两个不同的执行流当父进程调用fork创建出子进程后父进程就可以继续从监听套接字当中获取新连接而不用关心获取上来的连接是否服务完毕。 我们知道子进程创建成功以后父子进程共享一个文件描述符表但是父进程创建子进程后由于进程间独立性父子进程之间并不会相互影响所以父进程文件描述符的变化并不会影响子进程就像匿名管道一样父进程先调用pipe函数得到两个文件描述符一个是管道读端的文件描述符一个是管道写端的文件描述符此时父进程创建出来的子进程就会继承这两个文件描述符之后父子进程一个关闭管道的读端另一个关闭管道的写端这时父子进程文件描述符表的变化是不会相互影响的此后父子进程就可以通过这个管道进行单向通信了。 对于套接字文件也是一样的父进程创建的子进程也会继承父进程的套接字文件此时子进程就能够对特定的套接字文件进行读写操作进而完成对对应客户端的服务此时父进程关闭监听套接字返回的文件描述符并不会对子进程产生影响 等待子进程问题 子进程创建成功以后父进程需要等待子进程退出要不就会造成子进程成为僵尸进程而造成内存泄漏 但是此时不论是阻塞等待还是非阻塞等待都会存在一定的问题 如果服务端采用阻塞的方式等待子进程那么服务端还是需要等待服务完当前客户端才能继续获取下一个连接请求此时服务端仍然是以一种串行的方式为客户端提供服务。如果服务端采用非阻塞的方式等待子进程虽然在子进程为客户端提供服务期间服务端可以继续获取新连接但此时服务端就需要将所有子进程的PID保存下来并且需要不断花费时间检测子进程是否退出。 不等待子进程退出方式 让父进程不等待子进程退出常见的方式有两种 捕捉SIGCHLD信号将其处理动作设置为忽略。让父进程创建子进程子进程再创建孙子进程最后让孙子进程为客户端提供服务。 捕捉SIGCHLD信号 当子进程退出时给父进程发送SIGCHLD信号如果父进程将SIGCHLD信号进行捕捉并将该信号的处理动作设置为忽略此时父进程就只需专心处理自己的工作不必关心子进程了。 void start() {signal(SIGCHLD, SIG_IGN); //主动忽略SIGCHLD信号子进程退出自动释放自己僵尸状态while (true){// sleep(1);// 4. 获取连接struct sockaddr_in src;socklen_t len sizeof(src);int serversock accept(listensock, (struct sockaddr *)src, len);if (serversock 0){logMessage(ERROR, accept error:%d:%s, errno, strerror(errno));continue;}// 获取连接成功uint16_t client_port htons(src.sin_port);std::string client_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link success, sereversock:%d | %s | %d, serversock, client_ip.c_str(), client_port);//(2) 多进程版本pid_t id fork();assert(id ! -1);if(id 0){close(listensock);service(serversock, client_ip, client_port);exit(0);}close(serversock);} }创建监控脚本此时对代码进行测试当服务端此时运行起来时此时服务进程只有一个该服务进程就是不断获取新连接的进程而获取到新连接后也是由该进程创建子进程为对应客户端提供服务的。 此时创建客户端我们会发现运行一个客户端此时服务进程就会调用fork函数创建出一个子进程由该子进程为这个客户端提供服务再有一个客户端连接服务器此时服务进程会再创建出一个子进程让该子进程为这个客户端提供服务。 并且此时由于这两个客户端分别由两个不同的执行流提供服务因此这两个客户端可以同时享受到服务它们发送给服务端的数据都能够在服务端输出并且服务端也会对它们的数据进行响应。 当客户端一个个退出后在服务端对应为之提供服务的子进程也会相继退出但无论如何服务端都至少会有一个服务进程这个服务进程的任务就是不断获取新连接。 让孙子进程提供服务 命名说明 爷爷进程在服务端调用accept函数获取客户端连接请求的进程。爸爸进程由爷爷进程调用fork函数创建出来的进程。孙子进程由爸爸进程调用fork函数创建出来的进程该进程调用Service函数为客户端提供服务。 我们让爸爸进程创建完孙子进程后就立即退出此时爷爷进程调用wait/waitpid函数等待爸爸进程就会立即成功就可以继续调用accept函数获取其他客户的连接请求 由于爸爸进程创建完孙子进程后就立马退出此时孙子进程就变成了孤儿进程就会被1号进程领养当孙子进程完成客户端的需求以后就会被系统所回收并不需要爷爷进行的等待 关闭对应的文件描述符 因为创建完子进程以后父子进程独立所以关闭父进程文件描述符并不会影响子进程同样对于子进程来说他也并不关心监听套接字因此也可以将监听套接字关闭掉 对于服务进程来说当它调用fork函数后就必须将从accept函数获取的文件描述符关掉。因为服务进程会不断调用accept函数获取新的文件描述符服务套接字如果服务进程不及时关掉不用的文件描述符最终服务进程中可用的文件描述符就会越来越少。 而对于爸爸进程和孙子进程来说还是建议关闭从服务进程继承下来的监听套接字。实际就算它们不关闭监听套接字最终也只会导致这一个文件描述符泄漏但一般还是建议关上。因为孙子进程在提供服务时可能会对监听套接字进行某种误操作此时就会对监听套接字当中的数据造成影响。 void start() {// signal(SIGCHLD, SIG_IGN); //主动忽略SIGCHLD信号子进程退出自动释放自己僵尸状态while (true){// 4. 获取连接struct sockaddr_in src;socklen_t len sizeof(src);int serversock accept(listensock, (struct sockaddr *)src, len);if (serversock 0){logMessage(ERROR, accept error:%d:%s, errno, strerror(errno));continue;}// 获取连接成功uint16_t client_port htons(src.sin_port);std::string client_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link success, sereversock:%d | %s | %d, serversock, client_ip.c_str(), client_port);// 开始通信//(2.1) 孙子进程提供服务pid_t id fork();assert(id ! -1);if(id 0){close(listensock);if(fork() 0) exit(0);service(serversock, client_ip, client_port);exit(1);}waitpid(id, nullptr, 0);close(serversock);} }创建监控脚本此时创建客户端我们会发现运行一个客户端此时爸爸进程就会调用fork函数创建出一个孙子进程由该孙子进程为这个客户端提供服务再有一个客户端连接服务器此时爸爸进程会再创建出一个孙子进程让该孙子进程为这个客户端提供服务孙子进程的PID为1表示他是孤儿进程被1号进程领养。 并且此时由于这两个客户端分别由两个不同的孤儿进程提供服务因此这两个客户端可以同时享受到服务它们发送给服务端的数据都能够在服务端输出并且服务端也会对它们的数据进行响应。 当客户端一个个退出后在服务端对应为之提供服务的孙子进程也会相继退出但无论如何服务端都至少会有一个服务进程这个服务进程的任务就是不断获取新连接。 多线程版的TCP网络程序 创建进程的成本是很高的创建进程时需要创建该进程对应的进程控制块task_struct、进程地址空间mm_struct、页表等数据结构。而创建线程的成本比创建进程的成本会小得多因为线程本质是在进程地址空间内运行创建出来的线程会共享该进程的大部分资源因此在实现多执行流的服务器时最好采用多线程进行实现。 在调用accept函数以后主线程就会创建一个新线程此时新线程就会为服务端提供服务但是主线程此时依然会等待新线程退出如果主线程退出了也会造成僵尸问题。此时我们就可以调pthreda_deach函数来进行分离线程当线程分离以后主线程退出就不会影响新线程了。 文件描述符是否可以关闭 对于主线程和新线程来说他们是共享一张文件描述符表的对于新线程来说主线程如果关闭其文件描述符新线程也会随之关闭所以文件描述符并不可以被主线程关闭只有当新线程为客户端提供服务完成以后才可以关闭文件描述符。 同样监听套接字也不能新线程关闭因为主线程需要通过监听套接字来获取连接此时如果关闭了就会造成主线程无法获取连接了。 实际新线程在为客户端提供服务时就是调用service函数而调用service函数时是需要传入三个参数的分别是客户端对应的套接字、IP地址和端口号。因此主线程创建新线程时需要给新线程传入三个参数而实际在调用pthread_create函数创建新线程时只能传入一个类型为void*的参数。 这时我们可以设计一个参数结构体ThreadData此时这三个参数就可以放到ThreadData结构体当中当主线程创建新线程时就可以定义一个ThreadData对象将客户端对应的套接字、IP地址和端口号设计进这个ThreadData对象当中然后将ThreadData对象的地址作为新线程执行例程的参数进行传入。 此时新线程在执行例程当中再将这个void类型的参数强转为ThreadData类型然后就能够拿到客户端对应的套接字IP地址和端口号进而调用service函数为对应客户端提供服务。 static void service(int sock, const std::string clientip, const uint16_t clientport) {char buffer[1024];while (true){ssize_t s read(sock, buffer, sizeof(buffer) - 1);if (s 0) // 将发过来的数据当做字符串{buffer[s] 0;std::cout clientip : clientport # buffer std::endl;}else if (s 0){logMessage(NORMAL, %s:%d shutdown, me too!!!, clientip.c_str(), clientport);break;}else{logMessage(errno, read socket error:%d %s, errno, strerror(errno));break;}write(sock, buffer, strlen(buffer));}close(sock); }class ThreadData { public:ThreadData(int sock, std::string ip, uint16_t port) : _sock(sock), _ip(ip), _port(port){}~ThreadData(){}public:int _sock;std::string _ip;uint16_t _port; };class TcpServer { private:const static int gbacklog 20;static void *threadRountine(void *args){pthread_detach(pthread_self());ThreadData *td static_castThreadData *(args);service(td-_sock, td-_ip, td-_port);delete td;return nullptr;}public:TcpServer(uint16_t port, std::string ip ) : _port(port), _ip(ip), listensock(-1){}void Serverinit(){// 1.创建socketlistensock socket(AF_INET, SOCK_STREAM, 0);if (listensock 0){logMessage(FATAL, create socket error:%d:%s, errno, strerror(errno));exit(1);}logMessage(NORMAL, create socket sucess:%d, listensock);// 2. bindstruct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(_port);local.sin_addr.s_addr _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if (bind(listensock, (struct sockaddr *)local, sizeof(local)) 0){logMessage(FATAL, bind error:%d:%s, errno, strerror(errno));exit(2);}// 3. 建立连接if (listen(listensock, gbacklog) 0){logMessage(FATAL, listen error:%d:%s, errno, strerror(errno));exit(3);}logMessage(NORMAL, init server sucess);}void start(){while (true){// sleep(1);// 4. 获取连接struct sockaddr_in src;socklen_t len sizeof(src);int serversock accept(listensock, (struct sockaddr *)src, len);if (serversock 0){logMessage(ERROR, accept error:%d:%s, errno, strerror(errno));continue;}// 获取连接成功uint16_t client_port ntohs(src.sin_port);std::string client_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link success, sereversock:%d | %s | %d, serversock, client_ip.c_str(), client_port);// 开始通信// (3)多线程版本ThreadData *td new ThreadData(serversock, client_ip, client_port);pthread_t tid;pthread_create(tid, nullptr, threadRountine, td);//close(serversock);}~TcpServer(){}private:uint16_t _port;std::string _ip;int listensock; };注意 这儿的threadRountine与service需要设置为静态函数因为类内成员函数都会传递一个this指针此时操作系统就会识别不了。 客户端创建套接字 我们在调用socket函数创建套接字过程与服务端是一样的但是服务端是不需要进行绑定和监听的服务端并不会去获取连接所以也就不需要进行监听。 #include iostream #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include cstring #include cstdiovoid usage(std::string proc) {std::cout \nUsage: proc serverIp serverPort\n std::endl; } int main(int argc, char *argv[]) {if (argc ! 3){usage(argv[1]);exit(1);}uint16_t server_port atoi(argv[2]);std::string server_ip argv[1];//1.创建套接字int sock socket(AF_INET, SOCK_STREAM, 0);if(sock 0){std::cerr socket error std::endl;exit(2);}return 0; }客户端链接服务器 connet 发起连接请求的函数叫做connect该函数的函数原型如下 int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 参数说明 sockfd特定的套接字表示通过该套接字发起连接请求。addr对端网络相关的属性信息包括协议家族、IP地址、端口号等。addrlen传入的addr结构体的长度。 返回值说明 连接或绑定成功返回0连接失败返回-1同时错误码会被设置。 //2.客户端连接服务器 struct sockaddr_in server; memset(server, 0, sizeof(server));server.sin_family AF_INET; server.sin_port htons(server_port); server.sin_addr.s_addr inet_addr(server_ip.c_str());if(connect(sock, (struct sockaddr*)server, sizeof(server)) 0) {std::cerr connect error std::endl;exit(3); }std::cout connect success std::endl;客户端发起请求 我们实现一个简单的回声服务器因此当客户端连接到服务端后客户端就可以向服务端发送数据了这里我们可以让客户端将用户输入的数据发送给服务端发送时调用send函数向套接字当中写入数据即可。 当客户端将数据发送给服务端后由于服务端读取到数据后还会进行回显因此客户端在发送数据后还需要调用recv函数读取服务端的响应数据然后将该响应数据进行打印以确定双方通信无误。 //客户端发起请求 while(true) {std::string message;std::cout 请输入# ;std::getline(std::cin, message);if(message quit) break;send(sock, message.c_str(), message.size(), 0);char buffer[1024];ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0);if(s 0){buffer[s] 0;std::cout server 回显: buffer std::endl;} }此时我们创建监控脚本进行测试运行服务端通过监控可以看到此时只有一个服务线程该服务线程就是主线程它现在在等待客户端的连接到来。 当一个客户端连接到服务端后此时主线程就会为该客户端构建一个参数结构体然后创建一个新线程将该参数结构体的地址作为参数传递给这个新线程此时该新线程就能够从这个参数结构体当中提取出对应的参数然后调用service函数为该客户端提供服务因此在监控当中显示了两个线程,当第二个客户端发来连接请求时主线程会进行相同的操作最终再创建出一个新线程为该客户端提供服务此时服务端当中就有了三个线程。 这两个客户端提供服务的也是两个不同的执行流因此这两个客户端可以同时享受服务端提供的服务它们发送给服务端的消息也都能够在服务端进行打印并且这两个客户端也都能够收到服务端的回显数据。 此时无论有多少个客户端发来连接请求在服务端都会创建出相应数量的新线程为对应客户端提供服务而当客户端一个个退出后为其提供服务的新线程也就会相继退出最终就只剩下最初的主线程仍在等待新连接的到来。 线程池版的TCP网络程序 当前多线程版的服务器存在的问题 每次主线程获取到一个连接时就会随之创建一个新线程来给服务端提供服务服务结束时新线程也就随之销毁此时过程比较繁琐并且效率低下而且如果存在需要大量线程提供服务时随着新线程创建的越来越多CPU的压力就越大因为CPU要不断在这些线程之间来回切换此时CPU在调度线程的时候线程和线程之间切换的成本就会变得很高。此外一旦线程太多每一个线程再次被调度的周期就变长了而线程是为客户端提供服务的线程被调度的周期变长客户端也迟迟得不到应答。 解决方法 此时就可以引入我们的线程池了 可以在服务端预先创建一批线程当有客户端请求连接时就让这些线程为客户端提供服务此时客户端一来就有线程为其提供服务而不是当客户端来了才创建对应的服务线程。当某个线程为客户端提供完服务后不要让该线程退出而是让该线程继续为下一个客户端提供服务如果当前没有客户端连接请求则可以让该线程先进入休眠状态当有客户端连接到来时再将该线程唤醒。服务端创建的这一批线程的数量不能太多此时CPU的压力也就不会太大。此外如果有客户端连接到来但此时这一批线程都在给其他客户端提供服务这时服务端不应该再创建线程而应该让这个新来的连接请求在全连接队列进行排队等服务端这一批线程中有空闲线程后再将该连接请求获取上来并为其提供服务。 threadPool #pragma once#include iostream #include vector #include queue #include unistd.h #include thread.hpp #include lockGuard.hpp #include log.hpp#define NUM 3template class T class ThreadPool { public:pthread_mutex_t *getMutex(){return lock;}bool isEmpty(){return task_queue_.empty();}void waitCond(){pthread_cond_wait(cond, lock);}T getTask(){T t task_queue_.front();task_queue_.pop();return t;}private:ThreadPool(int thread_num NUM) : num_(thread_num){pthread_mutex_init(lock, nullptr);pthread_cond_init(cond, nullptr);for (int i 1; i num_; i){threads_.push_back(new Thread(i, routine, this));}}ThreadPool(const ThreadPoolT other) delete;const ThreadPoolT operator(const ThreadPoolT other) delete;public:static ThreadPoolT *getThreadPool(int num NUM){if (thread_ptr nullptr){LockGuard lockguard(mutex);if (thread_ptr nullptr){thread_ptr new ThreadPoolT(num);}}return thread_ptr;}// 生产void run(){for (auto iter : threads_){iter-start();// std::cout iter-name() 启动成功 std::endl;logMessage(NORMAL, %s %s, iter-name().c_str(), 启动成功);}}static void *routine(void *args){ThreadData *td (ThreadData *)args;ThreadPoolT *tp (ThreadPoolT *)td-args_;while (true){T task;{LockGuard lockguard(tp-getMutex());while (tp-isEmpty())tp-waitCond();task tp-getTask();}// 处理任务task(td-name_);}}void pushTask(const T task){LockGuard lockguard(lock);task_queue_.push(task);pthread_cond_signal(cond);}~ThreadPool(){for (auto iter : threads_){iter-join();delete iter;}pthread_mutex_destroy(lock);pthread_cond_destroy(cond);}private:std::vectorThread * threads_; // 线程组int num_;std::queueT task_queue_; // 任务队列pthread_mutex_t lock; // 互斥锁pthread_cond_t cond; // 条件变量static ThreadPoolT *thread_ptr;static pthread_mutex_t mutex; };template typename T ThreadPoolT *ThreadPoolT::thread_ptr nullptr;template typename T pthread_mutex_t ThreadPoolT::mutex PTHREAD_MUTEX_INITIALIZER;thread.hpp #pragma once#include iostream #include string #include functional #include cstdiotypedef void *(*func_t)(void *);class ThreadData { public:std::string name_;void *args_; };class Thread { public:Thread(int num, func_t callback, void *args) : func_(callback){char nameBuffer[64];snprintf(nameBuffer, sizeof nameBuffer, Thread-%d, num);name_ nameBuffer;tdata_.args_ args;tdata_.name_ name_;}void start(){pthread_create(tid_, nullptr, func_, (void *)tdata_);}void join(){pthread_join(tid_, nullptr);}std::string name(){return name_;}~Thread(){}private:std::string name_; // 线程名int num_; // 线程个数func_t func_; // 回调函数pthread_t tid_; // 线程IDThreadData tdata_; };服务类新增线程池成员 此时我们需要再线程池中新增一个指向线程池的指针成员考虑到线程安全的问题我们可以使用unique_ptr智能指针 #pragma once#include iostream #include cstdio #include string #include sys/types.h #include sys/socket.h #include cerrno #include cstring #include cassert #include arpa/inet.h #include netinet/in.h #include unistd.h #include signal.h #include sys/wait.h #include pthread.h #include memory #include ThreadPool/log.hpp #include ThreadPool/threadPool.hpp #include ThreadPool/Task.hppclass TcpServer { private:const static int gbacklog 20; public:TcpServer(uint16_t port, std::string ip ): _port(port), _ip(ip), listensock(-1), _threadpool_ptr(ThreadPoolTask::getThreadPool()){}void Serverinit(){// 1.创建socketlistensock socket(AF_INET, SOCK_STREAM, 0);if (listensock 0){logMessage(FATAL, create socket error:%d:%s, errno, strerror(errno));exit(1);}logMessage(NORMAL, create socket sucess:%d, listensock);// 2. bindstruct sockaddr_in local;memset(local, 0, sizeof(local));local.sin_family AF_INET;local.sin_port htons(_port);local.sin_addr.s_addr _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());if (bind(listensock, (struct sockaddr *)local, sizeof(local)) 0){logMessage(FATAL, bind error:%d:%s, errno, strerror(errno));exit(2);}// 3. 建立连接if (listen(listensock, gbacklog) 0){logMessage(FATAL, listen error:%d:%s, errno, strerror(errno));exit(3);}logMessage(NORMAL, init server sucess);}void start(){_threadpool_ptr-run();// signal(SIGCHLD, SIG_IGN); //主动忽略SIGCHLD信号子进程退出自动释放自己僵尸状态while (true){// sleep(1);// 4. 获取连接struct sockaddr_in src;socklen_t len sizeof(src);int serversock accept(listensock, (struct sockaddr *)src, len);if (serversock 0){logMessage(ERROR, accept error:%d:%s, errno, strerror(errno));continue;}// 获取连接成功uint16_t client_port ntohs(src.sin_port);std::string client_ip inet_ntoa(src.sin_addr);logMessage(NORMAL, link success, sereversock:%d | %s | %d, serversock, client_ip.c_str(), client_port);// 开始通信// (4)线程池版本Task t(serversock, client_port, client_ip, service);_threadpool_ptr-pushTask(t);}}~TcpServer(){}private:uint16_t _port;std::string _ip;int listensock;std::unique_ptrThreadPoolTask _threadpool_ptr; };Task任务类设计 我们在此是设计一个任务类该任务类当中需要包含客户端对应的套接字、IP地址、端口号表示该任务是为哪一个客户端提供服务对应操作的套接字是哪一个。 此外任务类当中需要包含一个函数方法当线程池中的线程拿到任务后就会直接调用这个函数方法对该任务进行处理而实际处理这个任务的方法就是服务类当中的service函数服务端就是通过调用service函数为客户端提供服务的。 #pragma once#include iostream #include string #include functionalusing tfunc_t std::functionvoid(int, const std::string , uint16_t , const std::string );class Task { public:Task(){}Task(int sock, uint16_t port, std::string ip, tfunc_t func): _sock(sock), _port(port), _ip(ip), _func(func){}void operator()(const std::string name){_func(_sock, _ip, _port, name);}private:int _sock;uint16_t _port;std::string _ip;tfunc_t _func; };假设我们此时需要做的任务是大小写切换我们只需要更改service函数就可以因为我们只需要将service函数当做传入我们构造的Task任务对象中此时就会去回调我们service函数的方法 static void change(int sock, const std::string clientip,const uint16_t clientport, const std::string thread_name) {char buffer[1024];// read write 可以直接被使用ssize_t s read(sock, buffer, sizeof(buffer) - 1);if (s 0){buffer[s] 0; // 将发过来的数据当做字符串std::cout thread_name | clientip : clientport # buffer std::endl;std::string message;char *start buffer;while(*start){char c;if(islower(*start)) c toupper(*start);else c *start;message.push_back(c);start;}write(sock, message.c_str(), message.size());}else if (s 0) // 对端关闭连接{logMessage(NORMAL, %s:%d shutdown, me too!, clientip.c_str(), clientport);}else{ logMessage(ERROR, read socket error, %d:%s, errno, strerror(errno));}close(sock); }客户端代码改写 因为我们此时使用的是线程池线程池中的线程完成客户端传送过来的任务以后并不会退出而是进入休眠状态等待主线程连接以后下一个任务的到来此时我们定义一个alive变量来标记我们新线程的状态连接完成以后改变alive状态即可此时就会进行下一个线程连接 #include iostream #include string #include cstdio #include cstring #include unistd.h #include sys/socket.h #include sys/types.h #include arpa/inet.h #include netinet/in.hvoid usage(std::string proc) {std::cout \nUsage: proc serverIp serverPort\n std::endl; }// ./tcp_client targetIp targetPort int main(int argc, char *argv[]) {if (argc ! 3){usage(argv[0]);exit(1);}std::string serverip argv[1];uint16_t serverport atoi(argv[2]);bool alive false;int sock 0;std::string line;while (true) // TODO{if (!alive){sock socket(AF_INET, SOCK_STREAM, 0);if (sock 0){std::cerr socket error std::endl;exit(2);}struct sockaddr_in server;memset(server, 0, sizeof(server));server.sin_family AF_INET;server.sin_port htons(serverport);server.sin_addr.s_addr inet_addr(serverip.c_str());if (connect(sock, (struct sockaddr *)server, sizeof(server)) 0){std::cerr connect error std::endl;exit(3); // TODO}std::cout connect success std::endl;alive false;}std::cout 请输入# ;std::getline(std::cin, line);if (line quit)break;ssize_t s send(sock, line.c_str(), line.size(), 0);if (s 0){char buffer[1024];ssize_t s recv(sock, buffer, sizeof(buffer) - 1, 0);if (s 0){buffer[s] 0;std::cout server 回显# buffer std::endl;}else if (s 0){alive false;close(sock);}}else{alive false;close(sock);}}return 0; }创建监控脚本此时运行我们服务端会发现此时在服务端就已经有了4个线程其中有一个是接收新连接的服务线程而其余的3个是线程池当中为客户端提供服务的线程我们输入一段小写字母此时服务端接收到请求以后立马做出处理并打印出数据此时我们客户端回显出处理结果并进行新的连接。 当第二个客户端发起连接请求时服务端也会将其封装为一个任务类塞到任务队列然后线程池当中的线程再从任务队列当中获取到该任务进行处理此时也是不同的执行流为这两个客户端提供的服务因此这两个客户端也是能够同时享受服务的当客户端退出以后服务端也随之退出 与之前不同的是无论现在有多少客户端发来请求在服务端都只会有线程池当中的3个线程为之提供服务线程池当中的线程个数不会随着客户端连接的增多而增多这些线程也不会因为客户端的退出而退出。
http://www.zqtcl.cn/news/649665/

相关文章:

  • 官方网站娱乐游戏城自己做网站的好处
  • 查询建设规范的网站1元网站建设精品网站制作
  • 社交网站的优点和缺点个人网页制作软件
  • 做一家算命的网站有没有专门做淘宝客的网站
  • 网站站点管理在哪里建筑施工图设计
  • 众筹网站开发周期网页云原神
  • 哪些网站可以免费做h5东莞制作企业网站
  • 帝国cms 网站地址设置深圳住房和建设部网站
  • 专业网站建设价格最优网页游戏大全电脑版在线玩
  • 建设租车网站wordpress+js插件开发
  • 定制网站开发与模板商务酒店设计网站建设
  • php 网站部署后乱码wordpress禁止调用头部
  • 网站权重低营销型企业网站建站
  • 大港油田建设网站长春市网站优化公司
  • 嘉峪关市建设局建管科资质网站室内设计入门教程
  • 久久建筑网会员登陆中心百度的搜索引擎优化
  • 做网站好还是做程序员好wordpress new图标
  • 秀洲住房与建设局网站徐州建设工程招投标官方网站
  • 做公司网站要注意哪些问题做章的网站
  • 南京建设网站维护洛阳最新通告今天
  • 网站名称创意大全wordpress公开课插件
  • 淮安市城市建设档案馆网站可以做网页的软件
  • 网站空间服务器wordpress 排除置顶文章
  • 有域名后怎么做网站邯郸做移动网站的地方
  • 商标可以做网站吗网站开发的大学生应届简历
  • 长沙长沙网站建设公司saas系统架构
  • 成都销售型网站长春财经学院多大
  • 手机自己制作表白网站app项目网络计划图怎么画
  • 品牌网站如何做seo浏览器正能量网址
  • 开封做网站哪家好网页设计制作网站大一素材