本文針對初學socket epoll和多線程的小夥伴們
對庫函數的簡單概述:
註釋:
1.socket()函數------作用 :用於根據指定的地址族、數據類型和協議來分配一個套接口的描述字及其所用的資源(創建套接字)
第一個參數:一個地址描述;(本文用的是AF_INET)。
第二個參數:指定socket類型(本文用的是SOCK_STREAM)。
第三個參數:就是指定協議(本文用的是TCP協議)。
返回值是套接字。
2.setsockopt()函數 ------作用:用於任意類型、任意狀態套接口的設置選項值(端口複用)。
第一個參數:標識一個套接口的描述字。
第二個參數:選項定義的層次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
第三個參數:需設置的選項。
第四個參數:指針,指向存放選項待設置的新值的緩衝區。
第五個參數:optval緩衝區長度。
3.bind()函數------作用:將一本地地址與一套接口捆綁,通過給一個未命名套接口分配一個本地名字來爲套接口建立本地捆綁(主機地址/端口號)
第一個參數:就是socket的返回值(套接字)
第二個參數:sockaddr_in類型的指針(將地址和端口與套接字綁定在一起)
第三個參數:sockaddr_in的長度;
返回值爲0成功
4.listen()函數-----作用:創建一個套接口並監聽申請的連接
第一個參數:用於標識一個已捆綁未連接套接口的描述字(socket()返回值);
第二個參數 : 等待連接隊列的最大長度;
返回值爲0成功
5.accept()函數------作用 :在一個套接口接受一個連接
第一個參數:套接字描述符,該套接口在listen()後監聽連接。
第二個參數:指針,指向一緩衝區,其中接收爲通訊層所知的連接實體的地址。第二個參數的實際格式由套接口創建時所產生的地址族確定。
第三個參數:指針,輸入參數,配合addr一起使用,指向存有addr地址長度的整型數。
返回值 :新的套接字。
6.fcntl()函數------作用:可以改變已打開的文件性質 例:使accept() , recv(),send()等函數變爲非阻塞;
第一個參數:代表欲設置的文件描述符。
第二個參數:代表打算操作的指令:
F_DUPFD用來查找大於或等於參數arg的最小且仍未使用的文件描述符,並且複製參數fd的文件描述符。執行成功則返回新複製的文件描述符。
新描述符與fd共享同一文件表項,但是新描述符有它自己的一套文件描述符標誌,其中FD_CLOEXEC文件描述符標誌被清除。請參考dup2()。
F_GETFD取得close-on-exec旗標。若此旗標的FD_CLOEXEC位爲0,代表在調用exec()相關函數時文件將不會關閉。
F_SETFD 設置close-on-exec 旗標。該旗標以參數arg 的FD_CLOEXEC位決定。
F_GETFL 取得文件描述符狀態旗標,此旗標爲open()的參數flags。
F_SETFL 設置文件描述符狀態旗標,參數arg爲新旗標,但只允許O_APPEND、O_NONBLOCK和O_ASYNC位的改變,其他位的改變將不受影響。
F_GETLK 取得文件鎖定的狀態。
F_SETLK 設置文件鎖定的狀態。此時flcok 結構的l_type 值必須是F_RDLCK、F_WRLCK或F_UNLCK。如果無法建立鎖定,則返回-1,錯誤代碼爲EACCES 或EAGAIN。
F_SETLKW F_SETLK 作用相同,但是無法建立鎖定時,此調用會一直等到鎖定動作成功爲止。若在等待鎖定的過程中被信號中斷時,會立即返回-1,錯誤代碼爲EINTR。
第三個參數:參數lock指針爲flock 結構指針。
_type 有三種狀態:
F_RDLCK 建立一個供讀取用的鎖定
F_WRLCK 建立一個供寫入用的鎖定
F_UNLCK 刪除之前建立的鎖定
l_whence 也有三種方式:
SEEK_SET 以文件開頭爲鎖定的起始位置。
SEEK_CUR 以目前文件讀寫位置爲鎖定的起始位置
SEEK_END 以文件結尾爲鎖定的起始位置。
l_start 表示相對l_whence位置的偏移量,兩者一起確定鎖定區域的開始位置。
l_len表示鎖定區域的長度,若果爲0表示從起點(由l_whence和 l_start決定的開始位置)開始直到最大可能偏移量爲止。即不管在後面增加多少數據都在鎖的範圍內。
返回值 成功返回依賴於cmd的值,若有錯誤則返回-1,錯誤原因存於errno.
例::
flags = fcntl(sockfd, F_GETFL, 0); //獲取文件的flags值。
fcntl(sockfd, F_SETFL, flags | O_NONBLOCK); //設置成非阻塞模式;
flags = fcntl(sockfd,F_GETFL,0);
fcntl(sockfd,F_SETFL,flags&~O_NONBLOCK); //設置成阻塞模式;
7.epoll_create()函數-----作用:創建一個 epoll 的句柄(文件描述符),參數要大於 0, 沒有太大意義
返回值是文件描述符。
8.epoll_ctl()函數--------作用:改變被監聽的事件的類型。
它不同與select()是在監聽事件時告訴內核要監聽什麼類型的事件,而是在這裏先註冊要監聽的事件類型。
第一個參數是epoll_create()的返回值,
第二個參數表示動作,用三個宏來表示:EPOLL_CTL_ADD:1 註冊新的fd到epfd中;EPOLL_CTL_MOD:2 修改已經註冊的fd的監聽事件;EPOLL_CTL_DEL:3 從epfd中刪除一個fd;
第三個參數就是要監視的套接字;
第四個參數是告訴內核需要監聽什麼事
返回值成功時返回0,返回-1註冊失敗
9.epoll_wait()函數------作用:
等待事件的產生,類似於select()調用。
第一個參數:文件描述符(epoll_create()返回值);
第二個參數:用來從內核得到事件的集合,
第三個參數:表示每次能處理的最大事件數,告之內核這個wait_event有多大,
第四個參數:是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。
該函數返回需要處理的事件數目,如返回0表示已超時。
10.pthread_mutex_lock();------作用:該互斥鎖已被鎖定。線程調用該函數讓互斥鎖上鎖,如果該互斥鎖已被另一個線程鎖定和擁有,則調用該線程將阻塞,直到該互斥鎖變爲可用爲止(將線程上鎖);
參數:
返回值:在成功完成之後會返回零
11.pthread_mutex_unlock()-------作用:與pthread_mutex_lock成對存在。(釋放互斥鎖將線程解鎖)
參數:
返回值:在成功完成之後會返回零
12.pthread_create()---------作用:創建線程
第一個參數:爲指向線程標識符的指針。(線程id)
第二個參數:用來設置線程屬性。
第三個參數:是線程運行函數的起始地址。
第四個參數:是運行函數的參數。
創建成功返回零
#include <iostream>
#include <pthread.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <fcntl.h>
#include <arpa/inet.h>
#include <sys/epoll.h>
#include <sys/errno.h>
#include <sys/socket.h>
#define NUMBER 10
#define LISTENMAX 20
#define IP "127.0.0.1"
#define PORT 80000
#define LINET 10000
using namespace std;
static unsigned int threadParameter[NUMBER][8];//線程參數
pthread_t threadId[NUMBER];//線程id
pthread_mutex_t threadLock[NUMBER];//線程鎖
pthread_cond_t count_nonzero[NUMBER];
int count1[NUMBER]={0};
static struct dataPacket
{
struct epoll_event ev;
struct epoll_event waitEvent[LINET];
int sockNumber[LINET]={0};
int MAX=0;
int epfd=0;
}ThreaDataPackage;
void decrement_count (int i)
{
pthread_mutex_lock (threadLock+i);
while(count1[i]==0)
pthread_cond_wait( count_nonzero+i, threadLock+i);
count1[i]=0;
pthread_mutex_unlock (threadLock+i);
}
void increment_count(int i)
{
pthread_mutex_lock(threadLock+i);
pthread_cond_signal(count_nonzero+i);
count1[i]=1;
pthread_mutex_unlock(threadLock+i);
}
void * serverSocket(unsigned int *parameter)//線程主函數
{ char buf[1024];
char buff[1024];
pthread_detach(pthread_self());
while(1)
{
decrement_count (parameter[7]);
printf("啓動線程:%d\n",parameter[7]);
memset(buf,0,sizeof(buf));
memset(buff,0,sizeof(buff));
int len=recv(parameter[1], buf, 1024, MSG_NOSIGNAL);//非阻塞模式的消息接收
if(len>0)
{
printf("%s\n",buf);
}
if(len==0)
{
for(int i=0;i<LINET;i++)
{
if(parameter[1]==ThreaDataPackage.sockNumber[i])
{ ThreaDataPackage.MAX--;
ThreaDataPackage.sockNumber[i]=0;
close(ThreaDataPackage.sockNumber[i]);
printf("客戶端%d下線\n",ThreaDataPackage.MAX);
if (epoll_ctl(ThreaDataPackage.epfd, EPOLL_CTL_DEL,parameter[1], &ThreaDataPackage.ev) < 0)//加入epoll事件集合
{
perror("epoll_ctl error:");
}
break;
}
}
}
sprintf(buff ,"你好客戶端我是第%d您發送的是:",parameter[7]);
strcat(buff,buf);
len=send(parameter[1],buff,1024,MSG_NOSIGNAL);//非阻塞模式的消息發送
memset(buff,0,sizeof(buff));
parameter[0]= 0;//設置線程佔用標誌爲"空閒"
}
}
static int initThreadPool(void)//初始化數據
{ int a=0;
for(int i=0;i<NUMBER;i++)
{
threadParameter[i][0]=0;
threadParameter[i][7]=i;
pthread_cond_init(count_nonzero+i,NULL);
pthread_mutex_init(threadLock+i,NULL);
a= pthread_create( threadId+ i, NULL, (void* (*)(void *))serverSocket,(void *)(threadParameter[i]));
if(a!=0)
{
perror("pthread_create error:");
return -1;
}
}
return 0;
}
static int initListen(char*ip,int port,int listenMax)//初始化監聽
{ int a=0;
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
perror("sockt error:");
close(sockfd);
return -1;
}
struct sockaddr_in server_addr;
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family=AF_INET;
inet_pton(AF_INET,ip,&(server_addr.sin_addr));
server_addr.sin_port=htons(port);
int opt = 1;
setsockopt(sockfd, SOL_SOCKET,SO_REUSEADDR, (const void *) &opt, sizeof(opt));
a=bind(sockfd,(struct sockaddr*)&server_addr,sizeof(server_addr));
if(a<0)
{
perror("bind error:");
close(sockfd);
return -1;
}
a=listen(sockfd,listenMax);
if(a<0)
{
perror("listen error:");
close(sockfd);
return -1;
}
return sockfd;
}
bool setNonBlock(int fd)//設置文件描述符爲NonBlock
{
int flags = fcntl(fd, F_GETFL, 0);
flags |= O_NONBLOCK;
if(-1 == fcntl(fd, F_SETFL, flags))
{
return false;
}
return true;
}
int main()
{
int acceptSockfd=0;//accept返回的套接字
int sockfd=0;//服務器套接字
int nfds=0;//觸發事件的個數
socklen_t addrLen; //地址信息長度
struct sockaddr_in clinetAddr; //IPv4地址結構
if(0!=initThreadPool())
{
perror("initThreadPool error:");
exit(-1);
}
sockfd=initListen(IP,PORT,LISTENMAX);
ThreaDataPackage.sockNumber[0]=sockfd;
if(sockfd<0)
{
perror("initListen error:");
exit(-1);
}
ThreaDataPackage.epfd = epoll_create(8);//生成文件描述符
ThreaDataPackage.ev.events = EPOLLIN | EPOLLET;//對應的文件描述符可讀並且是et的epoll工作模式
ThreaDataPackage.ev.data.fd =sockfd ;
if (epoll_ctl(ThreaDataPackage.epfd , EPOLL_CTL_ADD,sockfd, &ThreaDataPackage.ev) < 0)//加入epoll事件集合
{
perror("epoll_ctl error:");
exit(-1);
}
while(1)
{
nfds = epoll_wait(ThreaDataPackage.epfd , ThreaDataPackage.waitEvent, ThreaDataPackage.MAX+1, -1);
printf("nfds::%d\n",nfds);
for(int i=0;i<nfds;i++)
{
if((sockfd==ThreaDataPackage.waitEvent[i].data.fd)&&(EPOLLIN==ThreaDataPackage.waitEvent[i].events&EPOLLIN))
{
addrLen=sizeof(struct sockaddr_in);
bzero(&clinetAddr,addrLen);
for(int j=0;j<LINET;j++)
{
if(ThreaDataPackage.sockNumber[j]==0)
{
ThreaDataPackage.sockNumber[j]= accept(sockfd, (struct sockaddr *)&clinetAddr, &addrLen);
if(ThreaDataPackage.sockNumber[j]<0)
{
perror("accept error:");
continue;
}
else
{
ThreaDataPackage.ev.data.fd = ThreaDataPackage.sockNumber[j];
ThreaDataPackage.ev.events = EPOLLIN|EPOLLET;
if (epoll_ctl(ThreaDataPackage.epfd , EPOLL_CTL_ADD,ThreaDataPackage.sockNumber[j], &ThreaDataPackage.ev) < 0)//加入epoll事件集合
{
perror("epoll_ctl error:");
exit(-1);
}
setNonBlock(ThreaDataPackage.sockNumber[j]);//設置爲非阻塞
ThreaDataPackage.MAX++;
printf("客戶端%d上線\n",ThreaDataPackage.MAX);
break;
}
}
}
}
else if(ThreaDataPackage.waitEvent[i].data.fd>3&&( EPOLLIN == ThreaDataPackage.waitEvent[i].events & (EPOLLIN|EPOLLERR)))
{
for(int j=0;j<NUMBER;j++)
{
if(0==threadParameter[j][0])
{
threadParameter[j][0]=1;//設置活動標誌爲"活動"
threadParameter[j][1]=ThreaDataPackage.waitEvent[i].data.fd;//客戶端的套接字
increment_count(j);
break;
}
}
}
}
}
return 0;
}