赞
踩
TCP通信阻塞原因:
accept与recv以及send都是阻塞接口,任意一个接口的调用,都有可能会导致服务端流程阻塞
本质原因:当前的服务端,因为不知道什么时候有新连接到来,什么时候那个客户端有数据到来,因此流程只能固定的去调用接口,但是这种调用方式有可能会造成阻塞
解决方案:
多执行流并发处理
为每个客户都创建一个执行流负责与这个客户端进行通信
好处:
1.主线程卡在获取新建连接这里,但是不影响客户端的通信
2. 某个客户端的通信阻塞,也不会影响主线程以及其他线程
在主线程中,获取新建连接,一旦获取到来则创建一个执行流,通过这个新建连接与客户端进行通信
多线程:
普通线程与主线县城数据共享,指定入口函数执行
注意事项:
主线程不能随意释放套接字,因为资源共享,一旦释放其他线程无法使用
tcpsocket.hpp
#include<cstdio> #include<iostream> #include<string> #include<unistd.h> #include<arpa/inet.h> #include<netinet/in.h> #include<sys/socket.h> #define CHECK_RET(q) if((q)==false){return -1;} #define LISTEN_BACKLOG 5 class TcpSocket{ private: int _sockfd; //套接字描述符 public: TcpSocket():_sockfd(-1){} //1.建立套接字 bool Socket(){ //int socket(地址域类型,套接字类型,协议类型) _sockfd=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if(_sockfd<0){ perror("socket error"); return false; } return true; } //2.套接字绑定地址信息 bool Bind(const std::string &ip,const uint16_t port){ sockaddr_in addr; //建立IPv4结构体 addr.sin_family=AF_INET; //设置地址域类型为IPv4 addr.sin_port=htons(port); //主机字节序端口转换为网络字节序端口 addr.sin_addr.s_addr=inet_addr(&ip[0]); //将点分十进制转换为网络字节序 socklen_t len=sizeof(sockaddr_in); //int bind(操作句柄,当前地址信息,地址信息长度) int ret=bind(_sockfd,(sockaddr*)&addr,len); if(ret<0){ perror("bind error"); return false; } return true; } //3.开始监听 //int listen(描述符,同一时间连接数) bool Listen(int backlog=LISTEN_BACKLOG){ int ret=listen(_sockfd,backlog); if(ret<0){ perror("listen error"); return false; } return true; } //4.客户端发送连接请求 bool Connect(const std::string &ip,const uint16_t port){ //int connect(描述符,服务端地址,地址长度) sockaddr_in addr; //创建IPv4结构体 addr.sin_family=AF_INET; //地址域类型为IPv4 addr.sin_port=htons(port); //主机字节序转换为网络字节序 addr.sin_addr.s_addr=inet_addr(&ip[0]); //将点分十进制主机字节序转换为网络字节序 socklen_t len=sizeof(sockaddr_in); int ret=connect(_sockfd,(sockaddr*)&addr,len); if(ret<0){ perror("connect error"); return false; } return true; } //5.服务端获取新连接 bool Accept(TcpSocket *sock,std::string *ip=NULL,uint16_t *port=NULL){ //int accept(监听套接字,客户端地址,长度) sockaddr_in addr; socklen_t len=sizeof(sockaddr_in); int newfd=accept(_sockfd,(sockaddr*)&addr,&len); if(newfd<0){ perror("accept error"); return false; } sock->_sockfd=newfd; if(ip!=NULL){ //新连接成功 *ip=inet_ntoa(addr.sin_addr); } if(port!=NULL){ //新连接成功 *port=ntohs(addr.sin_port); } return true; } //收发数据(tcp通信因为socket包含完整五元组因此不需要指定地址) bool Recv(std::string *buf){ //int recv(描述符,空间,数据长度,标志位) char tmp[4096]={0}; int ret=recv(_sockfd,tmp,4096,0); if(ret<0){ perror("recv error"); return false; }else if(ret==0){ printf("peer shutdown"); return false; } buf->assign(tmp,ret); return true; } bool Send(const std::string &data){ //int send(描述符,数据,长度,标志位) int total=0; while(total<data.size()){ int ret=send(_sockfd,&data[0]+total,data.size()-total,0); if(ret<0){ perror("send error"); return false; } total+=ret; } return true; } //关闭连接 bool Close(){ if(_sockfd!=-1){ close(_sockfd); } return true; } };
thread_srv.cpp
#include"tcpsocket.hpp" #include<pthread.h> //accept和recv以及send都是阻塞接口,任意一个接口的调用,都有可能导致服务器阻塞 //解决方案:多执行流并发操作 //1.多线程:普通线程与主线程数据共享,指定入口函数执行 //2.多进程:子进程复制父进程,但是数据独有 void *thr_entry(void *arg) { bool ret; TcpSocket *clisock=(TcpSocket*)arg; while(1){ //5.收发数据--使用获取的新建套接字进行通信 std::string buf; ret=clisock->Recv(&buf); if(ret==false){ clisock->Close(); delete clisock; return NULL; } std::cout<<"new client say:"<<buf<<std::endl; buf.clear(); std::cout<<"new server say: "; std::cin>>buf; ret=clisock->Send(buf); if(ret==false){ clisock->Close(); delete clisock; return NULL; } } clisock->Close(); delete clisock; return NULL; } int main(int argc,char* argv[]) { //通过程序运行参数指定服务端绑定的地址 // ./tcp_srv 172.17.0.4 9000 if(argc!=3){ printf("usage: ./tcp_srv 172.17.0.4",9000); return -1; } std::string srvip=argv[1]; //服务端IP uint16_t srvport=std::stoi(argv[2]); //服务端端口 TcpSocket lst_sock; //监听套接字 //1.创建套接字 CHECK_RET(lst_sock.Socket()); //2.绑定地址信息 CHECK_RET(lst_sock.Bind(srvip,srvport)); //3.开始监听 CHECK_RET(lst_sock.Listen()); while(1){ //4.获取新建链接 TcpSocket *clisock=new TcpSocket(); std::string cliip; uint16_t cliport; bool ret=lst_sock.Accept(clisock,&cliip,&cliport); if(ret==false){ continue; } std::cout<<"get newconnect:"<<cliip<<"-"<<cliport<<"\n"; //创建线程专门负责与指定客户端的通信 pthread_t tid; pthread_create(&tid,NULL,thr_entry,(void*)clisock); pthread_detach(tid); } //6.关闭套接字 lst_sock.Close(); return 0; }
tcp_cli.cpp
1 #include"tcpsocket.hpp" 2 3 int main(int argc,char *argv[]) 4 { 5 //通过参数传入要连接的服务器的地址信息 6 if(argc!=3){ 7 printf("usage:./tcp_cli srvip srvport\n"); 8 return -1; 9 } 10 std::string srvip=argv[1]; 11 uint16_t srvport=std::stoi(argv[2]); //将字符串转换为十进制 12 13 TcpSocket cli_sock; 14 //1.创建套接字 15 CHECK_RET(cli_sock.Socket()); 16 //2.绑定地址信息(不推荐)_ 17 //3.向服务端发起连接 18 CHECK_RET(cli_sock.Connect(srvip,srvport)); 19 while(1){ 20 //4.接收数据 21 std::string buf; 22 std::cout<<"client say:"; 23 std::cin>>buf; 24 CHECK_RET(cli_sock.Send(buf)); 25 26 buf.clear(); 27 CHECK_RET(cli_sock.Recv(&buf)); 28 std::cout<<"server say:"<<buf<<std::endl; 29 } 30 //5.关闭套接字 31 CHECK_RET(cli_sock.Close()); 32 return 0; 33 }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。