1. 程式人生 > >linux下socket程式設計和epoll的使用

linux下socket程式設計和epoll的使用

    這兩天在學Linux下的網路程式設計,於是便看了些關於socket和epoll的資料。
    首先介紹socket,socket程式設計我之前也接觸過,不過是在windows下接觸的。和windows不同的是,windows下關於socket程式設計,是直接給你一個socket的類,在上面建立自己的例項。而在linux中,你在建立socket時,它會給你一個檔案描述符(其實就是一個整數),這個整數和核心為你建立的socket相聯絡,這個整數其實就代表著建立的socket(在網上查到的是說,linux下一切皆檔案,socket其實也就是一種特殊的檔案,而檔案用檔案描述符來標記)。接著就是將這個檔案描述符(以下以sockfd代替)用bind函式與地址繫結(之後詳細解釋),如果是監聽socket就開始listen,如果是連線socket就與server連線。其實感覺無論在windows上還是在linux上,socket都是這麼使用的,下面講解下在這個過程中使用的函式。
    首先是使用socket函式,原型如下:
int socket(int domain, int type, int protocol);
    函式返回一個整型值,就是所建立的socket的檔案描述符。當返回值為-1是,說明建立socket失敗。第一個引數domain指定,它用於確定所建立的socket的通訊域,例如AF_INET就是ipv4,第二個引數type,指定建立的socket的型別,它定義了通訊的語義,例如SOCK_STREAM提供順序,可靠,雙向,基於連線的位元組流。最後一個引數protocol指定與套接字相匹配的協議,通常,只有單個協議存在以支援給定協議族內的特定套接字型別,在這種情況下協議可以被指定為0。但是可能存在多個協議和套接字匹配,此時就要手動指定協議。(三個引數的具體取值查詢man)。
    在建立了socket後,就是使用bind函式將其與地址繫結在一起。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    當繫結成功後,函式返回0,失敗時返回-1,此時可查詢errno來確定錯誤原因。此函式有三個引數,第一個引數就是你需要繫結的socket的檔案描述符。第二個引數是需要繫結的地址,第三個引數是地址的長度(位元組數)。對於第二個引數,對於ip協議,常使用sockaddr_in來代替,在繫結的時候強制轉換成sockaddr,((struct sockaddr*)&addr,addr為一個sockaddr_in型別的引數)。        
struct sockaddr_in
{
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
};

/* Internet address. */
struct in_addr 
{
    uint32_t       s_addr;     /* address in network byte order */
};
    對於ip協議,sin_family永遠被設為AF_INET,sin_port為埠號,sin_addr為具體的IP地址,可以將其設為INADDR_ANY來指定為任意ip地址(也可以近似於認為是本機地址),使用“127.0.0.1”來指定本機地址,或者自定義ip地址。使用inet_aton來將字串形式的ip地址轉換為標準的IP地址形式,`int inet_aton(const char *cp, struct in_addr *inp)`,第一個引數是字串形式ip地址,第二個引數是需要得到的地址,還有一些其他轉換的方式,具體見man。最後,注意主機位元組序和網路位元組序的差別。
    繫結成功後,便可以開始監聽socket了,使用listen函式:
int listen(int sockfd, int backlog);
    和bind函式一樣,若是函式成功,返回0,若是失敗,返回-1。第一個引數是監聽socket的檔案描述符,第二個引數指定sockfd監聽佇列的最大長度,當sockfd的監聽佇列已滿時,若還有新的連線,便會出現錯誤。      
    對於客戶端的連線socket,使用connect函式連線到伺服器socket上。(貌似對於客戶端socket不需要繫結地址,系統會自動為其指派地址和埠,這個具體還不太清楚,之後若是確定了會寫出來)。
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    函式的返回值規則和上述函式一樣,0為成功,-1為失敗。第一個引數為client的socket的檔案描述符,後面兩個引數則分別為所要連線到的server的ip地址和地址長度。地址使用和bind函式一樣。
    這樣,便完成了client和server的連線。
    連線成功後便可以使用send和recv函式來收發資料了。具體的例子如下(先只放出client的例子,server的程式碼之後和epoll程式碼一起放出。):
    client:
#include<sys/types.h>
#include<sys/socket.h>
#include<stdio.h>
#include<iostream>
#include<netinet/in.h>
#include<unistd.h>
#include<errno.h>
#include<arpa/inet.h>

using namespace std;

int main(void)
{
    int ClientFd;
    sockaddr_in ClientAddr;

    ClientFd=socket(AF_INET,SOCK_STREAM,0);
    if(ClientFd==-1)
    {
        cout<<"Client socket created falied!!"<<errno<<endl;
        return 0;
    }

    int ServerFd;
    sockaddr_in ServerAddr;
    ServerAddr.sin_family=AF_INET;
    if(inet_aton("127.0.0.1",&ServerAddr.sin_addr)==0)
    {
        cout<<"server IPAddress error!!"<<endl;
        return 0;
    }

    string ipAddress=inet_ntoa(ServerAddr.sin_addr);
    cout<<ipAddress<<endl;
    ServerAddr.sin_port=htons(8000);
    socklen_t ServerLen=sizeof(ServerAddr);

    if(connect(ClientFd,(struct sockaddr*)&ServerAddr,ServerLen)==-1)
    {
        cout<<"can't connect to server!!"<<endl;
        cout<<errno<<endl;
        return 0;
    }

    const char *buffer="Hello, My Server!!";
    send(ClientFd,buffer,18,0);
    shutdown(ClientFd,SHUT_RDWR);
    if(close(ClientFd)==-1)
        cout<<"close Client failed"<<endl;
    return 0;
}

