winsock编程入门

WinSocket自学开始

一、声明

本人在自学过程中在此记录学习心得,如果内容有欠妥的地方还请在此下方批评,或私信,谢谢阅读。

二、常用的基本函数

2.1 头文件

使用WinSocket编写程序的时候,要用到几个重要的文件,winsock2.h,静态链接库文件ws2_32.lib,以及动态链接库ws2_32.dll,头文件是用在源程序中,静态链接库是用来编译基于Winsock程序的,而动态链接库ws2_32.dll是运行基于WinSock的程序所必须的。

2.2 函数

参考书目:《网络安全编程技术与实例》08年。
参考文档:微软文档

2.2.1 htonl函数

将主机的u_long值转换成网络字节顺序(32位)。

//原型	u_long htonl(u_long hostlong)
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main(){
	u_long a = 0x12345678;
	u_long b = htonl(a);
	printf("%x\n", b);
}

2.2.2ntohl函数

将32位网络字节顺序转化为主机字节。

//原型	u_long ntohl(u_long netlong)
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main(){
	u_long a = 0x12345678;
	u_long b = ntohl(a);
	printf("%x\n", b);
}

2.2.3 ntohs函数

将16位网络字节转换为主机字节。

//原型	u_short ntohs(u_short netshort)
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main(){
	u_short a = 0x12345678;
	u_short b = ntohs(a);
	printf("%x\n", b);
}

2.2.4 htons函数

将16位主机字节转换为网络字节顺序。

//原型	u_short htons(u_short hostshort)
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main(){
	u_short a = 0x12345678;
	u_short b = htons(a);
	printf("%x\n", b);
}

2.2.5 inet_pton 和 inet_ntop 函数

参考这篇(点击阅读)文章,其中的有关这两个函数都有解释,我们这里直接给出样例。

#include<WinSock2.h>
#include<stdio.h>
#pragma comment(lib, "ws2_32.lib")
#include <Ws2tcpip.h>
void main(){
	char a[] = "192.168.1.3";	//创建一个字符串形式的IP地址
	struct in_addr s;			//创建一个接受IP地址转换后的变量,用来存储二进制形式的
	if (inet_pton(AF_INET, a, &s) == 1)printf("ok\n");	//如果成功会返回1
	printf("%s->%u\n", a, s);
	if (inet_ntop(AF_INET, &s, a, sizeof(a)) != NULL)puts("ok");	//再转换回去看看
	printf("%s->%u\n", a, s);	
}

其中结构体in_addr为:

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;
};

目前我们只需注意u_long S_addr就可以了。

2.2.6 WSAStartup 和 WSACleanup函数

WSAStartup其功能是为初始化Winsock,在使用套接字函数前都应该调用。其本质是调用合适的WinSock动态链接库。

int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

第一个参数是WinSock版本,用MAKEWORD宏来表示,例如:MAKEWORD(2,1)表示2.1版本。
第二个参数表示WSAData指针,它用来存储套接字信息。

typedef struct WSAData{...}WSADATA,*LPWSADATA;

其内容需要的话百度关键字。

int WSACleanup(void);

此函数表示停止使用Winsock 2 DLL。

2.2.7 gethostname函数

次函数用来获取主机名称,直接看例子。

#include<WinSock2.h>
#include<stdio.h>
#pragma comment(lib, "ws2_32.lib")
#include <Ws2tcpip.h>
void main()[
	char hostname[100];		//存放主机字符串的数组
	WSADATA wsaData;
	int Ret;
	if (Ret = WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		printf("uperror %d\n", Ret);
		exit(EXIT_SUCCESS);
	}
	//函数的使用
	if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
		printf("erroe gethostname");
		exit(EXIT_SUCCESS);
	}else {
		printf("hostname:%s\n", hostname);
	}
	if (WSACleanup() == SOCKET_ERROR) {
		printf("cleanerror\n");
		exit(0);
	}
}

2.2.8 getaddrinfo 和 getnameinfo函数

推荐两个中文的解释getaddrinfoaddrinfo。(其实是同一个)
另外我们也可以看看原官方文档
以下是我的见解:
(首先我先从以上内容中摘取一些方便观看,如有争议可以私信)

getaddrinfo:

getaddrinfo定义:

