C/C++ WebServer 1 Linux系统编程入门

Meqt

为了软件课设来看的这门课,简介上说的是C++,但其实大多数时候还是写的C-Style的代码,这门课里讲到了进程、多线程,还有同步、异步,项目里用到了epoll。但软件课设的后端相对比较简单,也没有什么多线程的问题,唯一的要求是要用数据库,这里面还没讲。感觉对软件课设帮助不大,就当看着玩吧。课程链接

因为最近在学C++,所以想用C++写后端,现在总之就是十分后悔。先是看了CGI,还装了Apache,后来又找了个C++的后端库oatpp。事已至此,只能硬着头皮写下去了。

gcc

Command Usage
-E Run the preprocessing stage
-S Run the previous stages as well as LLVM generation and optimization stages and target-specific code generation, producing an assembly file
-c Run all of the above, plus the assembler, generating a target “.o” object file
-o Write output to file
-I Add the specified directory to the search path for included files
-g Generate debug information
-D Adds an implicit #define into the predefined buffer which is read before the source file is being preprocessed.
-w
-Wall
-On
-l
-L
-fpic/-fPIC
-shared
-std
1
2
3
4
5
6
7
8
9
10
11
#include <stdio.h>

int main()
{
int a = 10;
#ifdef DEBUG
printf("message appear only when debugging\n");
#endif
for(int i=0; i<3; ++i) printf("hello, GCC!!!\n");
return 0;
}
1
gcc test.c -o test -DDEBUG

静态库的制作

Name

Linux : libxxx.a
Windows: libxxx.lib

1
2
gcc -c *.c
ar rcs libxxx.a *.o

动态库的制作和使用

linux: libxxx.so

1
2
3
4
#-fpic 与位置无关
gcc -c -fpic/-fPIC a.c b.c
gcc -shared a.o b.o -o libxxx.so
gcc main.c -o main -I include/ -L lib/ -l calc

cannot open shared object file: No such file or directory

动态库加载失败的原因

1
2
ldd main
DT_RPATH -> LD_LIBRARY_PATH -> /etc/ld.so.cache -> /lib/, /usr/lib

解决动态库加载失败问题

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
env
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dachuang/Downloads/CppWeb/Lesson06/library/lib
echo $LD_LIBRARY_PATH
ldd main

# path will disappear after close path
# user level setting
vim .bashrc
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dachuang/Downloads/CppWeb/Lesson06/library/lib

. .bashrc
source .bashrc
# root
sudo vim /etc/profile
export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/home/dachuang/Downloads/CppWeb/Lesson06/library/lib
source /etc/profile

# don't set both user level and system level
sudo vim /etc/ld.so.conf
# /home/dachuang/Downloads/CppWeb/Lesson06/library/lib
sudo ldconfig

# /lib /usr/lib don't recommend using

静态库和动态库的对比

静态库的优缺点

  • 优点
    • 小->静态库
    • 大->动态库
    • 静态库加载速度更快,
    • 发布程序无需提供静态库,发布方便
  • 缺点
    • 消耗系统资源
    • 浪费内存
    • 更新,部署,发布麻烦(需要重新编译)
      动态库的优缺点
  • 优点
    • 进程间资源共享
    • 更新部署发布简单(只用重新编译动态库,不用重编app)
    • 可以控制何时加载动态库
  • 缺点
    • 加载速度相对慢
    • 发布程序时需要提供依赖的动态库

Makefile

一个 Makefile 文件中可以有一个或者多个规则

1
2
3
obj : rely
command
...

