签到

05月05日
尚未签到

共有回帖数 0

    做月子

    等级:
    首先我在开头我就略说说一点网络的事儿
    大家现在在linux和windows等用的基本网络api都是基于bsd的
    网络是一个很重要的东西,一个网络程序写不好轻的话没所谓,重的话会很容易遭受ddos而不堪重负,吞吐量低,无法应付大数量的并发连接,影响利益。我这里我就先从几个处框架。

    第一。骨灰级:
    骨灰级就是说用很原始的阻塞io。阻塞io就是说,调用系统函数后(比如send,recv,read,write),系统会把传入缓冲区内的数据完全发送出去再返回。系统实现上是靠设备等待队列。
    也就是说,我发出了一个write,那么如果我传入缓冲区的数据一直没发送出去,那么就会一直阻塞。tcp的话阻塞的write做的事如下。

    首先做缓冲区对拷,拷到SNDBUF,然后滑动窗口协议开始。不断的把窗口像滑动一样打开,然后发送,如果丢包了(靠ack)就把丢了的窗口重发到成功。开了的窗口全部成功发送,就继续滑动。(详细就省了,发太多就等于发滑动窗口算法的原理了)

    比如这样:

    void test(int s, char *buf, int len)
    {
    int ret;
    ret = write(s, buf, len);
    /* 然后会在这里阻塞。 */
    ...
    }

    这样有一个明显的弊端,就是如果对方的网速很慢,那么就把整体效能拖下来了。
    第二。多线程解决法(著名的程序:apache):

    多线程解决法用的就是用多线程+阻塞来解决这个问题。
    也就是说,为每个连接分配一个进程/线程来分别的负责发送。#include unistd.h

    #include sys/types.h
    #include sys/socket.h
    #include sys/epoll.h
    #include netdb.h
    #include stdio.h
    #include stdlib.h
    #include string.h
    #include ctype.h
    #include errno.h
    #include malloc.h
    #include netinet/in.h
    #include arpa/inet.h
    #include sys/ioctl.h
    #include stdarg.h
    #include fcntl.h

    const char *SendHead =
    "HTTP/1.1 200 OKrn"
    "Content-Length: %lurnrn";

    const char *SendContent =
    "HTMLrn"
    "HEADrn"
    "TITLECrazy!/TITLErn"
    "/HEADrn"
    "BODYrn"
    "YOUR IP:%u.%u.%u.%urn"
    "YOUR PORT:%lurn"
    "/BODYrn"
    "/HTML";

    struct user {
    struct sockaddr_in sa;
    int s;
    };

    int thread(struct user *usp)
    {
    char send_buf[4096];
    char send_buf2[4096];
    char buf[8192];
    while(1) {
    int i = 0;
    int loop = 1073741824 / 1024;
    int err = read(usp-s, buf, 8192);
    if(err = 0) {
    close(usp-s);
    free(usp);
    return 0;
    }
    sprintf(send_buf, SendHead, 1073741824);
    write(usp-s, send_buf, strlen(send_buf));
    for(i = 0;i  loop;i += 1)
    write(usp-s, send_buf, 1024);
    }
    }

    int main()
    {
    pid_t ntid;
    struct sockaddr_in sa = {0};
    struct sockaddr_in bind_sa = {0};
    int addr_len = sizeof(struct sockaddr);
    int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    bind_sa.sin_family = AF_INET;
    bind_sa.sin_port = htons(80);
    bind_sa.sin_addr.s_addr = inet_addr("127.0.0.1");
    bind(s, (struct sockaddr *)&bind_sa, addr_len);
    listen(s, 10);
    while(1) {
    struct user *usp = malloc(sizeof(struct user));
    usp-s = accept(s, &usp-sa, &addr_len);
    pthread_create(&ntid, NULL, thread, usp)
    }
    }
    大家应该会觉得光靠这个多线程方法远远不足,对,确实多线程解决法不足之处很多。
    1.多线程解决法会产生很多进程,连接量多而活跃的话,对调度机有很大的负载。
    2.多线程解决法会产生数量庞大的上下文切换次数。
    3.过多的上下文切换会带来巨大的吞吐量的损耗。

    为了解决这一连串的问题,人们苦思冥想终于想出了一个很好的方法来解决
    现代。事件驱动(搭配异步io)。
    异步io,就是指在用户进行系统函数调用的时候,把传入缓冲区的数据拷贝到SNDBUF,搭配设备等待队列来发送
    事件驱动模型用的是自动机,来用少进程数处理大批请求。

    事件驱动有何好处呢?首先,事件驱动能大幅降低上下文切换的负荷,再者,因为少了大量上下文切换,速度自然也就快了很多。
    事件驱动首先调用多路io分发器(select, poll, epoll, kqueue, IOCP等)

    然后获取到自己拥有的fd是否就绪,返回就绪的fd。

    最后,根据fd的状态来决定下一步的走向,然后处理第二个fd。

    事件驱动固然好处多,但编写技巧也要很足够。
    首先,有很多人对于异步io的技巧并不够,当一个fd发送时出现了缓冲区不足,他们往往用
    int remain = len;
    while(remain) {
    int ret = read(usp-s, buf, remain);
    if(ret = 0) {
    ...
    }
    remain -= ret;
    }
    这种方法来处理。这种方法姑且不说了,完全把事件驱动天生的优势完全的败了。
    因此,正确的方法应该是:
    1.这一次尝试对所有数据进行io,如果一次性缓冲区够用的话,那么就做io后的处理。
    2.如果不能发掉所有数据(出现了EAGAIN或EWOULDBLOCK),那么就把缓冲区的位置保存下来,等待下次系统缓冲区有空间了(多路io分发触发)再进行io。
    3.io完毕后做io后的处理。

    这里由于这种方法篇幅比较大,在此不方便把源码放开。
    源码地址:https://birs-server-frame.googlecode.com/svn/trunk/reactor/reactor-simp.zip
    里面的红黑树可以不用理会,因为我只是把红黑树放在源码里用来做定时器,而目前定时器并没有时间去完全实现,所以请无视。

    楼主 2015-07-14 08:35 回复

共有回帖数 0
  • 回 帖
  • 表情 图片 视频
  • 发表

登录直线网账号

Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号 意见反馈 | 关于直线 | 版权声明 | 会员须知