int getaddrinfo(const char *restrict nodename, /* host 或者IP地址 */
    const char *restrict servname, /* 十进制端口号 或者常用服务名称如"ftp"、"http"等 */
    const struct addrinfo *restrict hints, /* 获取信息要求设置 */
    struct addrinfo **restrict res); /* 获取信息结果 */

void freeaddrinfo(struct addrinfo *ai); 

前两个参数就不过多赘述了,我们看后两个参数,两个都是相同的结构体指针,hints是用来设置接收这个结构体的信息的。res用来接收最终信息的结构体指针的指针,所以我们要传进去一个指针的地址。
返回值: 成功返回0,否则返回一个非零,用gai_strerrorA(int ecode)接受一个返回的字符串指针来检查错误。
freeaddrinfo用来释放获取的addrinfo结构体或者结构体链表一系列的内存空间。
相关结构体:
addrinfo定义:
来自

struct addrinfo {
    int ai_flags;   /* AI_PASSIVE, AI_CANONNAME, AI_NUMERICHOST */
    int ai_family;  /* PF_xxx */
    int ai_socktype;    /* SOCK_xxx */
    int ai_protocol;    /* 0 or IPPROTO_xxx for IPv4 and IPv6 */
    socklen_t ai_addrlen;   /* length of ai_addr */
    char    *ai_canonname;  /* canonical name for hostname */
    struct  sockaddr *ai_addr;  /* binary address */
    struct  addrinfo *ai_next;  /* next structure in linked list */
};

在初始化这个结构体的时候我们一般都初始化为0就可以了。(目前来说)
这里我们再集中学习几个结构体一起来分析:
sockaddr定义:

struct sockaddr
{
    sa_family_t  sa_family;   //ushort类型 
    char         sa_data[14]; // Socket address (variable-length data). 
};

sockaddr_in定义:

struct sockaddr_in
{
    sa_family_t     sin_family;   //AF_INET. 
    in_port_t       sin_port;     //Port number(网络字节序). 
    struct in_addr  sin_addr;     //IP address(网络字节序).
    char[8] sin_zero;			//目前我还不知道用途
};

sockaddr_in6定义:

struct sockaddr_in6{
	u_short sin6_family;
	u_short sin6_port;
	u_long sin6_flowinfo;
	in6_addr sin6_addr;
	u_long sin6_scope_id;
}
typedef struct sockaddr_in6 SOCKADDR_IN6;
typedef struct sockaddr_in6 *PSOCKADDR_IN6;
typedef struct sockaddr_in6 FAR *LPSOCKADDR_IN6;

sockaddr_insockaddr_in6分别对应IPv4和IPv6。
以上结构体关系我简单画了一个联系仅供参考:
winsock编程入门
其次sockaddr可以在使用的过程中强制转换成IPv4和IPv6的结构体类型,其中我们会发现IPv6结构体类型的内存大小会与sockaddr内存大小不一致,但在强制转换的过程中信息却可以完好的保留,在我的好友帮助下我们猜测应该是在使用内置函数的里面,存在一个内存分配的问题(因为结构体里面都是指针),所以强制转换可以实现应对不同的IP。

getnameinfo:

getnameinfo定义:

INT WSAAPI getnameinfo(
  const SOCKADDR *pSockaddr,	//包含地址和端口号的sockaddr结构体
  socklen_t      SockaddrLength,	//传入一个地址结构体的字节长度
  PCHAR          pNodeBuffer,	//用来接收返回的主机名字符串
  DWORD          NodeBufferSize,	//用于分配接收主机名字符串的空间,一般使用sizeof
  PCHAR          pServiceBuffer,	//同理是接收服务名的
  DWORD          ServiceBufferSize,	//同理一般使用sizeof
  INT            Flags
);

