IP地址:可以在网络环境中,唯一标识一台主机
端口号:可以定位网络的一台主机上,唯一标识一个进程
ip地址+端口号:可以在网络环境中,唯一标识一个进程
在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。
套接字通信原理如下图所示:
**在网络通信中,套接字一定是成对出现的。**一端的发送缓冲区对应对端的接收缓冲区。
大端和小端的区别
在计算机内部,数字通常被表示为二进制数。例如,整数42在计算机中的二进制表示为00101010。
在存储数字时,计算机将其划分为字节(Byte)。每个字节通常包含8个位(bit)。对于一个多字节的数字,比如一个32位整数,它会被分为4个字节。这些字节可以按照两种不同的方式进行存储:大端模式或小端模式。
在大端模式下,最高位字节被存储在内存地址最低的位置,而最低位字节被存储在内存地址最高的位置。也就是说,数字的最高有效字节(Most Significant Byte, MSB)位于存储地址的最低位。
在小端模式下,最高位字节被存储在内存地址最高的位置,而最低位字节被存储在内存地址最低的位置。也就是说,数字的最高有效字节(MSB)位于存储地址的最高位。
举个例子,考虑一个32位整数0x12345678,它的字节表示为:
- 大端模式:12 34 56 78
- 小端模式:78 56 34 12
在网络编程中,通常需要将数据从一个计算机发送到另一个计算机。由于不同计算机的处理器可能采用不同的字节序,因此在网络传输数据时,需要将数据序列化为一种固定的字节序。大多数网络协议都使用网络字节序,即大端模式,来进行数据传输。因此,在网络编程中,需要注意字节序的转换,以确保正确的数据传输。
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用以下库函数做网络字节序和主机字节序的转换。
#include <arpa/inet.h>
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
IP地址转换函数
IP地址—>string---->atoi—>int—>htonl—>网络字节序
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
af:AF_INET,AF_INET6为ipw4和ipw6
src:传入,IP地址(点分十进制类型)
dst:传出,转换后的网络字节序的IP地址。
返回值: 成功 1
异常: 0,说明src指向的不是一个有效的IP地址
失败:-1
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af:AF_INET,AF_INET6为ipw4和ipw6
src:传入,转换后的网络字节序的IP地址。
dst:传出,IP地址(点分十进制类型)
size:dst的大小
返回值: 成功:dst
失败:NULL
支持IPv4和IPv6
sockaddr地址结构:
struct sockaddr_in {
sa_family_t sin_family; 地址结构类型
in_port sin_port; 端口号
struct in_addr sin_addr; IP地址
struct in_addr{
uint32_t s_addr;
}
};
struct sockaddr_in addr;
addr.sin_family=AF_INET/AF_INET6
addr.sin_port=hton(9527);
{int dst;
inet_pton(AF_INET,"192.157.22.45",(void*)&dst);
addr.sin_addr.s_addr=dst;}
addr.sin_addr.s_addr=htonl(INADDR_ANY);
bind(fd,(struct sockaddr*)&addr,size);
网络套接字函数
socket模型创建流程图
socket();
bind();
listen();
accept();
socket函数
#include <sys/socket.h>
int socket(int domain,int type,int protocol)
domain:AF_INET,AF_INET6,AF_UNIX
type:SOCK_STREAM,SOCK_DGRAM流式协议(Tcp),报式协议(Udp)
protocol:0
返回值:成功: 新套接字所对应文件描述符
失败: -1 error
socket(AF_INET,SOCK_STREAM,0)
bind函数
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen)
sockfd:socket 返回值
struct sockaddr_in addr;
addr.sin_family=AF_INET;
addr.sin_port=hton(8888);
addr.sin_addr.s_addr=htonl(INADDR_ANY);
addr:(struct sockaddr*)&addr
addrlen:sizeof(addr)
返回值:
成功: 0
失败: -1 error
listen函数
int listen(int sockfd,int backlog)
sockfd:socket函数返回值
backlog:上限数值,最大值:128
返回值:
成功: 0
失败: -1 error
accept函数
int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
sockfd:socket函数返回值
addr:传出参数。成功与服务器建立连接的那个客户端的地址结构(IP+port)
addrlen:传入传出。入:addr的大小,出:客户端addr的实际大小
返回值:
成功:能与服务器进行数据通信的socket对应的文件描述符
失败: -1,error
connect函数
int connect(int sockfd,const struct sockaddr *addr,socklen_t addrlen);
sockfd:socket函数返回值
struct sockaddr_in srv_addr
srv_addr.sin_family=AF_INET;
srv_addr.sin_port=9527
inet_pton(AF_INET,"服务器的IP地址“,&srv_addr.sin_addr.s_addr);
addr:传入参数。服务器的地址结构
addrlen:服务器地址结构的大小
返回值:
成功:0
失败:-1 errno
如果不使用bind绑定客户端地址结构,采用"隐式绑定".
客户端和服务端的实例
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <ctype.h>
#define SERV_PORT 9527
void sys_err(const char *str){
perror(str);
exit(1);
}
int main(int argc,char *argv[]){
int lfd=0,cfd=0;
int ret;
char buf[BUFSIZ],client_IP[1024];
struct sockaddr_in serv_addr,clit_addr;
socklen_t clit_addr_len;
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERV_PORT);
serv_addr.sin_addr.s_addr=htonl(INADDR_ANY);
lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd==-1){
sys_err("socket error");
}
bind(lfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
listen(lfd,128);
clit_addr_len=sizeof(clit_addr);
cfd=accept(lfd,(struct sockaddr*)&clit_addr,&clit_addr_len);
if(cfd==-1){
sys_err("accept error");
}
printf("client ip:%s port:%d\n",inet_ntop(AF_INET,&clit_addr.sin_addr.s_addr,client_IP,sizeof(client_IP)),ntohs(clit_addr.sin_port));
while(1){
ret=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,ret);
for(int i=0;i<ret;i++){
buf[i]=toupper(buf[i]);
}
write(cfd,buf,ret);
}
close(lfd);
close(cfd);
return 0;
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <errno.h>
#include <pthread.h>
#include <ctype.h>
#define SERV_PORT 9527
void sys_err(const char *str){
perror(str);
exit(1);
}
int main(int argc,char *argv[]){
int cfd;
int conter=10;
char buf[BUFSIZ];
struct sockaddr_in serv_addr;
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(SERV_PORT);
inet_pton(AF_INET,"127.0.0.1",&serv_addr.sin_addr.s_addr);
cfd=socket(AF_INET,SOCK_STREAM,0);
if(cfd==-1){
sys_err("socket error");
}
int ret=connect(cfd,(struct sockaddr*)&serv_addr,sizeof(serv_addr));
if(ret!=0){
sys_err("connect err");
}
while(--conter){
write(cfd,"hello",5);
sleep(1);
ret=read(cfd,buf,sizeof(buf));
write(STDERR_FILENO,buf,ret);
}
close(cfd);
}
多进程并发服务器
1.socket() 创建监听套接字lfd
2.bind() 绑定地址结构Struct sockaddr_in addr
3.Listen()
4.while(1){
cfd=Accpet();
pid=fork();
if(pid==0){
close(lfd) ; 关闭用于建立连接的套接字 lfd
read();
小--》大
write();
}else if(pid>0){
close(cfd); 关闭用于客户端通信的套接字 cfd
contiue;
}
}
5.子进程
close(fd) read() 小--》大 write()
父进程:
注册信号捕捉函数 SIGCHLD
在回调函数中,完成子进程的回收
while(waitpid())
多线程并发服务器
1.socket() 创建监听套接字lfd
2.bind() 绑定地址结构Struct sockaddr_in addr
3.Listen()
4.while(1){
cfd=Accpet();
pthread_creat(&tid,NULL,tfn,NULL);
pthread_detach(tid);
5子线程
void *tfn(void *arg){
close(lfd) ; 关闭用于建立连接的套接字 lfd
read();
小--》大
write();
}
TCP状态时序图
netstat -apn|grep client 查看端口状态
netstat -apn|grep 8000 查看各个套接字的状态(LISTEN ESTABLISHED)
TIME_WAIT状态:只有主动关闭连接,会经历这个状态,然后在经历2MSL时长后关闭,在这2MSL时长里是不能再使用这个端口的。
2MSL时长:保证最后一个ACK能成功被对端接收(等待期间,对端没收到我发的ACK,对端会再次发送FIN请求)
端口复用
int opt=1;
setsockpt(lfd,SOL_SOCKET,SO_REUSEADDR,(void*)opt,sizeof(opt));
半关闭:通信双方,只有一方关闭通信。-----FIN_WAIT2
close(fd)
shutdown(int fd,int how);
how:SHUT_RD 关读端 SHUT_WR 关写端 SHUT_RDWR 关读写
shutdown在关闭多个文件描述符应用的文件时,采用全关闭。close只关闭一个
select多路IO转接
#include <sys/select.h>
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds: 从3开始,监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds: 监控有读数据到达文件描述符集合,传入传出参数
writefds: 监控写数据到达文件描述符集合,传入传出参数
exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout: 定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
返回值:>0:所有监听集合(读,写,异常)中,满足对应事件的总数
0: 没有满足监听条件的文件描述符
-1:errno
struct timeval {
long tv_sec;
long tv_usec;
};
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
思路分析
lfd=socket();
bind();
listen();
fd_set rset,allset;
FD_ZERO(&allset);
FD_SET(lfd,&allret);
while{
rset=allset
ret=select(lfd+1,&rset,NULL,NULL,NULL);
if(ret>0){
if(FD_ISSET(lfd,&rset){
cfd=accept();
FD_SET(cfd,&allset);
}
for(i=lfd+1;i<=最大文件描述符;i++){
if(FD_ISSET(i,&rset){
read();
小-》大;
write();
}
}
}
}
- select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
- 解决1024以下客户端时使用select是很合适的,但如果链接客户端过多,select采用的是轮询模型,会大大降低服务器响应效率,不应在select上投入更多精力
优点:跨平台。
poll多路IO转接
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd;
short events;
取值:POLLIN,POLLOUT,POLLERR.
short revents;
};
nfds 监控数组中有多少文件描述符需要被监控
timeout 毫秒级等待
-1:阻塞等,#define INFTIM -1 Linux中没有定义此宏
0:立即返回,不阻塞进程
>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
read函数返回值:
0:实际读到的字节数
=0:socket中,表示对端关闭。close()
-1:如果errorEINTR 被异常中断,需要重启
如果errorEAGIN或EWOULDBLOCK以非阻塞方式读数据,但是没有数据,需要再次读数据
如果error==ECONNRESET 说明连接被重置,需要close(),移除监听队列
poll缺点:不能跨平台。
突破1024文件描述符限制:
cat /proc/sys/fs/file-max
ulimit -a
修改:
打开 sudo vi /etc/security/limits.conf
soft nofile 65536 -->设置默认值,可以直接借助命令修改,注销用户使其生效
hard nofile 100000 -->命令修改上限
epoll
#include <sys/epoll.h>
int epoll_create(int size) size:创建的红黑树的监听节点的数目
返回值:成功:指向新创建的红黑树的根节点fd
失败:-1 error
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd: 为epoll_creat的句柄
op: 表示动作,用3个宏来表示:
EPOLL_CTL_ADD (注册新的fd到epfd),
EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
EPOLL_CTL_DEL (从epfd删除一个fd);
fd:待监听的fd
event: 告诉内核需要监听的事件(本质struct epoll_event结构体的地址
struct epoll_event {
__uint32_t events;
epoll_data_t data;
};
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLERR: 表示对应的文件描述符发生错误
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
events: 传出参数 数组,传出满足监听条件的那些fd结构体。
maxevents: 数组元素总个数,告之内核这个events有多大,这个maxevents的值不能大于创建epoll_create()时的size,
timeout: 是超时时间
-1: 阻塞
0: 立即返回,非阻塞
>0: 指定毫秒
返回值:
>0:成功返回有多少文件描述符就绪,
0:时间到时返回0,没有fd满足监听事件
-1:出错返回-1
epoll实现多路IO转接思路
lfd=socket();
bind();
listen();
int epfd=epoll_create(1024);
struct epoll_event tep,ep[1024];tep用来设置单个fd属性,ep是epoll_wait()传出的满足监听事件的数组
tep.events=EPOLLIN;
tep.data.fd=lfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&tep);
while(1){
ret=epoll_wait(epfd,ep,1024,-1);
for(int i=0;i<ret;i++){
if(ep[i].data.fd==lfd){
cfd=Accept();
tep.events=EPOLLIN;
tep.data.fd=cfd;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&tep);
}else{
n=read(ep[i].data.fd,buf,sizeof(buf));
if(n==0){
close(ep[i].data,fd);
epoll_ctl(epfd,EPOLL_CTL_DEL,ep[i].data.fd,NULL);
}else if(n>0){
小-->大
write(ep[i].data,fd);
}
}
}
epoll是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,因为它会复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合,另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。
epoll事件模型
EPOLL事件有两种模型:
Edge Triggered (ET) 边缘触发只有数据到来才触发,不管缓存区中是否还有数据。缓冲区剩余未读尽的数据不会导致epoll_wait返回。新的事件满足,才会触发。
struct epoll_event event;
event.events=EPOLLIN|EPOLLET;
Level Triggered (LT) 水平触发只要有数据都会触发。缓冲区剩余未读尽的数据会导致epoll_wait返回。
结论:
epoll的ET模式,高效模式,但是只支持非阻塞模式
struct epoll_event event;
event.events=EPOLLIN|EPOLLET;
epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&event)
int flg=fcntl(cfd,F_GETFL);
flg|=O_NDNBLOCK;
fcntl(cfd,F_SETFL,flg);
epoll反应堆模型
epollET模式+非阻塞+void *ptr
反应堆模型:不但要监听cfd的读事件,还要监听cfd的写事件
socket,bind,listen --- epoll_creat创建监听红黑树--返回epfd--epoll_ctl()向树上添加一个监听fd
--while(1)--epoll_wait()监听--对应监听fd有事件产生--返回监听满足数组--判断返回数组元素--lfd满足
--Accept--cfd满足--read()--小--》大--cfd从监听红黑树摘下--EPOLLOUT--回调函数--epoll_ctl()
--EPOLL_CTL_ADD重新放到红黑树上监听写事件---等待epoll_wait返回---说明cfd可写---write回去--cfd从监听树上摘下
--EPOLLIN--epoll_ctl()--EPOLL_CTL_ADD重新放到红黑树上监听事件--epoll_wait监听
eventset函数
设置回调函数: lfd—>>acceptconn();
cfd—>>recvdata();
eventadd函数
将一个fd,添加到监听红黑树,设置监听read事件,还是监听写事件
线程池在网络编程的使用
typedef struct {
void *(*function)(void *);
void *arg;
} threadpool_task_t;
struct threadpool_t {
pthread_mutex_t lock;
pthread_mutex_t thread_counter;
pthread_cond_t queue_not_full;
pthread_cond_t queue_not_empty;
pthread_t *threads;
pthread_t adjust_tid;
threadpool_task_t *task_queue;
int min_thr_num;
int max_thr_num;
int live_thr_num;
int busy_thr_num;
int wait_exit_thr_num;
int queue_front;
int queue_rear;
int queue_size;
int queue_max_size;
int shutdown;
};
线程池模块分析
1.main();
创建线程池
向线程池中添加任务,借助回调函数处理任务
销毁线程池
2.pthreadpool_create();
创建线程池结构体,指针
初始化线程池结构体(N个成员变量)
创建N个任务线程
创建1个管理者线程
失败时,销毁开辟的所有空间。(释放)
3.threadpool_thread()
进入子线程回调函数
接收参数void *arg–>>pool结构体
加锁–》lock–》整个结构体锁
判断条件变量–》wait
4.adjust_thread()
循环10s执行一次。
进入管理者线程回调函数
接收参数void *arg–》pool结构体
加锁–》lock–》整个结构体锁
获取管理线程池要用的变量。task_num,live_num,busy_num
根据既定算法,使用上述3变量,判断是否应该创建销毁线程池中指定步长的线程
5.threadpool_add()
总功能:
模拟产生任务 num[20]
设置回调函数 处理任务 sleep(1)代表处理完成
内部实现:
加锁
初始化任务队列结构体成员。回调函数function arg
利用环形队列机制,实现添加任务。借助队尾指针挪移%实现
环形阻塞在条件变量上的线程
解锁
6.从3.中的wait之后继续执行,处理任务
加锁
获取任务处理回调函数,及参数
利用环形队列机制,实现处理任务,借助对头挪移%实现
唤醒阻塞在条件变量上的server
解锁
加锁
改忙线程数++
解锁
执行处理任务的线程
加锁
改忙线程数–
解锁
7.创建和销毁线程
管理者线程根据task_num,live_num,busy_num
根据既定算法,使用上述3变量,判断是否应该创建销毁线程池中指定步长的线程
如果满足创建条件
pthread_creat(); 回调函数 live_num++
如果满足销毁条件
wait_exit_thr_num=10;
signal给阻塞在条件变量上的线程发送假条件满足信号
跳转至 --170wait阻塞线程会被假信号唤醒。判断:wait_exit_thr_num>0 pthread_exit();
TCP通信和UDP通信各自的优缺点
TCP:面向连接的,可靠数据包传输。对于不稳定的网络层,采取完全弥补的通信方式。丢包重传。
优点:稳定。数据流量稳定,速度稳定,顺序
缺点:传输速度慢。相率低,不追求效率
使用场景:数据的完整性要求较高,不追求效率;大数据的传输,文件传输。
UDP:无连接的,不可靠的数据报传递,对于不稳定的网络层,采取完全不弥补的通信方式,默认还原网络状态
优点:传输速度快,效率高,开销小
缺点:不稳定
使用场景:对时效性要求较高的场合。稳定性其次
游戏,视频会议,视频电话
腾讯,华为,阿里 ----应用层数据校验协议,弥补udp的不足。
UDP实现的C/S模型
recv()/send()只能
accept();------Connect();----被舍弃
server:
lfd=socket(AF_INET,STREAM,0); SOCK_DGRAM--报文协议
bind();
listen();--可有可无
while(1){
read(cfd,buf,sizeof)--被替换---recvform()---涵盖accept传出地址结构
ssize_t recvfrom(int sockfd,void *buf,size_t len,int flags,struct sockaddr *src_addr,socketlen_t *addrlen);
sockfd:lfd; buf:缓冲区地址; len:缓冲区大小; flags:0; src_addr:(struct sockaddr*)&addr传出,对端地址结构; addrlen:传入传出
返回值:成功接收数据字节数。失败:-1 errn. 0:对端关闭。
小--大
write();---被替换---sendto()
ssize_t sendto(int sockfd,const void *buf,size_t len,int flags,struct sockaddr *src_addr,socketlen_t *addrlen);
sockfd:lfd; buf:缓冲区地址; len:缓冲区大小; flags:0; src_addr:(struct sockaddr*)&addr传入,对端地址结构; addrlen:地址结构长度
返回值:成功写出的数据字节数。失败:-1 errn. 0:对端关闭。
}
close();
client:
connfd=socket(AF_INET,SOCK_DGRAM,0)
sendto('服务器的地址结构',地址结构大小)
recvform()
写到屏幕
close();
本地套接字
IPC:pipe,fifo,mmap,信号,本地套接字(domain)—CS模型
对比网络编程TCP C/S模型 注意以下几点:
地址结构:sockaddr_in---->sockaddr_un
struct sockaddr_in {
__kernel_sa_family_t sin_family; 地址结构类型
__be16 sin_port; 端口号
struct in_addr sin_addr; IP地址
};
struct sockaddr_un {
__kernel_sa_family_t sun_family; 地址结构类型
char sun_path[UNIX_PATH_MAX]; socket文件名(含路径)
};
struct sockaddr_in srv_addr;--->struct sockaddr_un srv_addr;
srv_addr.sin_family=AF_INET;--->srv_addr.sun_family=AF_UNIX;
srv_addr.sin_port=htons(8888); strcpy(srv_addr.sun_path,"srv_socket")
srv_addr.sin_addr.s_addr=htonl(INADDR_ANY); len=offsetof(struct sockaddr_un sun_path+strlen("srv.socket");
以下程序将UNIX Domain socket绑定到一个地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#define offsetof(type, member) ((int)&((type *)0)->MEMBER)
3.bind()函数调用成功,会创建一个socket.因此为保证bind成功,通常我们在bind之前,可以使用unlink(“srv.socket”);
4.客户端不能依赖“隐式绑定”。并且应该在通信建立过程中,创建且初始化2个地址结构
1)client_addr---->bind();
2)server_addr—>connect();
Libevent库
优点:开源,精简,跨平台(window,Linux,maxos,unix).专注于网络编程。
源码包安装:参考 README
./configure 检查安装环境生成amkefile
make 生成.o和可执行文件
sudo make install 将必要的资源cp置系统指定目录
进入sample目录,运行demo验证库安装使用情况
编译使用库的.c时,需要加-levent选项
库名libevent.so—>/user/local/
特性:基于“事件”异步通信模型。—回调(当某个条件到达时会回调)
libevent框架:
1.创建event_base
struct event_base *event_base_new(void);
struct event_base *base=event_base_new();
2.创建事件event
常规事件 event --> event_new();
缓冲区事件bufferevent --> bufferevent_socket_new();
3.将事件添加到base上
int event_add(struct event *ev, const struct timeval *tv)
4.循环监听事件满足
int event_base_dispatch(struct event_base *base);
event_base_dispatch(base);
5.释放event_base
event_base_free(base)
创建事件event:
struct event *ev;
struct event *event_new(struct event_base *base,evutil_socket_t fd,short what,event_callback_fn cb; void *arg);
base: event_base_new()返回值。
fd: 绑定到 event 上的 文件描述符
what:对应的事件(r、w、e)
EV_READ 一次 读事件
EV_WRTIE 一次 写事件
EV_PERSIST 持续触发。 结合 event_base_dispatch 函数使用,生效。
cb:一旦事件满足监听条件,回调的函数。
typedef void (*event_callback_fn)(evutil_socket_t fd, short, void *)
arg: 回调的函数的参数。
返回值:成功创建的 event
将事件添加到base上
添加事件到 event_base
int event_add(struct event *ev, const struct timeval *tv);
ev: event_new() 的返回值。
tv:为NULL,不会超时,意为:一直等到事件被触发,回调函数会被调用
为非0,等待期间,检查事件没有被触发,时间到,回调函数依旧被调用
从event_base上摘下事件 【了解】
int event_del(struct event *ev);
ev: event_new() 的返回值。
销毁事件
int event_free(struct event *ev);
ev: event_new() 的返回值。
未决和非未决:
非未决: 没有资格被处理
未决: 有资格被处理,但尚未被处理
event_new --> event —> 非未决 --> event_add --> 未决 --> dispatch() && 监听事件被触发 --> 激活态
–> 执行回调函数 --> 处理态 --> 非未决 event_add && EV_PERSIST --> 未决 --> event_del --> 非未决
带缓冲区的事件 bufferevent
#include <event2/bufferevent.h>
read/write 两个缓冲区. 也是队列实现,读走没,先入先出.
读:有数据—>读回调函数被调用–>使用bufferevent_read()—>读数据
写:使用bufferevent_write()---->向写缓冲中写数据---->该缓冲区有数据自动写出---->写完,回调函数被调用(鸡肋)
创建、销毁bufferevent:
struct bufferevent *ev;
struct bufferevent *bufferevent_socket_new(struct event_base *base, evutil_socket_t fd, enum bufferevent_options options);
base: event_base
fd: 封装到bufferevent内的 fd
options:BEV_OPT_CLOSE_ON_FREE(释放bufferevent是关闭底层传输端口,这将关闭底层套接字,释放底层的bufferevent
返回: 成功创建的 bufferevent事件对象。
void bufferevent_socket_free(struct bufferevent *ev);
给bufferevent设置回调:
对比event: event_new( fd, callback ); event_add() -- 挂到 event_base 上。
bufferevent_socket_new(fd) bufferevent_setcb( callback )
void bufferevent_setcb(struct bufferevent * bufev,
bufferevent_data_cb readcb,
bufferevent_data_cb writecb,
bufferevent_event_cb eventcb,
void *cbarg );
bufev: bufferevent_socket_new() 返回值
readcb: 设置 bufferevent 读缓冲,对应回调 read_cb{ bufferevent_read() 读数据 }
writecb: 设置 bufferevent 写缓冲,对应回调 write_cb { } -- 给调用者,发送写成功通知。 可以 NULL
eventcb: 设置 事件回调。 也可传NULL
typedef void (*bufferevent_event_cb)(struct bufferevent *bev, short events, void *ctx);
void event_cb(struct bufferevent *bev, short events, void *ctx)
{
。。。。。
}
events: BEV_EVENT_CONNECTED:请求的连接过程已经完成,实现客户端时可用
cbarg: 上述回调函数使用的 参数。
read 回调函数类型:
typedef void (*bufferevent_data_cb)(struct bufferevent *bev, void*ctx);
void read_cb(struct bufferevent *bev, void *cbarg )
{
.....
bufferevent_read(); --- read();
}
bufferevent_read()函数的原型:
size_t bufferevent_read(struct bufferevent *bev, void *buf, size_t bufsize);
write 回调函数类型:
int bufferevent_write(struct bufferevent *bufev, const void *data, size_t size);
启动、关闭 bufferevent的 缓冲区:
void bufferevent_enable(struct bufferevent *bufev, short events); 启动
events: EV_READ、EV_WRITE、EV_READ|EV_WRITE
默认、write 缓冲是 enable、read 缓冲是 disable
bufferevent_enable(evev, EV_READ); -- 开启读缓冲。
连接客户端:
socket();connect();
int bufferevent_socket_connect(struct bufferevent *bev, struct sockaddr *address, int addrlen);
bev: bufferevent 事件对象(封装了fd)
address、len:等同于 connect() 参2/3
创建监听服务器:
------ socket();bind();listen();accept();
struct evconnlistener * listner
struct evconnlistener *evconnlistener_new_bind (
struct event_base *base,
evconnlistener_cb cb,
void *ptr,
unsigned flags,
int backlog,
const struct sockaddr *sa,
int socklen);
base: event_base
cb: 回调函数。 一旦被回调,说明在其内部应该与客户端完成, 数据读写操作,进行通信。
该回调不由我们调用,是框架自动调用,因此,知晓参数含义即可,客户端连接才能回调。
ptr: 回调函数的参数
flags: LEV_OPT_CLOSE_ON_FREE: 释放bufferevent时关闭底层传输端口,这将关闭底层套接字,释放底层bufferevent
LEV_OPT_REUSEABLE:端口复用。这两个可以用“|”连接
backlog: listen() 2参。 -1 表最大值
sa:服务器自己的地址结构体
socklen:服务器自己的地址结构体大小。
返回值:成功创建的监听器。
释放监听服务器:
void evconnlistener_free(struct evconnlistener *lev);
服务器端 libevent 创建TCP连接:
- 创建event_base
- 创建bufferevent事件对象。bufferevent_socket_new();
- 使用bufferevent_setcb() 函数给 bufferevent的 read、write、event 设置回调函数。
- 当监听的 事件满足时,read_cb会被调用, 在其内部 bufferevent_read();读
- 使用 evconnlistener_new_bind 创建监听服务器, 设置其回调函数,当有客户端成功连接时,这个回调函数会被调用。
- 封装 listner_cb() 在函数内部。完成与客户端通信。
- 设置读缓冲、写缓冲的 使能状态 enable、disable
- 启动循环 event_base_dispath();
- 释放连接。
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)