winsock编程入门
2021/10/9 14:20:04
本文主要是介绍winsock编程入门,对大家解决编程问题具有一定的参考价值,需要的程序猿们随着小编来一起学习吧!
WinSocket自学开始
- 一、声明
- 二、常用的基本函数
- 2.1 头文件
- 2.2 函数
- 2.2.1 htonl函数
- 2.2.2ntohl函数
- 2.2.3 ntohs函数
- 2.2.4 htons函数
- 2.2.5 inet_pton 和 inet_ntop 函数
- 2.2.6 WSAStartup 和 WSACleanup函数
- 2.2.7 gethostname函数
- 2.2.8 getaddrinfo 和 getnameinfo函数
- **getaddrinfo:**
- **getnameinfo:**
- **family:**
- **例子:**
- 2.2.9 WSAGetLastError函数
一、声明
本人在自学过程中在此记录学习心得,如果内容有欠妥的地方还请在此下方批评,或私信,谢谢阅读。
二、常用的基本函数
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函数
推荐两个中文的解释getaddrinfo和addrinfo。(其实是同一个)
另外我们也可以看看原官方文档。
以下是我的见解:
(首先我先从以上内容中摘取一些方便观看,如有争议可以私信)
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_in
和sockaddr_in6
分别对应IPv4和IPv6。
以上结构体关系我简单画了一个联系仅供参考:
其次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运算符来计算地址结构体的大小。pNodeBuffer
和pServiceBuffer
是要预先分配好内存空间。Flags
有以下几种设置:
NI_NAMEREQD
被设置的时候,如果主机名不能被域名系统解析(DNS),则返回error。
NI_NOFQDN
被设置的时候,会返回给host参数一个本地主机具有的相对辨别名。(我也不清楚,还是看原文档的英文吧o(╥﹏╥)o)
NI_NUMERICHOST
被设置的时候,会返回一个主机名称的数字形式来代替他本身的名称。如果它的名称不能被域名系统解析(DNS),也会返回。
NI_NUMERICSERV
被设置的时候,返回服务的端口号,同时,如果主机名没有设置一个IP地址的话,主机名会作为IP地址被返回。
NI_DGRAM
Setting 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_UNSPEC
IPv4和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);
这篇关于winsock编程入门的文章就介绍到这儿,希望我们推荐的文章对大家有所帮助,也希望大家多多支持为之网!
- 2024-11-23增量更新怎么做?-icode9专业技术文章分享
- 2024-11-23压缩包加密方案有哪些?-icode9专业技术文章分享
- 2024-11-23用shell怎么写一个开机时自动同步远程仓库的代码?-icode9专业技术文章分享
- 2024-11-23webman可以同步自己的仓库吗?-icode9专业技术文章分享
- 2024-11-23在 Webman 中怎么判断是否有某命令进程正在运行?-icode9专业技术文章分享
- 2024-11-23如何重置new Swiper?-icode9专业技术文章分享
- 2024-11-23oss直传有什么好处?-icode9专业技术文章分享
- 2024-11-23如何将oss直传封装成一个组件在其他页面调用时都可以使用?-icode9专业技术文章分享
- 2024-11-23怎么使用laravel 11在代码里获取路由列表?-icode9专业技术文章分享
- 2024-11-22怎么实现ansible playbook 备份代码中命名包含时间戳功能?-icode9专业技术文章分享