webbench
测压软件 最高并发3W
webbench做测试时自身也会消耗CPU和内存资源,为了测试准确,需要将webbench安装在别的服务器上。
tar -xzvf webbench-1.5.tar.gz
cd webbench-1.5
apt-get install ctags
make && make install
测试
webbench -c 1000 -t 5 http://127.0.0.1:9999/index.html //1000个客户端 5秒钟时间
github
git clone https://github.com/SYaoJun/WebServer.git //把仓库中的内容拷贝到linux系统中
回调函数
函数指针本身是指针变量,指向某个函数的入口地址。
#include <stdio.h>
#include <unistd.h>
int run(void(*step)(void)){ //回调函数的传参是声明的格式
int cnt = 0;
while(1){
if(step != NULL) step();
cnt++;
sleep(1);
}
return 0;
}
void cb(void){ //回调函数
puts("记秒到时");
}
int main(){
run(cb);
return 0;
}
gcc cb.c main.c
信号函数
raise(SIGSEGV) //自己给自己进程发送信号 11
abort() //终止当前进程
测试1s打印多少个数
alarm函数
#include <unistd.h>
#include <stdio.h>
int main(){
alarm(1); //计时1s 到时后内核发送一个sigalarm信号终止
for(int i = 0;;i++) printf("%d\n", i);
return 0;
}
setitimer函数
#include <unistd.h>
#include <stdio.h>
#include <sys/time.h>
#include <stdlib.h>
int my_alarm(int sec){
struct itimerval it, oldit;
it.it_value.tv_sec = sec; //定时长度
it.it_value.tv_usec = 0; //微秒
it.it_interval.tv_sec = 0; //周期定时
it.it_interval.tv_usec = 0;
int ret = setitimer(ITIMER_REAL, &it, &oldit);
if(ret == -1){
perror("setitimer error");
exit(1);
}
return oldit.it_value.tv_sec;
}
int main(){
my_alarm(1); //计时1s 到时后内核发送一个sigalarm信号终止
for(int i = 0;;i++) printf("%d\n", i);
return 0;
}
创建线程
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
void* func(void* arg){ //必须为这种类型
printf("in thread : thread id: %lu, process id: %u\n", pthread_self(), getpid());
}
int main(void){
//创建线程
pthread_t tid; //重命名的unsigned long 类型
//创建线程API pthread_create(arg1, agr2, arg3, arg4)
//arg1: 传入的线程tid地址
//arg2: 线程属性 通常设置为NULL
//arg3: 线程执行的任务 函数指针
//arg4: 参数3传递的参数
printf("in main 1 : thread id: %lu, process id: %u\n", pthread_self(), getpid());
int ret = pthread_create(&tid, NULL, func, NULL);
if(ret != 0){
perror("pthread_create error");
exit(1);
}
sleep(1); //主线程 等待子线程执行完毕
printf("in main 2 : thread id: %lu, process id: %u\n", pthread_self(), getpid());
return 0;
}
创建多个子线程
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
void* func(void* arg){ //必须为这种类型
int i = (int)arg;
printf("%dth thread id: %lu, process id: %u\n", i, pthread_self(), getpid());
}
int main(void){
//创建线程
pthread_t tid; //重命名的unsigned long 类型
//创建线程API pthread_create(arg1, agr2, arg3, arg4)
//arg1: 传入的线程tid地址
//arg2: 线程属性 通常设置为NULL
//arg3: 线程执行的任务 函数指针
//arg4: 参数3传递的参数
int n = 5, ret;
for(int i = 0; i < n; i++){
ret = pthread_create(&tid, NULL, func, (void*)i);
if(ret != 0){
fprintf(stderr, "%s pthread_create error", strerror(ret));
exit(1);
}
pthread_detach(tid);
}
sleep(n); //主线程 等待子线程执行完毕
return 0;
}
mysql使用
mysql在linux环境下结合C语言的使用
sudo apt-get install mysql-client mysql-server //先安装mysql
sudo apt-get install libmysql++-dev //安装库
先启动mysql库 设置好密码 然后使用c程序连接
#include <stdio.h>
#include <string.h>
#include <mysql/mysql.h>
int main(){
MYSQL mysql;
MYSQL_RES *res;
MYSQL_ROW row;
int i, j;
char *query = "select * from people;";
mysql_init(&mysql); //初始化
if(!mysql_real_connect(&mysql,"localhost","root","password","yaojun",3306,NULL,0)){ //连接数据库
printf("Error in connecting");
}
if(mysql_query(&mysql,query)){ //查询数据库
printf("Error in querying");
}
res = mysql_store_result(&mysql);
while(row = mysql_fetch_row(res)){ //读取数据库中的一行
for(i=0;i<mysql_num_fields(res);i++){
fprintf(stdout,"%s ",row[i]);
}
printf("\n");
}
mysql_free_result(res); //释放
mysql_close(&mysql); //关闭
return 0;
}
编译的命令
gcc sql.c -lmysqlclient //生成a.out文件 执行
指针和数组
char str[]="hello"; //字符串含\0 6字节
char *url ="hello"; //指针64位机 8字节
cout<<sizeof(str)<<" "<<sizeof(url)<<endl;
CGI服务器
原理:输出到某个文件描述符上的内容直接发送到客户端连接对应的socket上,此处我们模拟了把服务器端输出到标准输出的内容,直接发送给客户端。
测试
telnet 127.0.0.1 6666
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <assert.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
//命令行参数指定IP和端口
int main(int argc, char *argv[]){
if(argc <= 2){
printf("at least 3 arguments: file ip port, but you give %d\n", argc);
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int lfd, cfd, ret;
struct sockaddr_in serv_addr, clie_addr;
lfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
assert(lfd >= 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &serv_addr.sin_addr);
//端口复用 此处没有卵用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ret = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr) );
assert(ret != -1);
ret = listen(lfd, 128); //最大同时连接数
assert(ret != -1);
socklen_t clie_addr_len; //套接字长度
clie_addr_len = sizeof(clie_addr);
cfd = accept(lfd, (struct sockaddr*)&clie_addr, &clie_addr_len );
if(cfd < 0){
printf("accept error: %d", errno);
}else{
close(STDOUT_FILENO);
dup(cfd); //创建一个新的文件描述符 该文件描述符和原文件描述符指向相同的文件 管道和网络连接
printf("good night! daisy!\n");
close(cfd);
}
close(lfd);
return 0;
}
EPOLLONESHOT事件
一个socket上的某个事件被触发多次,可能产生2个不同的线程处理同一个socket。我们期望的是一个socket在任一时刻都只被一个线程处理。对于注册了EPOLLONESHOT事件的文件描述符,操做系统最多触发其上注册的一个可读可写或者异常事件一次。注册了EPOLLONESHOT事件的socket一旦被某个线程处理完毕,应立即重置这个socket上的EPOLLONESHOT事件。
监听socket不应设置为EPOLLONESHOT。
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <assert.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/epoll.h>
#define MAX_SIZE 1024
//自定义的结构体
struct fds{
int epollfd;
int sockfd;
};
//设置文件描述符为非阻塞
void setnonblocking(int fd){
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
}
//添加到epoll内核事件表中
void addfd(int epollfd, int fd, bool oneshot){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;
if(oneshot){
event.events |= EPOLLONESHOT;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); //挂到红黑树上
setnonblocking(fd); //设置非阻塞 主要用在读写上
}
//重置EPOLLONESHOT事件
void reset_oneshot(int epollfd, int fd){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); //修改注册事件
}
//工作线程
void* worker(void* arg){
int sockfd = ((fds*)arg)->sockfd;
int epollfd = ((fds*)arg)->epollfd;
printf("start new thread to receive data on fd: %d\n", sockfd);
char buf[MAX_SIZE];
memset(buf, '\0', MAX_SIZE);
//循环读取sockfd上的数据 直到收到EAGAIN
while(1){
int ret = recv(sockfd, buf, MAX_SIZE-1, 0);
if(ret == 0){
close(sockfd);
printf("foreiner closed the connection\n");
break;
}else if(ret < 0){
if(errno == EAGAIN){
reset_oneshot(epollfd ,sockfd);
printf("read later\n");
break;
}
}else{
printf("get connection: %s\n", buf);
//休眠5s模拟数据处理过程
sleep(5);
}
}
printf("end thread receiving data on fd: %d\n", sockfd);
}
//命令行参数指定IP和端口
int main(int argc, char *argv[]){
if(argc <= 2){
printf("at least 3 arguments: file ip port, but you give %d\n", argc);
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int lfd, cfd, ret;
struct sockaddr_in serv_addr, clie_addr;
lfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
assert(lfd >= 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &serv_addr.sin_addr);
//端口复用 此处没有卵用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ret = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr) );
assert(ret != -1);
ret = listen(lfd, 128); //最大同时连接数
assert(ret != -1);
epoll_event events[MAX_SIZE]; //返回的数组上限
int epollfd = epoll_create(10);
assert(epollfd != -1);
//注意监听的套接字不能设置为oneshot
addfd(epollfd, lfd, false); //挂上红黑树监听
printf("addfd\n");
while(1){
ret = epoll_wait(epollfd, events, MAX_SIZE, -1); //永远不超时
if(ret < 0){
printf("epoll failure\n");
break;
}
for(int i = 0; i < ret; i++){ //循环处理有响应的事件
int sockfd = events[i].data.fd;
if(sockfd == lfd){ //如果是监听事件则建立新的连接
printf("lfd\n");
socklen_t clie_addr_len; //套接字长度
clie_addr_len = sizeof(clie_addr);
cfd = accept(lfd, (struct sockaddr*)&clie_addr, &clie_addr_len );
assert(cfd >= 0);
//注册为oneshot事件
addfd(epollfd, cfd, true); //刚刚这里加入的事件加错了
}else if(events[i].events & EPOLLIN){
pthread_t tid;
fds fds_for_new_worker;
fds_for_new_worker.epollfd = epollfd;
fds_for_new_worker.sockfd = sockfd;
//新启动一个线程为sockfd服务
pthread_create(&tid, NULL, worker, (void*)&fds_for_new_worker);
// pthread_detach(tid); //线程分离
}
}
}
close(lfd);
return 0;
}
信号屏蔽字
主要函数
sigaddset()
sigprocmask()
sigpending()
使用kill发送信号
kill -6 PID
测试程序
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void print(sigset_t* ped){
for(int i = 0; i < 32; i++){
if(sigismember(ped, i)==1){
putchar('1');
}else putchar('0');
}
printf("\n");
}
int main(void){
sigset_t myset, oldset, ped;
sigemptyset(&myset);
//设置自定义信号集
sigaddset(&myset, SIGINT);
sigaddset(&myset, SIGSEGV);
sigaddset(&myset, SIGQUIT);
sigaddset(&myset, SIGABRT);
//设置信号屏蔽字
sigprocmask(SIG_BLOCK, &myset, &oldset);
while(1){
sigpending(&ped);
print(&ped);
sleep(2);
}
return 0;
}
sigaction信号捕捉函数
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
void docatch(int signo){
printf("%d signal has been catched!\n", signo);
}
int main(void){
struct sigaction act;
act.sa_handler = docatch;
sigemptyset(&act.sa_mask);
sigaddset(&act.sa_mask, SIGINT);
sigaddset(&act.sa_mask, SIGSEGV);
sigaddset(&act.sa_mask, SIGQUIT);
act.sa_flags = 0; //默认属性 在信号函数处理期间 本信号再次到达默认屏蔽
sigaction(SIGINT, &act, NULL);
while(1);
return 0;
}
统一事件源
把信号事件的处理放到epoll监听事件中去处理,统一了事件源。当调用信号处理函数时,信号处理函数通过管道将信息传达给epoll上的读端监听事件,再在主循环中处理事件。
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <assert.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <signal.h>
#define MAX_SIZE 1024
//自定义的结构体
static int pipefd[2];
struct fds{
int epollfd;
int sockfd;
};
//设置文件描述符为非阻塞
void setnonblocking(int fd){
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
}
//添加到epoll内核事件表中
void addfd(int epollfd, int fd, bool oneshot){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;
if(oneshot){
event.events |= EPOLLONESHOT;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); //挂到红黑树上
setnonblocking(fd); //设置非阻塞 主要用在读写上
}
//信号处理函数
void sig_handler(int sig){
int save_errno = errno;
int msg = sig; //我猜想这里重新定义变量的原因在于传的是指针 同时为了保证可重入
send(pipefd[1], (char*)&msg, 1, 0); //pipefd[1]是非阻塞的如果发不出去直接返回
errno = save_errno;
}
//设置信号处理
void addsig(int sig){
struct sigaction sa;
memset(&sa, '\0', sizeof(sa));
sa.sa_handler = sig_handler;
sa.sa_flags |= SA_RESTART;
sigfillset(&sa.sa_mask);
// sigaddset(&sa.sa_mask, SIGSEGV);
assert(sigaction(sig, &sa, NULL) != -1); //注册监听的信号
}
//命令行参数指定IP和端口
int main(int argc, char *argv[]){
if(argc <= 2){
printf("at least 3 arguments: file ip port, but you give %d\n", argc);
return 1;
}
const char* ip = argv[1];
int port = atoi(argv[2]);
int lfd, cfd, ret;
struct sockaddr_in serv_addr, clie_addr;
lfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
assert(lfd >= 0);
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
inet_pton(AF_INET, ip, &serv_addr.sin_addr);
//端口复用 此处没有卵用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//忽略SIGPIPE信号
signal(SIGPIPE, SIG_IGN);
ret = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr) );
assert(ret != -1);
ret = listen(lfd, 128); //最大同时连接数
assert(ret != -1);
epoll_event events[MAX_SIZE]; //返回的数组上限
int epollfd = epoll_create(10);
assert(epollfd != -1);
//注意监听的套接字不能设置为oneshot
addfd(epollfd, lfd, false); //挂上红黑树监听
/*使用socketpair创建全双工的管道*/
ret = socketpair(PF_UNIX, SOCK_STREAM, 0, pipefd);
assert(ret != -1);
setnonblocking(pipefd[1]); //把写入的管道端置为非阻塞
addfd( epollfd, pipefd[0], false); //设置为oneshot
/*设置一些信号的处理函数*/
addsig(SIGHUP);
addsig(SIGCHLD);
addsig(SIGTERM);
addsig(SIGINT);
addsig(SIGSEGV);
bool stop_server = false;
while(!stop_server){
printf("epoll_wait...\n");
ret = epoll_wait(epollfd, events, MAX_SIZE, -1); //永远不超时
if((ret < 0) && (errno != EINTR)){ //这里要设置 被系统调用打断的不算失败 errno不是ret
perror("epoll failure");
exit(1);
}
for(int i = 0; i < ret; i++){ //循环处理有响应的事件
int sockfd = events[i].data.fd;
if(sockfd == lfd){ //如果是监听事件则建立新的连接
socklen_t clie_addr_len; //套接字长度
clie_addr_len = sizeof(clie_addr);
cfd = accept(lfd, (struct sockaddr*)&clie_addr, &clie_addr_len );
assert(cfd >= 0);
//注册为oneshot事件
addfd(epollfd, cfd, true);
/*如果就绪的文件描述符是pipefd[0],就处理信号*/
}else if((sockfd == pipefd[0]) &&(events[i].events & EPOLLIN)){
char buf[MAX_SIZE];
int num = recv(pipefd[0], buf, sizeof(buf), 0);
if(num == -1) continue;
else if(num == 0) continue;
else{
//可能管道有多个信号满足 每次读出一个字符进行处理
for(int j = 0; j < num; j++){
switch(buf[j]){
case SIGSEGV: puts("段错误信号已处理,收到请回答!"); break;
case SIGCHLD:
case SIGHUP: continue;
case SIGTERM:
case SIGINT: stop_server = true;break;
}
}
}
}else{}
}
}
printf("close fds\n");
close(lfd);
close(pipefd[0]);
close(pipefd[1]);
return 0;
}
单例模式
#include <iostream>
#include <atomic>
#include <mutex>
using namespace std;
class Singleton{
private: //无法调用构造函数
Singleton();
Singleton(const Singleton& other);
public:
static Singleton* getInstance();
static Singleton* m_instance;
}
Singleton* Singleton::m_instance = nullptr;
//懒汉式 单线程版
Singleton* Singleton::getInstance(){
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
}
//多线程版
Singleton* Singleton::getInstance(){
LOCK lock;
if(m_instance == nullptr){
m_instance = new Singleton();
}
return m_instance;
}
当前时间
#include <time.h>
#include <stdlib.h>
#include <string.h>
#include <stdio.h>
int main(void){
time_t tm;
time(&tm);
char time_string[128];
ctime_r(&tm, time_string);
printf("%s", time_string);
return 0;
}
初阶日志系统
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <assert.h>
#include <fcntl.h>
#include <mutex>
#include <atomic>
using namespace std;
class Logger{
public:
static Logger* get_instance();
static Logger* log;
static mutex log_mutex;
static void write_log(int level, char* s);
static void create_file();
static int fd;
private:
Logger();
Logger(const Logger& other);
~Logger();
};
Logger* Logger::log = NULL;
mutex Logger::log_mutex;
int Logger::fd = 0;
Logger::Logger(){};
Logger::~Logger(){
if(log != NULL){
delete log;
log = NULL;
}
close(fd);
}
Logger* Logger::get_instance(){
if(log == NULL){
log_mutex.lock();
if(log == NULL){
log = new Logger();
}
log_mutex.unlock();
}
return log;
}
void Logger::create_file(){
fd = open("logger.txt", O_RDWR|O_CREAT|O_TRUNC, 777);
assert(fd > 0);
}
void Logger::write_log(int level, char* s){
int n = strlen(s);
log_mutex.lock();
write(fd, s, n);
log_mutex.unlock();
}
单例模式
//C++11 atomic
std::atomic<Logger*> Logger::log;
std::mutex Logger::log_mutex;
Logger* Logger::get_instance(){
Logger* tmp = log.load(std::memory_order_relaxed);
std::atomic_thread_fence(std::memory_order_acquire); //获取内存fence
if(tmp == NULL){
std::lock_guard<std::mutex> lock(log_mutex);
tmp = log.load(std::memory_order_relaxed);
if(tmp == NUll){
tmp = new Logger;
std::atomic_thread_fence(std::memory_order_release); //释放内存fence
log.store(tmp, std::memory_order_relaxed);
}
}
return tmp;
}
主线程测试
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <stdlib.h>
#include <string.h>
#include "logger.h"
void* func(void* arg){
char buf[1024];
sprintf(buf, "thread id: %lu, process id: %u\n", pthread_self(), getpid());
Logger::log->write_log(2, buf);
}
int main(void){
pthread_t tid; //重命名的unsigned long 类型
Logger::log->create_file();
int n = 5, ret;
for(int i = 0; i < n; i++){
// int t = i;
ret = pthread_create(&tid, NULL, func, NULL);
if(ret != 0){
fprintf(stderr, "%s pthread_create error", strerror(ret));
exit(1);
}
pthread_detach(tid);
}
sleep(n-4); //主线程 等待子线程执行完毕
return 0;
}
时间堆
把监听文件描述符加入后,就启动定时器。
exec函数族
在一个程序中运行另一个程序。当进程调用exec函数时,该进程的用户空间代码和数据完全被新程序替换。调用exec并不创建新进程,所以调用exec前后进程的ID并不改变。
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
int main(){
pid_t pid = fork();
if(pid == -1){
perror("fork error!");
exit(-1);
}else if(pid == 0){
execlp("ls", "anythingok", "-l", "-a", NULL);
}else if(pid > 0){
sleep(1);
puts("parent over!");
}
return 0;
}
管道
#include <unistd.h>
#include <stdio.h>
#include <string.h>
int main(){
pid_t fd[2];
pipe(fd); //创建管道
int ret = fork();
if(ret > 0){ //父进程 父写 fd[0]读 fd[1]写
close(fd[0]);
char *str ="hello world\n";
write(fd[1], str, strlen(str));
sleep(1);
}else if(ret == 0){ //子进程 子读 fd[1]关闭
close(fd[1]);
char buf[1024];
int n = read(fd[0], buf, sizeof(buf));
//写到显示器上
write(1, buf, n);
}
return 0;
}
CGI使用管道和exec实现
hello里面的输入和输出都被重定向了
#include<stdio.h>
#include<stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
int main(int agrc, char* argv[]){
char bf[100];
read(STDIN_FILENO, bf, sizeof(bf));
sprintf(bf, "Content-Type:text/html;\r\n\r\nhello world, 你好!");
printf("%s", bf);
return 0;
}
主进程调用已经编译完毕的hello可执行文件
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(){
pid_t pid;
int status;
int cgi_input[2];
int cgi_output[2];
char buf[1024];
/*创建输入管道*/
if(pipe(cgi_input) < 0){
perror("pipe error");
exit(1);
}
/*创建输出管道*/
if(pipe(cgi_output) < 0){
perror("pipe error");
exit(1);
}
/*创建子进程*/
if((pid = fork()) < 0){
perror("fork error!");
exit(-1);
}
if(pid == 0){
dup2(cgi_input[0], 0); /*将子进程的STDIN重定向到cgi_input[0]*/
dup2(cgi_output[1], 1); /*将子进程的STDOUT重定向到cgi_output[1]*/
/*关闭剩余的两端*/
close(cgi_input[1]);
close(cgi_output[0]);
/*执行exec文件 即cgi*/
execl("hello", "anythingok", NULL);
exit(0);
}else if(pid > 0){ /*父进程先给子进程发送数据 然后等待子进程执行完 再读数据*/
/*关闭不用的两个端口*/
close(cgi_input[0]);
close(cgi_output[1]);
char tmp[100]="hello child process";
int len = strlen(tmp);
/*父进程先写数据*/
write(cgi_input[1], tmp, len);
/*父进程阻塞等待读入*/
int n = read(cgi_output[0], buf, sizeof(buf));
int ret = write(STDOUT_FILENO, buf, n);
/*关闭管道*/
close(cgi_input[1]);
close(cgi_output[0]);
waitpid(pid, &status, 0);
}
return 0;
}
多线程服务器
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <sys/epoll.h>
#include <errno.h>
#include <string.h>
#include <fcntl.h>
#include <sys/stat.h>
#include <signal.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/wait.h>
#define SERV_PORT 9999
#define MAXSIZE 2048
void wait_child(int signo){
while(waitpid(0, NULL, WNOHANG) > 0);
return;
}
int createlistenfd(){
struct sockaddr_in servaddr;
int ret;
int fd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
if(fd == -1){
perror("socket error");
exit(1);
}
//初始化servaddr结构体
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(SERV_PORT);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
//端口复用
int opt = 1;
setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
//绑定端口
ret = bind(fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
if (ret == -1){
perror("bind error");
exit(1);
}
//设置监听上限
ret = listen(fd, 128);
if(ret==-1){
perror("listen error");
exit(1);
}
return fd;
}
void work(int cfd, char* s){
char filename[100]={0};
sscanf(s, "GET /%s", filename);
char *mime;
if(strstr(s, ".html")) mime = "text/html";
else if(strstr(s, ".jpg")) mime="image/jpeg";
//构建响应头 发给客户端
// printf("%s\n", filename);
char response[MAXSIZE];
sprintf(response, "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n", mime);
//读取具体的文件内容
int filefd = open(filename, O_RDONLY);
if(filefd == -1){
perror("open error");
sleep(0.5);
filefd = open(filename, O_RDONLY); //很奇怪 为什么睡几秒钟之后再打开就不会出错了
if(filefd == -1) exit(1);
}
int len = strlen(response);
int n = read(filefd, response+len, sizeof(response)-len);
write(cfd, response, len+n);
close(filefd);
}
int main(void){
int lfd = createlistenfd();
struct sockaddr_in clientaddr;
socklen_t clientaddrlen = sizeof(clientaddr); //这是一个传出参数
//accept接受连接请求
pid_t pid;
int cfd;
while(1){
cfd = accept(lfd, (struct sockaddr *)&clientaddr, &clientaddrlen);
if (cfd == -1){
perror("accept error");
exit(1);
}
//创建子线程 主线程负责监听和回收子进程
pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
}
else if(pid == 0) break;
else{
close(cfd); //关闭接收文件描述符
signal(SIGCHLD, wait_child);
}
}
if(pid == 0){
close(lfd); //关闭监听文件描述符
//打印客户端IP和port
char buf[MAXSIZE] = {0};
printf("client IP: %s, client port: %d\n",
inet_ntop(AF_INET, &clientaddr.sin_addr, buf, sizeof(buf)),
ntohs(clientaddr.sin_port));
int n = read(cfd, buf, sizeof(buf));
if(n==0){
close(cfd);
}
// write(1, buf, n);
else work(cfd, buf);
}
return 0;
}
处理客户端注册信息的cgi
- 存在一些问题,每个网页好像没有传输完成,浏览器总是转圈。
- 需要结合上面hello程序查询数据库,hello程序的注册还没完善,只能做到查询。等以后有空再做吧。
#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <ctype.h>
#include <assert.h>
#include <netinet/in.h>
#include <errno.h>
#include <string.h>
#include <pthread.h>
#include <fcntl.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/wait.h>
#define MAX_SIZE 1024
//自定义的结构体
struct fds{
int epollfd;
int sockfd;
};
//设置文件描述符为非阻塞
void setnonblocking(int fd){
int old_option = fcntl(fd, F_GETFL);
int new_option = old_option | O_NONBLOCK;
fcntl(fd, F_SETFL, new_option);
}
//添加到epoll内核事件表中
void addfd(int epollfd, int fd, bool oneshot){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET;
if(oneshot){
event.events |= EPOLLONESHOT;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event); //挂到红黑树上
setnonblocking(fd); //设置非阻塞 主要用在读写上
}
//移除epoll事件
void removefd(int epollfd, int fd){
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, NULL);
close(fd);
}
//重置EPOLLONESHOT事件
void reset_oneshot(int epollfd, int fd){
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLONESHOT;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event); //修改注册事件
}
void accept_request(int epollfd, int sockfd, char *s){
char method[32], filename[32], account[32], password[32], phone[32];
sscanf(s, "%s /%s", method, filename);
if(strcasecmp(method, "POST") == 0){
int len = strlen(s);
int i;
for(i = 0; i < len; i++){
if(i+2<len && s[i]=='\n' && s[i+1]=='\r') break;
}
int j;
i=i+12;
for(j = 0; s[i+j]!='&'; j++) account[j] = s[j+i];
i = i+j+6;
for(j = 0; s[i+j]!='&'; j++) password[j] = s[j+i];
/*2020.4.7成功解析出来账号和密码*/
pid_t pid;
int status;
int cgi_input[2];
int cgi_output[2];
char buff[1024];
/*创建输入管道*/
if(pipe(cgi_input) < 0){
perror("pipe error");
exit(1);
}
/*创建输出管道*/
if(pipe(cgi_output) < 0){
perror("pipe error");
exit(1);
}
/*创建子进程*/
if((pid = fork()) < 0){
perror("fork error!");
exit(-1);
}
if(pid == 0){
dup2(cgi_input[0], 0); /*将子进程的STDIN重定向到cgi_input[0]*/
dup2(cgi_output[1], 1); /*将子进程的STDOUT重定向到cgi_output[1]*/
/*关闭剩余的两端*/
close(cgi_input[1]);
close(cgi_output[0]);
/*执行exec文件 即cgi*/
execl("hello", "anythingok", NULL);
exit(0);
}else if(pid > 0){ /*父进程先给子进程发送数据 然后等待子进程执行完 再读数据*/
/*关闭不用的两个端口*/
close(cgi_input[0]);
close(cgi_output[1]);
/*父进程先写数据*/
int len = strlen(account);
write(cgi_input[1], account, len);
/*父进程阻塞等待读入*/
int n = read(cgi_output[0], buff, sizeof(buff));
if(strcmp(buff, password)==0&&strlen(password) != 0){
puts("密码正确");
}else{
puts("密码错误");
strcpy(filename, "register.html");
}
// int ret = write(STDOUT_FILENO, buf, n);
/*关闭管道*/
close(cgi_input[1]);
close(cgi_output[0]);
waitpid(pid, &status, 0);
}
}
puts(filename);
char mime[64];
if(strstr(s, ".html")) strcpy(mime,"text/html");
else if(strstr(s, ".jpg")) strcpy(mime,"image/jpeg");
char response[MAX_SIZE+MAX_SIZE];
sprintf(response, "HTTP/1.1 200 OK\r\nContent-Type: %s\r\n\r\n", mime);
int filefd = open(filename, O_RDONLY);
if(filefd == -1){
perror("open error");
exit(1);
}
int len = strlen(response);
int n = read(filefd, response+len, sizeof(response)-len);
write(sockfd, response, len+n);
close(filefd);
}
//工作线程
void* worker(void* arg){
int sockfd = ((fds*)arg)->sockfd;
int epollfd = ((fds*)arg)->epollfd;
char buf[MAX_SIZE];
memset(buf, '\0', MAX_SIZE);
//循环读取sockfd上的数据 直到收到EAGAIN
while(1){
int ret = recv(sockfd, buf, MAX_SIZE-1, 0);
if(ret == 0){
removefd(epollfd, sockfd);
printf("foreiner closed the connection\n");
break;
}else if(ret < 0){
if(errno == EAGAIN){
// reset_oneshot(epollfd ,sockfd);
printf("read later\n");
break;
}
}else{
printf("get connection:\n");
//休眠5s模拟数据处理过程
accept_request(epollfd, sockfd, buf);
sleep(5);
break;
}
}
}
//命令行参数指定IP和端口
int main(int argc, char *argv[]){
if(argc < 2){
printf("at least 2 arguments: file port, but you give %d\n", argc);
return 1;
}
int port = atoi(argv[1]);
int lfd, cfd, ret;
struct sockaddr_in serv_addr, clie_addr;
lfd = socket(AF_INET, SOCK_STREAM, 0); //创建套接字
assert(lfd >= 0);
/*初始化服务器端的套接字地址*/
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(port);
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
//端口复用 此处没有卵用
int opt = 1;
setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
ret = bind(lfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr) );
assert(ret != -1);
ret = listen(lfd, 128); //最大同时连接数
assert(ret != -1);
epoll_event events[MAX_SIZE]; //返回的数组上限
int epollfd = epoll_create(10);
assert(epollfd != -1);
//注意监听的套接字不能设置为oneshot
addfd(epollfd, lfd, false); //挂上红黑树监听
while(1){
ret = epoll_wait(epollfd, events, MAX_SIZE, -1); //永远不超时
if(ret < 0){
printf("epoll failure\n");
break;
}
for(int i = 0; i < ret; i++){ //循环处理有响应的事件
int sockfd = events[i].data.fd;
if(sockfd == lfd){ //如果是监听事件则建立新的连接
socklen_t clie_addr_len; //套接字长度
clie_addr_len = sizeof(clie_addr);
cfd = accept(lfd, (struct sockaddr*)&clie_addr, &clie_addr_len );
assert(cfd >= 0);
//注册为oneshot事件
addfd(epollfd, cfd, false); //刚刚这里加入的事件加错了
}else if(events[i].events & EPOLLIN){
pthread_t tid;
fds fds_for_new_worker;
fds_for_new_worker.epollfd = epollfd;
fds_for_new_worker.sockfd = sockfd;
//新启动一个线程为sockfd服务
pthread_create(&tid, NULL, worker, (void*)&fds_for_new_worker);
// pthread_detach(tid); //线程分离
}
}
}
close(lfd);
return 0;
}
线程中调用fork函数
pthread_atfork函数确保fork调用后父进程和子进程都拥有一个清楚的锁状态。每个线程都可以独立的设置信号掩码。
waitpid回收子进程
#include <unistd.h>
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
void sigchild_handler(int sig){
//在执行SIGCHLD信号期间 可能有多个SIGCHLD到达,但是未决信号集只记录一次。
//所以需要调用while循环回收再退出
while(waitpid(-1, NULL, WNOHANG) > 0){
puts("回收成功");
}
return;
}
int main(){
pid_t pid;
int n = 5, i;
signal(SIGCHLD, sigchild_handler);
for(i = 0; i < n; i++){
pid = fork();
if(pid < 0){
perror("fork error");
exit(1);
}else if(pid == 0) break; //子进程 直接退出
}
if(i < n){ //子进程
sleep(i);
printf("I am %dth child.\n", i);
}else{
while(1){ //父进程不退出
sleep(1);
printf("I am parent %u\n", getpid());
}
}
return 0;
}
自旋锁
线程同步的一种方式。使用自旋锁的线程会反复检查锁变量是否可用。自旋锁不会让出CPU,一种忙等待状态。死循环等待锁被释放。自旋锁避免了进程或线程上下文的开销。自旋锁不适合在单CPU中使用。
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
const int N = 1e7;
int num = 0;
pthread_spinlock_t spin_lock;
void *producer(void *){
int times = N;
while(times--){
pthread_spin_lock(&spin_lock);
++num;
pthread_spin_unlock(&spin_lock);
}
}
void *comsumer(void*){
int times = N;
while(times--){
pthread_spin_lock(&spin_lock);
--num;
pthread_spin_unlock(&spin_lock);
}
}
int main(){
pthread_spin_init(&spin_lock, 0);
pthread_t th1, th2;
pthread_create(&th1, NULL, &producer, NULL);
pthread_create(&th2, NULL, &comsumer, NULL);
pthread_join(th1, NULL);
pthread_join(th2, NULL);
printf("num = %d\n", num);
return 0;
}
条件变量
条件变量本身不是锁,但它可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个汇合的场所。
pthread_cond_wait(&cond, &mutex)
1. 阻塞等待该条件变量直到满足 2.释放已经掌握的互斥锁 3.满足条件后重新拿锁
#include <unistd.h>
#include <stdio.h>
#include <pthread.h>
#include <stdlib.h>
const int MAX_BUF = 100;
int num = 0;
pthread_cond_t cond = PTHREAD_COND_INITIALIZER;
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
void *producer(void *){
while(true){
pthread_mutex_lock(&mutex);
while(num >= MAX_BUF){ //由于阻塞解除时,只有一个物品可用 但是却有多个线程解除阻塞 所以需要循环检测一次
//满了 等待消费者消费
pthread_cond_wait(&cond, &mutex);
printf("缓冲区满了 等待消费者消费\n");
}
//生产一个物品
++num;
printf("生产一个产品,当前产品数量为:%d\n", num);
sleep(1);
pthread_mutex_unlock(&mutex);
//通知消费者可消费了
pthread_cond_signal(&cond);
printf("通知消费者...\n");
sleep(1);
}
}
void *comsumer(void*){
while(true){
pthread_mutex_lock(&mutex);
while(num <= 0){
//缓冲区为空 等待生产者生产
pthread_cond_wait(&cond, &mutex);
printf("缓冲区空了 等待生产者生产\n");
}
//生产一个物品
--num;
printf("消费一个产品,当前产品数量为:%d\n", num);
sleep(1);
pthread_mutex_unlock(&mutex);
//通知消费者可消费了
pthread_cond_signal(&cond);
printf("通知生产者...\n");
}
}
int main(){
pthread_t th1, th2;
pthread_create(&th1, NULL, &producer, NULL);
pthread_create(&th2, NULL, &comsumer, NULL);
pthread_join(th1, NULL);
pthread_join(th2, NULL);
printf("num = %d\n", num);
return 0;
}