共有回帖数 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-11-05 13:59 回复
Copyright © 2010~2015 直线网 版权所有,All Rights Reserved.沪ICP备10039589号
意见反馈 |
关于直线 |
版权声明 |
会员须知