基础API

socket地址API

字节序

CPU的累加器一次至少装在4字节,字节在内存排序即字节序问题

大多PC采用小端:高高低低;大端相反

1
2
3
4
5
6
7
8
9
10
11
12
// which endian
#include <stdio.h>
void byteorder(){
union{
short value;
char union_bytes[sizeof(short)];
}test;
test.value = 0x0102;
if((test.union_bytes[0] == 1) && (test.union_bytes[2]) == 2) printf("big endian\n");
else if((test.union_bytes[0] == 2) && (test.union_bytes[2]) == 1) printf("little endian\n");
else printf("unknown\n");
}

网络字节序即大端字节序,主机于网络字节序转换api

1
2
3
4
5
#include <netinet/in.h>
unsigned long int htonl(unsigned long int hostlong);
unsigned short int htons(unsigned short int hostshort);
unsigned long int ntohl(unsigned long int netlong);
unsgined short int ntohs(unsigned short int netshort);

socket地址

通用地址sockaddr
1
2
3
4
5
6
7
8
9
10
11
12
13
#include <bits/socket.h>
struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
// 扩充内存的新通用地址
struct sockaddr__storage
{
sa_family_t sa_family;
unsigned long int __ss_align; // 内存对齐
char __ss_padding[128-sizeof(__s_align)];
};
协议族 内容
PF_UNIX 文件名路径,长度可到108btye
PF_INET 16bit和32bit,6byte
PF_INET6 16bit,32bit流标识,128bitIPv6,32bit范围ID,26byte

AF* PF*值相同,一般混用

专用socket地址
1
2
3
4
5
#include <sys/un.h>
struct sockaddr_un {
sa_family_t sin_family;
char sun_paath[108];
};
1
2
3
4
5
6
struct sockaddr_in{
sa_family_t sin_family;
u_int16_t sin_port;
struct in_addr sin_addr;
};
struct in_addr{ u_int32_t s_addr; };
1
2
3
4
5
6
7
8
struct sockaddr_in6{
sa_family_t sin6_family;
u_int16_t sin6_port;
u_int32_t sin6_flowinfo; // set 0
struct in6_addr sin6_addr;
u_int32_t sin6_scope_id;
};
struct in6_addr{ unsigned char sa_addr[16]; };
字符串ip地址转换
1
2
3
4
#include <arpa/inet.h>
in_addr_t inet_addr(const char* strptr); // 失败返回INADDR_NONE
int inet_aton(const char* cp, struct in_addr* inp); // 失败返回0
char* inet_ntoa(struct in_addr in); // 不可重入,静态内存
1
2
3
4
5
6
7
// 通用,失败返回0并设置errno
int inet_pton(int ad, const char *src, void *dst);
const char* inet_ntop(int af, const void* src, char* dst, socklen_t cnt);

#include <netinet/in.h>
#define INET_ADDRSTRLEN 16
#define INET6_ADDRSTRLEN 46

socket使用

默认成功返回0,失败返回-1并设置errno

创建
1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int socket(int dmain, int type, int protocol);
type protocal
SOCK_STREAM, SOCK_UGRAM SOCK_NONBLOCK, SOCK_CLOEXEC
命名
1
2
3
#include <sys/types.h>
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen);
errno 含义
EACCES 知名服务端口(0~1023)
EADDRINUSE 正在使用,比如TIME_WAIT
监听并test backlog参数
1
2
#include<sys/socket.h>
int listen(int sockfd, int backlog);

backlog提示内核监听队列最大长度,在内核版本2.2后,表示全连接状态的socket上限

半连接状态:/proc/sys/net/ipv4/tcp_max_syn_backlog内核参数定义,一般为1024

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
// test backlog
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <signal.h>
#include <unistd.h>
#include <stdlib.h>
#include <assert.h>
#include <stdio.h>
#include <string.h>
#include <stdbool.h>
static bool stop = false;
static void handle_term(int sig) { stop = true; }
int main(int argc, char *argv[])
{
signal(SIGTERM, handle_term);
if (argc < 3)
{
printf("useage:%s ip_address port_number backlog\n", argv[0]);
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
int backlog = atoi(argv[3]);
int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr);
int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, backlog);
assert(ret != -1);
while (!stop)
sleep(1);
close(sock);
return 0;
}
1
2
3
4
5
./test_backlog localhost 12345 0
tcp 0 0 127.0.0.1:35604 127.0.0.1:12345 ESTABLISHED
tcp 0 0 127.0.0.1:12345 127.0.0.1:35604 ESTABLISHED
tcp 0 1 127.0.0.1:35606 127.0.0.1:12345 SYN_SENT
// 最多backlog+1个连接
接受连接
1
2
3
#include<sys/types.h>
#include<sys/socket.h>
int accept(int sockfd, struct sockaddr*addr, socklen_t*addrlen);

