Winsock主要有以下几个主要的头文件和库文件用于应用程序调用Winsock的API:
Winsock.h WSOCK32.lib
Winsock2.h WS2_32.lib
MSWSOCK.h Mswsock.lib
其它的一些常用的TCP/IP协议支持扩展API的头文件:
WS2tcpip.h
MSTcpip.h
Iphlpapi.h Iphlpapi.lib
同时包含Winsock2.h和Windows.h时注意在Windows.h前定义#define WIN32_LEAN_AND_MEAN 宏,或者可以在Windows.h前包含Winsock2.h或只包含Winsock2.h头文件
在Windows上使用socket需要初始化环境:
int WSAStartup(
__in WORD wVersionRequested,
__out LPWSADATA lpWSAData
);
wwVersionRequested有两个字节,用来指定需要初始化的Winsock版本号;一般初始化2.2版本
e.g. {wwVersionRequested = MAKEWORD(2, 2)}
typedef struct WSAData {
WORD wVersion;
WORD wHighVersion;
char szDescription[WSADESCRIPTION_LEN+1];
char szSystemStatus[WSASYS_STATUS_LEN+1]; unsigned short iMaxSockets;
unsigned short iMaxUdpDg;
char FAR* lpVendorInfo;
} WSADATA, *LPWSADATA;
wVersion是初始化成功的Winsock版本
wHighVersion是当前系统中最高的Winsock版本
(以上两字段中高字节是副版本号,低字节是主版本号)
该结构中其余字段都可以忽略不用(有其它方法可以得到比这些字段更准确的信息,如调用WSAEnumProtocols)
在调用完毕Winsock的功能后,最终应调用WSAClearup来释放Winsock库
int WSACleanup(void);
该函数还可以释放所有调用Winsock过程中占用的资源,并释放所有挂起的Winsock调用(异步调用中产生的)
当然如果不调用这个函数,最终应用程序退出时系统会自动清理这些资源并释放挂起的调用
但是不要依赖最终程序的退出进行释放,而总是主动的调用WSAClearup
SOCKET socket(int af, int type, int protocol)
int bind(SOCKET s,const struct sockaddr FAR *name, int namelen) //绑定套接字到指定的地址上
int listen(SOCKET s, int backlog) //将socket设置为监听模式
第二个参数指定了待处理的并发连接的最大长度
int connect(SOCKET s, const struct sockaddr FAR *name, int namelen)
SOCKET accept(SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen)
第二、三个参数用于返回连接进来的客户端的地址,在IP协议中这就是一个IP地址,需要强转为SOCKADDR_IN*类型 而返回值就表示与连接进入的客户端进行通讯操作的新的SOCKET的句柄
int send(SOCKET s,const char* buf,int len,int flags);
int recv(SOCKET s,char* buf,int len,int flags);
int sendto(SOCKET s,const char* buf,int len,int flags,const struct sockaddr* to,int tolen);
int recvfrom(SOCKET s,char* buf,int len,int flags,struct sockaddr* from,int* fromlen);
一方调用send发送时,另一方应该是调用recv接收数据,反之依然
如果两端同时调用send或recv函数那么稍后一端的调用会失败
int shutdown(SOCKET s, int how)
int closesocket(SOCKET s)
Windows下通过SOCKADDR_IN这个结构体来指定IP地址和端口信息
IP地址一般是用"Internet标准点分表示法"来表示的,形如a.b.c.d
其中每个字母代表一个字节数字(可以是十进制/十六进制/八进制表示,注意大小不超过256)
利用inet_addr工具函数,可以方便的将一个点分法表示的IP地址翻译为一个4字节的无符号整数:
unsigned long inet_addr(__in const char* cp);
注意这个函数参数为MBCS/ANSI string,没有UNICODE版,这个函数来源于古老的BSD Socket规范,那时候还没有UNICODE的说法
对于一个具体的网络协议来说,字节序是一个重要的特征
Internet协议(TCP/IP协议)的字节序是big-endian
而Intelx86系列主机的字节序却是little-endian
这样就存在一个主机字节序转换为网络字节序的问题
有一组函数用于将主机字节序转为网络字节序:
u_long htonl(__in u_long hostlong);
int WSAHtonl(__in SOCKET s,__in u_long hostlong,
__out u_long* lpnetlong);
u_short htons(__in u_short hostshort);
int WSAHtons(__in SOCKET s,__in u_short hostshort,
__out u_short* lpnetshort);
下一组函数执行相反的操作:
u_long ntohl(_in u_long netlong);
int WSANtohl(__in SOCKET s,__in u_long netlong,
__out u_long* lphostlong);
u_short ntohs(__in u_short netshort);
int WSANtohs(__in SOCKET s,__in u_short netshort,
__out u_short* lphostshort);
(以上函数的现代实现版本是很高效的)
int getpeername(SOCKET s,struct sockaddr FAR* name,int FAR* namelen);
该函数用于在面向连接通讯时取得通讯另一端的IP地址和端口信息
int WSADuplicateSocket(SOCKET s,DWORD dwProcessId,LPWSAPROTOCOL_INFO lpProtocolInfo);
该函数用于跨进程复制SOCKET句柄,与一般的复制句柄方法不同,该函数返回用于复制的SOCKET句柄的WSAPROTOCOL_INFO结构信息,另一进程得到这个信息后调用WSASocket方法传递该结构再创建一个新的SOCKET句柄
//////////////////////////////////////////////////////////////////
//可以取得本地IP
hostent* thisHost = gethostbyname("");
char *ip = inet_ntoa (*(struct in_addr *)*thisHost->h_addr_list);
SOCKADDR_IN.sin_addr.s_addr = inet_addr(ip);
//////////////////////////////////////////////////////////////////
getsockname 可以获取指定socke的地址
相对于抽象的网络协议,而程序员更关心的是如何对网络进行编程
这需要对抽象的协议的具体化方面的认知以及对一些协议共有特征方面的知识
具体的网络编程目前在各个平台上主要通过套接字编程模型来实现
基本的套接字模型甚至主要的函数方法在各个平台上(Windows/UNIX/Linux等)几乎是一样的,只是各自扩展部分或高级部分不相同
套接字编程模型是与具体的协议无关的网络编程接口
套接字只关注网络协议的抽象特征部分,而不是关系体局是什么协议,比如:关系是否面向连接/地址如何表达等
所以,在进行网路编程的时候,需要考虑的是网络写的具有的一些特征:
- 面向消息 - 伪流
- 面向连接和无连接
- 可靠性和次序性
- 从容关闭
- 广播数居
- 多播数据
- 服务质量
- 部分消息
- 路由选择的考虑
- 字节序
- 最大传输单元
- 其他
閱讀更多 攻防基地 的文章