EL | ||
client_fork.cpp | ||
client_poll.cpp | ||
client.cpp | ||
README.md | ||
server.cpp | ||
tcpgetfiles.cpp | ||
tcpputfiles.cpp |
面试专用贴
网络编程TCPdemo
tcpgetfiles tcppullfiles分别为上传和下载模块
demo中使用的框架类函数定义和实现,在EL文件夹中
socket通讯类定义
// socket通讯的客户端类
class ctcpclient
{
private:
int m_connfd; // 客户端的socket.
string m_ip; // 服务端的ip地址。
int m_port; // 服务端通讯的端口。
public:
ctcpclient(): m_connfd(-1),m_port(0) { } // 构造函数。
// 向服务端发起连接请求。
// ip:服务端的ip地址。
// port:服务端通讯的端口。
// 返回值:true-成功;false-失败。
bool connect(const string &ip,const int port);
// 接收对端发送过来的数据。
// buffer:存放接收数据缓冲区。
// ibuflen: 打算接收数据的大小。
// itimeout:等待数据的超时时间(秒):-1-不等待;0-无限等待;>0-等待的秒数。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。
bool read(string &buffer,const int itimeout=0); // 接收文本数据。
bool read(void *buffer,const int ibuflen,const int itimeout=0); // 接收二进制数据。
// 向对端发送数据。
// buffer:待发送数据缓冲区。
// ibuflen:待发送数据的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool write(const string &buffer); // 发送文本数据。
bool write(const void *buffer,const int ibuflen); // 发送二进制数据。
// 断开与服务端的连接
void close();
~ctcpclient(); // 析构函数自动关闭socket,释放资源。
};
// socket通讯的服务端类
class ctcpserver
{
private:
int m_socklen; // 结构体struct sockaddr_in的大小。
struct sockaddr_in m_clientaddr; // 客户端的地址信息。
struct sockaddr_in m_servaddr; // 服务端的地址信息。
int m_listenfd; // 服务端用于监听的socket。
int m_connfd; // 客户端连接上来的socket。
public:
ctcpserver():m_listenfd(-1),m_connfd(-1) {} // 构造函数。
// 服务端初始化。
// port:指定服务端用于监听的端口。
// 返回值:true-成功;false-失败,一般情况下,只要port设置正确,没有被占用,初始化都会成功。
bool initserver(const unsigned int port,const int backlog=5);
// 从已连接队列中获取一个客户端连接,如果已连接队列为空,将阻塞等待。
// 返回值:true-成功的获取了一个客户端连接,false-失败,如果accept失败,可以重新accept。
bool accept();
// 获取客户端的ip地址。
// 返回值:客户端的ip地址,如"192.168.1.100"。
char *getip();
// 接收对端发送过来的数据。
// buffer:存放接收数据的缓冲区。
// ibuflen: 打算接收数据的大小。
// itimeout:等待数据的超时时间(秒):-1-不等待;0-无限等待;>0-等待的秒数。
// 返回值:true-成功;false-失败,失败有两种情况:1)等待超时;2)socket连接已不可用。
bool read(string &buffer,const int itimeout=0); // 接收文本数据。
bool read(void *buffer,const int ibuflen,const int itimeout=0); // 接收二进制数据。
// 向对端发送数据。
// buffer:待发送数据缓冲区。
// ibuflen:待发送数据的大小。
// 返回值:true-成功;false-失败,如果失败,表示socket连接已不可用。
bool write(const string &buffer); // 发送文本数据。
bool write(const void *buffer,const int ibuflen); // 发送二进制数据。
// 关闭监听的socket,即m_listenfd,常用于多进程服务程序的子进程代码中。
void closelisten();
// 关闭客户端的socket,即m_connfd,常用于多进程服务程序的父进程代码中。
void closeclient();
~ctcpserver(); // 析构函数自动关闭socket,释放资源。
};
socket通讯类实现
bool ctcpclient::connect(const string &ip,const int port)
{
// 如果已连接到服务端,则断开,这种处理方法没有特别的原因,不要纠结。
if (m_connfd!=-1) { ::close(m_connfd); m_connfd=-1; }
// 忽略SIGPIPE信号,防止程序异常退出。
// 如果send到一个disconnected socket上,内核就会发出SIGPIPE信号。这个信号
// 的缺省处理方法是终止进程,大多数时候这都不是我们期望的。我们重新定义这
// 个信号的处理方法,大多数情况是直接屏蔽它。
signal(SIGPIPE,SIG_IGN);
m_ip=ip;
m_port=port;
struct hostent* h;
struct sockaddr_in servaddr;
if ( (m_connfd = socket(AF_INET,SOCK_STREAM,0) ) < 0) return false;
if ( !(h = gethostbyname(m_ip.c_str())) )
{
::close(m_connfd); m_connfd=-1; return false;
}
memset(&servaddr,0,sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(m_port); // 指定服务端的通讯端口
memcpy(&servaddr.sin_addr,h->h_addr,h->h_length);
if (::connect(m_connfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
{
::close(m_connfd); m_connfd=-1; return false;
}
return true;
}
void ctcpclient::close()
{
if (m_connfd >= 0) ::close(m_connfd);
m_connfd=-1;
m_port=0;
}
ctcpclient::~ctcpclient()
{
close();
}
bool ctcpserver::initserver(const unsigned int port,const int backlog)
{
// 如果服务端的socket>0,关掉它,这种处理方法没有特别的原因,不要纠结。
if (m_listenfd > 0) { ::close(m_listenfd); m_listenfd=-1; }
if ( (m_listenfd = socket(AF_INET,SOCK_STREAM,0))<=0) return false;
// 忽略SIGPIPE信号,防止程序异常退出。
// 如果往已关闭的socket继续写数据,会产生SIGPIPE信号,它的缺省行为是终止程序,所以要忽略它。
signal(SIGPIPE,SIG_IGN);
// 打开SO_REUSEADDR选项,当服务端连接处于TIME_WAIT状态时可以再次启动服务器,
// 否则bind()可能会不成功,报:Address already in use。
int opt = 1;
setsockopt(m_listenfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
memset(&m_servaddr,0,sizeof(m_servaddr));
m_servaddr.sin_family = AF_INET;
m_servaddr.sin_addr.s_addr = htonl(INADDR_ANY); // 任意ip地址。
m_servaddr.sin_port = htons(port);
if (bind(m_listenfd,(struct sockaddr *)&m_servaddr,sizeof(m_servaddr)) != 0 )
{
closelisten(); return false;
}
if (listen(m_listenfd,backlog) != 0 )
{
closelisten(); return false;
}
return true;
}
bool ctcpserver::accept()
{
if (m_listenfd==-1) return false;
int m_socklen = sizeof(struct sockaddr_in);
if ((m_connfd=::accept(m_listenfd,(struct sockaddr *)&m_clientaddr,(socklen_t*)&m_socklen)) < 0)
return false;
return true;
}
char *ctcpserver::getip()
{
return(inet_ntoa(m_clientaddr.sin_addr));
}
bool ctcpserver::read(void *buffer,const int ibuflen,const int itimeout) // 接收二进制数据。
{
if (m_connfd==-1) return false;
return(tcpread(m_connfd,buffer,ibuflen,itimeout));
}
bool ctcpserver::read(string &buffer,const int itimeout) // 接收文本数据。
{
if (m_connfd==-1) return false;
return(tcpread(m_connfd,buffer,itimeout));
}
bool ctcpclient::read(void *buffer,const int ibuflen,const int itimeout) // 接收二进制数据。
{
if (m_connfd==-1) return false;
return(tcpread(m_connfd,buffer,ibuflen,itimeout));
}
bool ctcpclient::read(string &buffer,const int itimeout) // 接收文本数据。
{
if (m_connfd==-1) return false;
return(tcpread(m_connfd,buffer,itimeout));
}
bool ctcpserver::write(const void *buffer,const int ibuflen) // 发送二进制数据。
{
if (m_connfd==-1) return false;
return(tcpwrite(m_connfd,(char*)buffer,ibuflen));
}
bool ctcpserver::write(const string &buffer)
{
if (m_connfd==-1) return false;
return(tcpwrite(m_connfd,buffer));
}
bool ctcpclient::write(const void *buffer,const int ibuflen)
{
if (m_connfd==-1) return false;
return(tcpwrite(m_connfd,(char*)buffer,ibuflen));
}
bool ctcpclient::write(const string &buffer)
{
if (m_connfd==-1) return false;
return(tcpwrite(m_connfd,buffer));
}
void ctcpserver::closelisten()
{
if (m_listenfd >= 0)
{
::close(m_listenfd); m_listenfd=-1;
}
}
void ctcpserver::closeclient()
{
if (m_connfd >= 0)
{
::close(m_connfd); m_connfd=-1;
}
}
ctcpserver::~ctcpserver()
{
closelisten(); closeclient();
}
bool tcpread(const int sockfd,void *buffer,const int ibuflen,const int itimeout) // 接收二进制数据。
{
if (sockfd==-1) return false;
// 如果itimeout>0,表示需要等待itimeout秒,如果itimeout秒后还没有数据到达,返回false。
if (itimeout>0)
{
struct pollfd fds;
fds.fd=sockfd;
fds.events=POLLIN;
if ( poll(&fds,1,itimeout*1000) <= 0 ) return false;
}
// 如果itimeout==-1,表示不等待,立即判断socket的缓冲区中是否有数据,如果没有,返回false。
if (itimeout==-1)
{
struct pollfd fds;
fds.fd=sockfd;
fds.events=POLLIN;
if ( poll(&fds,1,0) <= 0 ) return false;
}
// 读取报文内容。
if (readn(sockfd,(char*)buffer,ibuflen) == false) return false;
return true;
}
日志类定义
// 日志文件
class clogfile
{
ofstream fout; // 日志文件对象。
string m_filename; // 日志文件名,建议采用绝对路径。
ios::openmode m_mode; // 日志文件的打开模式。
bool m_backup; // 是否自动切换日志。
int m_maxsize; // 当日志文件的大小超过本参数时,自动切换日志。
bool m_enbuffer; // 是否启用文件缓冲区。
spinlock_mutex m_splock; // 自旋锁,用于多线程程序中给写日志的操作加锁。
public:
// 构造函数,日志文件的大小缺省100M。
clogfile(int maxsize=100):m_maxsize(maxsize){}
// 打开日志文件。
// filename:日志文件名,建议采用绝对路径,如果文件名中的目录不存在,就先创建目录。
// openmode:日志文件的打开模式,缺省值是ios::app。
// bbackup:是否自动切换(备份),true-切换,false-不切换,在多进程的服务程序中,如果多个进程共用一个日志文件,bbackup必须为false。
// benbuffer:是否启用文件缓冲机制,true-启用,false-不启用,如果启用缓冲区,那么写进日志文件中的内容不会立即写入文件,缺省是不启用。
// 注意,在多进程的程序中,多个进程往同一日志文件写入大量的日志时,可能会出现小混乱,但是,多线程不会。
// 1)多个进程往同一日志文件写入大量的日志时,可能会出现小混乱,这个问题并不严重,可以容忍;
// 2)只有同时写大量日志时才会出现混乱,在实际开发中,这种情况不多见。
// 3)如果业务无法容忍,可以用信号量加锁。
bool open(const string &filename,const ios::openmode mode=ios::app,const bool bbackup=true,const bool benbuffer=false);
// 把日志内容以文本的方式格式化输出到日志文件,并且,在日志内容前面写入时间。
template< typename... Args >
bool write(const char* fmt, Args... args)
{
if (fout.is_open()==false) return false;
backup(); // 判断是否需要切换日志文件。
m_splock.lock(); // 加锁。
fout << ltime1() << " " << sformat(fmt,args...); // 把当前时间和日志内容写入日志文件。
m_splock.unlock(); // 解锁。
return fout.good();
}
// 重载<<运算符,把日志内容以文本的方式输出到日志文件,不会在日志内容前面写时间。
// 注意:内容换行用\n,不能用endl。
template<typename T>
clogfile& operator<<(const T &value)
{
m_splock.lock();
fout << value;
m_splock.unlock();
return *this;
}
private:
// 如果日志文件的大小超过m_maxsize的值,就把当前的日志文件名改为历史日志文件名,再创建新的当前日志文件。
// 备份后的文件会在日志文件名后加上日期时间,如/tmp/log/filetodb.log.20200101123025。
// 注意,在多进程的程序中,日志文件不可切换,多线的程序中,日志文件可以切换。
bool backup();
public:
void close() { fout.close(); }
~clogfile() { close(); };
};
日志类实现
bool clogfile::open(const string &filename,const ios::openmode mode,const bool bbackup,const bool benbuffer)
{
// 如果日志文件是打开的状态,先关闭它。
if (fout.is_open()) fout.close();
m_filename=filename; // 日志文件名。
m_mode=mode; // 打开模式。
m_backup=bbackup; // 是否自动备份。
m_enbuffer=benbuffer; // 是否启用文件缓冲区。
newdir(m_filename,true); // 如果日志文件的目录不存在,创建它。
fout.open(m_filename,m_mode); // 打开日志文件。
if (m_enbuffer==false) fout << unitbuf; // 是否启用文件缓冲区。
return fout.is_open();
}
bool clogfile::backup()
{
// 不备份
if (m_backup == false) return true;
if (fout.is_open() == false) return false;
// 如果当前日志文件的大小超过m_maxsize,备份日志。
if (fout.tellp() > m_maxsize*1024*1024)
{
m_splock.lock(); // 加锁。
fout.close(); // 关闭当前日志文件。
// 拼接备份日志文件名。
string bak_filename=m_filename+"."+ltime1("yyyymmddhh24miss");
rename(m_filename.c_str(),bak_filename.c_str()); // 把当前日志文件改名为备份日志文件。
fout.open(m_filename,m_mode); // 重新打开当前日志文件。
if (m_enbuffer==false) fout << unitbuf; // 判断是否启动文件缓冲区。
m_splock.unlock(); // 解锁。
return fout.is_open();
}
return true;
}
bool cifile::open(const string &filename,const ios::openmode mode)
{
// 如果文件是打开的状态,先关闭它。
if (fin.is_open()) fin.close();
m_filename=filename;
fin.open(m_filename,mode);
return fin.is_open();
}
xml函数族定义
// 解析xml格式字符串的函数族。
// xml格式的字符串的内容如下:
// <filename>/tmp/_public.h</filename><mtime>2020-01-01 12:20:35</mtime><size>18348</size>
// <filename>/tmp/_public.cpp</filename><mtime>2020-01-01 10:10:15</mtime><size>50945</size>
// xmlbuffer:待解析的xml格式字符串。
// fieldname:字段的标签名。
// value:传入变量的地址,用于存放字段内容,支持bool、int、insigned int、long、unsigned long、double和char[]。
// 注意:当value参数的数据类型为char []时,必须保证value数组的内存足够,否则可能发生内存溢出的问题,也可以用ilen参数限定获取字段内容的长度,ilen的缺省值为0,表示不限长度。
// 返回值:true-成功;如果fieldname参数指定的标签名不存在,返回失败。
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,string &value,const int ilen=0);
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,char *value,const int ilen=0);
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,bool &value);
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,int &value);
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,unsigned int &value);
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,long &value);
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,unsigned long &value);
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,double &value);
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,float &value);
xml函数族实现
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,string &value,const int ilen)
{
string start="<"+fieldname+">"; // 数据项开始的标签。
string end="</"+fieldname+">"; // 数据项结束的标签。
int startp=xmlbuffer.find(start); // 在xml中查找数据项开始的标签的位置。
if (startp==string::npos) return false;
int endp=xmlbuffer.find(end); // 在xml中查找数据项结束的标签的位置。
if (endp==string::npos) return false;
// 从xml中截取数据项的内容。
int itmplen=endp-startp-start.length();
if ( (ilen>0) && (ilen<itmplen) ) itmplen=ilen;
value=xmlbuffer.substr(startp+start.length(),itmplen);
return true;
}
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,char *value,const int len)
{
if (value==nullptr) return false;
if (len>0) memset(value,0,len+1); // 调用者必须保证value的空间足够,否则这里会内存溢出。
string str;
getxmlbuffer(xmlbuffer,fieldname,str);
if ( (str.length()<=(unsigned int)len) || (len==0) )
{
str.copy(value,str.length());
value[str.length()]=0; // string的copy函数不会给C风格字符串的结尾加0。
}
else
{
str.copy(value,len);
value[len]=0;
}
return true;
}
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,bool &value)
{
string str;
if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false;
toupper(str); // 转换为大写来判断(也可以转换为小写,效果相同)。
if (str=="TRUE") value=true;
else value=false;
return true;
}
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,int &value)
{
string str;
if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false;
try
{
value = stoi(picknumber(str,true)); // stoi有异常,需要处理异常。
}
catch(const std::exception& e)
{
return false;
}
return true;
}
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,unsigned int &value)
{
string str;
if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false;
try
{
value = stoi(picknumber(str)); // stoi有异常,需要处理异常。不提取符号 + -
}
catch(const std::exception& e)
{
return false;
}
return true;
}
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,long &value)
{
string str;
if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false;
try
{
value = stol(picknumber(str,true)); // stol有异常,需要处理异常。
}
catch(const std::exception& e)
{
return false;
}
return true;
}
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,unsigned long &value)
{
string str;
if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false;
try
{
value = stoul(picknumber(str)); // stoul有异常,需要处理异常。不提取符号 + -
}
catch(const std::exception& e)
{
return false;
}
return true;
}
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,double &value)
{
string str;
if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false;
try
{
value = stod(picknumber(str,true,true)); // stod有异常,需要处理异常。提取符号和小数点。
}
catch(const std::exception& e)
{
return false;
}
return true;
}
bool getxmlbuffer(const string &xmlbuffer,const string &fieldname,float &value)
{
string str;
if (getxmlbuffer(xmlbuffer,fieldname,str)==false) return false;
try
{
value = stof(picknumber(str,true,true)); // stof有异常,需要处理异常。提取符号和小数点。
}
catch(const std::exception& e)
{
return false;
}
return true;
}