Listener类
在服务端,main函数通过命令行或环境变量设置了测量参数之后,生成Listener类的实例。由该Listener类的实例在指定的端口上等待客户端
的连接。
Listener类是Thread类的PerfSocket和Thread类的派生类。它的定义和实现位于文件src/Listener.hpp和src/Listener.cpp中。一个Thread函
数就是一个独立的执行线程,在main函数中调用Listener类的实例theListener的Start方法。Start方法通过pthread_create系统调用生成一个
新的执行线程,新线程通过Thread基类的Run_Wrapper调用Listener类的Run方法。有关以上调用关系的详情可以参照前文对Thread类的说明。
Run函数是Listener类里最重要的成员函数,其代码如下:
/* -------------------------------------------------------------------
* Listens for connections and starts Servers to handle data.
* For TCP, each accepted connection spawns a Server thread.
* For UDP, handle all data in this thread for Win32 Only, otherwise
* spawn a new Server thread.
* ------------------------------------------------------------------- */
void Listener::Run( void ) {
extern Mutex clients_mutex;
extern Iperf_ListEntry *clients;
Audience *theAudience=NULL;
SocketAddr *client = NULL;
if ( mSettings->mHost != NULL ) {
client = new SocketAddr( mSettings->mHost, mSettings->mPort, mSettings->mDomain );
}
ext_Settings *tempSettings = NULL;
iperf_sockaddr peer;
Iperf_ListEntry *exist, *listtemp;
if ( mUDP ) {
struct UDP_datagram* mBuf_UDP = (struct UDP_datagram*) mBuf;
client_hdr* hdr = (client_hdr*) (mBuf_UDP + 1);
// UDP uses listening socket
// The server will now run as a multi-threaded server
// Accept each packet,
// If there is no existing client, then start
// a new thread to service the new client
// The main server runs in a single thread
// Thread per client model is followed
do {
peer = Accept_UDP();
if ( client != NULL ) {
if ( !SocketAddr::Hostare_Equal( client->get_sockaddr(),
(sockaddr*) &peer ) ) {
continue;
}
}
clients_mutex.Lock();
exist = Iperf_present( &peer, clients);
clients_mutex.Unlock();
int32_t datagramID = ntohl( mBuf_UDP->id );
if ( exist == NULL && datagramID >= 0 ) {
int rc = connect( mSock, (struct sockaddr*) &peer,
// Some OSes do not like the size of sockaddr_storage so we
// get more exact here..
#ifndef IPV6
sizeof(sockaddr_in));
#else
(((struct sockaddr*)&peer)->sa_family == AF_INET ?
sizeof(sockaddr_in) : sizeof(sockaddr_in6)));
#endif // IPV6
FAIL_errno( rc == SOCKET_ERROR, "connect UDP" );
listtemp = new Iperf_ListEntry;
memcpy(listtemp, &peer, sizeof(peer));
listtemp->next = NULL;
clients_mutex.Lock();
exist = Iperf_hostpresent( &peer, clients);
if ( exist != NULL ) {
listtemp->holder = exist->holder;
exist->holder->AddSocket(mSock);
} else {
clients_mutex.Unlock();
tempSettings = NULL;
if ( !mSettings->mCompat ) {
Settings::GenerateSpeakerSettings( mSettings, &tempSettings,
hdr, (sockaddr*) &peer );
}
theAudience = new Audience( mSettings, mSock );
if ( tempSettings != NULL ) {
Speaker *theSpeaker = new Speaker( tempSettings );
theSpeaker->OwnSettings();
theSpeaker->DeleteSelfAfterRun();
if ( tempSettings->mMode == kTest_DualTest ) {
theSpeaker->Start();
} else {
theAudience->StartWhenDone( theSpeaker );
}
}
listtemp->holder = theAudience;
// startup the server thread, then forget about it
theAudience->DeleteSelfAfterRun();
theAudience->Start();
theAudience = NULL;
clients_mutex.Lock();
}
Iperf_pushback( listtemp, &clients );
clients_mutex.Unlock();
// create a new socket
mSock = -1;
Listen( mSettings->mLocalhost, mSettings->mDomain );
mClients--;
}
} while ( !mCount || ( mCount && mClients > 0 ) );
} else {
// TCP uses sockets returned from Accept
client_hdr buf;
int connected_sock;
do {
connected_sock = Accept();
if ( connected_sock >= 0 ) {
Socklen_t temp = sizeof( peer );
getpeername( connected_sock, (sockaddr*)&peer, &temp );
if ( client != NULL ) {
if ( !SocketAddr::Hostare_Equal( client->get_sockaddr(),
(sockaddr*) &peer ) ) {
close( connected_sock );
continue;
}
}
tempSettings = NULL;
clients_mutex.Lock();
exist = Iperf_hostpresent( &peer, clients);
listtemp = new Iperf_ListEntry;
memcpy(listtemp, &peer, sizeof(peer));
listtemp->next = NULL;
if ( exist != NULL ) {
listtemp->holder = exist->holder;
exist->holder->AddSocket(connected_sock);
} else {
clients_mutex.Unlock();
if ( !mSettings->mCompat ) {
if ( recv( connected_sock, (char*)&buf, sizeof(buf), 0) > 0 ) {
Settings::GenerateSpeakerSettings( mSettings, &tempSettings,
&buf, (sockaddr*) &peer );
}
}
theAudience = new Audience( mSettings, connected_sock );
if ( tempSettings != NULL ) {
Speaker *theSpeaker = new Speaker( tempSettings );
theSpeaker->OwnSettings();
theSpeaker->DeleteSelfAfterRun();
if ( tempSettings->mMode == kTest_DualTest ) {
theSpeaker->Start();
} else {
theAudience->StartWhenDone( theSpeaker );
}
}
listtemp->holder = theAudience;
// startup the server thread, then forget about it
theAudience->DeleteSelfAfterRun();
theAudience->Start();
theAudience = NULL;
clients_mutex.Lock();
}
Iperf_pushback( listtemp, &clients );
clients_mutex.Unlock();
mClients--;
}
} while ( !mCount || ( mCount && mClients > 0 ) );
}
} // end Run
在服务器端,正在通信的客户端被组织为一个链表结构,链表的每个单元是Iperf_ListEntry类的实例。clients是该链表的表头指针。在多线
程环境下,通过互斥锁clients_mutex同步各线程对链表的访问。clients和clients_mutex的声明位于文件src/List.cpp中。
如果在参数设置中mHost不为空,则生成一个用来表示对端地址的类SocketAddr的实例client。察看Iperf用来组织参数的类Settings的实现,
会发现mHost是通过命令行选项-c设置的。因此这里的地址结构client的生成仅发生在客户端,且发生在使用双向测试(dualtest或tradeoff)
下。
如果mUDP不为零(mUDP是在分析命令行参数时设定的,见Settings类的相关方法),则表明使用UDP协议。此时,结构体UDP_datagram的指针
mBuf_UDP指向接收缓冲区mBuf的首地址,结构体client_hdr的指针hdr指向mBuf中紧接在UDP_datagram结构之后的地址。前文已经分析了Iperf
中传输的各种数据包的结构以及相应的结构体,这里根据UDP数据包的内容和结构声明mBuf_UDP和hdr两个指针以便接下来的数据提取。
讨论: UDP是无连接的协议,一种可能的设计方法是,使用唯一的线程处理所有的UDP数据包的收发,因为UDP中不需要同时维护使用TCP时存在
的并行连接,这种单线程的实现是可能的。由于服务器要为每一个客户维护一份网络参数的统计数据,使用单线程的实现就要维护一个复杂的
数据表,每一个客户对应于表中的一项,该项纪录了这个客户的网络连接参数。在这种情况下,使用Hash表是一个不错的选择,可以将客户端
地址或者地址与端口号的组合作为Hash的键值。
对于UDP连接,Iperf并没有使用上述的单线程实现,而是对应每个客户,使用一个独立的线程处理。在该线程的私有地址空间中维护对应于该
客户的网络参数,并由该线程向客户发送报告测量结果的数据包。为了比较方便的建立线程与客户连接间的对应关系,Iperf利用了UDP连接上
的connect系统调用,该调用可以将某个socket套接字与一个UDP通信对端(peer)绑定,系统会纪录下该通信对端的地址和端口,此后所有从
该对端发来的UDP包都会送到这个socket上,而当本地线程使用该socket外发数据时,在没有指定具体的目的地址的情况下,数据包将发往已经
绑定的通信对端。这样就实现了某种基于UDP的网络“连接”。负责与这个对端通信的线程使用这个已经绑定的socket,就可以建立于该对端的
对应关系了。
比较一下单线程和多线程的实现,可以发祥单线程的实现效率比较高,可以直接获得汇总的统计数据。但需要使用复杂的数据结构(如hash表
),编程实现比较复杂。多线程的实现使用的数据结构比较简单(局部变量),connect系统调用也为建立线程与客户间的对应关系提供了方便
。但在多线程的实现中,需要有一个监控线程负责收集并处理汇总的统计数据。同时要注意线程间的同步问题。
在处理UDP连接的do-while循环中,Listener首先通过Accept_UDP方法等待一个连接请求。Accpet_UDP是PerfSocket类的方法,其定义在文件
src/PerfSocket_UDP.cpp中。它使用recvfrom系统调用在Listener的监听socket上等待一个UDP数据包,把接收的UDP数据包的内容复制到接收
缓冲区mBuf中,记录下这个数据包的发端网络地址(IP地址+端口),并返回这个对端地址。
在双向测试模式下,在客户端(client!=NULL)需要检测发起反向连接的对端是不是用户指定的服务器,如果不是将忽略此连接,这就是
Accpet_UDP后的if代码段的作用。
在通过使用互斥锁的Lock方法获得客户链表的排他访问权后,Listener在客户链表中查找此对端是否已经出现(在这次查找中,相同意味着IP
地址和端口好相等)。如果没有出现,并且收到的数据包不是最后一个(Iperf使用负的datagramID值表示连接中的最后一个数据包),则
Listener首先通过connect系统调用建立socket与客户间的关联,此后生成一个新的客户链表的节点结构并设置该结构的各成员变量。Listener
再次在客户链表查找这个客户,不过这一次只要求IP地址相同。
如果已经建立了与同一IP地址的其他UDP连接,只是使用的端口不同(这对应于使用多线程并行测量的情况),则Listener将新连接的听者监控
线程设置为已有连接的听者监控线程(听者监控线程即Audiance对象,客户链表的节点结构中的holder指针指向这一对象),并调用该监控对
象的AddSocket方法使其新生成一个听者线程处理该连接)。
如果查找的结果为空,则说明这确实是一个新的连接。Listener调用Settings类的GenerateSpeakerSettings方法,该方法从接收到的数据包中
提取测试参数设置,并结合本地设置(保存在mSettings中),生成用户反向连接的参数(这里通过对!mSettings->mCompat的测试检查是否使
用兼容模式,在兼容模式下,服务器不发起连接,就不必提取反向连接的参数)。在客户指定不使用双向测试的情况下,
GenerateSpeakerSettings会使tempSpeaker保持NULL的取值。
接下来,Listener生成一个听者监控对象(Audience类的实例)theAudience,使用保存本地参数的对象mSettings和已经完成绑定的套接字
mSock初始化。在需要发起反向连接的情况下(tempSettings != NULL),Listener生成说者监控对象(Speaker类的实例)theSpeaker。在杜
altest模式下,立刻通过theSpeaker的Start方法启动说者监控线程;在tradeoff模式下,通过theAudience的StartWhenDone方法使线程
theAudience在退出时启动说者监控线程。在将theAudience指定为现在的UDP对端对应的听者监控对象后,Listener启动通过theAudience的
Start方法听者监控线程的执行。此后,Lisnenter将该客户的节点结构插入客户链表。
最后,Listener调用Listen方法,生成一个新的socket并在该socket上监听新的连接请求。mClient纪录了服务器在退出前需要服务的客户数。
如果mCount为零,表示服务器将永远运行;若mCount不为零,它表示服务器在退出前需要处理的字节数或时间。Listener根据这些标量是否为
零决定是否退出do-while循环(即从Run函数中返回)。需要说明的一点时,当Listener类的Run函数返回时,主线程的执行也临近终止了。
Listener类处理TCP连接请求的方法与处理UDP类似。在此不再赘述。