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服务端端口地址,然后发送数据,服务端将数据打屏

你可能感兴趣的:(RAII封装TCP连接)