检查更新:目标与依赖文件的更新时间
预定义变量

  • AR=ar
  • CC=cc
  • CXX=g++
  • $@:目标的完整名称
  • $<:第一个依赖文件的名称
  • $^:所有的依赖文件
  • $(变量名)
  • Wildcard 通配符
  • Patsubst 替换
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    src = $(wildcard ./*.cpp) 
    objs = $(patsubst %.cpp, %.o,$(src))
    target=app
    $(target):$(objs)
    $(CXX) $^ -o $@
    %.o:%.cpp
    $(CXX) -c $< -o $@

    .PHONY:clean
    clean:
    rm $(objs) -f

GDB调试

1
2
3
4
5
6
7
8
9
10
gcc -g -Wall program.c -o program

gdb
quit
set args 10 20
show args
help
list/l
show list/listsize
set list/listsize 行数
1
2
3
4
5
6
7
break/b
info break
break/b file:func
break/b file:line
d/delete Num of break
disable/enable Num of break
b/break 16 if i=5
1
2
3
4
5
6
7
8
9
10
11
start
run
c/continue
n/next (won't enter func) vs s/step
p/print
ptype
display num
i/info display
undisplay
set var
until

标准C库IO函数和Linux系统IO函数对比

C库调用Linux的IO API
C 的函数有缓冲区,系统IO无buffer

1
2
3
4
5
fflush
/* buffer filled (8k) */
flose
return()
exit()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
File* fp
int _fileno;
char* _IO_read_ptr; /* Current read pointer */
char* _IO_read_end; /* End of get area. */
char* _IO_read_base; /* Start of putback+get area. */
char* _IO_write_base; /* Start of put area. */
char* _IO_write_ptr; /* Current put pointer. */
char* _IO_write_end; /* End of put area. */
char* _IO_buf_base; /* Start of reserve area. */
char* _IO_buf_end; /* End of reserve area. */
/* The following fields are used to support backing up and undo. */
char *_IO_save_base; /* Pointer to start of non-current get area. */
char *_IO_backup_base; /* Pointer to first valid character of backup area */
char *_IO_save_end; /* Pointer to end of non-current get area. */

虚拟地址空间

MMU 虚拟地址映射到真实地址
0-3G 用户区

  • 0-4k 保护区
  • .text
  • data
  • heap 由低到高
  • Stack < heap 由高到低
  • Args
    3G-4G内核区
  • 内存管理
  • 进程管理
  • 设备驱动
  • Virtual File System

文件描述符

PCB 文件描述符表 1024

0 STDIN_FILENO
1 STDOUT_FILENO
2 STDERR_FILENO

Fopen 同一个文件的文件描述符不同,打开最小的文件描述符

open创建新文件

1
2
man 2 open
umask 022 # set umask
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
/**
@param flags: O_RDONLY, O_WRONLY, O_RDWR optionly O_CREAT
@param mode: mode(oct num rwxrwxrwx) of file is (mode & ~umask)
int open(const char *pathname, int flags, mode_t mode);
*/
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <unistd.h>
int main() {
int fd = open("create.txt", O_RDWR | O_CREAT, 0777);
if (fd==-1) perror("open");
close(fd);
return 0;
}

read、write函数

1
2
man 2 read
man 2 write
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
// copyfile.c
#include <unistd.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main() {
int srcfd = open("english.txt", O_RDONLY);
if (srcfd==-1) {perror("open");return -1;}

int dstfd = open("cpy.txt", O_WRONLY|O_CREAT, 0664);
if (dstfd==-1) {perror("open");return -1;}

char buf[1024] = {0};
int len=0;
while((len = read(srcfd, buf, sizeof(buf))) > 0 ) write(dstfd, buf, len);
close(dstfd);
close(srcfd);
}

lseek

1
man 2 lseek
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 <stdio.h>
int fseek(FILE *stream, long offset, int whence);

#include <sys/types.h>
#include <unistd.h>
/**
@param offset
@param whence
SEEK_SET:
SEEK_CUR: current + offset
SEEK_END: file_end + offset
*/
off_t lseek(int fd, off_t offset, int whence);
lseek(fd, 0, SEEK_SET); //文件头
lseek(fd, 0, SEEK_CUR); //当前文件位置
lseek(fd, 0, SEEK_END); //file end
lseek(fd, 100, SEEK_END); //extend file, 需要写入数据

