Socket 类
Socket的定义和实现分别在文件Socket.hpp和 Socket.cpp中。它的主要功能是封装了socket文件描述符、此socket对应的端口号,以及socket接口中的listen, accept, connect和close等函数,为用户提供了一个简单易用而又统一的接口。同时 作为其他派生类的基类。
Socket类的定义如下:
#ifndef SOCKET_H #define SOCKET_H #include "headers.h" #include "SocketAddr.hpp" /* ------------------------------------------------------------------- */ class Socket { public: // stores server port and TCP/UDP mode Socket( unsigned short inPort, bool inUDP = false ); // destructor virtual ~Socket(); protected: // get local address SocketAddr getLocalAddress( void ); // get remote address SocketAddr getRemoteAddress( void ); // server bind and listen void Listen( const char *inLocalhost = NULL, bool isIPv6 = false ); // server accept int Accept( void ); // client connect void Connect( const char *inHostname, const char *inLocalhost = NULL ); // close the socket void Close( void ); // to put setsockopt calls before the listen() and connect() calls virtual void SetSocketOptions( void ) { } // join the multicast group void McastJoin( SocketAddr &inAddr ); // set the multicast ttl void McastSetTTL( int val, SocketAddr &inAddr ); int mSock; // socket file descriptor (sockfd) unsigned short mPort; // port to listen to bool mUDP; // true for UDP, false for TCP }; // end class Socket #endif // SOCKET_H
Socket类主要提供了四个函数:Listen,Accept,Connect和Close。getLocalAddress和GetRemoteAddress的作用分别是获得socket本端的地址和对端的地址,两个函数均返回一个SocketAddr实例。SetSocketOptions的作用是设置socket的属性,它是一个虚函数,因此不同的socket的派生类在实现此函数时会执行不同的操作。下面重点看一下Socket类的几个函数的实现。
getLoaclAddress和GetRemoteAddress函数
SocketAddr Socket::getLocalAddress( void ) { iperf_sockaddr sock; Socklen_t len = sizeof(sock); int rc = getsockname( mSock, (struct sockaddr*) &sock, &len ); FAIL_errno( rc == SOCKET_ERROR, "getsockname" ); return SocketAddr( (struct sockaddr*) &sock, len ); } // get remote address SocketAddr Socket::getRemoteAddress( void ) { iperf_sockaddr peer; Socklen_t len = sizeof(peer); int rc = getpeername( mSock, (struct sockaddr*) &peer, &len ); FAIL_errno( rc == SOCKET_ERROR, "getpeername" ); return SocketAddr( (struct sockaddr*) &peer, len ); }
getsockname和getRemote函数都是系统提供的,因此可以直接使用!
Listen 函数
/* ------------------------------------------------------------------- * Setup a socket listening on a port. * For TCP, this calls bind() and listen(). * For UDP, this just calls bind(). * If inLocalhost is not null, bind to that address rather than the * wildcard server address, specifying what incoming interface to * accept connections on. * ------------------------------------------------------------------- */ void Socket::Listen( const char *inLocalhost, bool isIPv6 ) { int rc; //Function setHostname() transfer the Hostname(ASCII) to Hostname(binary), //Socket building. SocketAddr serverAddr( inLocalhost, mPort, isIPv6 ); // create an internet TCP socket int type = (mUDP ? SOCK_DGRAM : SOCK_STREAM); int domain = (serverAddr.isIPv6() ? #ifdef IPV6 AF_INET6 #else AF_INET #endif : AF_INET); #ifdef DBG_MJZ // DBG MJZ fprintf(stderr, "inLocalhost=%s domain=%d\n", inLocalhost, domain); #endif /* DBG_MJZ */ #ifdef WIN32 if ( mUDP && serverAddr.isMulticast() ) { mSock = WSASocket( domain, type, 0, 0, 0, WSA_FLAG_MULTIPOINT_C_LEAF | WSA_FLAG_MULTIPOINT_D_LEAF ); FAIL_errno( mSock == INVALID_SOCKET, "socket" ); } else #endif { mSock = socket( domain, type, 0 ); FAIL_errno( mSock == INVALID_SOCKET, "socket" ); } SetSocketOptions(); // reuse the address, so we can run if a former server was killed off int boolean = 1; Socklen_t len = sizeof(boolean); // this (char*) cast is for old headers that don't use (void*) setsockopt( mSock, SOL_SOCKET, SO_REUSEADDR, (char*) &boolean, len ); #ifdef DBG_MJZ { // DBG MJZ struct sockaddr *sa = serverAddr.get_sockaddr(); int len = serverAddr.get_sizeof_sockaddr(); fprintf(stderr, "len: %d salen: %d fam: %d addr chars %2x %2x %2x %2x ...\n", len, sa->sa_len, sa->sa_family, sa->sa_data[0], sa->sa_data[1], sa->sa_data[2], sa->sa_data[3]); } #endif /* DBG_MJZ */ // bind socket to server address #ifdef WIN32 if ( serverAddr.isMulticast() ) { rc = WSAJoinLeaf( mSock, serverAddr.get_sockaddr(), serverAddr.get_sizeof_sockaddr(),0,0,0,0,JL_BOTH); FAIL_errno( rc == SOCKET_ERROR, "bind" ); } else #endif { rc = bind( mSock, serverAddr.get_sockaddr(), serverAddr.get_sizeof_sockaddr()); FAIL_errno( rc == SOCKET_ERROR, "bind" ); } // listen for connections (TCP only). // default backlog traditionally 5 if ( ! mUDP ) { rc = listen( mSock, 5 ); FAIL_errno( rc == SOCKET_ERROR, "listen" ); } #ifndef WIN32 // if multicast, join the group if ( mUDP && serverAddr.isMulticast() ) { McastJoin( serverAddr ); } #endif } // end Listen
首先构造一个包含本地服务器地址结构的 SocketAddr实例,inLocalhost是本地IP地址(点分十进制字符串或URL,后者在创建SocketAddr实例是完成地址解析),mPort是Socket构造函数中设置的端口。再通过socket系统调用创建一个socket。SetSocketOptions方法设置此 socket的属性。因为SetSocketOptions是虚函数,在Socket类的实现中是一个空函数,而不同的Socket的派生类在覆盖 (overwrite)该函数执行的操作是不同的,这是多态特性的应用。此后设置socket的可重用(reuse)属性,使服务器在重启后可以重用以前的地址和端口。此时该socket还没有绑定到某个网络端点(IP地址、端口对)上,bind系统调用完成此功能。最后,如果该socket用于一个 TCP连接,则调用listen函数,一来向系统说明可以接受到socket绑定端口上的连接请求,二来设定请求等待队列的长度为5。
Socket的Listen方法将地址解析(地址结构生成)、socket、bind和listen等系统调用组合为一个函数。在应用时,调用一个Listen方法就可以完成Server端socket初始化的所有工作。
Accept函数
Accept函数是Server完成socket初始化,等待连接请求时调用的函数。代码如下:
/* ------------------------------------------------------------------- * After Listen() has setup mSock, this will block * until a new connection arrives. Handles interupted accepts. * Returns the newly connected socket. * ------------------------------------------------------------------- */ int Socket::Accept( void ) { iperf_sockaddr clientAddr; Socklen_t addrLen; int connectedSock; while ( true ) { // accept a connection addrLen = sizeof( clientAddr ); connectedSock = accept( mSock, (struct sockaddr*) &clientAddr, &addrLen ); // handle accept being interupted if ( connectedSock == INVALID_SOCKET && errno == EINTR ) { continue; } return connectedSock; } } // end Accept
Accept函数为accept系统调用增添了在中断后自动重启的功能。Server线程在执行accept函数是后被阻塞,直到有请求到达,或是接收到某个信号。若是后面一种情况,accept会返回 INVALID_SOCKET并置errno为EINTR。Accept方法检查这种情况,并重新调用accept函数。
Connect函数
Connect函数Client端调用的函数,其作用是连接指定的Server。代码如下:
/* ------------------------------------------------------------------- * Setup a socket connected to a server. * If inLocalhost is not null, bind to that address, specifying * which outgoing interface to use. * ------------------------------------------------------------------- */ void Socket::Connect( const char *inHostname, const char *inLocalhost ) { int rc; SocketAddr serverAddr( inHostname, mPort ); assert( inHostname != NULL ); // create an internet socket int type = (mUDP ? SOCK_DGRAM : SOCK_STREAM); int domain = (serverAddr.isIPv6() ? #ifdef IPV6 AF_INET6 #else AF_INET #endif : AF_INET); // DBG MJZ #ifdef DBG_MJZ fprintf(stderr, "inHostname=%s domain=%d\n", inHostname, domain); #endif /* DBG_MJZ */ mSock = socket( domain, type, 0 ); FAIL_errno( mSock == INVALID_SOCKET, "socket" ); SetSocketOptions(); if ( inLocalhost != NULL ) { SocketAddr localAddr( inLocalhost ); #ifdef DBG_MJZ // DBG MJZ fprintf(stderr, "inLocalhost=%s sockaddrlen=%d\n", inHostname, localAddr.get_sizeof_sockaddr()); { // DBG MJZ struct sockaddr *sa = localAddr.get_sockaddr(); int len = localAddr.get_sizeof_sockaddr(); fprintf(stderr, "LOC len: %d salen: %d fam: %d data chars %2x %2x %2x %2x ...\n", len, sa->sa_len, sa->sa_family, sa->sa_data[0], sa->sa_data[1], sa->sa_data[2], sa->sa_data[3]); } #endif /* DBG_MJZ */ // bind socket to local address rc = bind( mSock, localAddr.get_sockaddr(), localAddr.get_sizeof_sockaddr()); FAIL_errno( rc == SOCKET_ERROR, "bind" ); } #ifdef DBG_MJZ { // DBG MJZ struct sockaddr *sa = serverAddr.get_sockaddr(); int len = serverAddr.get_sizeof_sockaddr(); fprintf(stderr, "SRV len: %d salen: %d fam: %d data chars %2x %2x %2x %2x ...\n", len, sa->sa_len, sa->sa_family, sa->sa_data[0], sa->sa_data[1], sa->sa_data[2], sa->sa_data[3]); } #endif /* DBG_MJZ */ // connect socket rc = connect( mSock, serverAddr.get_sockaddr(), serverAddr.get_sizeof_sockaddr()); FAIL_errno( rc == SOCKET_ERROR, "connect" ); } // end Connect
首先构造一个SocketAddr实例保存Server端的地址(IP地址,端口对),同时按需完成地址解析。socket系统调用生成 socket接口。虚函数 SetSocketOptions利用多态特性使不同的派生类按需要设置socket属性。如果传入的inLocalhost参数不是空指针,说明调用者希望指定某个本地接口作为连接的本地端点,此时通过bind系统调用把该socket绑定到这个接口对应的IP地址上。最后调用 connect函数完成与远端Server的连接。
讨论: TCP和UDP在调用connect函数是的操作有何不同?
对于TCP连接,调用connect函数会发起建立TCP连接的三次握手(3-way handshaking)过程。当connect调用返回时,此过程已经完成,连接已经建立。因为TCP连接使用字符流模型,因此在建立好的连接上交换数据时,就好像从一个字符流中读取,向另一个字符流中写入一样。
而UDP是无连接的协议,使用数据报而不是连接的模型,因此调用connect函数并不发起连接的过程,也没有任何数据向Server发送,而只是通知操 作系统,发往该地址和端口的数据报都送到这个socket连接上来,也就是说,把这个(地址、端口)对和该socket关联起来。UDP在IP协议的基础 上提供了多路访问(multiplex)的服务,UDP的connect系统调用对这种多路提供了socket接口与对端地址间的对应关系。在UDP连接 中,connect提功的这种功能是很有用的。例如,Server可以在接收到一个 Client的数据报后,分配一个线程执行connect函数与该 Client绑定,处理与该client的后继交互,其他的线程继续在原来的UDP端口上监听新的请求.因为在Client端和Server端都执行了 connect函数,所以一个Server与多个Client间的连接不会发生混乱。在 Iperf对UDP的处理中,就使用了这种技巧。
以上简要讨论了Iperf提供的库中几个比较重要的类的定义与实现。