UDP是无连接的,一个UDP包发出之后,对端接收到,事情就完了,即使对端没有接收到,事情也随之结束,两端都不会保存任何信息(UDP connect函数仅仅绑定了一个元组,不会对协议通信有影响)。因此无法像TCP那样实现accept。而TCP服务的多处理机制基本都是基于accept的,TCP的侦听socket只负责接受连接,进而调度给一个进程或者线程,accept/fork机制已经成了多处理的必杀技,由于UDP无法实现accept,也就很难实现多处理以及xinetd那样的服务调度程序,可是事情并没有完!
虽然UDP不保存任何连接信息,可是Linux内核Netfilter的nf_conntrack为之保存了一个连接,这个conntrack如果能被使用,那么就可以利用它实现UDP的多处理,接下来唯一的问题就是端口的问题。多进程的UDP服务必须绑定不同的端口(或者不同secondary IP的同一个端口),比如6000-6004五个端口对应五个UDP服务进程,剩下的工作就是将客户端的访问定向到这五个服务中的一个:
iptables -t nat -A LBalance -p udp --dport $port_base -j DNAT --to-destination $LISTEN_ADDR:6000-6004
这样就好了。NAT是有状态的,conntrack模块负责保持这个连接,且NAT仅仅对一个流的头包有效,这样就可以把同一个连接的数据定向到同一个进程了,唯一需要注意的就是,nf_conntrack模块对于保持的连接是有超时时间的,如果过了超时时间,连接数据结构就会被释放掉,因此如果不能保证数据持续传输的情形下,最好在应用层有一定的心跳机制。
何时?iptables可以拥有一个模块,可以将连接定向到一个进程,进程PID可以通过procfs或者sysfs设置,到那时,UDP服务就可以不绑定那么多的端口了。左右思索,觉得还是自己实现一个为好,可以参考TPROXY以及2.4内核Netfilter的owner-pid来实现,在HOOK function中可以有以下的片断:
struct task_struct *p;
struct files_struct *files;
int i;
p = pid_task(find_get_pid(进程PID,可以通过procfs来配置), PIDTYPE_PID);
if (!p)
goto out;
task_lock(p);
files = p->files;
if(files) {
for (i=0; i < 目前打开的描述符; i++) {
struct file *f = files->fd_array[i];
if (S_ISSOCK(f->f_path.dentry->d_inode->i_mode)){
从file结构体中取出private_data;
从private_data映射到socket,进而取出sock结构体;
将sock结构体和skb关联;
}
}
}
task_unlock(p);
以上只是一个片断,其实还要和conntrack结合对应到具体的流,可能还需要mark机制。
如果一个技术本身没有提供某个机制,那么肯定有其它的层可以为之提供该种功能,这个层可以在上面也可以在下面,对于UDP,你可以在应用层为之封装一个层,类似OpenVPN或者DTLS那样,也可以利用底层的ip_conntrack。