#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
int fd = open("hello.txt", O_RDWR);
if (fd==-1) {
perror("open");
return -1;
}
int ret = lseek(fd, 100, SEEK_END);
if (ret==-1) {
perror("lseek");
return -1;
}
write(fd, " ", 1);
close(fd);
return 0;
}

stat、lstat函数

1
2
man 2 stat
stat
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
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>

int stat(const char *pathname, struct stat *statbuf);
int fstat(int fd, struct stat *statbuf);
int lstat(const char *pathname, struct stat *statbuf);

truct stat {
dev_t st_dev; /* ID of device containing file */
ino_t st_ino; /* Inode number */
mode_t st_mode; /* File type and mode */
0-2: others
3-5: group
6-8: user
9-11:特殊权限位
12-15:文件类型
nlink_t st_nlink; /* Number of hard links */
uid_t st_uid; /* User ID of owner */
gid_t st_gid; /* Group ID of owner */
dev_t st_rdev; /* Device ID (if special file) */
off_t st_size; /* Total size, in bytes */
blksize_t st_blksize; /* Block size for filesystem I/O */
blkcnt_t st_blocks; /* Number of 512B blocks allocated */
/* Since Linux 2.6, the kernel supports nanosecond
precision for the following timestamp fields.
For the details before Linux 2.6, see NOTES. */

struct timespec st_atim; /* Time of last access */
struct timespec st_mtim; /* Time of last modification */
struct timespec st_ctim; /* Time of last status change */

#define st_atime st_atim.tv_sec /* Backward compatibility */
#define st_mtime st_mtim.tv_sec
#define st_ctime st_ctim.tv_sec
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdio.h>

int main() {
struct stat statbuf;
int ret = stat("a.txt", &statbuf);
if (ret==-1) {
perror("stat");
return -1;
}
printf("size: %ld\n", statbuf.st_size);
return 0;
}
1
2
ln -s a.txt b.txt
lstat 获取软连接到文件的信息

模拟实现 ls -l 命令

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 <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <pwd.h>
#include <grp.h>
#include <time.h>
#include <string.h>

/**
* simulate ls -l command
* @param argc number of arguments
* @param argv value of arguments
*/
int main(int argc, char* argv[]) {
if (argc<2) {
printf("%s filename\n", argv[0]);
return -1;
}
// access file state
struct stat statbuf;
if( stat(argv[1], &statbuf) == -1 ) {
perror("stat");
return -1;
}

char perms[] = "-rwxrwxrwx";
for (int i=0; i<9; i++) if ( ~(statbuf.st_mode>>i)&1) perms[9-i] = '-';

char * time = ctime(&statbuf.st_mtime);
char mtime[512];
strncpy(mtime, time, strlen(time)-1);

printf("%s %ld %s %s %ld %s %s\n", perms, statbuf.st_nlink, getpwuid(statbuf.st_uid)->pw_name, getgrgid(statbuf.st_gid)->gr_name, statbuf.st_size, mtime, argv[1]);

return 0;
}

文件属性操作函数

1
2
3
4
man 2 access
man 2 chmod
man 2 chown
man 2 truncate

目录操作函数

1
2
3
4
5
man 2 mkdir
man 2 rmdir
man 2 rename
man 2 chdir
man 2 getcwd

目录遍历函数

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
DIR *opendir(const char *name);
struct dirent *readdir(DIR *dirp);
int closedir(DIR *dirp);

#include <sys/types.h>
#include <dirent.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>

int getFileNum(const char * path);
// 读取某个目录下所有的普通文件的个数

int main(int argc, char * argv[]) {
if(argc < 2) {
printf("%s path\n", argv[0]);
return -1;
}
int num = getFileNum(argv[1]);
printf("普通文件的个数为:%d\n", num);
return 0;
}

// 用于获取目录下所有普通文件的个数
int getFileNum(const char * path) {
// 1.打开目录
DIR * dir = opendir(path);
if(dir == NULL) {
perror("opendir");
exit(0);
}
struct dirent *ptr;
// 记录普通文件的个数
int total = 0;
while((ptr = readdir(dir)) != NULL) {
// 获取名称
char * dname = ptr->d_name;
// 忽略掉. 和..
if(strcmp(dname, ".") == 0 || strcmp(dname, "..") == 0) continue;
// 判断是否是普通文件还是目录
if(ptr->d_type == DT_DIR) {
// 目录,需要继续读取这个目录
char newpath[256];
sprintf(newpath, "%s/%s", path, dname);
total += getFileNum(newpath);
}
if(ptr->d_type == DT_REG) total++;
}
// 关闭目录
closedir(dir);
return total;
}

dup, dup2 函数

1
2
3
4
5
6
7
8
9
#include <unistd.h>
/**
The dup() system call creates a copy of the file descriptor oldfd, using the lowest-numbered unused file descriptor for the new descriptor.
*/
int dup(int oldfd);
/**
The dup2() system call performs the same task as dup(), but instead of using the lowest-numbered unused file descriptor, it uses the file descriptor number specified in newfd. If the file descriptor newfd was previously open, it is silently closed before being reused.
*/
int dup2(int oldfd, int newfd);
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 <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>

int main() {
int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
if (fd==-1) {
perror("open");
return -1;
}

int fd1 =dup2(fd, dup(fd));
close(fd);
printf("fd:%d, fd1:%d\n", fd, fd1);
char *str = "helloworld";
if( write(fd1, str, strlen(str)) ==-1 ) {
perror("write");
return -1;
}
close(fd1);
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 <unistd.h>
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <string.h>

int main() {
int fd = open("a.txt", O_RDWR | O_CREAT, 0664);
int fd1 = open("b.txt", O_RDWR|O_CREAT, 0664);
if (fd==-1 | fd1==-1) {
perror("open");
return -1;
}

int fd2 =dup2(fd, fd1);
char *str = "helloworld";
if( write(fd1, str, strlen(str)) ==-1 ) {
perror("write");
return -1;
}
printf("fd:%d, fd1:%d, fd2:%d\n", fd, fd1, fd2);
close(fd2);
return 0;
}

fcntl

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
#include <unistd.h>
#include <fcntl.h>

/**
fcntl() performs one of the operations described below on the open file
descriptor fd. The operation is determined by cmd.
*/
int fcntl(int fd, int cmd, ... /* arg */ );


#include <unistd.h>
#include <fcntl.h>
#include <stdio.h>
#include <string.h>

int main() {

// 1.复制文件描述符
// int fd = open("1.txt", O_RDONLY);
// int ret = fcntl(fd, F_DUPFD);

// 2.修改或者获取文件状态flag
int fd = open("1.txt", O_RDWR|O_CREAT, 0664);
if(fd == -1) {
perror("open");
return -1;
}

// 获取文件描述符状态flag
int flag = fcntl(fd, F_GETFL);
if(flag == -1) {
perror("fcntl");
return -1;
}
flag |= O_APPEND; // flag = flag | O_APPEND

// 修改文件描述符状态的flag,给flag加入O_APPEND这个标记
int ret = fcntl(fd, F_SETFL, flag);
if(ret == -1) {
perror("fcntl");
return -1;
}

char * str = "nihao";
write(fd, str, strlen(str));

close(fd);

return 0;
}
  • Post title:C/C++ WebServer 1 Linux系统编程入门
  • Post author:Meqt
  • Create time:2022-12-17 16:40:08
  • Post link:https://meqtmac.github.io/2022/12/17/WebServerNote/webserver-note1/
  • Copyright Notice:All articles in this blog are licensed under BY-NC-SA unless stating additionally.