测试accept如何取出监听队列的连接,

三次握手进入全连接队列,这时候客户端掉线或网络断开,服务端依旧认为是ESTABLISHED

从结果发现accept只负责取出连接,不关心网络状态变化

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
#include<sys.socket.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
int main(int argc, char *argv[])
{
if (argc <= 2)
{
printf("useage:%s ip_address port_number\n", argv[0]);
}
const char *ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, 5);
assert(ret != -1);
sleep(20);
struct sockaddr_in client;
socklen_t client_addrlen = sizeof(client);
int connfd = connect(sock, (struct sockaddr *)&client, client_addrlen);
if (connfd < 0)
printf("errno is:%d\n", errno);
else
{
char remote[INET_ADDRSTRLEN];
printf("connected with ip:%s and port:%d\n",
inet_ntop(AF_INET, &client.sin_addr, remote, INET_ADDRSTRLEN), ntohs(client.sin_port));
close(connfd);
}
close(sock);
return 0;
}
发起连接
1
2
3
#inlcude<sys/types.h>
#include<sys/socket.h>
int connet(int sockfd, const struct sockaddr* serv_addr, socklen_t addrlen);
errno 含义
ECONNREFUSED 目标端口不存在,连接被拒绝
ETIMEDOUT 连接超时
关闭连接
1
2
3
4
#include<unistd.h>
int close(int fd); // 引用计数
#include<sys/socket.h>
int shutdown(int sockfd, int howto);
howto 含义
SHUT_RD 关闭读通道,丢弃接受缓存区
SHUT_WR 关闭写通道,发送发送缓存区
SHUT_RDWR 同时关闭读写

数据读写

1
2
3
4
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recv(int sockfd, void* buf, size_t len, int flags); // flags set 0
ssize_t send(int sockfd, const void* buf, size_t len, int flags);

send成功返回写入长度,失败返回-1并设置errno(好像并不常见)MSG_MORE,MSG_OOB