参考文档:链接
getnameinfo函数可以实现地址到主机名,端口号到服务名的解析。
参数说明:

  • pSockaddr如果不是sockaddr类型,相应的IPv4传递sockaddr_in,IPv6传递sockaddr_in6。
  • SockaddrLength如果是addrinfo指针,我们可以使用这个结构体里的ai_addrlen。如果是IPv4或者IPv6我们分别使用sizeof运算符来计算地址结构体的大小。
  • pNodeBufferpServiceBuffer是要预先分配好内存空间。
  • Flags有以下几种设置:
    NI_NAMEREQD被设置的时候,如果主机名不能被域名系统解析(DNS),则返回error。
    NI_NOFQDN被设置的时候,会返回给host参数一个本地主机具有的相对辨别名。(我也不清楚,还是看原文档的英文吧o(╥﹏╥)o)
    NI_NUMERICHOST被设置的时候,会返回一个主机名称的数字形式来代替他本身的名称。如果它的名称不能被域名系统解析(DNS),也会返回。
    NI_NUMERICSERV被设置的时候,返回服务的端口号,同时,如果主机名没有设置一个IP地址的话,主机名会作为IP地址被返回。
    NI_DGRAMSetting the NI_DGRAM flag indicates that the service is a datagram service. This flag is necessary for the few services that provide different port numbers for UDP and TCP service.

返回值: 成功返回零,非零使用WSAGetLastError(void)检查错误(整形返回值)。错误列表可以自行搜索。

family:

AF_INET对应的IPv4;
AF_INET6对应IPv6;
AF_UNSPECIPv4和IPv6都可以。
余下有关参数请参考文档

例子:

结合前面获取的主机名,我们来解析IP。

	//解析地址
	char hostname[100];
	WSADATA wsaData;
	int Ret;
	if (Ret = WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		printf("uperror");
		exit(EXIT_SUCCESS);
	}
	if (gethostname(hostname, sizeof(hostname)) == SOCKET_ERROR) {
		printf("erroe gethostname");
		exit(EXIT_SUCCESS);
	}else {
		printf("hostname:%s\n", hostname);
	}
	
	//设置接收信息,创建接收返回信息的结构体指针getaddr,会在函数内分配空间
	addrinfo *addrstr, *getaddr;
	addrstr = (addrinfo*)malloc(sizeof(addrinfo));
	//初始化为0
	memset(addrstr, 0, sizeof(addrinfo));
	//地址族类型设置为AF_UNSPEC,即可为IPv4也可为IPv6
	addrstr->ai_family = AF_UNSPEC;

	//创建临时变量
	sockaddr_in* ipv4;
	sockaddr_in6* ipv6;

	int s;
	if (s = getaddrinfo(hostname, "https", addrstr, &getaddr) != 0) {
		printf("error getaddrstr:%s", gai_strerror(s));
		exit(EXIT_SUCCESS);
	}
	//创建内存空间用来接收转换后的字符串
	char buf[1025];

	for (addrinfo* re = getaddr; re != NULL; re = re->ai_next) {
		switch(re->ai_family) {
		case AF_INET:
			ipv4 = (sockaddr_in*)re->ai_addr;
			inet_ntop(re->ai_family, &ipv4->sin_addr, buf, sizeof(buf));
			break;
		case AF_INET6:
			ipv6 = (sockaddr_in6*)re->ai_addr;
			inet_ntop(re->ai_family, &ipv6->sin6_addr, buf, sizeof(buf));
			break;
		}
		printf("[ipv%d]:%s\n", re->ai_family == AF_INET ? 4: 6, buf);

	}

	puts("--------------------------------");

	//反过来应用getnameinfo函数解析
	int ret = 0;
	addrinfo* res_p;
	for (res_p = getaddr; res_p != NULL; res_p = res_p->ai_next) {
		char host[1024], servername[1024] = {0};
		memset(host, 0, sizeof(host));
		puts("going");
		ret = getnameinfo(res_p->ai_addr, res_p->ai_addrlen, host, sizeof(host), servername, sizeof(servername), NI_NAMEREQD);
		if (ret != 0)
		{
			printf("getnameinfo: %s\n", gai_strerror(ret));
		}
		else
		{
			printf("hostname: %s	%s\n", host, servername);
		}
	}
	
	//释放刚刚创建的getaddr指针空间
	freeaddrinfo(getaddr);
	if (WSACleanup() == SOCKET_ERROR) {
		printf("cleanerror\n");
		exit(0);
	}

2.2.9 WSAGetLastError函数

在WinSock编程中遇到错误可以用此函数检查。

int WSAGetLastError(void);
上一篇:Qt Creator编译,存在中文导致错误: error: C2001: 常量中有换行符


下一篇:socket函数