介绍利用套接字编程时需要的一些函数。
1. WSAStartup 函数
利用套接字编程时,第一步需要加载套接字库,通过 WSAStartup 函数来实现。
该函数有两个功能:
- 加载套接字库
- 进行套接字库的版本协商——确定使用的Socket版本
函数原型如下:
1 |
int WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData ); |
两个参数含义如下:
- wVersionRequested :用来指定准备加载的Winsock库的版本。因为是一个双字,一般高位字节为WinSock的副版本号,低位字节为主版本号。通常版本号为2.1,则2是主版本号,1是副版本号。可以使用 MAKEWORD(x,y) 宏方便获得 wVersionRequested 的值。(x是高位字节,y是低位字节)
- lpWSAData :一个返回值,指向WSADATA结构的指针。WSAStartup 函数用其加载的库版本有关的信息填在这个结构中。
WSADATA的结构定义如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
/** * This file has no copyright assigned and is placed in the Public Domain. * This file is part of the mingw-w64 runtime package. * No warranty is given; refer to the file DISCLAIMER.PD within this package. */ #ifndef __MINGW_WSADATA_H #define __MINGW_WSADATA_H #define WSADESCRIPTION_LEN 256 #define WSASYS_STATUS_LEN 128 typedef struct WSAData { WORD wVersion; WORD wHighVersion; #ifdef _WIN64 unsigned short iMaxSockets; unsigned short iMaxUdpDg; char *lpVendorInfo; char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; #else char szDescription[WSADESCRIPTION_LEN+1]; char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets; unsigned short iMaxUdpDg; char *lpVendorInfo; #endif } WSADATA, *LPWSADATA; #endif /* __MINGW_WSADATA_H */ |
WSAData 参数解析:
- wVersion :目前使用的Winsock版本。
- wHighVersion : 这个DLL能够支持的Windows Sockets规范的最高版本。
- iMaxSockets : 单个进程能够打开的socket的最大数目。
- iMaxUdpDg : Windows Sockets应用程序能够发送或接收的最大的用户数据包协议(UDP)的数据包大小,以字节为单位。如果实现方式没有限制,那么iMaxUdpDg为零。
- lpVendorInfo : 指向销售商的数据结构的指针。这个结构的定义(如果有)超出了WindowsSockets规范的范围。WinSock2.0版中已被废弃。
- szDescription : 以null结尾的ASCII字符串,Windows Sockets DLL将对Windows Sockets实现的描述拷贝到这个字符串中,包括制造商标识。
- szSystemStatus : 以null结尾的ASCII字符串,Windows Sockets DLL把有关的状态或配置信息拷贝到该字符串中。
对于每一个 WSAStartup 函数的成功调用(成功加载WinSock动态库),在最后都对应一个WSACleanUp调用,以便释放为该应用程序分配的资源,终止对WinSock动态库的使用。
2. socket 函数
加载了套接字库后,就可以调用socket函数创建套接字,函数原型声明如下:截取自:<Winsock2.h>
1 |
WINSOCK_API_LINKAGE SOCKET WSAAPI socket(int af,int type,int protocol); |
看着稍乱,可以理解为:
1 |
SOCKET socket(int af,int type,int protocol); |
socket函数接受三个参数:
- af 指定地址族,对于TCP/IP协议的套接字,它只能是 AF_INET 或者 PF_INET ;
- type 指定 Socket 类型,对于1.1版本的Socket只支持两种类型的套接字:SOCK_STREAM(流式套接字) 和 SOCK_DGRAM(数据报套接字)。
- protocol 是与特定的地址家族相关的协议,如果指定为0,那么系统就会根据地址格式和套接字类别,自动选择一个合适的协议。
如果socket调用成功,就会返回一个新的 SOCKET 数据类型的套接字描述符。
如果socket调用失败,就会返回一个 INVALID_SOCKET 值,错误信息可以通过 WSAGetLastError 函数返回。
这里详细讲述下socket相关的参数,可以当做了解。
以下代码大多来自: <Winsock2.h> 和 <ws2def.h>
socket的版本应该是2.2版本:
1 2 3 |
#ifndef WINSOCK_VERSION #define WINSOCK_VERSION MAKEWORD(2,2) #endif |
af 的值列表及解释:很多都很难找到…望留言可修改…
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
#define AF_UNSPEC 0 // unspecified #define AF_UNIX 1 // local to host (pipes, portals) #define AF_INET 2 // internetwork: UDP, TCP, etc. #define AF_IMPLINK 3 // arpanet imp addresses #define AF_PUP 4 // pup protocols: e.g. BSP #define AF_CHAOS 5 // mit CHAOS protocols #define AF_NS 6 // XEROX NS protocols #define AF_IPX AF_NS // IPX protocols: IPX, SPX, etc. #define AF_ISO 7 // ISO protocols #define AF_OSI AF_ISO // OSI is ISO #define AF_ECMA 8 // european computer manufacturers #define AF_DATAKIT 9 // datakit protocols #define AF_CCITT 10 // CCITT protocols, X.25 etc #define AF_SNA 11 // IBM SNA #define AF_DECnet 12 // DECnet #define AF_DLI 13 // Direct data link interface #define AF_LAT 14 // LAT #define AF_HYLINK 15 // NSC Hyperchannel #define AF_APPLETALK 16 // AppleTalk #define AF_NETBIOS 17 // NetBios-style addresses #define AF_VOICEVIEW 18 // VoiceView #define AF_FIREFOX 19 // Protocols from Firefox #define AF_UNKNOWN1 20 // Somebody is using this! #define AF_BAN 21 // Banyan #define AF_ATM 22 // Native ATM Services #define AF_INET6 23 // Internetwork Version 6 #define AF_CLUSTER 24 // Microsoft Wolfpack #define AF_12844 25 // IEEE 1284.4 WG AF #define AF_IRDA 26 // IrDA #define AF_NETDES 28 // Network Designers OSI & gateway #if(_WIN32_WINNT < 0x0501) #define AF_MAX 29 #else //(_WIN32_WINNT < 0x0501) #define AF_TCNPROCESS 29 #define AF_TCNMESSAGE 30 #define AF_ICLFXBM 31 #if(_WIN32_WINNT < 0x0600) #define AF_MAX 32 #else //(_WIN32_WINNT < 0x0600) #define AF_BTH 32 // Bluetooth RFCOMM/L2CAP protocols #if(_WIN32_WINNT < 0x0601) #define AF_MAX 33 #else //(_WIN32_WINNT < 0x0601) #define AF_LINK 33 #define AF_MAX 34 #endif //(_WIN32_WINNT < 0x0601) #endif //(_WIN32_WINNT < 0x0600) #endif //(_WIN32_WINNT < 0x0501) |
Windows下的AF对应的PF:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 |
#define PF_UNSPEC AF_UNSPEC #define PF_UNIX AF_UNIX #define PF_INET AF_INET #define PF_IMPLINK AF_IMPLINK #define PF_PUP AF_PUP #define PF_CHAOS AF_CHAOS #define PF_NS AF_NS #define PF_IPX AF_IPX #define PF_ISO AF_ISO #define PF_OSI AF_OSI #define PF_ECMA AF_ECMA #define PF_DATAKIT AF_DATAKIT #define PF_CCITT AF_CCITT #define PF_SNA AF_SNA #define PF_DECnet AF_DECnet #define PF_DLI AF_DLI #define PF_LAT AF_LAT #define PF_HYLINK AF_HYLINK #define PF_APPLETALK AF_APPLETALK #define PF_VOICEVIEW AF_VOICEVIEW #define PF_FIREFOX AF_FIREFOX #define PF_UNKNOWN1 AF_UNKNOWN1 #define PF_BAN AF_BAN #define PF_ATM AF_ATM #define PF_INET6 AF_INET6 #define PF_BTH AF_BTH #define PF_MAX AF_MAX |
type的值:
1 2 3 4 5 |
#define SOCK_STREAM 1 /*流式套接字-双向连续可信赖*/ #define SOCK_DGRAM 2 /*数据报套接字-不连续不可信赖*/ #define SOCK_RAW 3 /*原始套接字*/ #define SOCK_RDM 4 /*数据报套接字——可信赖*/ #define SOCK_SEQPACKET 5 /*数据报套接字——连续可信赖的*/ |
protocol的值:protocol用来指定socket所使用的传输协议编号,通常此参考不用管它,设为0即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
#define IPPROTO_IP 0 #define IPPROTO_HOPOPTS 0 #define IPPROTO_ICMP 1 #define IPPROTO_IGMP 2 #define IPPROTO_GGP 3 #define IPPROTO_IPV4 4 #define IPPROTO_TCP 6 #define IPPROTO_PUP 12 #define IPPROTO_UDP 17 #define IPPROTO_IDP 22 #define IPPROTO_IPV6 41 #define IPPROTO_ROUTING 43 #define IPPROTO_FRAGMENT 44 #define IPPROTO_ESP 50 #define IPPROTO_AH 51 #define IPPROTO_ICMPV6 58 #define IPPROTO_NONE 59 #define IPPROTO_DSTOPTS 60 #define IPPROTO_ND 77 #define IPPROTO_ICLFXBM 78 #define IPPROTO_RAW 255 #define IPPROTO_MAX 256 |
大致就是以上这些。
3. bind 函数
创建了套接字后,应将该套接字绑定到本地的某个地址和端口上,这需要通过 bind 函数实现。
1 |
WINSOCK_API_LINKAGE int WSAAPI bind(SOCKET s,const struct sockaddr *name,int namelen); |
稍长,可以理解为:
1 |
int bind(SOCKET s,const struct sockaddr *name,int namelen); |
bind函数接受三个参数:
- s 指定要绑定的套接字。
- name 制定了该套接字的本地地址信息。
- namelen 很明显是name的长度。
bind的函数调用成功,则返回0,如果调用失败,则返回一个 SOCKET_ERROR,错误信息通过 WSAGetLastError 函数返回。
这里要讲的是 name 即 sockaddr 的结构:
1 2 3 4 |
struct sockaddr { u_short sa_family; char sa_data[14]; }; |
sockaddr 的结构有两个字段:
- 第一个字段(sa_family) 指定地址家族,对于 TCP/IP 类型的套接字,必须指定为 AF_INET .
- 第二个字段(sa_data)仅仅是表示要求一块内存分配区,起到占位的作用,该区域指定与协议相关的具体地址信息。
对于不同的协议家族,使用不同的结构来替换 sockaddr。除了 sa_family 之外,sockaddr 是按网络字节顺序表示的。
在基于 TCP/IP 的socket编程中,可以用 sockaddr_in 结构替换 sockaddr,以方便我们填写地址信息。
1 2 3 4 5 6 |
struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; |
sockaddr_in 的成员:
- sin_family 表示地址族,对于IP地址,一直是 AF_INET 。
- sin_port 表示分配给套接字的端口号。
- sin_addr 给出的是套接字的主机IP地址。
- sin_zero 只是一个填充数,以使 sockaddr_in 和 sockaddr 长度相同。
另外 sin_addr 成员的类型是 in_addr 其类型是: <inaddr.h>
1 2 3 4 5 6 7 |
typedef struct in_addr { union { struct { u_char s_b1, s_b2, s_b3, s_b4; } S_un_b; struct { u_short s_w1, s_w2; } S_un_w; u_long S_addr; } S_un; } IN_ADDR, *PIN_ADDR, *LPIN_ADDR; |
可以看到这是一个union,共用体的结构!
通常用这个结构将一个点分十进制的ip地址转换为u_long 类型,并将结果赋给 S_addr 。
4. inet_addr 和 inet_ntoa 函数
将IP地址指定为 INADDR_ANY ,允许套接字向任何分配给 本地机器的 IP 地址 发送或接受数据。
INADDR_ANY 在<Winsock2.h> 文件中有定义:
1 2 3 4 5 6 |
#define INADDR_ANY (u_long)0x00000000 #define INADDR_LOOPBACK 0x7f000001 #define INADDR_BROADCAST (u_long)0xffffffff #define INADDR_NONE 0xffffffff #define ADDR_ANY INADDR_ANY |
啥意思呢,就是多数情况下,每一个机器只有一个IP,但有的机器可能会有多个网卡, 每个网卡都有自己的IP地址,用 INADDR_ANY 可以简化应用程序的编写,即我所有的网卡都可以侦听或者发送数据。
当然如果我们只想让套接字(socket)使用多个ip中的一个地址,就必须使用实际的地址。
要做到这一点,可以使用 inet_addr 函数来实现。原型如下:
1 |
WINSOCK_API_LINKAGE unsigned __LONG32 WSAAPI inet_addr(const char *cp); |
简化版就是这样的:
1 |
unsigned __LONG32 inet_addr(const char *cp); |
一个字符串作为其参数,该字符串指定了以点分十进制格式表示的IP地址 (如:192.168.1.1)
而且 inet_addr 函数 会返回一个适合分配给 S_addr 的 u_long 类型的数据。(参考前面的 bind )
inet_ntoa 函数会完成相反的转换,它接受一个 in_addr 结构体类型作为参数,并会返回一个点分十进制格式表示的IP地址字符串。
函数原型如下:
1 |
WINSOCK_API_LINKAGE char *WSAAPI inet_ntoa(struct in_addr in); |
简化版是这样样子的:
1 |
char * inet_ntoa(struct in_addr in); |
5. listen 函数
该函数作用是将指定的套接字设置为监听模式…函数原型如下:<Winsock2.h>
1 |
WINSOCK_API_LINKAGE int WSAAPI listen(SOCKET s,int backlog); |
简化版就是这样的:
1 |
int listen(SOCKET s,int backlog); |
两个参数:
- 第一个参数 s 是套接字描述符 。
- 第二个参数 backlog 是 等待队列的最大长度 ,如果设置为 SOMAXCONN ,那么下层的服务提供者将这个套接字设置为最大的合理值。
一个返回值:
无错误发生,返回0。
有错误发生,返回-1,可通过 WSAGetLastError() 获取错误代码。
注意点:
- listen 函数 仅适用于支持连接的套接口,如SOCK_STREAM类型的。
- backlog 参数 是等待队列的最大长度而不是一个端口上同时连接的数目。例如:backlog设置为2,如果有三个请求到来,前两个放入等待请求连接队列中,应用程序依次处理这些请求服务,第三个请求则被拒绝。
6. accept 函数
该函数是接受客户端发送的连接请求。函数原型如下:
1 |
WINSOCK_API_LINKAGE SOCKET WSAAPI accept(SOCKET s,struct sockaddr *addr,int *addrlen); |
简化版如下:
1 |
SOCKET accept(SOCKET s,struct sockaddr *addr,int *addrlen); |
有三个参数,返回一个和客户端通信的Socket
- 第一个参数 s 是套接字描述符
- 第二个参数 addr 是指向一个缓冲区的指针,该缓冲区接受连接实体的地址,也就是当客户端向服务端发起连接,服务端接受这个连接时,保存发起连接的这个客户端的Ip地址信息和端口信息。
- 第三个参数 addrlen 也是一个返回值,整型指针,包含地址信息的长度。
7. send 函数
通过已建立连接的套接字发送数据。函数原型如下:
1 |
WINSOCK_API_LINKAGE int WSAAPI send(SOCKET s,const char *buf,int len,int flags); |
简化版如下:
1 |
int send(SOCKET s,const char *buf,int len,int flags); |
四个参数:
- 第一个参数 s 是已建立连接的套接字。
- 第二个参数 buf 指向一个缓冲区,该缓冲区包含将要传递的数据。
- 第三个参数 len 是缓冲区的长度。
- 第四个参数 flags 设定的值将影响函数的行为,一般设置为0即可。
返回值:
如果无错误,返回值为所发送数据的总数。
如果有错误,返回SOCKET_ERROR。
8. recv 函数
从一个已连接的套接字接受数据。函数原型如下:
1 |
WINSOCK_API_LINKAGE int WSAAPI recv(SOCKET s,char *buf,int len,int flags); |
简化版如下:
1 |
int recv(SOCKET s,char *buf,int len,int flags); |
四个参数:
- 第一个参数 s 是接受数据的套接字。
- 第二个参数 buf 是指向缓冲区的指针,用来保存接收的数据。
- 第三个参数 len 是缓冲区长度。
- 第四个参数 flags 设定会影响函数的行为。
返回值:
recv函数返回其实际copy的字节数。
如果在copy时出错,那么它返回SOCKET_ERROR;
如果在等待协议接收数据时网络中断了,那么它返回0。
9.connect 函数
该函数的作用是与一个特定的套接字建立连接。函数原型如下:
1 |
WINSOCK_API_LINKAGE int WSAAPI connect(SOCKET s,const struct sockaddr *name,int namelen); |
简化后如下:
1 |
int connect(SOCKET s,const struct sockaddr *name,int namelen); |
三个参数:
- 第一个参数 s 是即将在其上建立连接的套接字
- 第二个参数 name 设定连接的服务器端地址信息
- 第三个参数 namelen 指定服务端地址长度
返回值:
成功则返回0,失败返回非0.
10.recvfrom 函数
该函数接受一个数据报信息并保存源地址。原型声明如下:
1 |
WINSOCK_API_LINKAGE int WSAAPI recvfrom(SOCKET s,char *buf,int len,int flags,struct sockaddr *from,int *fromlen); |
简化后如下:
1 |
int recvfrom(SOCKET s,char *buf,int len,int flags,struct sockaddr *from,int *fromlen); |
六个参数:
- 第一个参数 s 是准备接受数据的套接字。
- 第二个参数 buf 是一个指向缓冲区的指针。
- 第三个参数 len 是缓冲区的长度。
- 第四个参数 flags 的值会影响函数行为。
- 第五个参数 from 是一个指向地址结构的指针,主要是用来接受发送方的地址信息。
- 第六个参数 fromlen 是一个整型指针,并且它是一个 in/out 类型的参数,表明在调用前需要给它指定一个初始值,函数调用过后,会通过这个参数返回一个值,该返回值是地址结构的大小。
UDP使用recvfrom()函数接收数据,成功则返回接收到的字符数,失败返回-1。
11. sendto 函数
向一个特定的目标发送数据。函数原型如下:
1 |
WINSOCK_API_LINKAGE int WSAAPI sendto(SOCKET s,const char *buf,int len,int flags,const struct sockaddr *to,int tolen); |
简化后如下:
1 |
int sendto(SOCKET s,const char *buf,int len,int flags,const struct sockaddr *to,int tolen); |
六个参数:
- 第一个参数 s 是一个套接字描述符。
- 第二个参数 buf 是一个指向缓冲区的指针,包含要发送的数据。
- 第三个参数 len 是缓冲区中数据长度。
- 第四个参数 flags 的设置会影响函数调用行为。
- 第五个参数 to 是一个可选的指针,指定目标套接字地址。
- 第六个参数 tolen 是指定地址的长度。
UDP用sendto()函数发送数据,如果成功,则返回发送的字节数,失败则返回SOCKET_ERROR。
12. htons 和 htonl 函数
htons 把一个 u_short 类型的值从主机字节顺序转换为 TCP/IP 网络字节顺序。
htonl 把一个 u_long 类型的值从主机字节顺序转换为 TCP/IP 网络字节顺序。
两个函数原型如下:
1 2 |
WINSOCK_API_LINKAGE u_long WSAAPI htonl(u_long hostlong); WINSOCK_API_LINKAGE u_short WSAAPI htons(u_short hostshort); |
简化后如下所示:
1 2 |
u_long htonl(u_long hostlong); u_short htons(u_short hostshort); |
相关链接:
TCP应用程序:
完~