linux下socket程式設計和epoll的使用
阿新 • • 發佈:2019-01-06
這兩天在學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;
}