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。