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