本文针对初学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;
}