——————————————————————-華麗的分界線—————————————————————————

    以上就是關於我關於socket的一些理解。下面介紹下epoll。
    在學習關於epoll之前,我曾經使用過完成埠,感覺和完成埠相比,epoll的使用就簡單很多了,只需要三個函式,epoll_create,epoll_vtl,和epoll_wait.當然完成epoll的第一步就是先建立一個監聽socket,將其作為第一個socket加進epoll的檔案描述符中,之後每有一個客戶端連線到這個sockfd時,就將這個客戶端加進epoll中。首先介紹epoll_create函式:
int epoll_create(int size);
    在過去的版本中,size引數用來指定能在epoll中新增的sockfd的數量,但從Linux 2.6.8開始,size引數被忽略,但必須為一個大於0的數。此函式返回一個新的epoll的例項的檔案描述符,此檔案描述符用於之後對epoll的操作。當不再需要此文教描述符時,應該使用close函式關閉此檔案描述符,當所有引用此epoll例項的檔案描述符關閉時,系統核心會銷燬此epoll例項以釋放資源(這句話應該也說明了能夠以不同的檔案描述符呼叫一個epoll例項)。當函式返回-1時,說明函式失敗,可以檢視errno來確定錯誤原因。
    在建立了epoll例項,得到引用其的檔案描述符之後,就可以呼叫epoll_ctl函式將已經建立好的監聽socket加入epoll佇列中了。
 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    epoll_ctl有四個引數,第一個引數epfd為epoll的檔案描述符,也就是剛剛使用epoll_create建立的epoll檔案描述符。第二個引數op(operation),它指定需要對目標檔案描述符fd進行的動作,op引數有效值為以下三個:EPOLL_CTL_ADD,它將目標檔案描述符fd註冊到epoll的佇列之中,並且使fd檔案描述符所引用的檔案和event(第四個引數)相關聯;EPOLL_CTL_MOD,用來改變目標檔案描述符fd相關聯的事件event;EPOLL_CTL_DEL,用來將目標檔案描述符fd,從epoll佇列中移除,在這個操作下,event引數被忽略,可以被設定為NULL。第三個引數fd就是需要被操作的目標檔案描述符fd。第四個引數event描述和fd連線到一起的操作(請原諒我的語文水平,不過看到對引數值的講解時應該都能夠理解event的含義)。epoll_event的結構如下:
           typedef union epoll_data {
               void        *ptr;
               int          fd;
               uint32_t     u32;
               uint64_t     u64;
           } epoll_data_t;

           struct epoll_event {
               uint32_t     events;      /* Epoll events */
               epoll_data_t data;        /* User data variable */
           };
    其中events變數是一組位掩碼,可以使用|來同時選中幾個不同的events引數。可選擇的引數如下:EPOLLIN,相關檔案可用於read操作;EPOLLOUT,相關檔案可用於write操作;對於stream socket來說,可以檢測出對面客戶端關閉(close)或者半關閉連線(shutdown)(但我在程式碼中測試過,沒有能夠成功檢測出來,可能是我的程式碼寫錯了,但是可以用recv來檢測對面是否關閉連線,如果recv的返回值為0的話說明對面關閉了連線,這點我測試過);還有一些其他的events引數,如EPOLLPRI,EPOLLERR等,我也沒有進行測試,可以查詢man來了解具體含義。
    在將監聽socket註冊到epoll中後,便可以呼叫epoll_wait來開始進行epoll的監聽工作。
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout);
    epoll_wait函式等待發生在epoll佇列中的檔案描述符的事件,如果沒有事件發生,它會阻塞住程式。第一個引數epfd還是epoll例項的檔案描述符。第二個引數events指向記錄發生的事件的記憶體區域,可用events[i]來呼叫發生的事件。第三個引數maxevents指定epoll所能返回的最大的事件數量,此引數必須大於0,第四個引數timeout指定epoll_wait阻塞住程式的超時時間(epoll_wait將會阻塞住程式直到以下三種情況之一發生:epoll佇列中的一個檔案描述符上發生了事件,epoll-wait被訊號中斷,超時時間到)。當timeout為-1時會導致如果沒有事件到達,程式將會被無限期阻塞住,而如果timeout為0,epoll-wait將會立即返回,即使任何事件都沒有發生。epoll-wait函式返回發生的事件數目,如果為0,說明超時,沒有時間發生,如果返回-1,說明函式錯誤,可查詢errno來確定錯誤原因。
    在呼叫完epoll_wait後,如果有客戶端連線到監聽socket,便可以接收到,接收到後便新建一個sockfd來專門和這個客戶端進行通訊,再呼叫epoll_ctl函式將這個sockfd註冊到epoll中,如此迴圈便可。程式如下:

