RAII封装TCP连接
文件描述符的分配方式:使用当前最小可用的文件描述符。在多线程环境中文件描述符容易出错:若一个线程A持有一个描述符fd,另一个线程B在close(fd)后立即open一个描述符刚好值等于前面的fd,那么线程A拿着fd读写是要出错的,线程不能感知描述符的死活。采用RAII手法封装描述符,对象析构时关闭描述符,只要对象还活着就不会有其它对象和它有一样的描述符。
给出一个简单粗糙的例子:服务端主线程采用epoll监听端口sockfd可写表明有客户连接请求,此时主线程将创建个子线程处理该连接请求,那么该客户连接的生命周期也应该由子线程管理。
服务端程序功能:有客户连接,子线程将客户发送的数据打屏
#include<sys/types.h>#include<sys/socket.h>#include<netinet/in.h>#include<arpa/inet.h>#include<assert.h>#include<stdio.h>#include<unistd.h>#include<errno.h>#include<string.h>#include<fcntl.h>#include<stdlib.h>#include<sys/epoll.h>#include<signal.h>#include<sys/wait.h>#include<sys/mman.h>#include<iostream>using namespace std;class TcpConnection{//客户TCP连接类,采用RAII封装客户连接 public: TcpConnection(int listenfd){//构造函数中就accept一个客户连接 struct sockaddr_in client; socklen_t client_addrlength=sizeof(client); connfd=accept(listenfd,(struct sockaddr*)&client,&client_addrlength); if(connfd<0) cout<<"connect error"<<endl; cout<<"TcpConnection"<<endl; } int get(){ return connfd; } ~TcpConnection(){//析构时关闭TCP连接 close(connfd); cout<<"~TcpConnection"<<endl; } private: int connfd;};int setnonblocking(int fd){//设置描述符非阻塞 int old_option=fcntl(fd,F_GETFL); int new_option=old_option|O_NONBLOCK; fcntl(fd,F_SETFL,new_option); return old_option;}void* worker(void* arg){//子线程管理TCP客户连接 int* listenfd=(int*)arg; TcpConnection one(*listenfd);//accept一个客户连接 int fd=one.get(); char buf[1024]; while(1){ memset(buf,'\0',1024); int ret=recv(fd,buf,1024-1,0); if(ret<=0){ cout<<"shutdown the TcpConnection"<<endl; break; } else{ cout<<buf<<endl;//输出客户发送的内容 } }}void addfd(int epollfd,int fd){//将监听端口加入事件表 epoll_event event; event.data.fd=fd; event.events=EPOLLIN; epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,&event); setnonblocking(fd);}int main(int argc,char* argv[]){ if(argc<=2){ cout<<"argc<=2"<<endl; return 1; } const char* ip=argv[1]; int port=atoi(argv[2]); struct sockaddr_in address; bzero(&address,sizeof(address)); address.sin_family=AF_INET; inet_pton(AF_INET,ip,&address.sin_addr); address.sin_port=htons(port); int sockfd=socket(PF_INET,SOCK_STREAM,0); assert(sockfd>=0); int ret=bind(sockfd,(struct sockaddr*)&address,sizeof(address)); assert(ret!=-1); ret=listen(sockfd,5); int epollfd=epoll_create(5); assert(epollfd!=-1); addfd(epollfd,sockfd); epoll_event events[65535]; pthread_t pid; while(1){ int ret=epoll_wait(epollfd,events,65535,-1); if(ret<0){ cout<<"epoll error"<<endl; break; } for(int i=0;i<ret;i++){ int sock=events[i].data.fd; if(sock==sockfd){//若有客户连接请求,则创建一个子线程管理该客户连接 pthread_create(&pid,NULL,worker,&sock); } else{ cout<<"unknown fd"<<endl; } } }}客户端telnet服务端端口地址,然后发送数据,服务端将数据打屏