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

中国水电建设招标网站seo外链友情链接

中国水电建设招标网站,seo外链友情链接,怎样快速建设网站模板,俄语淘宝网站建设【Linux网络编程五】Tcp套接字编程(四个版本服务器编写#xff09; [Tcp套接字编程]一.服务器端进程#xff1a;1.创建套接字2.绑定网络信息3.设置监听状态4.获取新连接5.根据新连接进行通信 二.客户端进程#xff1a;1.创建套接字2.连接服务器套接字3.连接成功后进行通信 三… 【Linux网络编程五】Tcp套接字编程(四个版本服务器编写 [Tcp套接字编程]一.服务器端进程1.创建套接字2.绑定网络信息3.设置监听状态4.获取新连接5.根据新连接进行通信 二.客户端进程1.创建套接字2.连接服务器套接字3.连接成功后进行通信 三.version1单进程版四.version2多进程版五.version3多线程版六.version4线程池版七.解决细节问题完善服务器和客户端1.write写入会存在偶发性失败进程被信号杀死2.客户端每次请求都需要重新发起连接3.服务器端出现问题客户端需要尝试自救4.复用ip地址和端口号 [Tcp套接字编程] 一般使用网络套接字编程时都需要引用以上头文件。 一.服务器端进程 我们首先先进行服务器端的编写服务器端肯定是需要知道有自己的ip地址和端口号的。 1.创建套接字 跟Udp套接字编程一样通信前需要创建套接字创建套接字的本质就是打开一个网络文件。 要注意的是Tcp是面向字节流的创建套接字时按照字节流形式创建。 //服务器端启动之前创建套接字绑定。//一开始的这个套接字是属于监听套接字_listensocksocket(AF_INET,SOCK_STREAM,0);if(_listensock0){lg(Fatal,sock create errno:%d errstring:%s,errno,strerror(errno));exit(SockError);}//创建套接字成功lg(Info,sock create sucess listensock:%d,_listensock);2.绑定网络信息 创建完套接字后也就是打开一个网络文件我们需要绑定该服务器的网络信息比如ip地址和端口号等。这样该套接字才能找到服务器端。客户端往该套接字写入时服务器端就能从该套接字里读取到。 服务器往该套接字里写入时连接该套接字的客户端就能接收到。 //创建成功后就要绑定服务器的网络信息struct sockaddr_in local;memset(local,0,sizeof(local));//填充信息local.sin_familyAF_INET;local.sin_porthtons(_port);inet_aton(_ip.c_str(),local.sin_addr);//填充完毕真正绑定if((bind(_listensock,(struct sockaddr*)local,sizeof(local)))0){lg(Fatal,bind errno:%d errstring:%s,errno,strerror(errno));exit(BindError);}lg(Info,bind socket success listensock:%d,_listensock);//绑定成功将字符粗类型转int类型使用inet_aton()调用简单的那个存在线程安全。 3.设置监听状态 Tcp与Udp不同之处在于Tcp是面向连接的什么意思呢就是在真正通信前需要先建立连接才能正式通信。比如Udp在绑定完网络信息后就可以直接进行通信了但Tcp不行Tcp在绑定完网络信息后还需要建立连接。 Tcp相比较Udp比较被动在通信之前需要随时随地地等待别人连接上去才能进行通信。所以服务器要一直处于一个等待到来连接的状态。 而等待到新连接需要一种能力这个能力就是监听只有我们将套接字设置为监听状态该套接字才能获取到新连接。不然获取不到别人的连接。 //udp中绑定成功后就可以进行通信了但tcp与udp不同。tcp是面向连接的在通信之前//需要先获取新连接获取到新连接才能进行通信。没有获取连接那么就要等待连接等待新连接的过程叫做监听监听有没有新连接。//需要将套接字设置成监听状态listen(_listensock,backlog);//用来监听等待新连接只有具备监听状态才能识别到连4.获取新连接 以上都是服务器的一些网络信息初始化步骤接下来才是启动服务器。服务器启动服务呢 首先服务器在通信之前需要获取到客户端的连接。这样才可以和客户端进行通信。那么如何获取到客户端的连接呢 1.前提是该服务器的套接字已经设置为监听状态只有套接字设置为监听状态才可以获取到客户端发起的连接。 2.利用accept函数就可以获取到客户端发起的连接。 3.accept函数和recvfrom函数类似它从套接字里获取到连接不仅仅可以获取到客户端发起的连接还可以获取到客户端的网络信息。而获取到客户端的网络信息是通过两个输出型参数带出来的。 4.注意accept函数的返回值是一个文件描述符。为什么呢也就是accept返回的也是一个套接字文件。accept从一个套接字文件里获取到连接然后再返回套接字文件什么意思呢 【问题】为什么会有两个套接字 accept从我们创建的套接字里获取连接结果又返回一个套接字文件为什么呢这个新的套接字是干啥的 1.也就是曾经被创建的被绑定网络信息的设置为监听状态的套接字是专门用来获取客户端发起的连接的然后将该连接返回给一个新的套接字让服务器通过新的套接字与客户端通信而原来的套接字就又处于监听状态继续来监听客户端发起的新连接。 2.其实就是为了提高通信效率一个套接字既去获取连接又去通信效率比较低而让一个套接字一直处于监听状态获取到新连接后就转让给另一个套接字服务器其实是通过accept返回的套接字与连接端的客户进行通信的。 3.未来获取的连接会越来越多也就是返回的套接字会越来越多而监听的套接字只有一个。 lg(Info,tcpserver is running);while(true){struct sockaddr_in client;socklen_t lensizeof(client);//将套接字设置成监听状态后就可以获取新连接int sockfdaccept(_listensock,(struct sockaddr*)client,len);//获取从监听套接字那里监听到的连接。然后返回一个新套接字通过这个套接字与连接直接通信而监听套接字继续去监听。if(sockfd0){lg(Fatal,accept error,errno: %d, errstring: %s,errno,strerror(errno));exit(AcceptError);}//获取新连接成功//将客户端端网络信息带出来uint16_t clientportntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,client.sin_addr,clientip,sizeof(clientip));}将客户端网络信息带出来注意需要将网络字节序转换成用户字节序。 还有我们的传输的内容会自动转换的。 5.根据新连接进行通信 一旦服务器端获取到新连接成功后就可以与对端的客户通过返回的套接字进行通信。 而Tcp是面向字节流的面向字节流就可以直接使用文件操作因为文件操作也是面向字节流的也就是文件操作里的readwrite就可以往套接字里读取和写入。 1.我们就可以根据获取到的套接字与客户端进行通信。 2.我们可以将该工作分离成一个函数service。与客户端通信需要知道连接的套接字和客户端的网络信息。 void Service(int sockfd,const std::string clientip,uint16_t clientport){char inbuffer[1024];while(true){ssize_t nread(sockfd,inbuffer,sizeof(inbuffer));if(n0){inbuffer[n]0;std::coutclient say# inbufferstd::endl;//加工处理一下std::string echo_stringtcpserver加工处理数据;echo_stringinbuffer;//将加工处理的数据发送会去write(sockfd,echo_string.c_str(),echo_string.size());}else if(n0)//如果没有用户连接了那么就会读到0.服务器端也就不要再读了{lg(Info,%s:%d quit, server close sockfd: %d,clientip.c_str(),clientport,sockfd);break;}else{lg(Fatal,read errno: %d, errstring: %s,errno,strerror(errno));}}}1.当服务器获取到连接后read读取时如果读取成功就会返回读取的个数大小。 2.当服务器获取到连接后read读取之前或者连接又全部断掉那么read就会读取到0。也就是在read的时候没有用户连接了那么服务器就没有必要再从该套接字里读取了。那么服务器读取的工作就退出不再读取了。后续更需要将该套接字关闭起来。因为没有人使用了就需要关闭起来。 3.read读取失败时就会返回小于0. 二.客户端进程 服务器端基本工作已经准备好接下来就是客户端客户端在创建完套接字后需要绑定网络信息吗 1.需要但不需要显示绑定操作系统会帮我们自动绑定。 2.客户端在发起连接时系统就会帮我们自动绑定。 客户端想和服务器连接那么用户肯定是知道该服务器端的套接字信息的不然无法进行通信。所以在用户进行通信之前我们首先将服务器端的网络信息给处理下。 比如将服务器的ip字符粗类型转换成int类型 有一下三个函数可以使用 std::string serveripargv[1];uint16_t serverport std::stoi(argv[2]);struct sockaddr_in server;socklen_t lensizeof(server);server.sin_familyAF_INET;server.sin_porthtons(serverport);inet_pton(AF_INET,serverip.c_str(),server.sin_addr);1.创建套接字 不管是服务器端还是客户端要想网络通信都需要创建套接字根据通信类型创建Tcp套接字还是Udp套接字根据选择。我们这里创建Tcp套接字按照面向字节流创建 //创建套接字int sockfdsocket(AF_INET,SOCK_STREAM,0);if(sockfd0){std::coutcreate sockfd err std::endl;}//创建套接字成功创建完套接字后该干什么?2.连接服务器套接字 创建完套接字后该干什么呢因为用户已经知道服务器端的套接字信息所以我们就根据服务器端的套接字信息来向服务器端发起连接。 因为Tcp是面向连接的所以必须先发起连接对方获取到连接才能进行通信。 //连接服务器端的套接字所以客户端用户需要知道服务器端的网络信息的int nconnect(sockfd,(struct sockaddr*)server,len);if(n0){std::coutconnect sock err...std::endl;exit(2);}//连接成功//连接成功后就可以直接通信了就可以直接给对方写消息了。3.连接成功后进行通信 连接成功后就可以与连接的服务器进行通信了就可以通过套接字进行数据的传输了。 1.往套接字里写入就使用write从套接字里读取就使用read。 2.注意使用完该套接字后就要将套接字关闭。 std::string message;while(true){std::coutPlease enter#;getline(std::cin,message);//往套接字里写write(sockfd,message.c_str(),message.size());char outbuffer[1024];//接收服务器发送的加工处理消息int nread(sockfd,outbuffer,sizeof(outbuffer));if(n0){outbuffer[n]0;std::coutoutbufferstd::endl;}}close(sockfd);三.version1单进程版 Tcpserver.hpp #pragma once #include iostream #include string #include cstring #include cstdlib #include sys/wait.h #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include unistd.h #include pthread.h #include Log.hpp #include TASK.hpp #include ThreadPool.hpp Log lg; const std::string defaultip0.0.0.0; const int defaultfd-1; int backlog10;//一般不要设置太大 enum {SockError2,BindError,AcceptError, }; class Tcpserver { public:Tcpserver(const uint16_t port,const std::string ipdefaultip):_listensock(-1),_port(port),_ip(ip){}void Init(){//服务器端启动之前创建套接字绑定。//一开始的这个套接字是属于监听套接字_listensocksocket(AF_INET,SOCK_STREAM,0);if(_listensock0){lg(Fatal,sock create errno:%d errstring:%s,errno,strerror(errno));exit(SockError);}//创建套接字成功lg(Info,sock create sucess listensock:%d,_listensock);//创建成功后就要绑定服务器的网络信息struct sockaddr_in local;memset(local,0,sizeof(local));//填充信息local.sin_familyAF_INET;local.sin_porthtons(_port);inet_aton(_ip.c_str(),local.sin_addr);//填充完毕真正绑定if((bind(_listensock,(struct sockaddr*)local,sizeof(local)))0){lg(Fatal,bind errno:%d errstring:%s,errno,strerror(errno));exit(BindError);}lg(Info,bind socket success listensock:%d,_listensock);//绑定成功//udp中绑定成功后就可以进行通信了但tcp与udp不同。tcp是面向连接的在通信之前//需要先获取新连接获取到新连接才能进行通信。没有获取连接那么就要等待连接等待新连接的过程叫做监听监听有没有新连接。//需要将套接字设置成监听状态listen(_listensock,backlog);//用来监听等待新连接只有具备监听状态才能识别到连接}void START(){lg(Info,tcpserver is running);while(true){struct sockaddr_in client;socklen_t lensizeof(client);//将套接字设置成监听状态后就可以获取新连接int sockfdaccept(_listensock,(struct sockaddr*)client,len);//获取从监听套接字那里监听到的连接。然后返回一个新套接字通过这个套接字与连接直接通信而监听套接字继续去监听。if(sockfd0){lg(Fatal,accept error,errno: %d, errstring: %s,errno,strerror(errno));exit(AcceptError);}//获取新连接成功//将客户端端网络信息带出来uint16_t clientportntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,client.sin_addr,clientip,sizeof(clientip));//根据新连接进行通信lg(Info,get a new link...sockfd: %d,clientip: %s,clientport: %d,sockfd,clientip,clientport);//-----------version1 单进程版本Service(sockfd,clientip,clientport); close(sockfd);//不用了就关闭}}void Service(int sockfd,const std::string clientip,uint16_t clientport){char inbuffer[1024];while(true){ssize_t nread(sockfd,inbuffer,sizeof(inbuffer));if(n0){inbuffer[n]0;std::coutclient say# inbufferstd::endl;//加工处理一下std::string echo_stringtcpserver加工处理数据;echo_stringinbuffer;//将加工处理的数据发送会去write(sockfd,echo_string.c_str(),echo_string.size());}else if(n0)//如果没有用户连接了那么就会读到0.服务器端也就不要再读了{lg(Info,%s:%d quit, server close sockfd: %d,clientip.c_str(),clientport,sockfd);break;}else{lg(Fatal,read errno: %d, errstring: %s,errno,strerror(errno));}}}~Tcpserver(){}private:int _listensock;//监听套接字只有一个监听套接字用来不断获取新的连接。返回新的套接字std::string _ip;uint16_t _port; };Main.cc #include tcpserver.hpp #include iostream #include memory #include cstdio void Usage(std::string proc) {std::cout\n\rUsage: proc port[1024]\nstd::endl; } //./tcpserver port int main(int args,char*argv[]) {if(args!2){Usage(argv[0]);exit(-1);}//定义一个服务器对象uint16_t portstd::stoi(argv[1]);std::unique_ptrTcpserver tcpsvr(new Tcpserver(port));tcpsvr-Init();tcpsvr-Run(); }【存在问题】上面属于单进程版本只有一个进程在执行只能服务一个客户端其他客户端要想再使用服务器需要等当前客户端使用完服务器退出才能使用。这也太low了。 所以我们想改进为多进程版本。 单进程就是因为当前进程获取到连接后该进程就去服务客户端端了其他客户端就无法再使用该进程了。所以我们可以这样改进让父进程获取新连接然后创建子进程让子进程去提供服务。子进程在服务的过程中父进程就一直在等待新的连接。 四.version2多进程版 【处理文件描述符】 多进程版我们首先需要处理一下文件描述符也就是被打开的套接字。 当获取到新连接后我们的进程就可以创建子进程让子进程去跟客户端进行通信。而父进程继续去等待新连接。 因为创建子进程子进程会继承父进程的文件描述符表。也就是父子进程具有相同的文件描述符表。 1.父进程的主要任务是获取新连接然后将获取到的套接字给子进程使用。所以它是不需要获取到的套接字的所以父进程需要将获取到的新套接字关闭掉。 2.而子进程的主要任务是根据新连接服务客户端它是需要监听套接字的所以需要将监听套接字关闭掉。父子进程相互关闭文件描述符是不会影响对方的。 【父子进程并发执行】 因为单进程中就是因为进程只能在处于要么在监听中要么在服务中不能处于既在监听中又在服务中。所以我们需要让父进程去获取连接子进程去服务客户端。而如何做到呢 父进程是需要等待子进程的如果子进程不退出那么它就要一直在阻塞等待。那么它就无法再去获取新连接了。这就跟单进程一样了。 所以我们需要让父进程非阻塞等待子进程。或者通过这样的方法 让子进程再创建一个子进程当创建成功时就直接退出那么父进程就会等待成功。就会继续往下执行回去新的连接。 而让孙子进程去服务客户端。 Tcpserver.hpp #pragma once #include iostream #include string #include cstring #include cstdlib #include sys/wait.h #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include unistd.h #include pthread.h #include Log.hpp #include TASK.hpp #include ThreadPool.hpp Log lg; const std::string defaultip0.0.0.0; const int defaultfd-1; int backlog10;//一般不要设置太大 enum {SockError2,BindError,AcceptError, };class Tcpserver { public:Tcpserver(const uint16_t port,const std::string ipdefaultip):_listensock(-1),_port(port),_ip(ip){}void Init(){//服务器端启动之前创建套接字绑定。//一开始的这个套接字是属于监听套接字_listensocksocket(AF_INET,SOCK_STREAM,0);if(_listensock0){lg(Fatal,sock create errno:%d errstring:%s,errno,strerror(errno));exit(SockError);}//创建套接字成功lg(Info,sock create sucess listensock:%d,_listensock);//创建成功后就要绑定服务器的网络信息struct sockaddr_in local;memset(local,0,sizeof(local));//填充信息local.sin_familyAF_INET;local.sin_porthtons(_port);inet_aton(_ip.c_str(),local.sin_addr);//填充完毕真正绑定if((bind(_listensock,(struct sockaddr*)local,sizeof(local)))0){lg(Fatal,bind errno:%d errstring:%s,errno,strerror(errno));exit(BindError);}lg(Info,bind socket success listensock:%d,_listensock);//绑定成功//udp中绑定成功后就可以进行通信了但tcp与udp不同。tcp是面向连接的在通信之前//需要先获取新连接获取到新连接才能进行通信。没有获取连接那么就要等待连接等待新连接的过程叫做监听监听有没有新连接。//需要将套接字设置成监听状态listen(_listensock,backlog);//用来监听等待新连接只有具备监听状态才能识别到连}void START(){lg(Info,tcpserver is running);while(true){struct sockaddr_in client;socklen_t lensizeof(client);//将套接字设置成监听状态后就可以获取新连接int sockfdaccept(_listensock,(struct sockaddr*)client,len);//获取从监听套接字那里监听到的连接。然后返回一个新套接字通过这个套接字与连接直接通信而监听套接字继续去监听。if(sockfd0){lg(Fatal,accept error,errno: %d, errstring: %s,errno,strerror(errno));exit(AcceptError);}//获取新连接成功//将客户端端网络信息带出来uint16_t clientportntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,client.sin_addr,clientip,sizeof(clientip));//根据新连接进行通信lg(Info,get a new link...sockfd: %d,clientip: %s,clientport: %d,sockfd,clientip,clientport);//-----------version1 单进程版本//Service(sockfd,clientip,clientport); //close(sockfd);//不用了就关闭//-----------version2 多进程版本pid_t idfork();if(id0)//子进程用来处理服务父进程用来获取新连接{close(_listensock);//子进程不需要该文件关闭if(fork()0)exit(0);//再创建一个子进程然后让该进程退出让孙子进程执行下面的服务子进程就退出父进程就等待成功就会重新获取连接Service(sockfd,clientip,clientport);close(sockfd);exit(0);}//父进程只负责用来获取新连接获取完毕后就交给子进程自己是不用的所以关闭close(sockfd);pid_t ridwaitpid(id,nullptr,0);//阻塞等待(void)rid;}}void Service(int sockfd,const std::string clientip,uint16_t clientport){char inbuffer[1024];while(true){ssize_t nread(sockfd,inbuffer,sizeof(inbuffer));if(n0){inbuffer[n]0;std::coutclient say# inbufferstd::endl;//加工处理一下std::string echo_stringtcpserver加工处理数据;echo_stringinbuffer;//将加工处理的数据发送会去write(sockfd,echo_string.c_str(),echo_string.size());}else if(n0)//如果没有用户连接了那么就会读到0.服务器端也就不要再读了{lg(Info,%s:%d quit, server close sockfd: %d,clientip.c_str(),clientport,sockfd);break;}else{lg(Fatal,read errno: %d, errstring: %s,errno,strerror(errno));}}}~Tcpserver(){}private:int _listensock;//监听套接字只有一个监听套接字用来不断获取新的连接。返回新的套接字std::string _ip;uint16_t _port; };五.version3多线程版 【存在问题】当客户端发起一个连接服务器获取一个连接时服务器才忙着创建子进程呢而创建一个进程的成本是很高的 所以我们想当在服务器端获取到一个新连接后不创建进程而创建线程让子线程去服务客户端。而主线程继续去获取新的连接。 1.所以只要一旦获取到一个新连接我们只要当前进程创建线程将获取的连接的套接字信息以函数参数的形式发送给线程。 2.要让线程去服务连接的客户端线程不仅需要知道客户端的套接字还需要知道是要服务谁也就是客户端的网络信息。 所以我们还需要构建一个线程参数数据结构体包含线程所需要的客户端的套接字网络信息等。 【问题1】主线程需要等待子线程吗 这个问题又回归到刚刚的多进程和单进程中的存在的问题了如果主线程阻塞等待子线程那么主线程就无法再重新获取新的连接了。 所以主线程不等待子线程让子线程自己分离即可。也就是只要获取到一个新连接那么主线程就创建子线程创建完后就不管子线程了就继续回去获取新的连接。 子线程将自己分离后就直接去通信即可。 【问题2】在多线程这里需要处理多余的文件描述符吗 不需要因为线程所拥有的资源都是共享的一旦关闭其他线程就会收到影响。 【问题3】在类内部创建多线程需要将线程函数设置为静态成员函数而静态成员函数又无法使用通信函数Service,怎么办呢 所以我们可以在构造线程数据结构体时将该服务器类对象的指针也构建进去当创建完线程线程就可以根据该类指针找到通信函数Service。 在创建线程之前我们首先将线程所需要的参数数据构建初始化好。然后就可以创建线程让线程去执行通信函数。 #pragma once #include iostream #include string #include cstring #include cstdlib #include sys/wait.h #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include unistd.h #include pthread.h #include Log.hpp #include TASK.hpp #include ThreadPool.hpp Log lg; const std::string defaultip0.0.0.0; const int defaultfd-1; int backlog10;//一般不要设置太大 enum {SockError2,BindError,AcceptError, }; class Tcpserver;//构建线程所需要的参数数据获取连接的套接字客户端端的网络信息服务器类的指针 class ThreadData { public:ThreadData(int fd,const std::string ip,uint16_t port,Tcpserver* svr):_sockfd(fd),_ip(ip),_port(port),_svr(svr){} public: int _sockfd;std::string _ip;uint16_t _port;Tcpserver* _svr;//通过该类指针可以找到类内部函数 };class Tcpserver { public:Tcpserver(const uint16_t port,const std::string ipdefaultip):_listensock(-1),_port(port),_ip(ip){}void Init(){//服务器端启动之前创建套接字绑定。//一开始的这个套接字是属于监听套接字_listensocksocket(AF_INET,SOCK_STREAM,0);if(_listensock0){lg(Fatal,sock create errno:%d errstring:%s,errno,strerror(errno));exit(SockError);}//创建套接字成功lg(Info,sock create sucess listensock:%d,_listensock);//创建成功后就要绑定服务器的网络信息struct sockaddr_in local;memset(local,0,sizeof(local));//填充信息local.sin_familyAF_INET;local.sin_porthtons(_port);inet_aton(_ip.c_str(),local.sin_addr);//填充完毕真正绑定if((bind(_listensock,(struct sockaddr*)local,sizeof(local)))0){lg(Fatal,bind errno:%d errstring:%s,errno,strerror(errno));exit(BindError);}lg(Info,bind socket success listensock:%d,_listensock);//绑定成功//udp中绑定成功后就可以进行通信了但tcp与udp不同。tcp是面向连接的在通信之前//需要先获取新连接获取到新连接才能进行通信。没有获取连接那么就要等待连接等待新连接的过程叫做监听监听有没有新连接。//需要将套接字设置成监听状态listen(_listensock,backlog);//用来监听等待新连接只有具备监听状态才能识别到连接}static void* Routine(void *args)//静态成员函数无法使用成员函数再封装一个服务器对象{//子线程要和主线程分离主线程不需要等待子线程直接回去重新获取新连接pthread_detach(pthread_self());ThreadData* tdstatic_castThreadData*(args);//子线程用来服务客户端td-_svr-Service(td-_sockfd,td-_ip,td-_port);delete td;return nullptr;}void Run(){//一启动服务器就将线程池中的线程创建ThreadPoolTASK::GetInstance()-Start();//单例对象//静态函数通过类域就可以使用lg(Info,tcpserver is running);while(true){struct sockaddr_in client;socklen_t lensizeof(client);//将套接字设置成监听状态后就可以获取新连接int sockfdaccept(_listensock,(struct sockaddr*)client,len);//获取从监听套接字那里监听到的连接。然后返回一个新套接字通过这个套接字与连接直接通信而监听套接字继续去监听。if(sockfd0){lg(Fatal,accept error,errno: %d, errstring: %s,errno,strerror(errno));exit(AcceptError);}//获取新连接成功//将客户端端网络信息带出来uint16_t clientportntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,client.sin_addr,clientip,sizeof(clientip));//根据新连接进行通信lg(Info,get a new link...sockfd: %d,clientip: %s,clientport: %d,sockfd,clientip,clientport);//-----------version3 多线程版本ThreadData *tdnew ThreadData(sockfd,clientip,clientport,this);//首先将线程所需要的参数数据初始化好pthread_t tid;pthread_create(tid,nullptr,Routine,td);//Routine要设置成静态成员函数}}void Service(int sockfd,const std::string clientip,uint16_t clientport){char inbuffer[1024];while(true){ssize_t nread(sockfd,inbuffer,sizeof(inbuffer));if(n0){inbuffer[n]0;std::coutclient say# inbufferstd::endl;//加工处理一下std::string echo_stringtcpserver加工处理数据;echo_stringinbuffer;//将加工处理的数据发送会去write(sockfd,echo_string.c_str(),echo_string.size());}else if(n0)//如果没有用户连接了那么就会读到0.服务器端也就不要再读了{lg(Info,%s:%d quit, server close sockfd: %d,clientip.c_str(),clientport,sockfd);break;}else{lg(Fatal,read errno: %d, errstring: %s,errno,strerror(errno));}}}~Tcpserver(){}private:int _listensock;//监听套接字只有一个监听套接字用来不断获取新的连接。返回新的套接字std::string _ip;uint16_t _port; };六.version4线程池版 【服务器端构建线程池 【存在问题】虽然该多线程版比多进程版要好些成本要低一些。但还是存在问题 1.客户端发起连接服务器端获取到连接成功后线程才被创建处理而线程创建也是需要时间的所以我们想减少这个时间----我们可以通过线程池来完善。线程池中的线程都是早就被创建好的。只要获取到新连接就将对应的任务投入到线程池里让线程池里的线程去执行。 2.每来一个用户就会发起一个连接就要创建一个线程 如果客户端不退出那么线程就一直存在。那么就会存在很多线程的情况。这主要原因是我们的服务器的服务是长时间服务服务器端是主动退出的所以客户端不退的话线程就无法退出。------所以我们想让服务器的服务变成短时间服务服务完就立马关闭套接字不再服务。线程就会退出。 所以服务器端只要获取到一个新连接就将服务客户端的操作让线程池里的线程去执行。 不过线程池里的线程要执行必须是以任务的形式竞争所以我们需要将服务客户端的动作封装成一个任务然后将任务投递到线程池里线程池里线程就会被唤醒去竞争任务。 线程池---被封装成单例模式 #pragma once#include iostream #include vector #include string #include queue #include pthread.h #include unistd.hstruct ThreadInfo {pthread_t tid;std::string name; };static const int defalutnum 10;template class T class ThreadPool { public:void Lock(){pthread_mutex_lock(mutex_);}void Unlock(){pthread_mutex_unlock(mutex_);}void Wakeup(){pthread_cond_signal(cond_);}void ThreadSleep(){pthread_cond_wait(cond_, mutex_);}bool IsQueueEmpty(){return tasks_.empty();}std::string GetThreadName(pthread_t tid){for (const auto ti : threads_){if (ti.tid tid)return ti.name;}return None;}public:static void *HandlerTask(void *args){ThreadPoolT *tp static_castThreadPoolT *(args);std::string name tp-GetThreadName(pthread_self());while (true){tp-Lock();while (tp-IsQueueEmpty()){tp-ThreadSleep();}T t tp-Pop();tp-Unlock();t();}}void Start(){int num threads_.size();for (int i 0; i num; i){threads_[i].name thread- std::to_string(i 1);pthread_create((threads_[i].tid), nullptr, HandlerTask, this);}}T Pop(){T t tasks_.front();tasks_.pop();return t;}void Push(const T t){Lock();tasks_.push(t);Wakeup();Unlock();}static ThreadPoolT *GetInstance(){if (nullptr tp_) // ???{pthread_mutex_lock(lock_);if (nullptr tp_){std::cout log: singleton create done first! std::endl;tp_ new ThreadPoolT();}pthread_mutex_unlock(lock_);}return tp_;}private:ThreadPool(int num defalutnum) : threads_(num){pthread_mutex_init(mutex_, nullptr);pthread_cond_init(cond_, nullptr);}~ThreadPool(){pthread_mutex_destroy(mutex_);pthread_cond_destroy(cond_);}ThreadPool(const ThreadPoolT ) delete;const ThreadPoolT operator(const ThreadPoolT ) delete; // abc private:std::vectorThreadInfo threads_;std::queueT tasks_;pthread_mutex_t mutex_;pthread_cond_t cond_;static ThreadPoolT *tp_;static pthread_mutex_t lock_; };template class T ThreadPoolT *ThreadPoolT::tp_ nullptr;template class T pthread_mutex_t ThreadPoolT::lock_ PTHREAD_MUTEX_INITIALIZER;构建任务准备投递到线程池里的任务队列里 #pragma once #include Log.hpp #include iostream #include Init.hpp Init init;//刚加载程序时文件里的内容就加载到map里了。 extern Log lg; class TASK//构建任务就是一旦获取到连接后就将客户端的网络信息存到任务里让服务器根据这个信息去服务客户端 {public: TASK(int sockfd,const std::string ip,uint16_t port):_sockfd(sockfd),_clientip(ip),_clientport(port){}void run(){char inbuffer[1024];// while(true)ssize_t nread(_sockfd,inbuffer,sizeof(inbuffer));if(n0){inbuffer[n]0;std::coutclient key inbufferstd::endl;//加工处理一下std::string echo_stringinit.translation(inbuffer);//将加工处理的数据发送会去nwrite(_sockfd,echo_string.c_str(),echo_string.size());//写入过程也可能会失败操作系统会发送信号将该进程杀死的我们不想被杀死就要忽略这个信息if(n0){//失败了我们将信号忽略但将日志打印出来lg(Fatal,write error :%d ,errstring :%s,errno,strerror(errno));}}else if(n0)//如果没有用户连接了那么就会读到0.服务器端也就不要再读了{lg(Info,%s:%d quit, server close sockfd: %d,_clientip.c_str(),_clientport,_sockfd);}else{lg(Fatal,read errno: %d, errstring: %s,errno,strerror(errno));}}void operator()(){run();}~TASK(){} public:int _sockfd;std::string _clientip;uint16_t _clientport; };线程池版本 #pragma once #include iostream #include string #include cstring #include cstdlib #include sys/wait.h #include sys/types.h #include sys/socket.h #include arpa/inet.h #include netinet/in.h #include unistd.h #include pthread.h #include Log.hpp #include TASK.hpp #include ThreadPool.hpp Log lg;const std::string defaultip0.0.0.0; const int defaultfd-1; int backlog10;//一般不要设置太大 enum {SockError2,BindError,AcceptError, }; class Tcpserver;class ThreadData { public:ThreadData(int fd,const std::string ip,uint16_t port,Tcpserver* svr):_sockfd(fd),_ip(ip),_port(port),_svr(svr){} public: int _sockfd;std::string _ip;uint16_t _port;Tcpserver* _svr; }; class Tcpserver { public:Tcpserver(const uint16_t port,const std::string ipdefaultip):_listensock(-1),_port(port),_ip(ip){}void Init(){//服务器端启动之前创建套接字绑定。//一开始的这个套接字是属于监听套接字_listensocksocket(AF_INET,SOCK_STREAM,0);if(_listensock0){lg(Fatal,sock create errno:%d errstring:%s,errno,strerror(errno));exit(SockError);}//创建套接字成功lg(Info,sock create sucess listensock:%d,_listensock);//创建成功后就要绑定服务器的网络信息struct sockaddr_in local;memset(local,0,sizeof(local));//填充信息local.sin_familyAF_INET;local.sin_porthtons(_port);inet_aton(_ip.c_str(),local.sin_addr);//填充完毕真正绑定if((bind(_listensock,(struct sockaddr*)local,sizeof(local)))0){lg(Fatal,bind errno:%d errstring:%s,errno,strerror(errno));exit(BindError);}lg(Info,bind socket success listensock:%d,_listensock);//绑定成功//udp中绑定成功后就可以进行通信了但tcp与udp不同。tcp是面向连接的在通信之前//需要先获取新连接获取到新连接才能进行通信。没有获取连接那么就要等待连接等待新连接的过程叫做监听监听有没有新连接。//需要将套接字设置成监听状态listen(_listensock,backlog);//用来监听等待新连接只有具备监听状态才能识别到连接}static void* Routine(void *args)//静态成员函数无法使用成员函数再封装一个服务器对象{//子线程要和主线程分离主线程不需要等待子线程直接回去重新获取新连接pthread_detach(pthread_self());ThreadData* tdstatic_castThreadData*(args);//子线程用来服务客户端td-_svr-Service(td-_sockfd,td-_ip,td-_port);delete td;return nullptr;}void Run(){//一启动服务器就将线程池中的线程创建ThreadPoolTASK::GetInstance()-Start();//单例对象//静态函数通过类域就可以使用lg(Info,tcpserver is running);while(true){struct sockaddr_in client;socklen_t lensizeof(client);//将套接字设置成监听状态后就可以获取新连接int sockfdaccept(_listensock,(struct sockaddr*)client,len);//获取从监听套接字那里监听到的连接。然后返回一个新套接字通过这个套接字与连接直接通信而监听套接字继续去监听。if(sockfd0){lg(Fatal,accept error,errno: %d, errstring: %s,errno,strerror(errno));exit(AcceptError);}//获取新连接成功//将客户端端网络信息带出来uint16_t clientportntohs(client.sin_port);char clientip[32];inet_ntop(AF_INET,client.sin_addr,clientip,sizeof(clientip));//根据新连接进行通信lg(Info,get a new link...sockfd: %d,clientip: %s,clientport: %d,sockfd,clientip,clientport);//----------version4 线程池版本//当获取到新连接时就构建任务TASK t(sockfd,clientip,clientport); //将任务放进线程池里,线程就会到线程池里去执行任务。ThreadPoolTASK::GetInstance()-Push(t);}}void Service(int sockfd,const std::string clientip,uint16_t clientport){char inbuffer[1024];while(true){ssize_t nread(sockfd,inbuffer,sizeof(inbuffer));if(n0){inbuffer[n]0;std::coutclient say# inbufferstd::endl;//加工处理一下std::string echo_stringtcpserver加工处理数据;echo_stringinbuffer;//将加工处理的数据发送会去write(sockfd,echo_string.c_str(),echo_string.size());}else if(n0)//如果没有用户连接了那么就会读到0.服务器端也就不要再读了{lg(Info,%s:%d quit, server close sockfd: %d,clientip.c_str(),clientport,sockfd);break;}else{lg(Fatal,read errno: %d, errstring: %s,errno,strerror(errno));}}}~Tcpserver(){}private:int _listensock;//监听套接字只有一个监听套接字用来不断获取新的连接。返回新的套接字std::string _ip;uint16_t _port; };【服务器端构建短服务-----翻译字典】 服务器端的服务功能是交给线程池里的线程执行的所以最好是短服务如果是长服务的话那么就会存在很多线程不退出客户端不主动退出线程就不退出的场景。我们想要让服务器只服务一次服务完后就将对应的服务的套接字关闭不再服务。那么线程服务完一次就结束了。 所以我们可以设计一个翻译字典的功能让客户端用户发送字母服务器端给客户端翻译字母再将字母的中文发送回去。服务器端服务完一次后就将监听套接字获取到的新的套接字关闭不再服务。 如果客户端想要再次请求服务就需要重新发起连接让服务器端的监听套接字重新获取到新的套接字。因为原来获取到的套接字已经被关闭。 翻译字典功能 #pragma once #include iostream #include string #include fstream #include unordered_map #include Log.hpp const std::string dictname./dict.txt; const std::string sep:;//默认的分割符 extern Log lg; class Init//相当于加载配置文件的动作当创建出来文件内容就已经被加载到map里了 {public:void Spilt(std::string line,std::string *part1,std::string* part2){auto posline.find(sep);if(posstd::string::npos)return ;*part1line.substr(0,pos);*part2line.substr(pos1);return ;}Init(){std::ifstream in(dictname);//定义一个文件流成功就打开该文件if(!in.is_open()){lg(Fatal,ifstream open %s error,strerror(errno));exit(1);} //读取文件里的内容std::string line;//按行读取。读取到line里while(std::getline(in,line)){std::string part1,part2;Spilt(line,part1,part2);//然后分割到map里dic.insert({part1,part2});}in.close();}std::string translation(const std::string key){auto iterdic.find(key);//返回对应的该值的的迭代器if(iterdic.end())return Unkonwn;else return iter-second;} private:std::unordered_mapstd::string,std::string dic; };dict。txt对应的配置文件 apple:苹果 yellow:黄色 red:红色 bule:蓝色 man:男人 woman:女人 hello:你好让服务器端的翻译字典只服务服务一次就将对应的套接字关闭 #pragma once #include Log.hpp #include iostream #include Init.hpp Init init;//刚加载程序时文件里的内容就加载到map里了。 extern Log lg; class TASK//构建任务就是一旦获取到连接后就将客户端的网络信息存到任务里让服务器根据这个信息去服务客户端 {public: TASK(int sockfd,const std::string ip,uint16_t port):_sockfd(sockfd),_clientip(ip),_clientport(port){}void run(){char inbuffer[1024];// while(true)ssize_t nread(_sockfd,inbuffer,sizeof(inbuffer));if(n0){inbuffer[n]0;std::coutclient key inbufferstd::endl;//加工处理一下std::string echo_stringinit.translation(inbuffer);//将加工处理的数据发送会去nwrite(_sockfd,echo_string.c_str(),echo_string.size());//写入过程也可能会失败操作系统会发送信号将该进程杀死的我们不想被杀死就要忽略这个信息if(n0){//失败了我们将信号忽略但将日志打印出来lg(Fatal,write error :%d ,errstring :%s,errno,strerror(errno));}}else if(n0)//如果没有用户连接了那么就会读到0.服务器端也就不要再读了{lg(Info,%s:%d quit, server close sockfd: %d,_clientip.c_str(),_clientport,_sockfd);}else{lg(Fatal,read errno: %d, errstring: %s,errno,strerror(errno));}close(_sockfd);//任务只处理一次服务器端处理完任务后就会将该套接字关闭线程池里的线程就不会一直在执行//客户端如果想再使用服务需要重新连接而该套接字已经被关闭客户端也需要重新创建}void operator()(){run();}~TASK(){} public:int _sockfd;std::string _clientip;uint16_t _clientport; };七.解决细节问题完善服务器和客户端 1.write写入会存在偶发性失败进程被信号杀死 write写入功能也是会存在失败的这种失败是偶发性的比较小但也是会发生。比如说服务器端刚接收到客户端发送的消息正在处理的过程中客户端又将连接关闭那么服务器向往这个套接字里写入数据时就会写入失败。 而这种失败就会类似于管道里的读端关闭写端再往里写时系统就会觉得没有用就会发送信号将该进程杀死。所以这里如果出现了写入失败也会被操作系统杀死的。而我们不希望服务器端被杀死。因为只是出现了偶发性的写入失败。 所以我们当出现写入失败时我们应该忽略操作系统发送的信号。防止被杀死。 所以服务器端不仅要对读取做处理还要对写入做处理。 2.客户端每次请求都需要重新发起连接 因为服务器端当监听套接字获取到新连接时就会返回一个新的套接字给线程使用去服务客户端。而线程只服务一次就会将该套接字关闭。关闭完后对端的客户端就无zsxxxxxxxxxx法再使用服务了。虽然它对应的连接还在但是服务器端的连接被关闭了。 所以客户端端再次发送请求时服务器就接收不到。 【解决方案】 所以客户端在被服务完后想要再次获取服务就必须要重新发起连接这样服务器端才可以获取到对应的连接。并且还需要重新创建套接字然后再发起连接。因为原来的套接字已经被服务器关闭不能再使用了。 所以每次客户端想要服务时都必须重新创建套接字然后发起连接。 #include iostream #include cstdlib #include unistd.h #include strings.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include unistd.hvoid Usage(std::string proc) {std::cout\n\rUsage: proc port[1024]\nstd::endl; } //./tcpclient ip port int main(int args,char* argv[]) {if(args!3){Usage(argv[0]);exit(1);}std::string serveripargv[1];uint16_t serverport std::stoi(argv[2]);struct sockaddr_in server;socklen_t lensizeof(server);server.sin_familyAF_INET;server.sin_porthtons(serverport);inet_pton(AF_INET,serverip.c_str(),server.sin_addr);while(true){//创建套接字int sockfdsocket(AF_INET,SOCK_STREAM,0);if(sockfd0){std::coutcreate sockfd err std::endl;}//创建套接字成功创建完套接字后该干什么?//连接服务器端的套接字所以客户端用户需要知道服务器端的网络信息的connect(sockfd,(struct sockaddr*)server,len);std::string message;std::coutPlease enter#;getline(std::cin,message);//往套接字里写int nwrite(sockfd,message.c_str(),message.size());if(n0)//服务器端会将该套接字关闭然后就写不进去了。需要重新创建套接字连接{std::cerrwrite error...std::endl;continue;}char outbuffer[1024];//接收服务器发送的加工处理消息nread(sockfd,outbuffer,sizeof(outbuffer));if(n0){outbuffer[n]0;std::coutoutbufferstd::endl;}close(sockfd);}return 0;}3.服务器端出现问题客户端需要尝试自救 所以当服务器出现问题时比如挂掉了。客户端首先要尝试重新连接看能不能恢复正常。因为服务器端出现问题那么客户端的写入和读取就可能会出现问题甚至连接部分会出现问题这些问题一旦出现就要尝试重新连接。多次连接如果服务器又恢复连接了那么就可以重新连接成功。 否则还是失败失败了就重连接失败用户就退出吧不玩了。 1.只是连接部分或者写入读取部分出现错误客户端创建的套接字并没有问题所以不需要重新创建。只需要循环连接部分即可。 2.重新连接模块是客户端应该具备的功能。 #include iostream #include cstdlib #include unistd.h #include strings.h #include sys/types.h #include sys/socket.h #include netinet/in.h #include arpa/inet.h #include unistd.hvoid Usage(std::string proc) {std::cout\n\rUsage: proc port[1024]\nstd::endl; } //./tcpclient ip port int main(int args,char* argv[]) {if(args!3){Usage(argv[0]);exit(1);}std::string serveripargv[1];uint16_t serverport std::stoi(argv[2]);struct sockaddr_in server;socklen_t lensizeof(server);server.sin_familyAF_INET;server.sin_porthtons(serverport);inet_pton(AF_INET,serverip.c_str(),server.sin_addr);while(true){//创建套接字int sockfdsocket(AF_INET,SOCK_STREAM,0);if(sockfd0){std::coutcreate sockfd err std::endl;}//创建套接字成功创建完套接字后该干什么?//连接服务器端的套接字所以客户端用户需要知道服务器端的网络信息的int cnt10;bool isreconnectfalse;do{ int nconnect(sockfd,(struct sockaddr*)server,len);if(n0)//服务器关闭了肯定会连接失败{isreconnecttrue;cnt--;std::coutconnect sock err...,cnt: cntstd::endl;sleep(12);}else//重连成功了{break;}}while(cntisreconnect);//连接成功//连接成功后就可以直接通信了就可以直接给对方写消息了。if(cnt0){std::cerruser offline..std::endl;break;//用户直接不玩了}std::string message;std::coutPlease enter#;getline(std::cin,message);//往套接字里写int nwrite(sockfd,message.c_str(),message.size());if(n0)//服务器端会将该套接字关闭然后就写不进去了。需要重新创建套接字连接{std::cerrwrite error...std::endl;continue;}char outbuffer[1024];//接收服务器发送的加工处理消息nread(sockfd,outbuffer,sizeof(outbuffer));if(n0){outbuffer[n]0;std::coutoutbufferstd::endl;}close(sockfd);}return 0;}4.复用ip地址和端口号 将套接字设置成允许复用ip地址和端口号。
http://www.pierceye.com/news/499991/

相关文章:

  • 如何用社交网站开发客户学网络营销有前途吗
  • 做瞹瞹瞹视频免费网站wordpress不提示系统更新
  • 加气站类型的网站建设重庆工程招投标交易信息网
  • 如何做分类网站信息营销产品展示网站模板
  • 做网站用哪种编程语言网店营销策略
  • 已经注册了域名怎么做简单的网站知乎问答网站开发教程
  • html5做手机网站网页设计如何制作背景
  • vs2010网站开发示例wordpress apply filters
  • 产品网站建设建议定制软件的网站
  • 极速彩票网站建设创造网站需要多少钱
  • 不要域名能建网站么工作室建设规划
  • html网站首页图片切换一元购物网站怎么做
  • 焦作网站建设费用wordpress php最大输出变量
  • 移动端高端网站开发做私活的网站
  • 广东专业做网站浙江建设工程信息网高工评选
  • 当阳网站建设电话时尚类网站建设
  • 南平网站建设公司seo中文含义是什么
  • 重庆科技建设信息网站关键词做网站标题是什么意思
  • 潍坊中企动力做的网站怎么样哪个网站做黄金交易最好
  • 徐州金网网站建设西安网站建设制作公司
  • 中小企业网站建设好么做国外网站衣服码数要怎么写
  • 新浪做网站wordpress divi 主题
  • 微网站建设资讯网页游戏开发教程
  • 网站建设评估百度公司地址
  • python 做网站开发吗搜房网网站跳出率
  • 深圳企业模板网站建设做高性能的网站 哪门语言好
  • 网站后台不能上传微信公众平台网页版登陆
  • 广州网站营销seo费用成都建设网站那家好
  • 网站建设规划书结构简单wordpress
  • 域名注册网站哪个好山东淄博网络科技有限公司