此头文件最开始部分都是约定俗成的内容,这里就不再多啰嗦了。然后是包含自身软件包内的头文件,以及针对BSD平台或WINDOWS平台包含不同的头文件。这些都很好理解。
然后是常量定义。首先,为BSD平台定义INVALID_SOCKET和SOCKET_ERROR这两个windows平台下特有的常量,以及定义软件包特有的两个常量:su_sucess和su_failure。再为上述最后两个常量各定义一个别名:SU_SUCESS和SU_FAILURE。以及其他一些常用的长度常量。
/* ---------------------------------------------------------------------- */
/* Constant definitions */
#if SU_HAVE_BSDSOCK || DOCUMENTATION_ONLY
enum {
/** Invalid socket descriptor, error from socket() or accept() */
INVALID_SOCKET = -1,
#define INVALID_SOCKET ((su_socket_t)INVALID_SOCKET)
/** Error from other socket calls */
SOCKET_ERROR = -1,
#define SOCKET_ERROR SOCKET_ERROR
/** Return code for a successful call */
su_success = 0,
/** Return code for an unsuccessful call */
su_failure = -1
};
#if SYMBIAN && !defined(MSG_NOSIGNAL)
#define MSG_NOSIGNAL (0)
#endif
#elif SU_HAVE_WINSOCK
enum {
su_success = 0,
su_failure = 0xffffffffUL
};
#define MSG_NOSIGNAL (0)
#endif
/**@HI Maximum size of host name. */
#define SU_MAXHOST (1025)
/**@HI Maximum size of service name. */
#define SU_MAXSERV (25)
/**@HI Maximum size of address in text format. */
#define SU_ADDRSIZE (48)
/**@HI Maximum size of port number in text format. */
#define SU_SERVSIZE (16)
#define SU_SUCCESS su_success
#define SU_FAILURE su_failure
紧接着是socket描述符类型。
#if SU_HAVE_BSDSOCK || DOCUMENTATION_ONLY
typedef int su_socket_t;
#elif SU_HAVE_WINSOCK
typedef SOCKET su_socket_t;
#endif
我们都知道windows平台和其他平台下的socket描述符不一致。windows平台的描述符类型是SOCKET,而其他平台是int。为了抹平不同平台下的差异,Sofia软件包又定义了一个su_socket_t类型作为socket描述符类型。
之前在阅读su_configure.h头文件时,遇到过SU_HAVE_SOCKADDR_STORAGE宏,不知道它的具体含义。这次在这个头文件中看到了使用它的地方。当初在头文件中我是这么注释的:
/** Define as 1 if you have struct sockaddr_storage */
#define SU_HAVE_SOCKADDR_STORAGE 1 widnows平台下看到有这个结构体,因此为1。
含义很明显,因为有这个结构体所以这个宏为1。但这个结构体有什么作用呢?因此我上网仔细查了查。找到了MSDN网页:https://msdn.microsoft.com/en-us/library/windows/desktop/ms740504(v=vs.85).aspx。网页上第一段就是对这个结构体的描述:
The SOCKADDR_STORAGE structure stores socket address information. Since the SOCKADDR_STORAGE structure is sufficiently large to store address information for IPv4, IPv6, or other address families, its use promotes protocol-family and protocol-version independence and simplifies cross-platform development. Use the SOCKADDR_STORAGE structure in place of the sockaddr structure.
typedef struct sockaddr_storage {
short ss_family;
char __ss_pad1[_SS_PAD1SIZE];
__int64 __ss_align;
char __ss_pad2[_SS_PAD2SIZE];
} SOCKADDR_STORAGE, *PSOCKADDR_STORAGE;
说的很明白了,引入这个结构体是为了跨平台而用。而且它空间足够大,能够容纳任何地址家族的地址信息。再来看su.h头文件中接下来的代码:
#if !SU_HAVE_SOCKADDR_STORAGE
/*
* RFC 2553: protocol-independent placeholder for socket addresses
*/
#define _SS_MAXSIZE 128
#define _SS_ALIGNSIZE (sizeof(int64_t))
#define _SS_PAD1SIZE (_SS_ALIGNSIZE - sizeof(u_char) * 2)
#define _SS_PAD2SIZE (_SS_MAXSIZE - sizeof(u_char) * 2 - \
_SS_PAD1SIZE - _SS_ALIGNSIZE)
struct sockaddr_storage {
#if SU_HAVE_SOCKADDR_SA_LEN
unsigned char ss_len; /* address length */
unsigned char ss_family; /* address family */
#else
unsigned short ss_family; /* address family */
#endif
char __ss_pad1[_SS_PAD1SIZE];
int64_t __ss_align; /* force desired structure storage alignment */
char __ss_pad2[_SS_PAD2SIZE];
};
#endif
目的很明确,如果系统中没有sockaddr_storage结构体,那么Sofia就自己定义一个。在这之前定义了一些结构体会用到的宏。关于sockaddr结构体的其他一些信息,这里有一篇文章可以参考下:http://blog.chinaunix.net/uid-7596647-id-2607376.html。
/** Common socket address structure. */
union su_sockaddr_u {
#ifdef DOCUMENTATION_ONLY
uint8_t su_len; /**< Length of structure */
uint8_t su_family; /**< Address family. */
uint16_t su_port; /**< Port number. */
#else
short su_dummy; /**< Dummy member to initialize */
#if SU_HAVE_SOCKADDR_SA_LEN
#define su_len su_sa.sa_len
#else
#define su_len su_array[0]
#endif
#define su_family su_sa.sa_family
#define su_port su_sin.sin_port
#endif
char su_array[32]; /**< Presented as chars */
uint16_t su_array16[16]; /**< Presented as 16-bit ints */
uint32_t su_array32[8]; /**< Presented as 32-bit ints */
struct sockaddr su_sa; /**< Address in struct sockaddr format */
struct sockaddr_in su_sin; /**< Address in IPv4 format */
#if SU_HAVE_IN6
struct sockaddr_in6 su_sin6; /**< Address in IPv6 format */
#endif
#ifdef DOCUMENTATION_ONLY
uint32_t su_scope_id; /**< Scope ID. */
#else
#define su_scope_id su_array32[6]
#endif
};
注释文字显示这是一个通用的socket地址结构体。看到union内放入了很多其他的结构体类型:sockaddr、sockaddr_in和sockaddr_in6。不知为何要放这么多结构体类在一个union内。
接下来是:I/O vector for scatter-gather I/O。关于scatter-gather I/O是什么见后面的附录。应该就是一个针对scatter-gather I/O DMA模式的结构体。它在windows平台下的定义不同于其他平台。因为scatter-gather I/O DMA模式使用了不连续的空间,所以我认为这里的vector解释成向量也行,这个单词也有载体的意思。这个结构体将会被su_vsend()和su_vrecv()两个函数使用。
typedef u_long su_ioveclen_t;
typedef struct su_iovec_s {
su_ioveclen_t siv_len;
void *siv_base;
} su_iovec_t;
typedef struct __WSABUF {
u_long len;
char FAR* buf;
} WSABUF, *LPWSABUF;
这个结构体在windows平台下被定义成上面第二张图。仔细观察会发现它与WSABUF结构体一致。
typedef size_t su_ioveclen_t;
typedef struct su_iovec_s {
void *siv_base; /**< Pointer to buffer. */
su_ioveclen_t siv_len; /**< Size of buffer. */
} su_iovec_t;
struct iovec {
void *iov_base; // Pointer to data.
size_t iov_len; // Length of data.
};
这个结构体在非windows平台下被定义成上面第二张图。仔细观察会发现它与POSIX sockets下的iovec结构体一致。
最后各自又定义了SU_IOVECLEN_MAX:
/*non widows*/
#define SU_IOVECLEN_MAX SIZE_MAX
/*windows*/
#define SU_IOVECLEN_MAX ULONG_MAX
还有一点不同之处在于windows平台下结构体的成员顺序是颠倒的。长度成员在前,数据成员在后。注释里并没有给出具体的原因,但注释指出了一点:不要整体初始化这个结构体,而是分别给各个成员赋值。
最后一部分,是为了跨平台用又定义了一套socket相关的api。这里有初始化(su_init),有退出时调用的清理函数(su_deinit)等等。具体如何实现跨平台的,得看su.c文件了。除了这些引起我关注的是这个函数:
SOFIAPUBFUN int su_is_blocking(int errcode);
申明它并没有在任何平台相关的宏判断下,也就是说所有平台都会申明这个函数。在这之后,又看到了这样一些代码:
#if SU_HAVE_BSDSOCK
#define su_ioctl ioctl
/*
* Note: before 1.12.2, there was su_isblocking() which did not take argument
* and which was missing from WINSOCK
*/
#define su_is_blocking(e) \
((e) == EINPROGRESS || (e) == EAGAIN || (e) == EWOULDBLOCK)
#endif
这段代码的意思是如果这是在非windows平台下,再定义这两个宏。综合这两处代码,是否可以得出:在非windows平台下,先申明一个函数,然后还可以用一个宏来覆盖之前这个函数申明。特意去su.c文件内查看了下,su_is_blocking函数定义确实是在特定条件下才出现,即在windows平台下编译su_is_blocking函数定义才会出现。
#if SU_HAVE_WINSOCK
SOFIAPUBFUN int su_inet_pton(int af, char const *src, void *dst);
SOFIAPUBFUN const char *su_inet_ntop(int af, void const *src,
char *dst, size_t size);
SOFIAPUBFUN ssize_t
su_send(su_socket_t s, void *buffer, size_t length, int flags),
su_sendto(su_socket_t s, void *buffer, size_t length, int flags,
su_sockaddr_t const *to, socklen_t tolen),
su_recv(su_socket_t s, void *buffer, size_t length, int flags),
su_recvfrom(su_socket_t s, void *buffer, size_t length, int flags,
su_sockaddr_t *from, socklen_t *fromlen);
static __inline
uint16_t su_ntohs(uint16_t s)
{
return (uint16_t)(((s & 255) << 8) | ((s & 0xff00) >> 8));
}
static __inline
uint32_t su_ntohl(uint32_t l)
{
return ((l & 0xff) << 24) | ((l & 0xff00) << 8)
| ((l & 0xff0000) >> 8) | ((l & 0xff000000U) >> 24);
}
#define ntohs su_ntohs
#define htons su_ntohs
#define ntohl su_ntohl
#define htonl su_ntohl
#else
#define su_inet_pton inet_pton
#define su_inet_ntop inet_ntop
#define su_send(s,b,l,f) send((s),(b),(l),(f))
#define su_sendto(s,b,l,f,a,L) sendto((s),(b),(l),(f),(void const*)(a),(L))
#define su_recv(s,b,l,f) recv((s),(b),(l),(f))
#define su_recvfrom(s,b,l,f,a,L) recvfrom((s),(b),(l),(f),(void *)(a),(L))
#endif
上述这段代码也是非常有意思,我想大部分的跨平台C代码可能都会这些写。首先su模块内申明的通用接口一般与类linux接口相同。所以通用接口在非windows平台下都可以像上述#else部分一样,定义一些宏即可。windows平台下通用API的接口定义成与Linux接口一致,然后再在.c文件内完成具体实现。
附
scatter/gather I/O