带外数据发送和接收

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
// send
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <libgen.h>
int main(int argc, char *argv[])
{
if (argc <= 2)
{
printf("usage:%s ip_address port\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
memset(&address, 0, sizeof(address));
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd >= 0);
if (connect(sockfd, (struct sockaddr *)&address, sizeof(address)) < 0)
{
printf("connection failed\n");
}
else
{
const char *oob_data = "abc";
const char *normal_data = "123";
send(sockfd, normal_data, strlen(normal_data), 0);
send(sockfd, oob_data, strlen(oob_data), MSG_OOB);
send(sockfd, normal_data, strlen(normal_data), 0);
}
close(sockfd);
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
// recv
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <libgen.h>
#define BUF_SIZE 1024
int main(int argc, char *argv[])
{
if (argc <= 2)
{
printf("usage:%s ip_address port_number\n", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
socklen_t addrlen = sizeof(address);
memset(&address, 0, addrlen);
address.sin_family = AF_INET;
address.sin_port = htons(port);
inet_pton(AF_INET, ip, &address.sin_addr);
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
assert(sockfd >= 0);
int ret = bind(sockfd, (struct sockaddr *)&address, addrlen);
assert(ret != -1);
ret = listen(sockfd, 5);
int connfd = accept(sockfd, (struct sockaddr *)&address, &addrlen);
if (connfd < 0)
printf("errno is %d\n", errno);
else
{
char buff[BUF_SIZE];
memset(buff, '\0', BUF_SIZE);
ret = recv(connfd, buff, BUF_SIZE - 1, 0);
printf("get%d bytes of normal data %s\n", ret, buff);
memset(buff, '\0', BUF_SIZE);
ret = recv(connfd, buff, BUF_SIZE - 1, MSG_OOB);
printf("get%d bytes of oob data %s\n", ret, buff);
memset(buff, '\0', BUF_SIZE);
ret = recv(connfd, buff, BUF_SIZE - 1, 0);
printf("get%d bytes of normal data %s\n", ret, buff);
close(sockfd);
}
close(sockfd);
return 0;
}

flags仅生效一次,带外数据没有通过MSG_OOB读取的话后面的数据将给截断,

1
2
3
4
5
6
7
get5 bytes of normal data 123ab
get1 bytes of oob data c
get3 bytes of normal data 123
tcpdump -i lo port 8080
...
20:37:35.937946 IP localhost.44714 > localhost.webcache: Flags [P.U], seq 4:7, ack 1, win 342, urg 3, options [nop,nop,TS val 1603934628 ecr 1603934628], length 3
...

UDP数据读写

1
2
3
4
#include<sys/types.h>
#include<sys/socket.h>
ssize_t recvfrom(int sockfd, void* buf, size_t len, int flags, struct sockaddr* src_addr, socklen_t* addrlen);
ssize_t sendto(int sockfd, const void* buf,size_t len, int flags, const struct sockaddr*dest_addr, socklen_t addrlen);

通用数据读写(分散读,集中写)

1
2
3
#include<sys/socket.h>
ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);
ssize_t sendmsg(int sockfd, struct msghdr* msg, int flags);
1
2
3
4
5
6
7
8
9
struct msghdr{
void* msg_name; // socket地址结构变量,TCP时为0
socklen_t msg_namelen;
struct iovec* msg_iov;
int msg_iovlen;
void* msg_contrtol;
socklen_t msg_controllen;
int msg_flags;
};
1
2
3
4
struct iovec{
void* iov_base; // 内存起始地址
size_t iov_len; // 长度
};

带外标记

1
2
#include<sys.socket.h>
int sockatmark(int sockfd); //next if oob return 1

获取地址信息

1
2
3
#include<sys/socket.h>
int getsockname(int sockfd, struct sockaddr* address, socklen_t* address_len);
int getpeername(int sockfd, struct sockaddr* address, socklen_t* address_len);

socket选项

1
2
3
4
#include<sys/socket.h>
int getsockopt(int sockfd, int level, int option_name, void* option_value, socklen_t* __restrict__ option_len);
int setsockopt(int sockfd, int level, int option_name, const void* option_value, socklen_t option_len);
// level一般用通用选项SOL_SOCKET

对监听socket设置这些socket选项,那么accept返回的连 接socket将自动继承这些选项。这些socket选项包括:SO_DEBUG、 SO_DONTROUTE、SO_KEEPALIVE、SO_LINGER、 SO_OOBINLINE、SO_RCVBUF、SO_RCVLOWAT、SO_SNDBUF、 SO_SNDLOWAT、TCP_MAXSEG和TCP_NODELAY。而对客户端而 言,这些socket选项则应该在调用connect函数之前设置,因为connect调 用成功返回之后,TCP三次握手已完成。

SO_REUSEADDR

强制使用处于TIME_WAIT状态的sock连接

1
2
int sock = socket(PF_INET,SOCK_STREAM,0);
setsockopt(sock,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));

修改内核 参数/proc/sys/net/ipv4/tcp_tw_recycle

SO_RCVBUF和SO_SNDBUF

系统会将设置的值double,并且不小于某个最小值

/proc/sys/net/ipv4/tcp_rmem和/proc/sys/net/ipv4/tcp_wmem

高级IO函数

fd创建

失败返回-1并设置errno

pipe:

1
2
#include<unistd.h>
int pipe(int fd[2]);

双向管道:domain只能用本地协议族,可读可写

1
2
3
#include<sys/types.h>
#include<sys/socket.h>
int socketpair(int domain,int type,int protocol,int fd[2]);

dup和dup2:不继承原来属性

1
2
3
#include<unistd.h>
int dup(int file_descriptor);
int dup2(int oldfd,int newfd);

dup返回描述符最小可用值,dup2会直接关闭newfd

CGI服务器原理:关闭STDOUT_FILENO,dup(connfd)返回1fd,相当于重定位标准输出

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
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>
int main(int argc, char*argv[])
{
if(argc<=2)
{
printf("usage:%s ip_address port\b", basename(argv[0]));
return 1;
}
const char*ip=argv[1];
int port=atoi(argv[2]);
struct sockaddr_in address;
bzero(&address,sizeof(address));
address.sin_family=AF_INET;
inet_pton(AF_INET,ip,&address.sin_addr);
address.sin_port=htons(port);
int sock=socket(PF_INET,SOCK_STREAM,0);
assert(sock>=0);
int ret=bind(sock,(struct sockaddr*)&address, sizeof(address));
assert(ret!=-1);
ret=listen(sock,5);
assert(ret!=-1);
struct sockaddr_in client;
socklen_t client_addr_len = sizeof(client);
int connfd=accept(sock,(struct sockaddr*)&client, &client_addr_len);
if(connfd<0) printf("errno: is%d\n",errno);
else
{
close(STDOUT_FILENO);
dup(connfd);
printf("abcd\n");
close(connfd);
}
close(sock);
return 0;
}

数据读写

分散读和集中写

1
2
3
#include<sys/uio.h>
ssize_t readv(int fd,const struct iovec*vector,int count)
ssize_t writev(int fd,const struct iovec*vector,int count);

web服务器上集中写

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
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/uio.h>
#define BUFFER_SIZE 1024
static const char *status_line[2] = {"200 OK", "500 Internal server error"};
int main(int argc, char *argv[])
{
if (argc <= 3)
{
printf("usage:%s ip_address port\b", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
const char *file_name = argv[3];
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, 5);
assert(ret != -1);
struct sockaddr_in client;
socklen_t client_addr_len = sizeof(client);
int connfd = accept(sock, (struct sockaddr *)&client, &client_addr_len);
if (connfd < 0)
printf("errno: is%d\n", errno);
else
{
char header_buf[BUFFER_SIZE];
memset(header_buf, '\0', BUFFER_SIZE);
char *file_buf;
struct stat file_stat;
bool valid = true;
int len = 0;
if (stat(file_name, &file_stat) < 0)
valid = false;
else
{
if (S_ISDIR(file_stat.st_mode))
valid = false;
else if (file_stat.st_mode & S_IROTH)
{
int fd = open(file_name, 0, O_RDONLY);
file_buf = new char[file_stat.st_size + 1];
memset(file_buf, '\0', file_stat.st_size + 1);
if (read(fd, file_buf, file_stat.st_size) < 0)
valid = false;
}
else
valid = false;
}
if (valid)
{
ret = snprintf(header_buf, BUFFER_SIZE - 1, "%s%s\r\n", "HTTP/1.1", status_line[0]);
struct iovec iv[2];
iv[0].iov_base = header_buf;
iv[0].iov_len = strlen(header_buf);
iv[1].iov_base = file_buf;
iv[1].iov_len = file_stat.st_size;
ret = writev(connfd, iv, 2);
}
else
{
ret = snprintf(header_buf + len, BUFFER_SIZE - 1, "%s", "\r\n");
send(connfd, header_buf, strlen(header_buf), 0);
}
close(connfd);
delete[] file_buf;
}
close(sock);
return 0;
}

零拷贝

sendfile:网络传输文件专用:

in_fd不能是socket和管道,必须是真实文件;out_fd必须是socket

1
2
#include<sys/sendfile.h>
ssize_t sendfile(int out_fd,int in_fd,off_t*offset,size_t count);

类似上面web程序,但直接从内核读取发送,避免拷贝到用户区

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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdbool.h>
#include <sys/sendfile.h>
int main(int argc, char *argv[])
{
if (argc <= 3)
{
printf("usage:%s ip_address port\b", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
const char *file_name = argv[3];
int filefd = open(file_name, O_RDONLY);
assert(filefd > 0);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, 5);
assert(ret != -1);
struct sockaddr_in client;
socklen_t client_addr_len = sizeof(client);
int connfd = accept(sock, (struct sockaddr *)&client, &client_addr_len);
if (connfd < 0)
printf("errno: is%d\n", errno);
struct stat stat_buf;
fstat(filefd, &stat_buf);
sendfile(connfd, filefd, NULL, stat_buf.st_size);
close(connfd);
close(filefd);
close(sock);
return 0;
}

splice:必须包含管道文件描述符,且off为NULL

1
2
3
#include<fcntl.h>
ssize_t splice(int fd_in,loff_t*off_in,int
fd_out,loff_t*off_out,size_t len,unsigned int flags);

image-20241118205544323

零拷贝回射服务器:未涉及recv/send操作

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 <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <assert.h>
#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#include <errno.h>
#include <string.h>
#include <libgen.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char *argv[])
{
if (argc <= 2)
{
printf("usage:%s ip_address port\b", basename(argv[0]));
return 1;
}
const char *ip = argv[1];
int port = atoi(argv[2]);
struct sockaddr_in address;
bzero(&address, sizeof(address));
address.sin_family = AF_INET;
inet_pton(AF_INET, ip, &address.sin_addr);
address.sin_port = htons(port);
int sock = socket(PF_INET, SOCK_STREAM, 0);
assert(sock >= 0);
int ret = bind(sock, (struct sockaddr *)&address, sizeof(address));
assert(ret != -1);
ret = listen(sock, 5);
assert(ret != -1);
struct sockaddr_in client;
socklen_t client_addr_len = sizeof(client);
int connfd = accept(sock, (struct sockaddr *)&client, &client_addr_len);
if (connfd < 0)
printf("errno: is%d\n", errno);
int pipefd[2];
ret = pipe(pipefd);
ret = splice(connfd, NULL, pipefd[1], NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1);
ret = splice(pipefd[0], NULL, connfd, NULL, 32768, SPLICE_F_MORE | SPLICE_F_MOVE);
assert(ret != -1);
close(connfd);
close(sock);
return 0;
}

共享内存

1
2
3
#include<sys/mman.h>
void*mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset);
int munmap(void*start,size_t length);

start为0则地址随机

prot:PROT_READ, PROT_WRITE, PROT_EXEC, PROT_NONE不能访问

image-20241118204412311


tee:在两个管道间操作

1
2
#include<fcntl.h>
ssize_t tee(int fd_in,int fd_out,size_t len,unsigned int flags);

IO行为和属性控制

fcntl

1
2
#include<fcntl.h>
int fcntl(int fd,int cmd,…);

image-20241118210702034

设置非阻塞

1
2
3
4
5
6
7
int sentnonblocking(int fd)
{
int old_option=fcntl(fd,F_GETFL);
int new_option=old_option|O_NONBLOCK;
fcntl(fd,F_SETFL,new_option);
return old_option;
}

Linux服务器程序规范

一般以守护进程运行

拥有日志系统,记录于/var/log

非root身份运行

拥有配置文件,存放于/etc下

启动时生存PID文件并存在/var/run,记录该后台进程PID

高性能服务器框架

服务器模型:

  1. CS模型:通过中心点获取资源;实现简单适合资源集中场合;不适合高访问量
  2. P2P模型:通常带有发现服务器,每台主机即使客户也是服务端

编程框架:

一般基于以下框架,实现不同逻辑功能

对服务器集群来说,IO处理单元是一个专门接入服务器,为实现负载均衡

请求队列通常被实现为池的部分,线程池连接池等

image-20241118213103290

IO模型:

数据读写:根据应用程序和内核的交互方式

  • 同步
  • 异步

五种IO模型:

  1. 阻塞

image-20241118214445064

  1. 非阻塞

image-20241118214527443

  1. IO复用

image-20241118214602313

  1. 信号驱动

image-20241118214642709

前四种内核数据拷贝到用户空间都是阻塞的,看出区别:

同步IO模型通知IO就绪事件,需要你自己执行IO操作

异步IO模型通知IO完成事件,已经写入指定地址

  1. 异步

image-20241118214657951

事件处理模型

Reactor:同步

实现:Event事件,Reactor反应堆,Demultiplex事件分发器,EventHandler事件处理器

工作流程:

注册读就绪事件——>epoll_wait监听事件——>分发事件到队列——>工作线程读取事件并处理

Proactor:异步

工作流程:

  1. 主线程调用aio_read注册写完成事件,通知内核用户读写缓存区位置
  2. 内核发送一个信号,通知数据已可用
  3. 信号处理函数选择一个工作线程逻辑处理,完成后注册写完成事件
  4. 当数据写入socket后发送信号
  5. 信号处理函数选择一个工作线程善后处理

并发模式:

计算密集型和IO密集型

并发模式中,同步指代码顺序执行;异步指程序执行需要由系统事件来驱动,也就是事件驱动模型

半同步/半异步模式:

同步线程处理客户逻辑,异步线程处理IO事件

半同步/半反应堆模式:

主线程监听socket连接,然后往epoll内核事件表注册该socket读写事件;一旦该连接socket有读写事件发生,则将该连接放入工作队列,通知工作线程,通过竞争获得任务接管权

缺点:每次取出任务添加任务都要加锁;一个工作线程同一时间只能处理一个客户请求

提高:每个工作线程都维持自己的事件循环,可用独立监听不同事件

领导者/追随者模式:

每次由一个领导检测IO事件,然后推选新领导以便处理IO事件,包含以下组件

  1. 句柄集:
  2. 线程集
  3. 事件处理器:包含回调函数,绑定到句柄上,当句柄有事件发生,执行回调函数,具体的事件处理器是派生类,执行特定任务

IO复用

信号

定时器

Libevent