webbench

测压软件 最高并发3W

webbench做测试时自身也会消耗CPU和内存资源,为了测试准确,需要将webbench安装在别的服务器上。

1
2
3
4
tar -xzvf webbench-1.5.tar.gz
cd webbench-1.5
apt-get install ctags
make && make install

测试

1
webbench -c 1000 -t 5 http://127.0.0.1:9999/index.html //1000个客户端 5秒钟时间

github

1
git clone https://github.com/SYaoJun/WebServer.git //把仓库中的内容拷贝到linux系统中

回调函数

函数指针本身是指针变量,指向某个函数的入口地址。

1
2
3
4
5
6
7
8
9
10
11
#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;
}
1
2
3
4
5
6
7
void cb(void){  //回调函数
puts("记秒到时");
}
int main(){
run(cb);
return 0;
}
1
gcc cb.c main.c

信号函数

1
2
raise(SIGSEGV) //自己给自己进程发送信号 11
abort() //终止当前进程

测试1s打印多少个数

alarm函数

1
2
3
4
5
6
7
#include <unistd.h>
#include <stdio.h>
int main(){
alarm(1); //计时1s 到时后内核发送一个sigalarm信号终止
for(int i = 0;;i++) printf("%d\n", i);
return 0;
}

setitimer函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
#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;
}

创建线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#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;
}

创建多个子线程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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语言的使用

1
2
sudo apt-get install mysql-client mysql-server //先安装mysql
sudo apt-get install libmysql++-dev //安装库

先启动mysql库 设置好密码 然后使用c程序连接

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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;
}

编译的命令

1
gcc sql.c -lmysqlclient //生成a.out文件 执行

指针和数组

1
2
3
char str[]="hello"; //字符串含\0   6字节
char *url ="hello"; //指针64位机 8字节
cout<<sizeof(str)<<" "<<sizeof(url)<<endl;

CGI服务器

原理:输出到某个文件描述符上的内容直接发送到客户端连接对应的socket上,此处我们模拟了把服务器端输出到标准输出的内容,直接发送给客户端。

测试

1
telnet 127.0.0.1 6666
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
#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。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
#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;
}

信号屏蔽字

主要函数

1
2
3
sigaddset()
sigprocmask()
sigpending()

使用kill发送信号

1
kill -6 PID

测试程序

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
#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信号捕捉函数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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上的读端监听事件,再在主循环中处理事件。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
#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;
}

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#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;
}

当前时间

1
2
3
4
5
6
7
8
9
10
11
12
#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;
}

初阶日志系统

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
#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();
}

单例模式

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//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;
}

主线程测试

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#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并不改变。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#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;
}

管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
#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里面的输入和输出都被重定向了

1
2
3
4
5
6
7
8
9
10
11
12
#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可执行文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
#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;
}

多线程服务器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
#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程序的注册还没完善,只能做到查询。等以后有空再做吧。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
#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回收子进程

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
#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中使用。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
#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;
}

条件变量

条件变量本身不是锁,但它可以造成线程阻塞。通常与互斥锁配合使用。给多线程提供一个汇合的场所。

1
2
pthread_cond_wait(&cond, &mutex)
1. 阻塞等待该条件变量直到满足 2.释放已经掌握的互斥锁 3.满足条件后重新拿锁
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
#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;
}