loop in codes

Kevin Lynx BLOG

Select真的有限制吗

在刚开始学习网络编程时,似乎莫名其妙地就会被某人/某资料告诉select函数是有fd(file descriptor)数量限制的。在最近的一次记忆里还有个人笑说select只支持64个fd。我甚至还写过一篇不负责任甚至错误的博客(突破select的FD_SETSIZE限制)。有人说,直接重新定义FD_SETSIZE就可以突破这个select的限制,也有人说除了重定义这个宏之外还的重新编译内核。

事实具体是怎样的?实际上,造成这些混乱的原因恰好是不同平台对select的实现不一样。

Windows的实现

MSDN.aspx)上对select的说明:

int select(
  _In_     int nfds,
  _Inout_  fd_set *readfds,
  _Inout_  fd_set *writefds,
  _Inout_  fd_set *exceptfds,
  _In_     const struct timeval *timeout
);

nfds [in] Ignored. The nfds parameter is included only for compatibility with Berkeley sockets.

第一个参数MSDN只说没有使用,其存在仅仅是为了保持与Berkeley Socket的兼容。

The variable FD_SETSIZE determines the maximum number of descriptors in a set. (The default value of FD_SETSIZE is 64, which can be modified by defining FD_SETSIZE to another value before including Winsock2.h.) Internally, socket handles in an fd_set structure are not represented as bit flags as in Berkeley Unix.

Windows上select的实现不同于Berkeley Unix,后者使用位标志来表示socket

Muduo源码阅读

最近简单读了下muduo的源码,本文对其主要实现/结构简单总结下。

muduo的主要源码位于net文件夹下,base文件夹是一些基础代码,不影响理解网络部分的实现。muduo主要类包括:

  • EventLoop
  • Channel
  • Poller
  • TcpConnection
  • TcpClient
  • TcpServer
  • Connector
  • Acceptor
  • EventLoopThread
  • EventLoopThreadPool

其中,Poller(及其实现类)包装了Poll/EPoll,封装了OS针对设备(fd)的操作;Channel是设备fd的包装,在muduo中主要包装socket;TcpConnection抽象一个TCP连接,无论是客户端还是服务器只要建立了网络连接就会使用TcpConnection;TcpClient/TcpServer分别抽象TCP客户端和服务器;Connector/Acceptor分别包装TCP客户端和服务器的建立连接/接受连接;EventLoop是一个主控类,是一个事件发生器,它驱动Poller产生/发现事件,然后将事件派发到Channel处理;EventLoopThread是一个带有EventLoop的线程;EventLoopThreadPool自然是一个EventLoopThread的资源池,维护一堆EventLoopThread。

阅读库源码时可以从库的接口层着手,看看关键功能是如何实现的。对于muduo而言,可以从TcpServer/TcpClient/EventLoop/TcpConnection这几个类着手。接下来看看主要功能的实现:

Dhtcrawler的进程模型经验

距离写dhtcrawler已经有半年时间。半年前就想总结点心得经验,但最后写出来的并没有表达出我特别有感慨的地方。最近又被人问到这方面的经验问题,才静下心来思考整理了下。

我的经验是关于在写一个网络项目时所涉及到的架构(或者说是模型)。

在dhtcrawler中,一个主要的问题是:程序在网络中需要尽可能快尽可能多地收集请求,然后程序需要尽可能快地加工处理这些信息。本质上就这么简单,我觉得很多网络系统面临的都可能是类似的问题。

详细点说,dhtcrawler高峰期每天会收到2000万的DHT协议请求,收到这些请求后,dhtcrawler需要对这些请求做处理,包括:合并相同的请求;从外部网站请求下载种子文件;新增/更新种子信息到数据库;建立种子sphinx索引等。在实际运行期间,高峰期每天能新录入14万个种子。

那么如何架构这个系统来让处理速度尽可能地快呢?首先,毫无疑问这个系统是多线程/多进程,甚至是分布式的。写一个多线程程序学几个API谁都会,但是如何组织这些线程以让系统最优则是一个较困难的问题。根据dhtcrawler的经验,我简单总结了以下几种模型/架构:

My 2013

技术

这一年里个人的技术感觉进步不是那么大。一方面技术之外的事情多了起来,另一方面由于工作原因接触的技术也较为杂乱,没有机会专注。技术的提升还是得靠业余时间。

系统分析设计

学了些RUP的方法,对规范化的系统分析设计算是有了一定认识。但这个东西在实践的过程中往往较难运用,好的方法学还是得看项目的实际情况而定。

Scala主要特性一览

概述

scala语言包含了函数式语言和面向对象语言的语法特性,从我目前的感受来看,这不是一门简单的语言。同Ruby/Erlang相比,其语法集大多了。scala基于JVM或.NET平台,其可以几乎无缝地使用Java库(不但使用上没有负担,其运行效率上也不会增加负担),配合其强大的语言表达能力,还是很有吸引力。

类型

类/对象

scala中一切都是对象,虽然Java也是这样说的(其实ruby也是这样说的)。在Java中一个数字仅仅是个值,但在scala中却真的是对象:

    
println("2 type: " + 2.getClass())

scala同Java一样将所有类型都设定了一个基类:Any。不同的是,Any下还区分了AnyValAnyRef

类型推断

scala是一门静态类型语言,但是其强大的类型推断可以避免很多冗余信息的代码。例如:

    
val map:Map[String, Int] = new HashMap[String, Int]
// 可简写为
val map = new HashMap[String, Int]

def func():String = {
    "hello"
}
// 可简写为
def func() = {
    "hello"
}

类型推断可以根据表达式的类型决定这个变量/函数的类型,这就如同C++11中的auto关键字。

函数

scala既然包含了函数式语言的特性,那么函数作为first citizen就是自然而言的事情。而function literal的语法形式也就必须更自然(想想common lisp里lambda那蛋疼的关键字):

    
val factor = 3
val multiplier = (i:Int) => i * factor // function literal, lexical bind to factor
val l1 = List(1, 2, 3, 4, 5) map multiplier // map `multiplier` to every element in List l1

def add(a:Int, b:Int) = {
  a + b
}

val f:(Int, Int) => Int = add // f is a function type: (Int, Int) => Int
println("f:" + f(2, 3))