简单的网络并发服务器
并发服务基本介绍
- 在网络服务器设计中,当用户请求达到10W以及更多时候,服务端处理客户端请求能力就非常重要,在业界一般是采用异步网络IO+多线程方式来做这种方案。本例中仅仅是采用多进程的方式模拟这种场景,主要的目的是搞清楚涉及到网络编程中的函数基本原理
基本函数介绍
- socket函数:指明需要绑定什么版本的协议和具体协议类型,比如协议版本ipv4或者ipv6,具体的协议是tcp/udp/sctp协议
- bind函数:设置服务器端的地址,和socket函数返回的套接字绑定
- listen函数:监听这个套接字,同时设置内核的连接的最大队列,一个是未完成队列,一个是完成队列,未完成队列是程序发起syn之后和未完成三次握手之前,此时连接会被放到服务器端的未完成队列中;当完成三次握手,为被应用程序使用之前,此时这种连接会被放到服务器端TCP的完成队列中。
- accept函数:该函数就是从服务端的TCP的完成队列中取出一个可用连接以供应用程序使用
- connect函数: 程序发起syn包到服务端,之后的所有操作都是有tcp协议栈来做
样例代码中的疑惑
fork后为啥需要关闭listenfd?
讲道理父进程fork之后,在子进程中关闭了listenfd,子进程不需要读写整个listenfd的任何资源,子进程仅仅是一个工作进程。这个时候不是应该触发四次挥手(发送fin)断开的操作吗?在这里需要说清楚,fork之后,父子进程会共享listenfd和connfd,此时在内核看来,listenfd和connfd的文件描述符的引用计数在fork之前是1,fork后父子进程共享这2个文件描述符,这2个文件描述符的应用计数为2,在子进程中close(listenfd)不会触发发送fin包,仅仅是listenfd的引用计数从2减到了1而已。
那如果要真正来发送fin包该如何做?可以调用shutdown函数实现发送fin包,断开连接
父进程中为什么也要关闭connfd?
比如子进程处理connfd时间很长,在实例代码中,fork后返回2次,返回到父进程时候已经执行了close(connfd),那子进程不是读取数据失败?其实不是这样的,connfd和上面描述一样,都是引用计数,connfd的引用计数由原来的2减到1而已。这里关闭也有另外一个原因,父进程仅仅复杂监听来自客户单请求,不需要处理客户单请求。
基本实例代码
- 代码
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/*************************************************************************
> File Name: concurrent_tcp_server.c
> Author:perrynzhou
> Mail:perrynzhou@gmail.com
> Created Time: Wednesday, July 29, 2020 AM09:06:42
************************************************************************/
#include <stdio.h>
#include <stdint.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <arpa/inet.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netdb.h>
#include <ifaddrs.h>
#include <unistd.h>
#define TCP_MIN_BACKLOG (2048)
static void fetch_ip_from_host(char *buf, size_t buf_size)
{
struct ifaddrs *ifaddr, *ifa;
int family, s;
if (getifaddrs(&ifaddr) != -1)
{
const char *local_address = "127.0.0.1";
size_t local_address_len = strlen(local_address);
for (ifa = ifaddr; ifa != NULL; ifa = ifa->ifa_next)
{
if (ifa->ifa_addr == NULL)
{
continue;
}
s = getnameinfo(ifa->ifa_addr, sizeof(struct sockaddr_in), buf, NI_MAXHOST, NULL, 0, NI_NUMERICHOST);
if (s != 0)
{
continue;
}
if (ifa->ifa_addr->sa_family == AF_INET && strncmp(buf, local_address, local_address_len) != 0)
{
break;
}
bzero(buf, buf_size);
}
freeifaddrs(ifaddr);
}
}
void fetch_ip_from_fd(int client_fd, char *address, size_t address_size)
{
struct sockaddr_in addr;
socklen_t addr_size = sizeof(struct sockaddr_in);
int res = getpeername(client_fd, (struct sockaddr *)&addr, &addr_size);
strncpy(address, inet_ntoa(addr.sin_addr), address_size);
size_t alen = strlen((char *)&address);
snprintf((char *)&address + alen, address_size - alen, ":%d", htons(addr.sin_port));
}
static int init_socket(int domain, int type, int protocol, int backlog, const char *addr, int port)
{
int sock = socket(domain, type, protocol);
assert(sock != -1);
struct sockaddr_in serveraddr;
serveraddr.sin_family = domain;
serveraddr.sin_port = htons(port);
assert(inet_pton(AF_INET, addr, &serveraddr.sin_addr) > 0);
assert(bind(sock, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) != -1);
if (type == SOCK_STREAM)
{
int opt = 1;
setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
listen(sock, backlog);
}
return sock;
}
inline static int init_tcp_server_socket(const char *addr, int port, int backlog)
{
int real_backlog = (backlog < TCP_MIN_BACKLOG) ? TCP_MIN_BACKLOG : backlog;
return init_socket(AF_INET, SOCK_STREAM, IPPROTO_TCP, real_backlog, addr, port);
}
inline static int init_tcp_client_socket(const char *addr, int port)
{
int sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
assert(sock != 0);
struct sockaddr_in srvaddr;
srvaddr.sin_family = AF_INET;
srvaddr.sin_port = htons(port);
assert(inet_pton(AF_INET, addr, &srvaddr.sin_addr) > 0);
return sock;
}
int main(int argc, char *argv[])
{
int port = atoi(argv[1]);
char addr[128] = {'\0'};
fetch_ip_from_host((char *)&addr, 128);
int listenfd = init_tcp_server_socket((char *)&addr, port, 1024);
fprintf(stdout, "::server run on %s:%d\n", (char *)&addr, port);
int connfd = -1;
pid_t pid;
for (;;)
{
struct sockaddr_in cliaddr;
socklen_t len = sizeof(cliaddr);
connfd = accept(listenfd, (struct sockaddr *)&cliaddr, &len);
fflush(stdout);
if ((pid = fork()) == 0)
{
fetch_ip_from_fd(connfd, (char *)&addr, 128);
close(listenfd);
fprintf(stdout, "%d process handle %s connection\n", getpid(), (char *)&addr);
exit(0);
}
close(connfd);
}
}