课程链接: https://www.bilibili.com/video/BV1iJ411S7UA
课程视频资源和笔记: 链接:https://pan.baidu.com/s/10sKjOHfKKEE4CcZaqlYeTw 提取码:unix
练习代码:https://gitee.com/daniel187/Linux_Network
如何避免死锁:
读写锁: 当读次数远大于写次数时
条件变量指明了共享数据区值不值得访问(有无空位或产品), 锁是访问共享数据区的通行证
信号量semaphore–进化版互斥锁: 1–>N, 保证同步的同时, 提高了并发
sem_wait
:给信号量加锁, sem–, lock
sem_post
:给信号量解锁, sem++,unlock, 同时唤醒阻塞在该信号上的线程
FTP文件传输协议(雏形):
TCP协议侧重数据的传输, http协议注重数据的解释
OSI参考模型:
TCP/IP四层模型:
网络传输数据封装流程:
只有应用层协议在用户态可见, 往下的都处在内核中
TCP/IP数据包封装:
以太网帧中的目的地址和源地址是指MAC地址
ARP协议:根据IP地址获取MAC地址
ARP数据报格式:
刚开始时以太网目的地址
和目的以太网地址
都未知, 都填入FF:FF:FF:FF:FF:FF
IP数据报格式:
TTL: time to live, 跳的次数上限, 每经过一个路由结点, 该值–, 减到0丢弃
UDP:
16位源端口号(上限2^16-1=65535)
16位目的端口号
IP地址可以在网络环境中唯一标定一台主机, 而端口号可以在网络的一台主机上唯一的表示一个进程
IP地址+端口号
可以在网络环境中唯一的标定一个进程
80端口: http协议
多数应用程序使用5000以下的端口
B/S–Browser/Server
优点:
缺点:
C/S–Client/Server
优点:
缺点:
网络套接字(socket): 在通信过程中, 套接字一定是成对出现的
Linux内核套接字实现:
一个文件描述符指向一个套接字(该套接字内部由内核借助两个缓冲区实现)
小端法: 高位存在高地址, 低位存在低地址(计算机intel处理器本地采用)
大端法: 高位存在低地址, 低位存在高地址(网络通信采用)
调用库函数做网络字节序和主机字节序的转换
#include
uint32_t htonl(uint32_t hostlong); //主要针对IP
uint16_t htons(uint16_t hostshort); //主要针对port
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
早期没有int类型, 与之等效的是long类型
由于如192.168.45.2
这种的IP地址为点分十进制
表示, 需要转化为uint32_t
型, 有现成的函数(IPv4和IPv6都可以转换):
int inet_pton(int af,const char* src,void* dst); //p表示点分十进制的ip,n网络上的二进制ip
const char* inet_ntop(int af,const char* src,char* dst,socklen_t size);
int inet_pton(int af,const char* src,void* dst);
参数:
AF_INET
或AF_INET6
返回值:
const char* inet_ntop(int af,const char* src,char* dst,socklen_t size)
参数:
af: AF_INET
或AF_INET6
src:传入参数, 待转换的网络字节序的IP地址
dst:传出参数, 转换后的点分十进制IP地址, 是一块缓冲区
size指定了缓冲区的大小
返回值:
#include
#include
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
/*struct sockaddr是早已废弃的数据结构,已不再使用,用新的时注意强转一下*/
struct sockaddr_in addr;
int bind(int sockfd,(struct sockaddr*)&addr,size);
/*相关结构体定义,在man 7 ip*/
struct sockaddr_in{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
struct in_addr{
uint32_t s_addr;
};
初始化方法:
addr.sin_family=AF_INET/AF_INET6;
addr.sin_port=htons(9527); //端口号为short类型(16bit)
int dst;
inet_pton(AF_INET,"192.168.10.2",(void*)&dst);
addr.sin_addr.s_addr=dst;
/*或者采取下面的方法*/
addr.sin_addr.s_addr=htonl(INADDR_ANY) //取出系统中任意有效的IP地址
socket模型创建流程分析:模型中需要3个套接字
socket()
–创建一个套接字, 用fd索引
bind()
–绑定IP和port
listen()
–设置监听上限(同时与Server建立连接数)
accpet()
–阻塞监听客户端连接(传入一个上面创建的套接字, 传出一个连接的套接字)
在客户端中的connect()
中绑定IP和port, 并建立连接
socket创建一个套接字
#include
#include
int socket(int domain,int type,int protocol);
1.domain指定使用的协议(IPv4或IPv6)
2.type指定数据传输协议(流式或报式)
3.指定代表协议(一般传0)
成功返回新套接字的fd, 失败返回-1并设置errno
bind给socket绑定一个地址结构(IP+port)
#include
#include
int bind(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
struct sockaddr_in addr;
addr.sin_family=AF_INET/AF_INET6;
addr.sin_port=htons(9527); //端口号为short类型(16bit)
int dst;
inet_pton(AF_INET,"192.168.10.2",(void*)&dst);
addr.sin_addr.s_addr=dst;
/*或者采取下面的方法*/
addr.sin_addr.s_addr=htonl(INADDR_ANY) //取出系统中任意有效的IP地址
/*struct sockaddr是早已废弃的数据结构,已不再使用,用新的时注意强转一下*/
int bind(int sockfd,(struct sockaddr*)&addr,sizeof(addr));
addr.family应该与domain保持一致
成功返回0, 失败返回-1
listen设置最大连接数或者说能同时进行三次握手的最大连接数
int listen(int sockfd,int backlog);
sockfd
:仍然传入socket函数的返回值
backlog
:上限数值, 最大128
成功返回0, 失败返回-1并设置errno
accept阻塞等待客户端建立连接, 成功的话返回一个与客户端成功连接的socket文件描述符
int accept(int sockfd,sockaddr* addr, socklen_t* addrlen);
sockfd
–socket函数的返回值;
addr
–传出参数, 成功与Sever建立连接的那个客户端的地址结构
addrlen
–传入传出参数
socklen_t clit_addr_len=sizeof(addr)
入: 传入addr的大小
出: 客户端addr的实际大小
返回值:
成功: 返回能与Server进行通信的socket对应的文件描述符
失败: 返回-1并设置errno
connect使用现有的socket与服务器建立连接
int connect(int sockfd,const struct sockaddr* addr,socklen_t addrlen);
sockfd
:socket函数返回值
addr
:传入参数, 服务器的地址结构
addrlen
:服务器地址结构的长度
返回值: 成功返回0, 失败返回-1并设置errno
如果不适用bind()
函数绑定客户端的地址结构, 会采用**“隐式绑定”**
Server:
socket()
–创建socketbind()
–绑定Server地址结构listen()
–设置监听上限accept()
–阻塞监听客户端建立连接read()
–读socket获取客户端数据toupper()
–事务处理write()
–写回数据到客户端close()
Client:
socket()
–创建socketconnect()
–与服务器建立连接write()
–向socket(Server)写入数据read()
–读出处理后的数据close()
测试命令: nc 127.0.0.1 9527
–脑残命令: 向这个服务发送信息并打印回执
//server-simple.c
#define SERVER_PORT 9527
void perr_exit(const char* str) {
perror(str);
exit(1);
}
int main() {
//创建监听套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perr_exit("socket error");
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//绑定地址结构
int ret = bind(sockfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret == -1) {
perr_exit("bind error");
}
ret = listen(sockfd, 128);
if (ret == -1) {
perr_exit("listen error");
}
struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
//阻塞监听
int clientfd = accept(sockfd, (struct sockaddr*)&client_addr, &client_addr_len);
if (clientfd == -1) {
perr_exit("accept error");
}
char client_ip[128];
printf("client ip = %s, client port = %d\n", inet_ntop(AF_INET, &(client_addr.sin_addr.s_addr), client_ip, sizeof(client_ip)), ntohs(client_addr.sin_port));
char buf[128];
while (1) {
ssize_t len = read(clientfd, buf, sizeof(buf));
for (int i = 0; i < len; ++i) {
buf[i] = toupper(buf[i]);
}
write(clientfd, buf, len);
sleep(1);
}
close(sockfd);
close(clientfd);
return 0;
}
获取客户端地址结构:
printf("client ip = %s, client port = %d\n", inet_ntop(AF_INET, &(client_addr.sin_addr.s_addr), client_ip, sizeof(client_ip)), ntohs(client_addr.sin_port));
client_ip
是前面定义的客户端IP字符串的缓冲区, 大小为1024
//client-simple.c
#define SERVER_PORT 9527
void perr_exit(const char* str) {
perror(str);
exit(1);
}
int main() {
int clientfd = socket(AF_INET, SOCK_STREAM, 0);
if (clientfd < 0) {
perr_exit("socket error");
}
struct sockaddr_in server_addr;
server_addr.sin_family = AF_INET;
server_addr.sin_port = htons(SERVER_PORT);
int server_ip;
inet_pton(AF_INET, "192.168.163.128", &server_ip);
server_addr.sin_addr.s_addr = server_ip;
int ret = connect(clientfd, (struct sockaddr*)&server_addr, sizeof(server_addr));
if (ret < 0) {
perr_exit("connect error");
}
const char* str = "hello, world";
char buf[128];
for (int i = 0; i < 10; ++i) {
write(clientfd, str, strlen(str));
read(clientfd, buf, sizeof(buf));
printf("%s\n", buf);
sleep(1);
}
close(clientfd);
return 0;
}
Linux的特殊文件类型(伪文件): 管道, 套接字, 块设备, 字符设备, 对于套接字: 一个fd可以索引读写两个缓冲区
SYN包(1号包)不携带数据(0)
3次握手由内核完成, 体现在用户态的是accpt()
函数和connect()
函数调用成功
采用滑动窗口增大传输速率:
批量发送, 服务器ACK回执最后一个数据包, 且可以看到, 滑动窗口的大小win是动态变化的
半关闭: 由原来的双工通信变为了单工通信, 客户端只能接受数据(缓冲区中的数据)
实现原理: 关闭了客户端套接字的写缓冲区
之所以半关闭后Client仍能向Server发送ACK数据包, 是因为Client关闭的只是写缓冲, 连接还在
连接在内核层面, 写缓冲在用户层面
如果Server没有收到Client最后发来的ACK数据包, 它会一直发送FIN数据包, 直到Client回执为止
TCP报文格式:
窗口大小16位–滑动窗口不能超过65535
三次握手:
四次挥手:
通信时序与代码对应关系:
服务器经过socket, bind, listen, accept后, 阻塞监听
客户端用socket建立套接字后, 调用connect函数, 对应到内核就是发送SYN标志位, 开始三次握手
无论是谁调用close, 对应到kernel就是发了一个FIN数据包
为了保证程序的健壮性, 错误处理是必要的, 但如果用以前的sys_err()函数会很零散, 打乱了代码的整体逻辑, 于是提出错误处理函数封装:将原函数首字母大写进行错误处理, 这样还可以跳到原函数的manPage
//wrap.c
#include "wrap.h"
void perr_exit(const char* str) {
perror(str);
exit(1);
}
int Accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen) {
int n;
again:
if ((n = accept(sockfd, addr, addrlen)) < 0) {
/*If the error is caused by a singal,not due to the accept itself,try again*/
if ((errno == EINTR) || (errno == ECONNABORTED)) {
goto again;
} else {
perr_exit("accept error");
}
}
return n;
}
int Bind(int sockfd, const struct sockaddr* addr, socklen_t addrlen) {
int n = bind(sockfd, addr, addrlen);
if (n < 0) {
perr_exit("bind error");
}
return n;
}
int Connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen) {
int n = connect(sockfd, addr, addrlen);
if (n < 0) {
perr_exit("connect error");
}
return n;
}
int Listen(int sockfd, int backlog) {
int n = listen(sockfd, backlog);
if (n < 0) {
perr_exit("listen error");
}
return n;
}
int Socket(int domain, int type, int protocol) {
int n = socket(domain, type, protocol);
if (n < 0) {
perr_exit("listen error");
}
return n;
}
ssize_t Readn(int fd, void* vptr, size_t n) {
size_t nleft = n;
ssize_t nread;
char* ptr = vptr;
while (nleft > 0) {
if ((nread = read(fd, ptr, nleft)) < 0) {
if (errno == EINTR) {
nread = 0;
} else {
return -1;
}
} else if (nread == 0) {
break;
}
nleft = nleft - nread;
ptr = ptr - nread;
}
return n - nleft;
}
ssize_t Writen(int fd, const void* vptr, size_t n) {
size_t nleft = n;
ssize_t nwritten;
const char* ptr = vptr;
while (nleft > 0) {
if ((nwritten = write(fd, ptr, nleft)) <= 0) {
if ((errno == EINTR) && (nwritten < 0)) {
nwritten = 0;
} else {
return -1;
}
}
nleft = nleft - nwritten;
ptr = ptr - nwritten;
}
return n;
}
Socket(); //创建监听套接字lfd
Bind(); //绑定服务器地址结构
Listen(); //设置监听上限
while(1){
cfd=Accept();
pid=fork();
if(pid==0){
close(lfd); //子进程用不到lfd
read(cfd);
数据处理;
write(cfd);
}else if(pid>0){
close(cfd); //父进程用不到cfd
}
}
总结:
子进程:c
父进程:
服务器端伪码描述:
Socket(); //创建监听套接字lfd
Bind(); //绑定服务器地址结构
Listen(); //设置监听上限
while(1){
cfd=Accept(lfd,);
pthread_create(&tid,NULL,&tfn,NULL);
/*
*detach设置线程分离,但是这样不能获取线程退出状态
*如果想获取子线程退出状态,用pthread_join()函数,但是这样会造成主线程阻塞
*解决方案:create出一个新的子线程调用pthread_join()专门用于回收
*/
pthread_detach(tid);
}
//子线程:
void* tfn(void* arg){
close(lfd);
read(cfd,);
数据处理;
write(cfd,);
pthread_exit((void*)out); //线程退出状态
}
注意: 兄弟线程之间是可以进行回收的, 但是兄弟进程之间不可以进行回收, 爷孙也不行
将结构体中的内容清零:
#include
bzero(&serverAddr,sizeof(serverAddr));
多进程并发服务器模型程序:
//server-multiprocess.c
#include "wrap.h"
#define SERVER_PORT 9527
void catch_child(int signum) {
while (waitpid(0, NULL, WNOHANG) > 0)
;
}
int main(int argc, char* argv[]) {
int sockfd = Socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
Listen(sockfd, 128);
pid_t pid;
int clientfd;
while (1) {
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
clientfd = Accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
pid = fork();
if (pid < 0) {
perr_exit("fork error");
} else if (pid == 0) {
close(sockfd);
break;
} else {
close(clientfd);
struct sigaction act;
act.sa_handler = catch_child;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
int ret = sigaction(SIGCHLD, &act, NULL);
if (ret < 0) {
perr_exit("sigemptyset error");
}
}
}
//子进程专属执行流
if (pid == 0) {
char buf[128];
while (1) {
ssize_t n = read(clientfd, buf, sizeof(buf));
if (n == 0) {
close(clientfd);
exit(1);
}
write(STDOUT_FILENO, buf, n);
for (int i = 0; i < n; ++i) {
buf[i] = toupper(buf[i]);
}
write(clientfd, buf, n);
}
}
return 0;
}
服务器测试IP地址调整, 设置虚拟机网络为桥接模式
在虚拟机中手动指定IP地址:
多机测试时注意指定IP地址
将本地程序拷贝到远端服务器上去的命令: scp -r ./test/ [email protected]:/home/liudanbing/socket_server
//server-multithread.c
#include "wrap.h"
#define SERVER_PORT 9527
struct AddrFd {
struct sockaddr_in clientaddr;
int clientfd;
};
typedef struct AddrFd AddrFd;
void* work(void* arg) {
AddrFd* p_addr_fd = (AddrFd*)arg;
char client_ip[128];
printf("new connection: ip = %s, port = %d\n", inet_ntop(AF_INET, &(p_addr_fd->clientaddr.sin_addr), client_ip, sizeof(client_ip)), htons(p_addr_fd->clientaddr.sin_port));
char buf[128];
while (1) {
ssize_t n = read(p_addr_fd->clientfd, buf, sizeof(buf));
if (n == 0) {
printf("connection closed\n");
break;
}
write(STDOUT_FILENO, buf, n);
for (int i = 0; i < n; ++i) {
buf[i] = toupper(buf[i]);
}
write(p_addr_fd->clientfd, buf, n);
}
close(p_addr_fd->clientfd);
pthread_exit(0);
}
int main() {
int sockfd = Socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
Listen(sockfd, 128);
pthread_t tid;
AddrFd addr_fd[128];
int i = 0;
while (1) {
socklen_t clientaddr_len = sizeof(addr_fd[i].clientaddr);
addr_fd[i].clientfd = Accept(sockfd, (struct sockaddr*)&(addr_fd[i].clientaddr), &clientaddr_len);
pthread_create(&tid, NULL, work, (void*)&addr_fd[i]);
pthread_detach(tid);
i = (i + 1) % 128;
}
close(sockfd);
return 0;
}
这种多线程服务器是你能写出的最次最次的具有使用价值的服务器程序了
read返回值:
使用netstat -apn | grep client
或netstat -apn | grep 9527
查看TCP连接状态:
只有主动关闭连接一方会经历TIME_WAIT状态, 换句话说也只有主动关闭连接的一方会经历等待2MSL时长
被动接受连接请求端: CLOSE–LISTEN–接收SYN–LISTEN–发送ACK,SYN–SYN_RECD–接受ACK–ESTABLISHED(数据通信状态)
被动关闭连接: ESTABLISHED(数据通信状态)–接受FIN–ESTABLISHED(数据通信状态)–发送ACK–CLOSE_WAIT(说明对端[主动关闭连接请求端]处于半关闭状态)–发送FIN–LAST_ACK–接受ACK–CLOSE
2MSL存在的意义: 保证最后一个ACK回执能被Server接受到, 如果ACK丢失, Server重发FIN, 则在等待时长内能及时处理
2MSL时常一定出现在主动关闭连接请求一端
其他状态: 建立连接时, 如果最后一个ACK没有收到, RST重启
为了不等2MSL的时长
设置套接字复用函数原型:
int setsockopt(int sockfd,int level,int optname,const void* optval,socklen_t optlen);
一个最佳实践:
int opt=1; //只有两种取值,0或1
setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
成功返回0, 失败返回-1设置errno
半关闭及shutdown函数:
当TCP连接中A发送FIN请求关闭, B端回应ACK后(A端进入FIN_WAIT_2状态), B没有立即发送FIN给A时, A方处于半连接状态, 此时A可以接受B发送的数据, 但是A已经不能再向B发送数据
int shotdown(int connectFd,int how);
how的取值:
使用close关闭一个连接, 他只是减少描述符的引用次数, 并不直接关闭连接, 只用当描述符引用次数为0时才关闭连接
而shutdown直接全部关闭所有描述符, 这会影响其他使用该套接字的进程
这种思想很像CPU对IO的处理的发展历程, select的地位就像中断管理器, IO设备有中断请求时才通知CPU, 对应的, 只有当客户端有连接请求时才会通知server进行处理. 也就是说只要server收到通知, 就一定有数据待处理或连接待响应, 不会再被阻塞而浪费资源了
select函数参数简介:
#include
#include
#include
int select(int nfds, fd_set* readfds, fd_set* writefds,fd_set* exceptfds, struct timeval* timeout);
nfds
-所监听的套接字文件描述符的最大值+1, 在select内部用作一个for循环的上限
fd_set*
-都是传入传出参数
重点在于readfds:当客户端有数据发到服务器上时, 触发服务器的可读事件. 后面两个一般传NULL
三个传入传出参数都是位图, 每个二进制位代表了一个文件描述符的状态
传入的是你想监听的文件描述符集合(对应位置一), 传出来的是实际有事件发生的文件描述符集合(将没有事件发生的位置零)
返回值:
关于timeout:
由于对位图的运算涉及到位操作, 系统提供了相应的函数:
void FD_CLR(int fd,fd_set* set); //将给定的套接字fd从位图set中清除出去
void FD_SET(int fd,fd_set* set); //将给定的套接字fd设置到位图set中
void FD_ZERO(fd_set* set); //将整个位图set置零
int FD_ISSET(int fd,fd_set* set); //检查给定的套接字fd是否在位图里面,返回0或1
select实现多路IO转接设计思路:
listenFd=Socket(); //创建套接字
Bind(); //绑定地址结构
Listen(); //设置监听上限
fd_set rset; //创建读监听集合
fd_set allset;
FD_ZERO(&allset); //将读监听集合清空
FD_SET(listenFd,&allset); //将listenFd添加到所有读集合当中
while(1){
rset=allset; //保存监听集合
ret=select(listenFd,&rset,NULL,NULL,NULL); //监听文件描述符集合对应事件
if(ret>0){
if(FD_ISSET(listenFd,&rset)){
cfd=accept();
FD_SET(cfd,&allset); //添加到监听通信描述符集合中
}
for(i=listenFd+1;i<=cfd;++i){
FD_ISSET(i,&rset); //有read,write事件
read();
toupper();
write();
}
}
}
select实现多路IO转接代码实现:
#include "wrap.h"
#define SERVER_PORT 9527
int main(int argc, char* argv[]) {
int sockfd = Socket(AF_INET, SOCK_STREAM, 0);
int opt = 1;
Setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
serveraddr.sin_port = htons(SERVER_PORT);
serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
Bind(sockfd, (struct sockaddr*)&serveraddr, sizeof(serveraddr));
Listen(sockfd, 128);
fd_set rdset, allset;
FD_ZERO(&allset);
FD_SET(sockfd, &allset);
int maxfd = sockfd;
while (1) {
rdset = allset;
int N = select(maxfd + 1, &rdset, NULL, NULL, NULL);
if (N == -1) {
perr_exit("select error");
}
if (FD_ISSET(sockfd, &rdset)) {
struct sockaddr_in clientaddr;
socklen_t clientaddr_len = sizeof(clientaddr);
int clientfd = Accept(sockfd, (struct sockaddr*)&clientaddr, &clientaddr_len);
FD_SET(clientfd, &allset);
maxfd = (maxfd > clientfd) ? maxfd : clientfd;
if (N == 1) {
continue;
}
}
for (int i = sockfd; i <= maxfd; ++i) {
if (FD_ISSET(i, &rdset)) {
char buf[128];
ssize_t n = read(i, buf, sizeof(buf));
if (n == 0) {
printf("connection closed\n");
FD_CLR(i, &allset);
} else {
write(STDOUT_FILENO, buf, n);
for (int j = 0; j < n; ++j) {
buf[j] = toupper(buf[j]);
}
write(i, buf, n);
}
}
}
}
close(sockfd);
return 0;
}
这个版本的缺陷:当你只需要监听几个指定的套接字时, 需要对整个1024的数组进行轮询, 效率降低
select优缺点:
缺点:
优点: 跨平台, 各种系统都能支持
添加一个自己定义数组提高效率:
#include "033-035_wrap.h"
#define SERVER_PORT 9527
int main(int argc, char *argv[]){
int i, j, n, maxi;
/*将需要轮询的客户端套接字放入数组client[FD_SETSIZE]*/
int nready, client[FD_SETSIZE];
int listenFd, connectFd, maxFd, socketFd;
char buf[BUFSIZ], str[INET_ADDRSTRLEN];
struct sockaddr_in serverAddr, clientAddr;
socklen_t clientAddrLen;
/*得到监听套接字*/
listenFd = Socket(AF_INET, SOCK_STREAM, 0);
/*定义两个集合,将listenFd放入allset集合当中*/
fd_set rset, allset;
FD_ZERO(&allset);
FD_SET(listenFd, &allset);
/*设置地址复用*/
int opt = 1;
setsockopt(listenFd, SOL_SOCKET, SO_REUSEADDR, (void *)&opt, sizeof(opt));
/*填写服务器地址结构*/
bzero(&serverAddr, sizeof(serverAddr));
serverAddr.sin_family = AF_INET;
serverAddr.sin_addr.s_addr = htonl(INADDR_ANY);
serverAddr.sin_port = htons(SERVER_PORT);
/*绑定服务器地址结构*/
Bind(listenFd, (struct sockaddr *)&serverAddr, sizeof(serverAddr));
Listen(listenFd, 128);
/*将listenFd设置为数组中最大的Fd*/
maxFd = listenFd;
maxi = -1;
/*初始化自己的数组为-1*/
for (i = 0; i < FD_SETSIZE; ++i)
client[i] = -1;
while (1){
/*把allset给rest,让他去用*/
rset = allset;
nready = select(maxFd + 1, &rset, NULL, NULL, NULL);
if (nready == -1)
perr_exit("select error");
/*如果有了新的连接请求,得到connectFd,并将其放入自定义数组中*/
if (FD_ISSET(listenFd, &rset)){
clientAddrLen = sizeof(clientAddr);
connectFd = Accept(listenFd, (struct sockaddr *)&clientAddr, &clientAddrLen);
printf("Recived from %s at PORT %d\n", inet_ntop(AF_INET, &(clientAddr.sin_addr.s_addr), str, sizeof(str)), ntohs(clientAddr.sin_port));
for (i = 0; i < FD_SETSIZE; ++i)
if (client[i] < 0){
client[i] = connectFd;
break;
}
/*自定义数组满了*/
if(i==FD_SETSIZE){
fputs("Too many clients\n",stderr);
exit(1);
}
/*connectFd加入监听集合*/
FD_SET(connectFd, &allset);
/*更新最大的Fd*/
if (maxFd < connectFd)
maxFd = connectFd;
/*更新循环上限*/
if(i>maxi)
maxi=i;
/*select返回1,说明只有建立连接请求,没有数据传送请求,跳出while循环剩余部分(下面的for循环轮询过程)*/
if (--nready == 0)
continue;
}
/*select返回不是1,说明有connectFd有数据传输请求,遍历自定义数组*/
for (i = 0; i <= maxi; ++i){
if((socketFd=client[i])<0)
continue;
/*遍历检查*/
if (FD_ISSET(socketFd, &rset)){
/*read返回0说明传输结束,关闭连接*/
if ((n=read(socketFd,buf,sizeof(buf)))==0){
close(socketFd);
FD_CLR(socketFd, &allset);
client[i]=-1;
}else if(n>0){
for (j = 0; j < n; ++j)
buf[j] = toupper(buf[j]);
write(socketFd, buf, n);
write(STDOUT_FILENO, buf, n);
}
/*不懂:需要处理的个数减1?*/
if(--nready==0)
break;
}
}
}
close(listenFd);
return 0;
}
#include
int poll(struct pollfd* fds, nfds_t nfds, int timeout);
/*fds监听的文件描述符数组*/
struct pollfd {
int fd; /*待监听的文件描述符*/
short events; /*requested events:待监听的文件描述符对应的监听事件->POLLIN,POLLOUT,POLLERR*/
short revents; /*returned events:传入时给0,如果满足对应事件的话被置为非零->POLLIN,POLLOUT,POLLERR*/
};
/*自定义结构体数组并初始化*/
struct pollfd pfds[1024];
pfds[0].fd=lfd;
pfds[0].events=POLLIN;
pfds[0].revents=0;
pfds[1].fd=cfd1;
pfds[1].events=POLLIN;
pfds[1].revents=0;
pfds[2].fd=cfd2;
pfds[2].events=POLLIN;
pfds[2].revets=0;
...
while(1){
//pollfd=fds, nfds=5, timeout=-1
int ret=poll(fds,5,-1);
/*轮询是否有POLLIN需求*/
for(i=0;i<5;i++)
if(pfds[i].revents&POLLIN){
Accept();
Read()/Write();
}
}
read函数返回值:
#include"033-035_wrap.h"
#define MAXLINE 80
#define SERVER_PORT 9527
#define OPEN_MAX 1024
int main(int argc,char* argv[]){
int ret=0;
/*poll函数返回值*/
int nready=0;
int i,j,maxi;
int connectFd,listenFd,socketFd;
ssize_t n;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
socklen_t clientLen;
/*创建结构体数组*/
struct pollfd client[OPEN_MAX];
/*创建客户端地址结构和服务器地址结构*/
struct sockaddr_in clientAddr,serverAddr;
/*得到监听套接字listenFd*/
listenFd=Socket(AF_INET,SOCK_STREAM,0);
/*设置地址可复用*/
int opt=0;
ret=setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
if(ret==-1)
perr_exit("setsockopt error");
/*向服务器地址结构填入内容*/
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
serverAddr.sin_port=htons(SERVER_PORT);
/*绑定服务器地址结构到监听套接字,并设置监听上限*/
Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr));
Listen(listenFd,128);
/*初始化第一个pollfd为监听套接字*/
client[0].fd=listenFd;
client[0].events=POLLIN;
/*将pollfd数组的余下内容的fd属性置为-1*/
for(i=1;i<OPEN_MAX;++i)
client[i].fd=-1;
maxi=0;
while(1){
/*nready是有多少套接字有POLLIN请求*/
nready=poll(client,maxi+1,-1);
if(nready==-1)
perr_exit("poll error");
/*如果listenFd的revents有POLLIN请求,则调用Accept函数得到connectFd*/
if(client[0].revents&POLLIN){
clientLen=sizeof(clientAddr);
connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientLen);
/*打印客户端地址结构信息*/
printf("Received from %s at PORT %d\n",inet_ntop(AF_INET,&(clientAddr.sin_addr.s_addr),str,sizeof(str)),ntohs(clientAddr.sin_port));
/*将创建出来的connectFd加入到pollfd数组中*/
for(i=1;i<OPEN_MAX;++i)
if(client[i].fd<0){
client[i].fd=connectFd;
break;
}
if(i==OPEN_MAX)
perr_exit("Too many clients,I'm going to die...");
/*当没有错误时,将对应的events设置为POLLIN*/
client[i].events=POLLIN;
if(i>maxi)
maxi=i;
if(--nready<=0)
continue;
}
/*开始从1遍历pollfd数组*/
for(i=1;i<=maxi;++i){
/*到结尾了或者有异常*/
if((socketFd=client[i].fd)<0)
continue;
/*第i个客户端有连接请求,进行处理*/
if(client[i].revents&POLLIN){
if((n=read(socketFd,buf,sizeof(buf)))<0){
/*出错时进一步判断errno*/
if(errno=ECONNRESET){
printf("client[%d] aborted connection\n",i);
close(socketFd);
client[i].fd=-1;
}else
perr_exit("read error");
}else if(n==0){
/*read返回0,说明读到了结尾,关闭连接*/
printf("client[%d] closed connection\n",i);
close(socketFd);
client[i].fd=-1;
}else{
/*数据处理*/
for(j=0;j<n;++j)
buf[j]=toupper(buf[j]);
Writen(STDOUT_FILENO,buf,n);
Writen(socketFd,buf,n);
}
if(--nready==0)
break;
}
}
}
return 0;
}
优缺点分析:
优点:
缺点:
cat /proc/sys/fs/file-max
:查看当前计算机所能打开的最大文件个数, 受硬件影响;
ulimit -a
:当前用户进程所能打开的最大文件描述符个数(缺省为1024);
修改配置文件:
sudo vim /etc/security/limits.conf
:
* soft nofile 65536
:设置默认值(可以直接借助命令修改);
* hard nofile 100000
:设置修改上限;
ulimit -n 17000
:更改最大个数;
epoll_create()
会创建一个监听红黑树;
#include
int epoll_create(int size);
size
:创建红黑树的监听节点数量(仅供内核参考);
返回值: 成功返回指向新创建的红黑树的根节点的fd, 失败返回-1并设置errno;
epoll_ctl()
会操作监听红黑树;
#include
int epoll_ctl(int epfd, int op, int fd, struct epoll_event* 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 */
};
epfd: epoll_create的返回值;
op: 对该监听红黑树所做的操作:
fd: 待监听的fd;
event: struct poll_event结构体地址;
int epoll_wait(int epfd, struct epoll_event* events,int maxevents, int timeout);
epfd: epoll_create的返回值;
events: 传出参数, 是一个数组, 满足监听条件的那些fd结构体;
maxevents: 数组中元素的总个数, 例如如果定义了struct epoll_events events[1024], 那么他就是1024;
timeout:
返回值
#include "033-035_wrap.h"
#define SERVER_PORT 9527
#define MAXLINE 80
#define OPEN_MAX 1024
int main(int argc,char* argv[]){
int i=0,n=0,num=0;
int clientAddrLen=0;
int listenFd=0,connectFd=0,socketFd=0;
ssize_t nready,efd,res;
char buf[MAXLINE],str[INET_ADDRSTRLEN];
struct sockaddr_in serverAddr,clientAddr;
/*创建一个临时节点temp和一个数组ep*/
struct epoll_event temp;
struct epoll_event ep[OPEN_MAX];
/*创建监听套接字*/
listenFd=Socket(AF_INET,SOCK_STREAM,0);
/*设置地址可复用*/
int opt=1;
setsockopt(listenFd,SOL_SOCKET,SO_REUSEADDR,(void*)&opt,sizeof(opt));
/*初始化服务器地址结构*/
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
serverAddr.sin_port=htons(SERVER_PORT);
/*绑定服务器地址结构*/
Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr));
/*设置监听上限*/
Listen(listenFd,128);
/*创建监听红黑树*/
efd=epoll_create(OPEN_MAX);
if(efd==-1)
perr_exit("epoll_create error");
/*将listenFd加入监听红黑树中*/
temp.events=EPOLLIN;
temp.data.fd=listenFd;
res=epoll_ctl(efd,EPOLL_CTL_ADD,listenFd,&temp);
if(res==-1)
perr_exit("epoll_ctl error");
while(1){
/*阻塞监听写事件*/
nready=epoll_wait(efd,ep,OPEN_MAX,-1);
if(nready==-1)
perr_exit("epoll_wait error");
/*轮询整个数组(红黑树)*/
for(i=0;i<nready;++i){
if(!(ep[i].events&EPOLLIN))
continue;
/*如果是建立连接请求*/
if(ep[i].data.fd==listenFd){
clientAddrLen=sizeof(clientAddr);
connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientAddrLen);
printf("Received from %s at PORT %d\n",inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,str,sizeof(str)),ntohs(clientAddr.sin_port));
printf("connectFd=%d,client[%d]\n",connectFd,++num);
/*将新创建的连接套接字加入红黑树*/
temp.events=EPOLLIN;
temp.data.fd=connectFd;
res=epoll_ctl(efd,EPOLL_CTL_ADD,connectFd,&temp);
if(res==-1)
perr_exit("epoll_ctl errror");
}else{
/*不是建立连接请求,是数据处理请求*/
socketFd=ep[i].data.fd;
n=read(socketFd,buf,sizeof(buf));
/*读到0说明客户端关闭*/
if(n==0){
res=epoll_ctl(efd,EPOLL_CTL_DEL,socketFd,NULL);
if(res==-1)
perr_exit("epoll_ctl error");
close(socketFd);
printf("client[%d] closed connection\n",socketFd);
}else if(n<0){
/*n<0报错*/
perr_exit("read n<0 error");
res=epoll_ctl(efd,EPOLL_CTL_DEL,socketFd,NULL);
close(socketFd);
}else{
/*数据处理*/
for(i=0;i<n;++i)
buf[i]=toupper(buf[i]);
write(STDOUT_FILENO,buf,n);
Writen(socketFd,buf,n);
}
}
}
}
close(listenFd);
close(efd);
return 0;
}
epoll实现多路IO转接思路回顾:
epoll有两种事件模型:
EdgeTriggered:边沿触发, 只有数据到来才触发, 不管缓冲区中是否还有数据;
LevelTriggered:水平触发, 只要有数据都会触发(默认模式);
#define MAXLINE 10
int main(){
int efd,i;
int pfd[2];
pid_t pid;
char buf[MAXLINE],ch='a';
pipe(pfd);
pid=fork();
/*子进程每次向管道的写端写入10个字节:aaaa\nbbbb\n*/
if(pid==0){
close(pfd[0]); //子进程写
while(1){
for(i=0;i<(MAXLINE>>1);++i)
buf[i]=ch;
buf[i-1]='\n';
ch++;
for(;i<MAXLINE;++i)
buf[i]=ch;
buf[i-1]='\n';
ch++;
write(pfd[1],buf,sizeof(buf));
sleep(3);
}
close(pfd[1]);
}else if(pid>0){
struct epoll_event event;
struct epoll_event resevent[10];
int res,len;
close(pfd[1]);
efd=epoll_create(10);
/*设置边沿触发方式*/
event.events=EPOLLIN|EPOLLET;
//event.events=EPOLLIN;
event.data.fd=pfd[0];
epoll_ctl(efd,EPOLL_CTL_ADD,pfd[0],&event);
/*父进程死循环阻塞监听,每次读取一半*/
while(1){
res=epoll_wait(efd,resevent,10,-1);
printf("res=%d\n",res);
if(resevent[0].data.fd==pfd[0]){
len=read(pfd[0],buf,MAXLINE/2);
write(STDOUT_FILENO,buf,len);
}
}
close(pfd[0]);
close(efd);
}else{
perr_exit("fork error");
}
return 0;
}
现象:
当设置水平触发时, 只要管道中有数据, epoll_wait就会返回, 触发父进程读取数据, 所以虽然父进程每次只读取一半的数据, 但读完一半后剩下的一半又会触发父进程读取, 所以10个字节的数据都会显示出来;
当设置边沿触发时, 父进程阻塞读取, 而只有当子进程向管道中进行一次写入时才会触发父进程进行读取, 所以每次只会打印一半的数据;
总结:
服务器程序:
#define SERVER_PORT 9527
#define MAXLINE 10
int main(int argc,char* argv[]){
struct sockaddr_in serverAddr,clientAddr;
socklen_t clientAddrLen=0;
int listenFd,connectFd;
char buf[MAXLINE];
char str[INET_ADDRSTRLEN];
int len=0;
listenFd=Socket(AF_INET,SOCK_STREAM,0);
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
serverAddr.sin_port=htons(SERVER_PORT);
Bind(listenFd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr));
Listen(listenFd,128);
struct epoll_event event;
struct epoll_event resevent[10];
int res,efd;
efd=epoll_create(10);
event.events=EPOLLIN|EPOLLET;
//event.events=EPOLLIN;
printf("Accepting connections...\n");
clientAddrLen=sizeof(clientAddr);
connectFd=Accept(listenFd,(struct sockaddr*)&clientAddr,&clientAddrLen);
printf("Received from %s at port %d\n",inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,str,sizeof(str)),ntohs(clientAddr.sin_port));
event.data.fd=EPOLLIN|EPOLLET;
epoll_ctl(efd,EPOLL_CTL_ADD,connectFd,&event);
while(1){
res=epoll_wait(efd,resevent,10,-1);
printf("res=%d\n",res);
if(resevent[0].data.fd=connectFd){
len=read(connectFd,buf,MAXLINE/2);
write(STDOUT_FILENO,buf,len);
}
}
return 0;
}
客户端程序:
int main(int argc,char* argv[]){
struct sockaddr_in serverAddr;
char buf[MAXLINE];
int socketFd,i;
char ch='a';
socketFd=Socket(AF_INET,SOCK_STREAM,0);
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(SERVER_PORT);
inet_pton(AF_INET,"127.0.0.1",&serverAddr.sin_addr);
Connect(socketFd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
while(1){
for(i=0;i<(MAXLINE>>1);++i)
buf[i]=ch;
buf[i-1]='\n';
ch++;
for(;i<MAXLINE;++i)
buf[i]=ch;
buf[i-1]='\n';
ch++;
write(socketFd,buf,sizeof(buf));
sleep(3);
}
close(socketFd);
return 0;
}
如果epoll_wait设置为阻塞模式, 则当你调用Readn/Readline这种自己会阻塞的函数时, 会出大问题: 阻塞在了Read函数上, 不会被唤醒了;
LevelTriggered是缺省的工作方式, 同时支持block和none-block模式, 在这种做法中, 内核告诉你一个文件描述符是否就绪了, 然后你可以对这个就绪的fd进行IO操作. 如果你不做任何操作, 内核还是会继续通知你, 所以, 这种模式编程出错的可能性要小一点, 传统的select/poll都是这种模型的代表;
EdgeTriggered是高速工作方式, 只支持none-block模式. 在这种模式下, 当文件描述符从未就绪变为就绪时, 内核通过epoll告诉你. 然后他会假设你知道文件描述符已经就绪, 并且不会再为那个文件描述符发送更多的就绪通知. 如果一直不对这个fd作IO操作(从而导致它再次变为未就绪), 内核不会发送更多的通知(only once);
优点: 高效, 能突破1024文件描述符限制;
缺点: 只支持Linux, 不能跨平台;
LT:缓冲区有多少就读多少;
ET:不全部都读, 只读一部分, 剩下的如果没用可以直接丢弃掉;
比如对于一个大文件, 你只想读取文件头中的属性信息, 就可以采用ET模式;
采用非阻塞模式, 放入一个循环中: 忙轮询;
epoll: ET模式+非阻塞+回调函数void* ptr;
比如:
struct evt{ int fd; void(*func)(int fd);}*ptr;
函数指针封装在结构体里, 让他自动回调(什么叫用C实现面向对象啊,战术后仰);
原来的步骤:
socket, bind, listen;
epoll_create创建监听红黑树, 返回epfd;
epoll_ctl()向树上添加一个监听fd;
while(1){
epoll_wait监听;
对应监听fd有事件产生;
返回监听满足数组, 判断返回数组元素;
若lfd满足, 调用Accept建立连接;
若cfd满足, read(), 小写转大写, write写回去;
}
考虑到实际的网络情况, 对端可能半关闭或滑动窗口已满, 可能写失败, 所以在写之前要检查可写与否;
反应堆模型:
socket, bind, listen;
epoll_create创建监听红黑树, 返回epfd;
epoll_ctl()向树上添加一个监听fd;
while(1){
epoll_wait监听;
对应监听fd有事件产生;
返回监听满足数组, 判断返回数组元素;
若lfd满足, 调用Accept建立连接;
若cfd满足, read(), 小写转大写;
将cfd从监听红黑树上摘下;
EPOLLIN改为EPOLLOUT监听写事件;
回调函数;
epoll_cntl(EPOLL_CTL_ADD)重新放到红黑上监听写事件;
等待epoll_wait返回, 说明cfd可写;
write写回去;
再将cfd从监听红黑树上摘下;
EPOLLOUT改为EPOLLIN监听读事件;
epoll_cntl(EPOLL_CTL_ADD)重新放到红黑上监听读事件;
epoll_wait监听;
}
所以, 反应堆模型不但要监听反应堆的读事件, 还要监听写事件;
int main(int argc,char* argv[]){
/*选择默认端口号或指定端口号*/
unsigned short port=SERVER_PORT;
if(argc==2)
port=atoi(argv[1]);
/*创建红黑树根节点*/
g_efd=epoll_create(MAX_EVENTS+1);
if(g_efd<0)
perr_exit("epoll_create error");
/*初始化连接*/
initlistensocket(g_efd,port);
/*创建一个系统的epoll_event的数组,与my_events的规模相同*/
struct epoll_event events[MAX_EVENTS+1];
printf("Server running port[%d]\n",port);
int chekpos=0;
int i=0;
while(1){
/*验证超时,每次测试100个连接,不测试listenFd.当客户端60s没有和服务器通信,则关闭连接*/
long now=time(NULL);
for(i=0;i<100;++i,++chekpos){
if(chekpos==MAX_EVENTS)
chekpos=0;
if(g_events[chekpos].status!=1)
continue;
/*时间间隔*/
long duration=now-g_events[chekpos].last_active;
if(duration>=60){
close(g_events[chekpos].fd);
printf("fd=%d timeout\n",g_events[chekpos].fd);
eventdel(g_efd,&g_events[chekpos]);
}
}
/*监听红黑树g_efd*/
int nfd=epoll_wait(g_efd,events,MAX_EVENTS+1,1000);
if(nfd<0){
printf("epoll_wait error\n");
break;
}
for(i=0;i<nfd;++i){
struct my_events* ev=(struct my_events*)(events[i].data.ptr);
/*读就绪事件*/
if((events[i].events&EPOLLIN)&&(ev->events&EPOLLIN))
ev->call_back(ev->fd,events[i].events,ev->arg);
/*写就绪事件*/
if((events[i].events&EPOLLOUT)&&(ev->events&EPOLLOUT))
ev->call_back(ev->fd,events[i].events,ev->arg);
}
}
return 0;
}
/*调用者main:initlistensocket(g_efd,port)*/
void initlistensocket(int efd,unsigned short port){
struct sockaddr_in serverAddr;
int lfd=0;
int flg=0;
lfd=socket(AF_INET,SOCK_STREAM,0);
/*设置lfd非阻塞*/
flg=fcntl(lfd,F_GETFL);
flg=flg|O_NONBLOCK;
fcntl(lfd,F_SETFL,flg);
/*设置地址结构*/
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
serverAddr.sin_port=htons(port);
bind(lfd,(const struct sockaddr*)&serverAddr,sizeof(serverAddr));
listen(lfd,128);
/*void eventset(struct my_events* ev,int fd,void(*call_back)(int,int,void*),void* arg);*/
/*把g_events数组的最后一个元素设置为lfd,回调函数设置为acceptconn*/
eventset(&g_events[MAX_EVENTS],lfd,acceptconn,&g_events[MAX_EVENTS]);
/*void eventadd(int efd,int event,struct my_events* ev)*/
/*挂上树*/
eventadd(efd,EPOLLIN,&g_events[MAX_EVENTS]);
return;
}
给my_event结构体赋值:
/*挨个赋值*/
void eventset(struct my_events* ev,int fd,void (*call_back)(int,int,void*),void* arg){
ev->fd=fd;
ev->events=0;
ev->call_back=call_back; //设置回调函数
ev->arg=arg;
ev->status=0;
memset(ev->buf,0,sizeof(ev->buf));
ev->len=0;
ev->last_active=time(NULL); //调用eventset的时间
return;
}
accept得到connectFd, 设置回调函数并加入监听红黑树:
void acceptconn(int lfd,int events,void* arg){
struct sockaddr_in clientAddr;
socklen_t clientAddrLen=sizeof(clientAddr);
int connectFd,i;
if((connectFd=accept(lfd,(struct sockaddr*)&clientAddr,&clientAddrLen))==-1){
if((errno!=EAGAIN)&&(errno!=EINTR)){
/*暂时不做错误处理*/
}
perr_exit("accept error");
}
do{
/*找到空闲元素*/
for(i=0;i<MAX_EVENTS;++i)
if(g_events[i].status==0)
break;
if(i==MAX_EVENTS){
printf("%s:max connections limit[%d]\n",__func__,MAX_EVENTS);
break;
}
int flag=fcntl(connectFd,F_GETFL);
flag=flag|O_NONBLOCK;
if(fcntl(connectFd,F_SETFL,flag)==-1)
perr_exit("fcntl error");
/*给cfd设置一个my_events结构体,回调函数设置为recvdata*/
eventset(&g_events[i],connectFd,recvdata,&g_events[i]);
/*将connectFd加入监听红黑树g_efd中*/
eventadd(g_efd,EPOLLIN,&g_events[i]);
}while(0);
return;
}
void eventadd(int efd,int event,struct my_events* ev){
/*这是系统的epoll_event结构体*/
struct epoll_event epv={0,{0}};
int op=0;
/*将联合体中的ptr指向传入的my_events实例*/
epv.data.ptr=ev;
/*将成员变量events设置为传入的event,ev->events保持相同*/
epv.events=ev->events=event;
/*没在树上:设置op,并把epv挂在树上*/
if(ev->status==0){
op=EPOLL_CTL_ADD;
ev->status=1;
}
if(epoll_ctl(efd,op,ev->fd,&epv)<0)
printf("event add failed:fd=%d,event=%d",ev->fd,event);
else
printf("event add OK:fd=%d,event=%0X,op=%d",ev->fd,event,op);
return;
}
eventset函数(设置回调函数):
eventadd函数: 将一个fd添加到监听红黑树, 设置监听读事件还是写事件;
recvdata:
/*回调函数*/
void recvdata(int fd,int event,void* arg){
struct my_events* ev=(struct my_events*)arg;
int len;
/*从fd中接收数据到ev的buf中*/
len=recv(fd,ev->buf,sizeof(ev->buf),0);
eventdel(g_efd,ev);
if(len>0){
ev->len=len;
ev->buf[len]='\0';
printf("C[%d]:%s",fd,ev->buf);
/*设置为写回调*/
eventset(ev,fd,senddata,ev);
eventadd(g_efd,EPOLLOUT,ev);
}else if(len==0){
/*recv返回0,说明对端关闭了连接*/
close(ev->fd);
printf("fd=%d,pos=%ld closed\n",fd,ev-g_events);
}else{
/*返回值<0,出错了*/
close(ev->fd);
printf("fd=%d,error=%d:%s\n",fd,errno,strerror(errno));
}
return;
}
从红黑树上摘除节点
/*从epoll监听红黑树上摘除节点*/
void eventdel(int efd,struct my_events* ev){
struct epoll_event epv={0,{0}};
if(ev->status!=1)
return;
/*联合体中的指针归零,状态归零*/
epv.data.ptr=NULL;
ev->status=0;
/*将epv从树上摘下来*/
epoll_ctl(efd,EPOLL_CTL_DEL,ev->fd,&epv);
return;
}
senddata:
/*回调函数*/
void senddata(int fd,int event,void* arg){
struct my_events* ev=(struct my_events*)arg;
int len;
/*从ev的buf中发送数据给fd*/
len=send(fd,ev->buf,sizeof(ev->buf),0);
eventdel(g_efd,ev);
if(len>0){
printf("send fd=%d,len=%d:%s\n",fd,len,ev->buf);
eventset(ev,fd,recvdata,ev);
eventadd(g_efd,EPOLLIN,ev);
}else{
close(fd);
printf("send %d error:%s\n",fd,strerror(errno));
}
return;
}
...
/*验证超时,每次测试100个连接,不测试listenFd.当客户端60s没有和服务器通信,则关闭连接*/
long now=time(NULL);
for(i=0;i<100;++i,++chekpos){
if(chekpos==MAX_EVENTS)
chekpos=0;
if(g_events[chekpos].status!=1)
continue;
/*时间间隔*/
long duration=now-g_events[chekpos].last_active;
if(duration>=60){
close(g_events[chekpos].fd);
printf("fd=%d timeout\n",g_events[chekpos].fd);
eventdel(g_efd,&g_events[chekpos]);
}
}
...
回调函数由内核完成;
桩函数: 打一个桩在这, 自己去实现;
ctags ./* -R
在项目目录下生成ctags文件;
Ctrl+]
跳转到函数定义的位置;
Ctrl+t
返回此前的跳转位置;
Ctrl+o
屏幕左边列出文件列表, 再按关闭;
F4
屏幕右边列出函数列表, 再按关闭;
(还是VSCode比较香)
以往的服务模式: 当有一个Client建立连接, 服务器就Create一个线程进行数据处理, 处理完后就销毁
多路IO转接相比多线程/多进程模型的优势: 需要起的线程数大大减小, 节省系统资源
由于线程创建的开销很大, 而维护成本较低, 于是提出一次创建多个线程, 构成线程池(线程聚集的虚拟的地方)
刚开始所有线程阻塞在共享数据区的条件变量上, 有数据到来需要处理时Server唤醒一个线程进行数据处理, 处理完毕后让他回到线程池, 等待下次调用
上面的这些任务都是专门的管理者线程完成;ps -Lf pid
查看一个进程起的线程数
//threadpool.h
#define MANAGE_INTERVAL 10 //管理者线程轮询间隔
#define MIN_WAIT_TASK_NUM 10 //最小任务数
#define DEFAULT_THREAD_VARY 10 //增减线程的步长
/*线程任务结构体,包含线程的回调函数和其参数*/
typedef struct {
void* (*callback)(void* arg);
void* arg;
} threadpool_task_t;
/*描述线程池相关信息*/
struct threadpool_t {
pthread_mutex_t self_lock; //锁住本结构体
pthread_mutex_t busy_thr_num_lock; //记录忙状态的线程个数的锁,busy_thr_num
pthread_cond_t taskq_not_full; //当任务队列满时,添加任务的线程阻塞,等待此条件变量
pthread_cond_t taskq_not_empty; //当任务队列不为空时,通知等待任务的线程
pthread_t* worker_tids; //存放线程池中每个线程的tid,数组
pthread_t manager_tid; //管理者线程tid
threadpool_task_t* task_queue; //任务队列,每一个队员是(回调函数和其参数)结构体
int taskq_front; // task_queue队头下标
int taskq_rear; // task_queue队尾下标
int taskq_size; // task_queue队中实际任务数
int taskq_capacity; // task_queue队列可容纳任务数上限
int min_thr_num; //线程池最小线程数
int max_thr_num; //线程池最大线程数
int live_thr_num; //当前存活的线程个数
int busy_thr_num; //忙的线程个数
int dying_thr_num; //要销毁的线程个数
int shutdown; //标志位,线程池使用状态,true或false
};
typedef struct threadpool_t threadpool_t;
main测试函数:
//threadpool-test.c
#include "threadpool.h"
int main(void) {
threadpool_t* thp = threadpool_create(3, 100, 100);
/*模拟向线程池中添加任务*/
int num[20], i;
for (i = 0; i < 20; ++i) {
num[i] = i;
printf("add task:%d\n", i);
threadpool_addtask(thp, process, (void*)&num[i]);
}
sleep(10); //等待子线程完成任务
threadpool_destroy(thp);
return 0;
}
threadpool_t* threadpool_create(int min_thr_num, int max_thr_num, int taskq_capacity) {
threadpool_t* pool = NULL;
do {
pool = (threadpool_t*)malloc(sizeof(threadpool_t));
if (pool == NULL) {
printf("%s: malloc error\n", __func__);
break;
}
pool->min_thr_num = min_thr_num;
pool->max_thr_num = max_thr_num;
pool->busy_thr_num = 0;
pool->live_thr_num = min_thr_num;
pool->dying_thr_num = 0;
pool->taskq_front = 0;
pool->taskq_rear = 0;
pool->taskq_size = 0;
pool->taskq_capacity = taskq_capacity;
pool->shutdown = false;
pool->worker_tids = (pthread_t*)malloc(sizeof(pthread_t) * max_thr_num);
if (pool->worker_tids == NULL) {
printf("%s: malloc error\n", __func__);
break;
}
memset(pool->worker_tids, 0, sizeof(pthread_t) * max_thr_num);
pool->task_queue = (threadpool_task_t*)malloc(sizeof(threadpool_task_t) * taskq_capacity);
if (pool->task_queue == NULL) {
printf("%s: malloc error\n", __func__);
break;
}
/*初始化互斥锁和条件变量*/
if (pthread_mutex_init(&(pool->self_lock), NULL) || pthread_mutex_init(&(pool->busy_thr_num_lock), NULL) || pthread_cond_init(&(pool->taskq_not_empty), NULL) || pthread_cond_init(&(pool->taskq_not_full), NULL)) {
printf("init mutex or cond fail\n");
break;
}
/*创建N个任务线程*/
for (int i = 0; i < min_thr_num; ++i) {
pthread_create(&(pool->worker_tids[i]), NULL, worker_callback, (void*)pool); // pool指向当前线程池
printf("start thread 0x%x\n", (unsigned int)pool->worker_tids[i]);
}
/*创建管理者线程,管理者线程的回调函数为manager_tid,参数为整个线程池描述体指针*/
pthread_create(&(pool->manager_tid), NULL, manager_callback, (void*)pool);
return pool;
} while (0);
threadpool_free(pool);
return NULL;
}
void* worker_callback(void* threadpool) {
threadpool_t* pool = (threadpool_t*)threadpool;
threadpool_task_t task;
while (true) {
pthread_mutex_lock(&(pool->self_lock));
/*taskq_size==0说明没有任务(并且线程池没有关闭),调用wait函数阻塞在条件变量上,若有任务,跳过该while*/
while (pool->taskq_size == 0 && !pool->shutdown) {
/*刚创建线程池时,没有任务,所有线程都阻塞在queue_not_empty这个条件变量上*/
printf("thread 0X%x is waiting\n", (unsigned int)pthread_self());
pthread_cond_wait(&(pool->taskq_not_empty), &(pool->self_lock));
/*清除指定数目的空闲线程,如果要结束的线程个数>0,结束线程*/
if (pool->dying_thr_num > 0) {
pool->dying_thr_num--;
/*如果线程池里存活线程个数>最小值,可以结束当前线程*/
if (pool->live_thr_num > pool->min_thr_num) {
printf("thread 0X%x is exiting\n", (unsigned int)pthread_self());
pool->live_thr_num--;
pthread_mutex_unlock(&(pool->self_lock));
pthread_exit(NULL);
}
}
}
if (pool->shutdown) {
pthread_mutex_unlock(&(pool->self_lock));
printf("thread 0x%x is exiting\n", (unsigned int)pthread_self());
// pthread_detach(pthread_self());
pthread_exit(NULL);
}
task.callback = pool->task_queue[pool->taskq_front].callback;
task.arg = pool->task_queue[pool->taskq_front].arg;
pool->taskq_front = (pool->taskq_front + 1) % pool->taskq_capacity;
pool->taskq_size--;
/*通知线程有新的任务被处理*/
pthread_cond_broadcast(&(pool->taskq_not_full));
pthread_mutex_unlock(&(pool->self_lock));
printf("thread 0x%x start working\n", (unsigned int)pthread_self());
pthread_mutex_lock(&(pool->busy_thr_num_lock));
pool->busy_thr_num++;
pthread_mutex_unlock(&(pool->busy_thr_num_lock));
/*执行任务处理函数*/
(*(task.callback))(task.arg);
printf("thread 0x%x finish working\n", (unsigned int)pthread_self());
pthread_mutex_lock(&(pool->busy_thr_num_lock));
pool->busy_thr_num--;
pthread_mutex_unlock(&(pool->busy_thr_num_lock));
}
pthread_exit(NULL);
}
void* manager_callback(void* threadpool) {
int i = 0;
threadpool_t* pool = (threadpool_t*)threadpool;
while (!pool->shutdown) {
/*管理者线程每隔10s管理一次*/
sleep(MANAGE_INTERVAL);
/*从线程池中获取线程状态,由于要访问共享数据区,要进行加锁和解锁*/
pthread_mutex_lock(&(pool->self_lock));
int taskq_size = pool->taskq_size;
int live_thr_num = pool->live_thr_num;
pthread_mutex_unlock(&(pool->self_lock));
pthread_mutex_lock(&(pool->busy_thr_num_lock));
int busy_thr_num = pool->busy_thr_num;
pthread_mutex_unlock(&(pool->busy_thr_num_lock));
/*创建新线程:任务数大于最小线程个数,且存活线程数小于最大线程个数*/
if ((taskq_size >= MIN_WAIT_TASK_NUM) && (live_thr_num <= pool->max_thr_num)) {
pthread_mutex_lock(&(pool->self_lock));
int cnt = 0;
/*每次增加DEFAULT_THREAD_VARY个子线程*/
for (i = 0; i < pool->max_thr_num && pool->live_thr_num < pool->max_thr_num && cnt < DEFAULT_THREAD_VARY; ++i) {
if (pool->worker_tids[i] == 0 && !is_thread_alive(pool->worker_tids[i])) {
pthread_create(&(pool->worker_tids[i]), NULL, worker_callback, (void*)pool);
cnt++;
pool->live_thr_num++;
}
}
pthread_mutex_unlock(&(pool->self_lock));
}
/*销毁多余子线程*/
if (busy_thr_num * 2 < live_thr_num && live_thr_num > pool->min_thr_num) {
pthread_mutex_lock(&(pool->self_lock));
pool->dying_thr_num = DEFAULT_THREAD_VARY;
pthread_mutex_unlock(&(pool->self_lock));
for (i = 0; i < DEFAULT_THREAD_VARY; ++i) {
/*通知处在空闲状态的线程,他们会自行终止*/
pthread_cond_signal(&(pool->taskq_not_empty));
}
}
}
pthread_exit(NULL);
}
int threadpool_addtask(threadpool_t* pool, void* (*callback)(void* arg), void* arg) {
pthread_mutex_lock(&(pool->self_lock));
while ((pool->taskq_size == pool->taskq_capacity) && (!pool->shutdown)) {
pthread_cond_wait(&(pool->taskq_not_full), &(pool->self_lock));
}
/*如果线程池被关闭了,通知所有线程自杀*/
if (pool->shutdown) {
pthread_cond_broadcast(&(pool->taskq_not_empty));
pthread_mutex_unlock(&(pool->self_lock));
return 0;
}
/*任务队列的队尾元素的参数设置为NULL(为什么)*/
if (pool->task_queue[pool->taskq_rear].arg != NULL) {
pool->task_queue[pool->taskq_rear].arg = NULL;
}
/*给任务队列的队尾添加任务:设置回调函数和其参数*/
pool->task_queue[pool->taskq_rear].callback = callback;
pool->task_queue[pool->taskq_rear].arg = arg;
pool->taskq_rear = (pool->taskq_rear + 1) % pool->taskq_capacity;
pool->taskq_size++;
/*唤醒阻塞在条件变量上的线程*/
pthread_cond_signal(&(pool->taskq_not_empty));
pthread_mutex_unlock(&(pool->self_lock));
return 0;
}
int threadpool_destroy(threadpool_t* pool) {
if (pool == NULL) {
return -1;
}
pool->shutdown = true;
pthread_join(pool->manager_tid, NULL);
int i = 0;
for (i = 0; i < pool->live_thr_num; ++i) {
pthread_cond_broadcast(&(pool->taskq_not_empty));
}
/*销毁任务线程*/
for (i = 0; i < pool->live_thr_num; ++i) {
pthread_join(pool->worker_tids[i], NULL);
}
threadpool_free(pool);
return 0;
}
int threadpool_free(threadpool_t* pool) {
if (pool == NULL) {
return -1;
}
if (pool->task_queue) {
free(pool->task_queue);
}
if (pool->worker_tids) {
free(pool->worker_tids);
}
pthread_mutex_destroy(&(pool->self_lock));
pthread_mutex_destroy(&(pool->busy_thr_num_lock));
pthread_cond_destroy(&(pool->taskq_not_empty));
pthread_cond_destroy(&(pool->taskq_not_full));
free(pool);
return 0;
}
UDP的socket函数的参2传入SOCK_DGRAM, 表示报式协议
UDP实现的C/S模型:
recv()和send只能用于TCP通信, 以代替read和write
由于UDP无连接的特性, accept()和connect()过程被舍弃, 流程分析如下:
Server:
lfd=socket(AF_INET,SOCK_DGRAM,0);
bind();
while(1){
recvfrom();
小写->大写;
sendto();
}
close();
Client:
connectFd=socket(AF_INET,SOCK_DGRAM,0);
sendto(服务器地址结构,地址结构大小);
recvfrom();
写到屏幕;
close();
recvfrom:
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags,struct sockaddr* src_addr, socklen_t* addrlen);
recvfrom涵盖accpet传出地址结构的作用
src_addr是传出参数: 传出对端地址结构
addrlen是传入传出参数: 传入本端地址结构大小, 传出对端地址结构大小
成功返回接受数据字节数, 失败返回-1并设置errno
sendto:
ssize_t sendto(int sockfd,const void* buf,size_t len,int flags,const struct sockaddr* dest_addr,socklen_t addrlen);
dest_addr是传入参数: 传入对端的地址结构
成功返回写出的字节数, 失败返回-1并设置errno
UDP服务器本身就是并发的, 因为它无需建立连接
服务器:
int main(void){
/*服务器地址结构和客户端地址结构*/
struct sockaddr_in server_addr,client_addr;
socklen_t client_addr_len;
int sockfd;
ssize_t n=0;
int i=0;
char buf[BUFSIZ];
char str[INET_ADDRSTRLEN];
/*得到套接字,注意采用报式协议*/
sockfd=socket(AF_INET,SOCK_DGRAM,0);
/*初始化服务器地址结构*/
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family=AF_INET;
server_addr.sin_addr.s_addr=htonl(INADDR_ANY);
server_addr.sin_port=htons(SERVER_PORT);
/*绑定地址结构到sockfd*/
bind(sockfd,(const struct sockaddr*)&server_addr,sizeof(server_addr));
printf("Accepting connection...\n");
while(1){
client_addr_len=sizeof(client_addr);
/*recvfrom相当于accept的作用,后两个是传出参数*/
n=recvfrom(sockfd,buf,BUFSIZ,0,(struct sockaddr*)&client_addr,&client_addr_len);
if(n==-1)
perror("recvfrom error");
/*打印客户端地址结构*/
printf("Received from %s at port:%d\n",
inet_ntop(AF_INET,&client_addr.sin_addr.s_addr,str,sizeof(str)),
htons(client_addr.sin_port));
/*数据处理*/
for(i=0;i<n;++i)
buf[i]=toupper(buf[i]);
/*发送回给客户端*/
n=sendto(sockfd,buf,n,0,(struct sockaddr*)&client_addr,sizeof(client_addr));
if(n==-1)
perror("sendto error");
}
close(sockfd);
return 0;
}
客户端:
int main(int argc,char* argv[]){
struct sockaddr_in serverAddr;
int sockfd,n;
char buf[BUFSIZ];
/*获取套接字*/
sockfd=socket(AF_INET,SOCK_DGRAM,0);
/*初始化服务器地址结构*/
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_port=htons(SERVER_PORT);
inet_pton(AF_INET,"127.0.0.1",&serverAddr.sin_addr);
/*不必bind*/
//bind(sockfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
while(fgets(buf,BUFSIZ,stdin)!=NULL){
/*发送给服务器*/
n=sendto(sockfd,buf,strlen(buf),0,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if(n==-1)
perr_exit("sendto error");
/*从服务器收回数据*/
n=recvfrom(sockfd,buf,BUFSIZ,0,NULL,0);
if(n==-1)
perr_exit("recvfrom error");
/*写到标准输出*/
write(STDOUT_FILENO,buf,n);
}
close(sockfd);
return 0;
}
int socket(int domain, int type, int protocol);
AF_UNIX
或AF_LOCAL
;SOCK_STREAM
或SOCK_DGRAM
都⭐;注意地址结构变为sockaddr_un:
#include
;
socket是伪文件, 文件大小始终是0;
服务器端:
#define SERVER_ADDR "server.socket"
int main(int argc,char* argv[]){
int lfd,cfd,len,size,i;
struct sockaddr_un serverAddr,clientAddr;
char buf[BUFSIZ];
lfd=Socket(AF_UNIX,SOCK_STREAM,0);
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sun_family=AF_UNIX;
strcpy(serverAddr.sun_path,SERVER_ADDR);
len=offsetof(struct sockaddr_un,sun_path)+strlen(serverAddr.sun_path);
unlink(SERVER_ADDR);
Bind(lfd,(struct sockaddr*)&serverAddr,len);
Listen(lfd,4);
printf("Accepting...\n");
while(1){
len=sizeof(clientAddr);
cfd=Accept(lfd,(struct sockaddr*)&clientAddr,(socklen_t*)&len);
len-=offsetof(struct sockaddr_un,sun_path);
clientAddr.sun_path[len]='\0';
printf("Client bind filename:%s\n",clientAddr.sun_path);
while((size=read(cfd,buf,sizeof(buf)))>0){
for(i=0;i<size;++i)
buf[i]=toupper(buf[i]);
write(cfd,buf,size);
}
close(cfd);
}
close(lfd);
return 0;
}
客户端:
int main(void){
int cfd,len;
struct sockaddr_un clientAddr,serverAddr;
char buf[BUFSIZ];
cfd=Socket(AF_UNIX,SOCK_STREAM,0);
/*绑定客户端的地址结构*/
bzero(&clientAddr,sizeof(clientAddr));
clientAddr.sun_family=AF_UNIX;
strcpy(clientAddr.sun_path,CLIENT_ADDR);
len=offsetof(struct sockaddr_un,sun_path)+strlen(clientAddr.sun_path);
unlink(CLIENT_ADDR);
Bind(cfd,(struct sockaddr*)&clientAddr,len);
/*服务器的地址结构也要绑定*/
bzero(&serverAddr,sizeof(serverAddr));
serverAddr.sun_family=AF_UNIX;
strcpy(serverAddr.sun_path,SERVER_ADDR);
len=offsetof(struct sockaddr_un,sun_path)+strlen(serverAddr.sun_path);
Connect(cfd,(struct sockaddr*)&serverAddr,len);
while(fgets(buf,sizeof(buf),stdin)!=NULL){
write(cfd,buf,strlen(buf));
len=read(cfd,buf,sizeof(buf));
write(STDOUT_FILENO,buf,len);
}
close(cfd);
return 0;
}
总结:
源码包安装三部曲(参考README):
./configure
-检查安装环境, 生成makefile;make
-生成.o文件和可执行文件;sudo make install
-将必要的资源cp至系统指定目录;安装完成后进入sample目录,运行demo验证库安装情况;
编译使用库的.c源码时, 要指定库路径:-l event
;
库路径:
/usr/local/lib
特性:基于"事件"的异步通信模型–回调;
异步:函数"注册"时间和函数真正被执行的时间不同, 函数真正是被内核调用(等待某一条件满足);
base相当于插排, 后面到来的事件都插在base上;
event_base_dispatch相当于在while(1)中的epoll, 所以后面三行几乎执行不到了;
应用五部曲:
1-创建event_base(事件底座);
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);
int main(int argc,char* argv[]){
int i=0;
struct event_base* base=event_base_new();
const char** buf;
const char* str;
/*查看支持哪些多路IO*/
buf=event_get_supported_methods();
/*查看当前使用的多路IO*/
str=event_base_get_method(base);
/*打印结果*/
printf("str=%s\n",str);
for(i=0;i<10;++i){
printf("buf[i]=%s\n",buf[i]);
}
return 0;
}
创建事件:
struct event* event_new(struct event_base* base,evutil_socket_t fd,short what,event_callback_fd cb,void* arg);
参数:
返回值:
what的取值:
EV_READ 读一次;
EV_WRITE 写一次;
EV_PERSIST 持续触发, 可以理解为while(read())或while(write());
回调函数:
typedef void (*event_callback_fn)(evutil_socket_t fd,short what,void* arg);
将事件添加到event_base上:
int event_add(struct event* ev,const strcut timeval* tv);
参数:
返回值: 成功返回0, 失败返回-1;
从event_base上摘下事件(不常用):
int event_del(struct event* ev);
ev是要摘下的事件对象, 就是event_new的返回值;
销毁事件:
int event_free(strcut event* ev);
ev是要销毁的事件对象, 就是event_new的返回值;
读进程:
/*回调函数*/
void read_cb(evutil_socket_t fd,short what,void* arg){
char buf[BUFSIZ]={0};
read(fd,buf,sizeof(buf));
/*提示信息*/
printf("Read from writer:%s\n",buf);
printf("what=%s\n",what&EV_READ?"Yes":"No");
sleep(1);
return;
}
int main(int argc,char* argv[]){
int fd=0;
/*创建一个fifo*/
unlink("myfifo");
mkfifo("myfifo",0644);
/*以只读的方式打开fifo,拿到fd*/
fd=open("myfifo",O_RDONLY|O_NONBLOCK);
if(fd==-1)
perr_exit("open error");
/*创建基事件*/
struct event_base* base=event_base_new();
/*创建事件对象*/
struct event* ev=NULL;
ev=event_new(base,fd,EV_READ|EV_PERSIST,read_cb,NULL);
/*将事件插到基事件上*/
event_add(ev,NULL);
/*由内核阻塞监听去了*/
event_base_dispatch(base);
/*释放内存资源*/
event_base_free(base);
event_free(ev);
close(fd);
return 0;
}
写进程:
void write_cb(evutil_socket_t fd,short what,void* arg){
char buf[]="hello libevent";
write(fd,buf,strlen(buf)+1);
sleep(1);
return;
}
int main(int argc,char* argv[]){
int fd=0;
/*以只写的方式打开fifo,拿到fd*/
fd=open("myfifo",O_WRONLY|O_NONBLOCK);
if(fd==-1)
perr_exit("open error");
/*创建基事件*/
struct event_base* base=event_base_new();
/*创建事件对象*/
struct event* ev=NULL;
ev=event_new(base,fd,EV_WRITE|EV_PERSIST,write_cb,NULL);
/*将事件对象插到基事件上*/
event_add(ev,NULL);
/*由内核阻塞监听去了*/
event_base_dispatch(base);
/*释放内存资源*/
event_base_free(base);
event_free(ev);
close(fd);
return 0;
}
未决: 有资格被处理, 但还没有被处理;
libevent框架:
创建事件:
将事件添加到基事件:
摘除和销毁事件:
原理: bufferent利用队列实现两个缓冲区(数据读走就没, FIFO);
读: 有数据, 读回调函数被调用, 使用bufferevent_read()读数据;
写: 使用bufferevent_write, 向写缓冲中写数据, 该缓冲区中有数据自动写出, 写完后, 回调函数被调用(鸡肋);
创建bufferevent:
struct bufferevent* bufferevent_socket_new(struct event_base* base,
evutil_socket_t fd,
enum bfferevent_options options)
base: 基事件, event_base_new函数的返回值;
fd:封装到bufferevent内的fd(绑定在一起);
enum表示枚举类型, 一般取BEV_OPT_CLOSE_ON_FREE;
成功返回bufferevent事件对象;
销毁bufferevent:
void bufferevent_socket_free(struct bufferevent* ev)
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()读, 而不是read());
writecb: 鸡肋, 传NULL即可;
eventcb: 可传NULL;
cbarg: 回调函数的参数;
readcb对应的回调函数:
typedef void (*bufferevent_data_cb)(struct bufferevent* bev,void* ctx);
void read_cb(struct bufferevent* bev,void* arg){
...
bufferevent_read();
}
读数据: 从bufferevent输入缓冲区中移除数据;
size_t bufferevent_read(struct bufferevent* bufev,void* data,size_t size);
写数据:
int bufferevent_write(struct bufferevent* bufev,const void* data,size_t size)
eventcb对应的回调函数:
typedef void (*bufferevent_event_cb)(struct bufferevent* bev,short events,void* ctx)
默认新建的bufferevent写缓冲是enable的, 而读缓冲是disable的;
通过两个函数操作缓冲区读写使能:
void bufferevent_enable(struct bufferevent* bufev,short events); //启用缓冲区
void bufferevnet_disable(struct bufferevent* bufev,short events); //禁用
/*例如:开启读缓冲*/
void bufferevent_enable(bufev,EV_READ);
events的值可传入三个宏:
获取缓冲区的禁用状态:
short bufferevent_get_enable(struct bufferevent* bufev)
具体的状态需要借助&来得到;
客户端建立连接:
int bufferevent_socket_connect(struct bufferevent* bev,struct sockaddr* address,int addrlen);
bev:bufferevent事件对象(封装了fd);
address, len:等同于connect()的参2和参3;
服务器创建监听器:
struct evconnlistener* evconnlistener_new_bind(struct event_base* base,
evconnlistener_cb cb,
void* ptr,
unsigned flags,
int backlog,
const struct sockaddr* sa,
int socklen);
cb:监听回调函数(建立连接后用户要做的操作);
ptr:回调函数的参数;
flags:可识别的标志, 通常传:
LEV_OPT_CLOSE_ON_FREE(释放bufferevent时关闭底层传输端口, 这将关闭底层套接字, 释放底层bufferevent等)
LEV_OPT_REUSEABLE(可以端口复用);
backlog:相当于listen的参2, 传-1表示使用默认的最大值;
sa:服务器自己的地址结构;
socklen:sa的大小;
这一个函数可以完成socket(),bind(),listen(),accept()
四个函数的作用;
回调函数的类型:
typedef void (*evconnlistener_cb)(struct evconnlistener* listener,
evutil_socker_t sock,
struct sockaddr* addr,
int len,
void* ptr);
该回调函数不由我们调用, 是框架自动调用, 因此只需知晓参数含义即可;
/*读回调函数*/
void read_cb(struct bufferevent* bev,void* arg){
char buf[1024]={0};
/*使用bufferevnet_read从被包裹的套接字中读数据到buf*/
bufferevent_read(bev,buf,sizeof(buf));
printf("Client says:%s\n",buf);
char* p="fuckyou\n";
/*给客户端回应*/
bufferevent_write(bev,p,strlen(p)+1);
sleep(1);
return;
}
/*写回调函数,向buffer中写完后调用此函数打印提示信息*/
void write_cb(struct bufferevent* bev,void* arg){
printf("I'm server,write to client successfully |:)\n");
return;
}
/*事件回调函数,处理异常*/
void event_cb(struct bufferevent* bev,short events,void* arg){
if(events&BEV_EVENT_EOF)
printf("Connection closed\n");
else if(events&BEV_EVENT_ERROR)
printf("Connectiong error\n");
bufferevent_free(bev);
printf("bufferevent has been free\n");
return;
}
/*监听器的监听器的回调函数*/
void cb_listener(struct evconnlistener* listener,evutil_socket_t fd,struct sockaddr* addr,int len,void* ptr){
printf("New client connected\n");
/*把传进来的基事件指针接收下来*/
struct event_base* base=(struct event_base*)ptr;
/*创建bufferevnet事件对象*/
struct bufferevent* bev;
bev=bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);
/*设置上回调函数*/
bufferevent_setcb(bev,read_cb,write_cb,event_cb,NULL);
/*设置读缓冲使能*/
bufferevent_enable(bev,EV_READ);
return;
}
int main(int argc,char* argv[]){
/*定义并初始化服务器的地址结构*/
struct sockaddr_in serverAddr;
memset(&serverAddr,0,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
serverAddr.sin_port=htons(SERVER_PORT);
/*创建基事件*/
struct event_base* base;
base=event_base_new();
/*创建监听器,把基事件作为参数传递给他*/
struct evconnlistener* listener;
listener=evconnlistener_new_bind(base,cb_listener,base,LEV_OPT_CLOSE_ON_FREE|LEV_OPT_REUSEABLE,36,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
/*循环监听*/
event_base_dispatch(base);
/*释放资源*/
evconnlistener_free(listener);
event_base_free(base);
return 0;
}
void read_cb(struct bufferevent* bev,void* arg){
char buf[1024]={0};
bufferevent_read(bev,buf,sizeof(buf));
printf("Server says:%s\n",buf);
bufferevent_write(bev,buf,strlen(buf)+1);
sleep(1);
return;
}
void write_cb(struct bufferevent* bev,void* arg){
printf("I'm client's write_cb,I'm usless,:(\n");
return;
}
void event_cb(struct bufferevent* bev,short events,void* arg){
if(events&BEV_EVENT_EOF)
printf("End of file\n");
else if(events&BEV_EVENT_ERROR)
printf("Something error\n");
else if(events&BEV_EVENT_CONNECTED)
printf("Server connected\n");
bufferevent_free(bev);
return;
}
void read_terminal(evutil_socket_t fd,short what,void* arg){
char buf[1024]={0};
struct bufferevent* bev=(struct bufferevent*)arg;
int len=0;
len=read(fd,buf,sizeof(buf));
bufferevent_write(bev,buf,len+1);
return;
}
int main(int argc,char* argv[]){
int fd=0;
struct sockaddr_in serverAddr;
memset(&serverAddr,0,sizeof(serverAddr));
struct event_base* base=NULL;
base=event_base_new();
struct bufferevent* bev;
bev=bufferevent_socket_new(base,fd,BEV_OPT_CLOSE_ON_FREE);
fd=socket(AF_INET,SOCK_STREAM,0);
serverAddr.sin_family=AF_INET;
inet_pton(AF_INET,"127.0.0.1",&serverAddr.sin_addr.s_addr);
serverAddr.sin_port=htons(SERVER_PORT);
bufferevent_socket_connect(bev,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
bufferevent_setcb(bev,read_cb,write_cb,event_cb,NULL);
bufferevent_enable(bev,EV_READ);
struct event* ev=event_new(base,STDIN_FILENO,EV_READ|EV_PERSIST,read_terminal,bev);
event_add(ev,NULL);
event_base_dispatch(base);
event_free(ev);
event_base_free(base);
return 0;
}
实现一个简单的web服务器myhttpd。能够给浏览器提供服务,供用户借助浏览器访问主机中的文件
大多数标签成对儿出现, 不成对儿出现的被称为短标签
DOCTYPE html>
<html>
<head>
<title>I'm headtitle>
head>
<body>
<h1>I'm header1h1>
<h2>I'm header2h2>
<h3>I'm header3h3>
<font color="red" size="7">I'm bodyfont>
<br/>
<strong>I'm strongstrong>
<br/>
<em>I'm emem>
<br/>
<del>I'm deletedel>
<br/>
<ins>I'm insins>
<br/>
<p>I'm pp>
<p>I'm pp>
<hr size=70 color="green"/>
<div align="center">
I'm div1
div>
body>
html>
404页面html:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>404NotFoundtitle>
head>
<body>
<h1>404 not foundh1>
body>
<style>
h1 {
width: 500px;
margin: 0px auto;
}
style>
html>
列表&图片和超链接:
DOCTYPE html>
<html>
<head>
<title>A Demotitle>
head>
<body>
<p id="top">
<ul type="circle">
<li>option1li>
<li>option2li>
<li>option3li>
<li>option4li>
ul>
<ol type="A">
<li>option1li>
<li>option2li>
<li>option3li>
<li>option4li>
ol>
<img src="/home/daniel/图片/Vincent-Willem-Van-Gogh .jpg" alt="图片加载失败" title="VanGogh" width="300"/>
<img src="/home/daniel/图片/Vincent-Willem-Van-Gogh .jpg" alt="图片加载失败" title="VanGogh" />
<img src="/home/daniel/图片/Vincent-Willem-Van-Gogh .jpg" alt="图片加载失败" title="VanGogh" />
<img src="/home/daniel/图片/Vincent-Willem-Van-Gogh .jpg" alt="图片加载失败" title="VanGogh" />
<img src="/home/daniel/图片/Vincent-Willem-Van-Gogh .jpg" alt="图片加载失败" title="VanGogh" />
<a href="http://jd.com" target="_blank" title="去京东">请跳转至京东a>
<a href="http://jd.com" target="_blank" title="去京东">
<img src="/home/daniel/图片/Vincent-Willem-Van-Gogh .jpg" alt="图片加载失败" title="VanGogh" width="100"/>
a>
<a href="#top">回到顶部a>
body>
html>
通常HTTP消息包括客户机向服务器的请求和服务器向客户机的响应消息;
请求消息(浏览器发给服务器):
以下是浏览器发送给服务器的http协议头内容举例:
GET /hello.c HTTP/1.1
Host:localhost:2222
User-Agent:Mozilla/5.0(X11;Ubuntu;Linux i686;rv:24.0)Gecko/201001 01 Firefox/24.0
Accept:text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language:zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3
Accept-Encoding:gzip,deflate
Connection:keep-alive
If-Modified-Since:Fri,18 Jul 2014 08:36:36 GMT
\r\n
响应消息(服务器发给浏览器):
HTTP/1.1 200 OK
Server:xhttpd
Date:Fri,18 Jul 2014 14:34:26 GMT
Content-Type:text/plain;charset=iso-8859-1
Content-Length:32
Content-Language:zh-CN
Last-Modified:Fri,18,Jul 2014 08:36:36 GMT
Connection:close
\r\n
响应正文
Server框架:
int init_listen_fd(int port,int epfd){
int ret=0;
int lfd=socket(AF_INET,SOCK_STREAM,0);
if(lfd==-1)
perr_exit("socket error");
struct sockaddr_in serverAddr;
memset(&serverAddr,0,sizeof(serverAddr));
serverAddr.sin_family=AF_INET;
serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
serverAddr.sin_port=htons(SERVER_PORT);
/*设置端口复用*/
int opt=1;
setsockopt(lfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt));
/*给lfd绑定地址结构*/
ret=bind(lfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
if(ret==-1)
perr_exit("bind error");
/*设置监听上限*/
ret=listen(lfd,128);
if(ret==-1)
perr_exit("listen error");
/*将lfd挂到监听红黑树*/
struct epoll_event ev;
ev.events=EPOLLIN;
ev.data.fd=lfd;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,lfd,&ev);
if(ret==-1)
perr_exit("epoll_ctl error");
return lfd;
}
void do_accept(int lfd,int epfd){
int cfd=0;
int flag=0;
int ret=0;
struct sockaddr_in clientAddr;
socklen_t clientAddrLen=sizeof(clientAddr);
cfd=accept(lfd,(struct sockarr*)&clientAddr,&clientAddrLen);
if(cfd==-1)
perr_exit("accept error");
char client_ip[64]={0};
printf("New Client IP=%s,Port=%d,cfd=%d\n",
inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,client_ip,sizeof(client_ip)),
ntohs(clientAddr.sin_port),
cfd);
/*设置非阻塞*/
flag=fcntl(cfd,F_GETFL);
flag=flag|O_NONBLOCK;
fcntl(cfd,F_SETFL,flag);
struct epoll_event ev;
ev.data.fd=cfd;
/*设置非阻塞*/
ev.events=EPOLLIN|EPOLLET;
ret=epoll_ctl(epfd,EPOLL_CTL_ADD,cfd,&ev);
if(ret==-1)
perr_exit("epoll_ctl add cfd error");
return;
}
void do_read(int cfd,int epfd){
/*读取一行http协议,拆分,获取get文件名和协议号*/
return;
}
void epoll_run(int port){
int i=0;
int ret=0;
struct epoll_event all_events[MAXSIZE];
/*创建监听红黑树树根节点*/
int epfd=epoll_create(MAXSIZE);
if(epfd==-1){
perror("epoll_create error");
exit(1);
}
/*创建lfd,并添加至监听树*/
int lfd=init_listen_fd(port,epfd);
while(1){
ret=epoll_wait(epfd,all_events,MAXSIZE,-1);
if(ret==-1)
perr_exit("epoll_wait error");
for(i=0;i<ret;++i){
/*只处理读事件*/
struct epoll_event* pev=all_events+i;
if(!(pev->events&EPOLLIN))
continue;
/*处理连接请求*/
if(pev->data.fd==lfd)
do_accept(lfd,epfd);
/*读数据*/
else
do_read(pev->data.fd,epfd);
}
}
return NULL;
}
int main(int argc,char* argv[]){
/*从cmd参数中获取端口和server提供的目录*/
if(argc<3)
printf("Please input in this format:./server port path\n");
/*用户输入的端口*/
int port=atoi(argv[1]);
/*切换进程的工作目录*/
int ret=chdir(argv[2]);
if(ret!=0){
perror("chdir error");
exit(1);
}
/*启动epoll监听*/
epoll_run(port);
return 0;
}
getline函数, 用于读取http协议头:
int get_line(int cfd,char* buf,int size){
int i=0;
char c='\0';
int n=0;
while((i<size-1)&&(c!='\n')){
n=recv(cfd,&c,1,0);
if(n>0){
if(c=='\r'){
/*拷贝读一次*/
n=recv(cfd,&c,1,MSG_PEEK);
if((n>0)&&(c=='\n')){
recv(cfd,&c,1,0);
}else{
c='\n';
}
}
buf[i]=c;
i++;
}else{
c='\n';
}
}
buf[i]='\0';
if(n==-1){
i=n;
}
return i;
}
HTTP/1.1 200 OK
Server:xhttpd
Date:Fri,18 Jul 2014 14:34:26 GMT
Content-Type:text/plain;charset=iso-8859-1
Content-Length:32
Content-Language:zh-CN
Last-Modified:Fri,18,Jul 2014 08:36:36 GMT
Connection:close
\r\n
错误处理函数:
void disconnect(int cfd,int epfd){
int ret=epoll_ctl(epfd,EPOLL_CTL_DEL,cfd,NULL);
if(ret!=0)
perr_exit("epoll_ctl del error");
close(cfd);
return;
}
读数据的思路:
void do_read(int cfd,int epfd){
/*读取一行http协议,拆分,获取get文件名和协议号*/
char line[1024]={0};
int len=get_line(cfd,line,sizeof(line));
if(len==0){
printf("Client close\n");
disconnect(cfd,epfd);
}else{
/*字符串拆分*/
}
return;
}
sscanf()函数:
int sscanf(const char* str, const char* format, ...);
正则表达式没有语言隔阂, 所有语言通用;
下面贴的源码跟刚开始的几集不一致(下面的是成熟的, 视频里的是演示);
void do_read(int cfd,int epfd){
/*读取一行http协议,拆分,获取get文件名和协议号*/
char line[1024]={0};
int len=get_line(cfd,line,sizeof(line));
if(len==0){
printf("Client close\n");
disconnect(cfd,epfd);
}else{
printf("----请求头----\n");
printf("请求行数据:%s\n",line);
/*清除多余数据,不让他们拥塞缓冲区*/
while(1){
char buf[1024]={0};
len=get_line(cfd,buf,sizeof(buf));
if(buf[0]=='\n'){
break;
}else if(len==-1){
break;
}
}
printf("----请求尾----\n");
}
/*确定是GET方法(忽略大小写比较字符串前n个字符)*/
if(strncasecmp("get",line,3)==0){
http_request(line,cfd);
}
disconnect(cfd,epfd);
return;
}
strncasecmp
:忽略大小写比较字符串前n个字节;
/*处理http请求-OK*/
void http_request(const char* request,int cfd){
/*拆分http请求行*/
char method[12],path[1024],protocol[12];
sscanf(request,"%[^ ] %[^ ] %[^ ]",method,path,protocol);
printf("method=%s,path=%s,protocol=%s\n",method,path,protocol);
/*解码:将不能识别的中文乱码转换为中文*/
decode_str(path,path);
char* file=path+1;
/*如果没有指定访问的资源,默认显示资源目录中的内容*/
if(!strcmp(path,"/")){
file="./";
}
/*获取文件属性*/
struct stat st;
int ret=0;
ret=stat(file,&st);
if(ret==-1){
send_error(cfd,404,"Not Found","No such file or direntry");
return;
}
if(S_ISDIR(st.st_mode)){
send_respond_head(cfd,200,"OK",get_file_type(".html"),-1);
send_dir(cfd,file);
}
if(S_ISREG(sbuf.st_mode)){
/*回发http协议头*/
send_respond_head(cfd,200,"OK",get_file_type(filename),sbuf.st_size);
send_file(cfd,file);
}
return;
}
void send_respond_head(int cfd,int no,const char* desp,const char* type,long len){
char buf[1024]={0};
sprintf(buf,"HTTP/1.1 %d %s\r\n",no,desp);
send(cfd,buf,strlen(buf),0);
sprintf(buf,"Content-Type:%s\r\n",type);
sprintf(buf+strlen(buf),"Content-Length:%d\r\n",len);
send(cfd,buf,strlen(buf),0);
send(cfd,"\r\n",2,0);
return;
}
void send_file(int cfd,const char* filename){
int n=0;
int ret=0;
int fd=0;
char buf[BUFSIZ]={0};
fd=open(filename,O_RDONLY);
if(fd==-1){
send_error(cfd,404,"Not Found","No such file or direntry");
exit(1);
}
while((n=read(fd,buf,sizeof(buf)))>0){
ret=send(cfd,buf,n,0);
if(ret==-1){
if(errno==EAGAIN){
perror("send error:");
continue;
}else if(errno==EINTR){
perror("send error:");
continue;
}else{
perror("send error:");
exit(1);
}
}
}
if(n==-1)
perr_exit("read file error");
close(fd);
return;
}
const char* get_file_type(const char* name){
char* dot;
dot=strrchr(name,'.');
if(dot==NULL)
return "text/plain; charset=utf-8";
if(strcmp(dot,".html")==0||strcmp(dot,"htm")==0)
return "text/html; charset=utf-8";
if(strcmp(dot,".jpg")==0||strcmp(dot,"jpeg")==0)
return "image/jpeg";
if(strcmp(dot,".gif")==0)
return "image/gif";
if(strcmp(dot,".png")==0)
return "image/png";
if(strcmp(dot,".css")==0)
return "text/css";
if(strcmp(dot,".wav")==0)
return "audio/wav";
if(strcmp(dot,".mp3")==0)
return "audio/mpeg";
if(strcmp(dot,".avi")==0)
return "video/x-msvideo";
return "text/plain; charset=utf-8";
}
返回值一定要检查, 尤其在开发初期;
void send_error(int cfd,int status,char* title,char* text){
char buf[BUFSIZ]={0};
sprintf(buf,"%s %d %s\r\n","HTTP/1.1",status,title);
sprintf(buf+strlen(buf),"Content-Type:%s\r\n","text/html");
sprintf(buf+strlen(buf),"Content-Length:%d\r\n",-1);
sprintf(buf+strlen(buf),"Contention:close\r\n");
send(cfd,buf,strlen(buf),0);
send(cfd,"\r\n",2,0);
memset(buf,0,BUFSIZ);
sprintf(buf,"%d %s \n",status,title);
sprintf(buf+strlen(buf),"%d %s\n"
,status,title);
sprintf(buf+strlen(buf),"%s\n",text);
sprintf(buf+strlen(buf),"
\n\n\n");
send(cfd,buf,strlen(buf),0);
return;
}
/*发送目录数据-OK*/
void send_dir(int cfd,const char* dirname){
int i=0;
int ret=0;
int num=0;
char buf[4096]={0};
sprintf(buf,"目录名:%s ",dirname);
sprintf(buf+strlen(buf),"当前目录:%s
",dirname);
char enstr[1024]={0};
char path[1024]={0};
struct dirent** ptr;
num=scandir(dirname,&ptr,NULL,alphasort);
for(i=0;i<num;++i){
char* name=ptr[i]->d_name;
sprintf(path,"%s/%s",dirname,name);
printf("path=%s\n",path);
struct stat st;
stat(path,&st);
/*编码生成Unicode编码:诸如%E5%A7...等*/
encode_str(enstr,sizeof(enstr),name);
if(S_ISREG(st.st_mode)){
sprintf(buf+strlen(buf),"%s %ld ",
enstr,name,(long)st.st_size);
}else if(S_ISDIR(st.st_mode)){
sprintf(buf+strlen(buf),"%s %ld ",
enstr,name,(long)st.st_size);
}
ret=send(cfd,buf,strlen(buf),0);
if(ret==-1){
if(errno==EAGAIN){
perror("send error:");
continue;
}else if(errno==EINTR){
perror("send error:");
continue;
}else{
perror("send error:");
exit(1);
}
}
memset(buf,0,sizeof(buf));
}
sprintf(buf+strlen(buf),"
");
send(cfd,buf,strlen(buf),0);
printf("dir message send OK\n");
return;
}
/*判断文件类型*/
const char* get_file_type(const char* name){
char* dot;
dot=strrchr(name,'.');
if(dot==NULL)
return "text/plain; charset=utf-8";
if(strcmp(dot,".html")==0||strcmp(dot,"htm")==0)
return "text/html; charset=utf-8";
if(strcmp(dot,".jpg")==0||strcmp(dot,"jpeg")==0)
return "image/jpeg";
if(strcmp(dot,".gif")==0)
return "image/gif";
if(strcmp(dot,".png")==0)
return "image/png";
if(strcmp(dot,".css")==0)
return "text/css";
if(strcmp(dot,".wav")==0)
return "audio/wav";
if(strcmp(dot,".mp3")==0)
return "audio/mpeg";
if(strcmp(dot,".avi")==0)
return "video/x-msvideo";
/*其他的文件一律当作文本文件处理*/
return "text/plain; charset=utf-8";
}
每一个汉字在浏览器前端中会被转码成Unicode码进行显示;
因此在访问带有汉字的文件时, 应该在服务器回发数据给浏览器时进行编码操作, 在浏览器请求资源目录的汉字文件时进行解码操作;
/*16进制字符转化为10进制-OK*/
int hexit(char c){
if(c>='0'&&c<='9')
return c-'0';
if(c>='a'&&c<='f')
return c-'a'+10;
if(c>='A'&&c<='F')
return c-'A'+10;
return 0;
}
/*解码函数-OK*/
void decode_str(char* to,char* from){
for(;*from!='\0';++to,++from){
if(from[0]=='%'&&isxdigit(from[1])&&isxdigit(from[2])){
*to=hexit(from[1])*16+hexit(from[2]);
from+=2;
}else{
*to=*from;
}
}
*to='\0';
return;
}
/*编码函数-OK*/
void encode_str(char* to,int tosize,const char* from){
int tolen=0;
for(tolen=0;(*from!='\0')&&(tolen+4<tosize);++from){
if(isalnum(*from)||strchr("/_.-~",*from)!=(char*)0){
*to=*from;
++to;
++tolen;
}else{
sprintf(to,"%%%02x",(int)*from&0xff);
to+=3;
tolen+=3;
}
}
*to='\0';
return;
}
可使用telnet命令, 借助IP和port, 模拟浏览器行为, 在终端中对访问的服务器进行调试, 方便查看服务器会发给浏览器的http协议数据:
telnet 127.0.0.1 9527
GET /hello.c http/1.1
此时在终端中可查看到服务器回发给浏览器的http应答协议及数据内容, 可根据该信息进行调试