Linux 高并发服务器开发

Linux 高并发服务器开发

Linux 系统编程入门

GCC

image-20230411142304527

image-20230411142953910

静态库

库文件用于保存代码

1.不能单独运行

2.代码保密性好,方便部署与分发

静态库是指在编译时将库的代码和数据编译成可执行程序中的一个单独的可执行模块。这意味着静态库的代码在编译时被复制到可执行程序中,因此它们不需要在运行时加载。静态库的好处是可以获得更快的执行速度,因为代码和数据已经在可执行程序中,而不需要在运行时进行加载和链接。但是静态库的缺点是它会使得可执行程序的大小变得更大,并且在多个可执行程序之间共享代码会变得更加困难。

动态库是在运行时加载的库,它们不像静态库那样被编译到可执行程序中。当可执行程序运行时,它会通过动态链接器加载动态库并将其链接到可执行程序中。这使得动态库可以被多个可执行程序共享,从而节省磁盘空间和内存。另外,动态库可以被更新和替换而不需要重新编译可执行程序,因此它们更加灵活。但是,动态库的缺点是在加载和链接时需要一定的时间,因此可能会导致一定的性能损失。

命名规则:

Linux : libxxx.a

Windows: libxxx.lib

静态库制作

1.用Gcc 获得.o文件

2.将.o文件打包

1
2
3
4
ar rcs libxxx.a xxx.o
r-将文件存入备存文件
c-建立备存文件
s-索引

其中 xxx为库名

静态库使用

image-20230411154022370

1
gcc main.c -o app -I ./include/ -L ./lib/ -l suanshu

-I 指定include包含文件的搜索目录

-L指定编译时候,搜索的库目录

-l指定使用的库名

动态库

动态库文件命名规则:

Linux : libxxx.so

Windows: libxxx.dll

动态库制作

1.gcc 得到.o 文件 得到和位置无关的代码 gcc -c -fpic/-fPIC a.c b.c

2.gcc 得到动态库 gcc shared a.o b.o -o libxxx.so

使用

image-20230411162220648

gcc main.c -o app -I ./include/ -L ./lib/ -l calc

类似于静态库

由于动态库的代码不会被打包到可执行程序中

所以在运行用到了动态库的程序之前要将 动态库加载到内存中

ldd来检查可执行程序的动态库链接情况

写完动态库之后需要改变环境变量 或者 /etc/ld.so.cache 文件 来使程序能使用动态库

LD_LIBRARY_PATH

export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径

ps:这种方法并不用会把动态库放到/usr/bin/中,重新打开一个终端的话,需要重新将库路径添加到环境变量 LD_LIBRARY_PATH 中,是临时的

修改 .bashrc

在home 目录下 用vim 打开 .bashrc 在最后插入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径

修改完之后 . .bashrc更新环境

此方法是用户级别的

修改 /etc/profile

sudo vim /etc/profile

同样在最下面 插入export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:动态库的绝对路径

之后 source /etc/profile 更新即可

/etc/ld/so.cache

因为是二进制文件所有需要间接修改此文件

打开 vim /etc/ld.so.conf在最后直接添加 动态库的绝对路径即可

/lib/ /usr/lib

将动态库移动在这两个目录下即可,但是不推荐,因为有可能覆盖原有的文件

动态库和静态库对比

编译过程

linux

Makefile

IO函数

文件描述符

缓冲区地址

读写指针

虚拟内存地址

image-20230416124327957

实际上并不存在,生命周期等同程序生命周期

Linux网络编程

socket

小端字节序

字节序分为大端字节序(Big-Endian) 和小端字节序(Little-Endian)。大端字节序是指一个整数的最高位字节(23 ~ 31 bit)存储在内存的低地址处低位字节(0 ~ 7 bit)存储在内存的高地址处;小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处

0x 01 02 03 04 - ff = 255

内存的方向 —–> 内存的低位 —–> 内存的高位

以 0x 11 22 33 44 12 34 56 78 为例子

image-20230421143355158

大端字节序

0x 01 02 03 04

内存的方向 —–> 内存的低位 —–> 内存的高位

0x 12 34 56 78 11 22 33 44 为例子

image-20230421143416999

TCP多进程实现并发服务器

服务端

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
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <signal.h>
#include <wait.h>
#include <errno.h>

void recyleChild(int arg) {
while(1) {
int ret = waitpid(-1, NULL, WNOHANG);
if(ret == -1) {
// 所有的子进程都回收了
break;
}else if(ret == 0) {
// 还有子进程活着
break;
} else if(ret > 0){
// 被回收了
printf("子进程 %d 被回收了\n", ret);
}
}
}

