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 }
版权声明:本文为weixin_48273955原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。