epoll.h:

#include<sys/socket.h>
#include<sys/epoll.h>
#include<sys/types.h>
#include<iostream>
#include<string>
#include<errno.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#include<stdio.h>
#include<string.h>
#include<fcntl.h>
#include<unistd.h>

#define MAX_SIZE 500
#define BUFF_SIZE 1000
#define MAX_EVENTS 100

using namespace std;
class myEpollServer
{
public:
    myEpollServer(){};
    ~myEpollServer(){};
    int setnonblocking(int socketFd);

    void start();

    bool initializeEpoll(int maxSize);
    bool initializeServerSocket();
private:
    //listen socket info
    int ServerFd=0;
    sockaddr_in ServerAddr;
    int ServerPort; 
};

epoll.cpp

#include"MyEpollPort.h"

bool myEpollServer::initializeServerSocket()
{
    using namespace std;

    if((ServerFd=socket(AF_INET,SOCK_STREAM,0))==-1)
    {

        cout<<"create server socket fail!!,error:"<<strerror(errno)<<endl;
        return false;
    }   

    memset(&ServerAddr,0,sizeof(ServerAddr));
    ServerAddr.sin_family=AF_INET;
    ServerAddr.sin_addr.s_addr=htons(INADDR_ANY);
    ServerAddr.sin_port=htons(8000);

    if(bind(ServerFd,(struct sockaddr*)&ServerAddr,sizeof(ServerAddr))==-1)
    {
        cout<<"bind server addr fail,error:"<<strerror(errno)<<endl;
        return false;
    }   

    if(listen(ServerFd,10)==-1)
    {
        cout<<"server listen fail,error:"<<strerror(errno)<<endl;
        return false;
    }  
    return true;
}

bool myEpollServer::initializeEpoll(int maxSize)
{
    struct epoll_event ev,events[100];
    int connectFd,nfds,epollFd;

    epollFd=epoll_create(maxSize);
    if(epollFd==-1)
    {
        perror("epoll_create");
        return false;
    }
    ev.events=EPOLLIN|EPOLLRDHUP;
    ev.data.fd=ServerFd;
    if(epoll_ctl(epollFd,EPOLL_CTL_ADD,ServerFd,&ev)==-1)
    {
        perror("epoll_ctl:ServerFd");
        return false;
    }

    for(;;)
    {
        nfds=epoll_wait(epollFd,events,MAX_EVENTS,-1);
        if(nfds==-1)
        {
            perror("epoll_wait");
            close(ServerFd);
            return false;
        }

        for(int i=0;i<nfds;++i)
        {
            cout<<i<<endl;
            if(events[i].data.fd==ServerFd)
            {
                cout<<1<<endl;
                sockaddr_in clientAddr;
                socklen_t len;
                connectFd=accept(ServerFd,(struct sockaddr*)&clientAddr,&len);
                if(connectFd==-1)
                {
                    perror("accept");
                    close(ServerFd);
                    return false;
                }
                cout<<"client addr is: "<<inet_ntoa(clientAddr.sin_addr)<<endl;
                setnonblocking(connectFd);
                ev.events=EPOLLIN|EPOLLET;
                ev.data.fd=connectFd;
                if(epoll_ctl(epollFd,EPOLL_CTL_ADD,connectFd,&ev)==-1)
                {
                    perror("epoll_ctl:connectFd");
                    close(ServerFd);
                    return false;
                }
            }
            else
            {
                cout<<2<<endl;
                if(events[i].events&EPOLLRDHUP||events[i].events&EPOLLERR)
                {
                    sockaddr_in addr;
                    socklen_t len=sizeof(addr);
                    if(getpeername(events[i].data.fd,(struct sockaddr*)&addr,&len)==-1)
                    {
                        cout<<"get client address fail!1"<<endl;
                    }
                    cout<<"client:"<<inet_ntoa(addr.sin_addr)<<"out of link!!"<<endl;

                }
                char buff[BUFF_SIZE]; 
                int n=recv(events[i].data.fd,buff,1000,0);
                if(n==0)
                {
                    sockaddr_in addr;
                    socklen_t len=sizeof(addr);
                    if(getpeername(events[i].data.fd,(struct sockaddr*)&addr,&len)==-1)
                    {
                        cout<<"get client address fail!1"<<endl;
                    }
                    cout<<"client:"<<inet_ntoa(addr.sin_addr)<<"out of link!!"<<endl;


                }
                buff[n]='\0';
                cout<<buff<<endl;
            }
        }
    }

    return true;

}


void myEpollServer::start()
{
    initializeServerSocket();
    initializeEpoll(100);
}

int myEpollServer::setnonblocking(int sockFd)
{
    if(fcntl(sockFd,F_SETFL,fcntl(sockFd,F_GETFD,0)|O_NONBLOCK)==-1)
    {
        return -1;
    }
    return 0;
}