perrynzhou

专注于系统组件研发

0%

简单的网络并发服务器

简单的网络并发服务器

并发服务基本介绍

  • 在网络服务器设计中,当用户请求达到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);
    }
    }