int main() {

struct sigaction act;
act.sa_flags = 0;
sigemptyset(&act.sa_mask);
act.sa_handler = recyleChild;
// 注册信号捕捉
sigaction(SIGCHLD, &act, NULL);


// 创建socket
int lfd = socket(PF_INET, SOCK_STREAM, 0);
if(lfd == -1){
perror("socket");
exit(-1);
}

struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
saddr.sin_addr.s_addr = INADDR_ANY;

// 绑定
int ret = bind(lfd,(struct sockaddr *)&saddr, sizeof(saddr));
if(ret == -1) {
perror("bind");
exit(-1);
}

// 监听
ret = listen(lfd, 128);
if(ret == -1) {
perror("listen");
exit(-1);
}

// 不断循环等待客户端连接
while(1) {

struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);
// 接受连接
int cfd = accept(lfd, (struct sockaddr*)&cliaddr, &len);
if(cfd == -1) {
if(errno == EINTR) {
continue;
}
perror("accept");
exit(-1);
}

// 每一个连接进来,创建一个子进程跟客户端通信
pid_t pid = fork();
if(pid == 0) {
// 子进程
// 获取客户端的信息
char cliIp[16];
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, cliIp, sizeof(cliIp));
unsigned short cliPort = ntohs(cliaddr.sin_port);
printf("client ip is : %s, prot is %d\n", cliIp, cliPort);

// 接收客户端发来的数据
char recvBuf[1024];
while(1) {
int len = read(cfd, &recvBuf, sizeof(recvBuf));

if(len == -1) {
perror("read");
exit(-1);
}else if(len > 0) {
printf("recv client : %s\n", recvBuf);
} else if(len == 0) {
printf("client closed....\n");
break;
}
write(cfd, recvBuf, strlen(recvBuf) + 1);
}
close(cfd);
exit(0); // 退出当前子进程
}

}
close(lfd);
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
// TCP通信的客户端
#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>

int main() {

// 1.创建套接字
int fd = socket(AF_INET, SOCK_STREAM, 0);
if(fd == -1) {
perror("socket");
exit(-1);
}

// 2.连接服务器端
struct sockaddr_in serveraddr;
serveraddr.sin_family = AF_INET;
inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr.s_addr);
serveraddr.sin_port = htons(9999);
int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

if(ret == -1) {
perror("connect");
exit(-1);
}

// 3. 通信
char recvBuf[1024];
int i = 0;
while(1) {

sprintf(recvBuf, "data : %d\n", i++);

// 给服务器端发送数据
write(fd, recvBuf, strlen(recvBuf)+1);

int len = read(fd, recvBuf, sizeof(recvBuf));
if(len == -1) {
perror("read");
exit(-1);
} else if(len > 0) {
printf("recv server : %s\n", recvBuf);
} else if(len == 0) {
// 表示服务器端断开连接
printf("server closed...");
break;
}

sleep(1);
}

// 关闭连接
close(fd);

return 0;
}

UDP通信

服务端

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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main() {

// 1.创建一个通信的socket
int fd = socket(PF_INET, SOCK_DGRAM, 0);

if(fd == -1) {
perror("socket");
exit(-1);
}

struct sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(9999);
addr.sin_addr.s_addr = INADDR_ANY;

// 2.绑定
int ret = bind(fd, (struct sockaddr *)&addr, sizeof(addr));
if(ret == -1) {
perror("bind");
exit(-1);
}

// 3.通信
while(1) {
char recvbuf[128];
char ipbuf[16];

struct sockaddr_in cliaddr;
int len = sizeof(cliaddr);

// 接收数据
int num = recvfrom(fd, recvbuf, sizeof(recvbuf), 0, (struct sockaddr *)&cliaddr, &len);

printf("client IP : %s, Port : %d\n",
inet_ntop(AF_INET, &cliaddr.sin_addr.s_addr, ipbuf, sizeof(ipbuf)),
ntohs(cliaddr.sin_port));

printf("client say : %s\n", recvbuf);

// 发送数据
sendto(fd, recvbuf, strlen(recvbuf) + 1, 0, (struct sockaddr *)&cliaddr, sizeof(cliaddr));

}

close(fd);
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
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <arpa/inet.h>

int main() {

// 1.创建一个通信的socket
int fd = socket(PF_INET, SOCK_DGRAM, 0);

if(fd == -1) {
perror("socket");
exit(-1);
}

// 服务器的地址信息
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(9999);
inet_pton(AF_INET, "127.0.0.1", &saddr.sin_addr.s_addr);

int num = 0;
// 3.通信
while(1) {

// 发送数据
char sendBuf[128];
sprintf(sendBuf, "hello , i am client %d \n", num++);
sendto(fd, sendBuf, strlen(sendBuf) + 1, 0, (struct sockaddr *)&saddr, sizeof(saddr));

// 接收数据
int num = recvfrom(fd, sendBuf, sizeof(sendBuf), 0, NULL, NULL);
printf("server say : %s\n", sendBuf);

sleep(1);
}

close(fd);
return 0;
}

Linux 高并发服务器开发
http://example.com/2023/04/11/linux/
作者
CynicCat
发布于
2023年4月11日
许可协议