Windows API 教程(九) 网络编程

茵蒂克丝

基础概念

IP 地址

IP 是英文 Internet Protocol (网络之间互连的协议)的缩写,也就是为计算机网络相互连接进行通信而设计的协议。任一系统,只要遵守 IP协议就可以与因特网互连互通。

所谓IP地址就是给每个遵循tcp/ip协议连接在Internet上的主机分配的一个32bit地址。按照TCP/IP协议规定,IP地址用二进制来表示,每个IP地址长32bit,比特换算成字节,就是4个字节。为了方便人们的使用,IP地址经常被写成十进制的形式,中间使用符号 “.” 分开不同的字节,如:192.168.1.1。在因特网中,主机的标识便是它ip地址。

常见的获取服务器ip的方法是使用系统自带的ping命令。例,打开cmd输入:
ping www.baidu.com

输出:
ping_baidu

其中的 115.239.210.27 就是baidu服务器的地址了

服务端与客户端

服务端与客户端在计算机的世界里,凡是提供服务的一方我们称为服务端(Server),而接受服务的另一方我们称作客户端(Client)。

网站提供网页数据的服务,使用者接受网站所提供的数据服务,所以使用者在这里就是客户端,响应使用者要求的网站即称为服务端。

不过客户端及服务端的关系不见得一定建立在两台分开的机器上,同一台机器中也有这种主从关系的存在,提供服务的服务端及接受服务的客户端也有可能都在同一台机器上。

例如我们在提供网页的服务器上执行浏览器浏览本机所提供的网页,这样在同一台机器上就同时扮演服务端及客户端。

Socket

socket的英文原义是“孔”或“插座”。作为4BDS UNIX的进程通信机制,取后一种意思。通常也称作"套接字",用于描述IP地址和端口,是一个通信链的句柄。

Scoket程勋分为服务端与客户端,服务端程序监听端口,等待客户端程序的连接。客户端程序发起连接,等待服务端的相应。客户端想要连接上服务端的话,需要 知道服务端的ip地址。

例如,客户想要访问百度网站的首页,通过浏览器访问http://www.baidu.com。浏览器发出请求之后,先是DNS服务器将百度的域名解析成ip地址之后,访问到ip地址为115.239.210.27服务器的80端口(http协议默认在80端口),请求发送后,百度的服务器作为响应将页面的源代码发送至客户端(通过浏览器右键->源代码,或者ctrl+u可以看到服务器返回的页面源代码),随后浏览器将源代码渲染成页面。这样用户就看到了百度网站的首页。

头文件和库文件

在windows系统上,当前的Windows Socket接口是Windows Sockets 2,所有接口函数都是有Ws2_32.dll导出。在进行程序设计时,祥光的数据类型、结构定义、函数声明等卫浴头文件winsock2.h中,编译时需要包含此文件,并且连接到库Ws2_32.lib。

常用函数

常见的socket函数

WSAStartup初始化Ws2_32.lib库 WSACleanup关闭Ws2_32.lib库 Socket创建socket closesocket关闭socket sockaddr和sockaddr_in结构体socket地址 bind绑定 listen监听 accept接收 connect连接 send发送 recv获取返回

WSAStartup () 函数

WSAStartup函数的功能使加载Ws2_32.dll等Socket程序运行的环境。在程序初始化后,Socket程序运行所依赖的动态链接库不一定已经在家,WSAStartup保证了Socket 动态链接库的加载。

1
2
3
4
int WSAStartup(
__in WORD wVersionRequested,
__out LPWSADATA lpWSAData
);

参数一wVersionRequested
Socket程序库的版本,一般使用MAKEWORD(2,2)宏生成。

参数二lpWSAData
输出参数,指向 WSADATA 结构的指针,用于返回Socket库初始化的信息。

返回值 用来判断程序是否成功加载

WSACleanup () 函数

与 WSAStartup 相反, WSACleanup 释放Ws2_32.dll,函数无参数。

Socket () 函数

Socket函数的功能是建立一个绑定到制定协议和传输类型的Socket。

1
2
3
4
5
SOCKET WSAAPI socket(
__in int af,
__in int type,
__in int protocol
);

参数一af
address family的缩写,制定该Socket使用哪一种类型的网络地址。可以是IPv4(AF_INET),也可以是IPv6(AF_INET6)、蓝牙(AF_BTM)、NetBios(AF_NETBIOS)等。

参数二type
设置传输类型,常见类型有SOCK_STREAMSOCK_DGRAMSOCK_RAMSOCK_SEQPACKET等。SOCK_STREAM类型是基于连接的(TCP),所收的数据时数据流形式的,传输层的数据包已经经过处理。SOCK_DGRAM是基于报文(UDP)。如果制定为SOCK_RAM,那么可以建立原始套接字,所收到的数据是以数据包(包括包头)的形式存在的。

参数三protocol
设置传输协议,常用的协议为IPPROTO_TCPIPPROTO_UDP

colsesocket() 函数

与 socket 函数对应,用于关闭 socket

1
2
3
int WSAAPI closesocket(
__in SOCKET s
);

参数一s
指定要关闭的socket

sockaddrsockaddr_in结构体

sockaddrSOCKADDRsockaddr_inSOCKADDR_IN这 几个结构用于表示地址很端口。在IPv4下,这几个结构体是可以通用的。

1
2
3
4
5
6
7
8
9
10
typedef struct sockaddr {
#if (_WIN32_WINNT < 0x0600)
u_short sa_family;
#else
ADDRESS_FAMILY sa_family; // Address family.
#endif //(_WIN32_WINNT < 0x0600)
CHAR sa_data[14]; // Up to 14 bytes of direct address.
} SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR;
1
2
3
4
5
6
7
8
9
10
11
12
typedef struct sockaddr_in {
#if(_WIN32_WINNT < 0x0600)
short sin_family;
#else //(_WIN32_WINNT < 0x0600)
ADDRESS_FAMILY sin_family;
#endif //(_WIN32_WINNT < 0x0600)
USHORT sin_port;
IN_ADDR sin_addr;
CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;

bind() 函数

服务器端,将Socket与网络地址和端口绑定起来,函数原型如下:

1
2
3
4
5
int WSAAPI bind(
__in SOCKET s,
__in_bcount(namelen) const struct sockaddr FAR * name,
__in int namelen
);

参数一s
要绑定的socket

参数二name
要绑定的网络地址结构体

参数三namelen
name参数所指向的结构体的大小

listen() 函数

设置 socket 的状态为监听,使客户端程序可以进行连接,函数原型:

1
2
3
4
int WSAAPI listen(
__in SOCKET s,
__in int backlog
);

参数一s
绑定的socket

参数二backlog
指定最大的连接数

accept() 函数

接受客户端的连接

1
2
3
4
5
SOCKET WSAAPI accept(
__in SOCKET s,
__out_bcount_opt(*addrlen) struct sockaddr FAR * addr,
__inout_opt int FAR * addrlen
);

参数一s
正在监听的socket,该 socket 使用 listen() 设置。

参数二addr (可选)
指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。

参数三addrlen (可选)
指针,指向存有addr地址长度的整形数。

返回:
成功:非负描述字
失败:-1

accept 默认会阻塞进程,直到有客户端建立连接后返回,它返回的是连接用的 socket 。如果 accept 成功返回,则服务器与客户已经正确建立连接了,此时服务器通过 accept 返回的 socket 来完成与客户的通信。

connect() 函数

connect函数的功能使与服务器建立连接。这个函数只能由客户端程序调用。

1
2
3
4
5
int WSAAPI connect(
__in SOCKET s,
__in_bcount(namelen) const struct sockaddr FAR * name,
__in int namelen
);

参数一s
socket函数建立的套接字。

参数二name
指向sockaddr结构体指针,包括了所要连接的服务端的地址和端口等。

参数三namelen
sockaddr结构的长度

send() 函数

向连接的另一端发送数据,不论是客户还是服务器应用程序都用send函数来向 TCP 连接的另一端发送数据。

1
2
3
4
5
6
int WSAAPI send(
__in SOCKET s,
__in_bcount(len) const char FAR * buf,
__in int len,
__in int flags
);

参数一s
指定发送端socket描述符。

参数二*buf
指明存放要发送的数据的缓冲区。

参数三len
指明实际要发送的数据的字节数。

参数四flags
指明发送的方法,常见宏MSG_DONTROUTEMSG_OOB等,一般置0。

将*buf指向的字符串发送至客户端

recv() 函数

不论是客户还是服务器应用程序都用 recv 函数 从 TCP 连接的另一端接收数据。

1
2
3
4
5
6
int WSAAPI recv(
__in SOCKET s,
__out_bcount_part(len, return ) __out_data_source(NETWORK) char FAR * buf,
__in int len,
__in int flags
);

参数一s
指定接收端socket描述符;

参数二*buf
指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

参数三len
指明buf的长度;

参数四flags
指明接收的方法,常见宏MSG_DONTROUTEMSG_OOB等,一般置0。
获取到客户端返回的字符串并将其写入到buf中

简单的 socket 通信示例

服务端(Server)

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main()
{
int err; // 错误信息
int len;
char sendBuf[100]; // 发送至客户端的字符串
char recvBuf[100]; // 接受客户端返回的字符串
SOCKET sockServer; // 服务端 Socket
SOCKADDR_IN addrServer; // 服务端地址
SOCKET sockClient; // 客户端 Scoket
SOCKADDR_IN addrClient; // 客户端地址
WSADATA wsaData; // winsock 结构体
WORD wVersionRequested; // winsock 的版本
// 配置 Windows Socket版本
wVersionRequested = MAKEWORD( 2, 2 );
// 初始化 Windows Socket
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
// 启动错误,程序结束
return ;
}
/*
* 确认WinSock DLL支持2.2
* 请注意如果DLL支持的版本大于2.2至2.2
* 它仍然会返回wVersion2.2的版本
*/
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
{
// 启动错误,程序结束
WSACleanup(); // 终止Winsock 2 DLL (Ws2_32.dll) 的使用
return ;
}
// 定义服务器端socket
sockServer = socket(AF_INET, SOCK_STREAM, 0);
// 设置服务端 socket
addrServer.sin_addr.S_un.S_addr = htonl(INADDR_ANY); // 本机IP
addrServer.sin_family = AF_INET; // 协议类型是INET
addrServer.sin_port = htons(6000); // 绑定端口6000
// 将服务器端socket绑定在本地端口
bind(sockServer, (SOCKADDR *)&addrServer, sizeof (SOCKADDR));
// Listen 监听端口
listen(sockServer, 5); // 5 为等待连接数目
printf ( "服务器已启动:\n监听中...\n" );
len = sizeof (SOCKADDR);
// accept 会阻塞进程,直到有客户端连接上来为止
sockClient = accept(sockServer, (SOCKADDR *)&addrClient, &len);
// 当客户端连接上来时, 拼接如下字符串
sprintf (sendBuf, "欢迎 ip: %s 的用户连接, 这里是 Lellansin 的服务器\n" , inet_ntoa(addrClient.sin_addr));
// 向客户端发送字符串
send(sockClient, sendBuf, strlen (sendBuf) + 1, 0);
// 获取客户端返回的数据
recv(sockClient, recvBuf, 100, 0);
// 打印客户端返回的数据
printf ( "%s\n" , recvBuf);
// 关闭socket
closesocket(sockClient);
getchar ();
}

启动后防火墙有阻挡提示,这里接受就可以了。另外下图是原来博主测试时截的图,跟上面代码的输出部分稍稍有些区别。

tcp_server_test

客户端(Client)

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
53
54
55
56
57
58
59
60
#include <Winsock2.h>
#include <stdio.h>
#pragma comment(lib, "ws2_32.lib")
void main()
{
int err;
char *message;
char recvBuf[100];
SOCKET sockClient; // 客户端 Scoket
SOCKADDR_IN addrServer; // 服务端地址
WSADATA wsaData;
WORD wVersionRequested;
wVersionRequested = MAKEWORD( 2, 2 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 )
{
return ;
}
if ( LOBYTE( wsaData.wVersion ) != 2 || HIBYTE( wsaData.wVersion ) != 2 )
{
// 启动错误,程序结束
WSACleanup( );
return ;
}
// 新建客户端 scoket
sockClient = socket(AF_INET, SOCK_STREAM, 0);
// 定义要连接的服务端地址
addrServer.sin_addr.S_un.S_addr = inet_addr( "127.0.0.1" ); // 目标IP (127.0.0.1是本机地址)
addrServer.sin_family = AF_INET; // 协议类型是INET
addrServer.sin_port = htons(6000); // 连接端口1234
// 让 sockClient 连接到 服务端
connect(sockClient, (SOCKADDR *)&addrServer, sizeof (SOCKADDR));
// 从服务端获取数据
recv(sockClient, recvBuf, 100, 0);
// 打印数据
printf ( "%s\n" , recvBuf);
message = "服务端你好~" ;
// 发送数据到服务端
send(sockClient, message, strlen (message) + 1, 0);
// 关闭socket
closesocket(sockClient);
WSACleanup();
getchar (); // 暂停
}

tcp_client_test

客户端与服务端更多通信,例子:《木马,你好!(二)最简单的木马》

简单的端口扫描程序

hostent结构体

host entry的缩写,该结构记录主机的信息,包括主机名、别名、地址类型、地址长度和地址列表。之所以主机的地址是一个列表的形式,原因是当一个主机有多个网络接口时会有多个地址。

1
2
3
4
5
6
7
8
struct hostent {
char FAR * h_name; /* 主机正式名称 */
char FAR * FAR * h_aliases; /* 主机别名 */
short h_addrtype; /* 地址类型 */
short h_length; /* 地址长度 */
char FAR * FAR * h_addr_list; /* 主机网络地址指针 */
#define h_addr h_addr_list[0] /* 指向第一个地址 */
};

代码

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
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
#include <winsock2.h>
#include <stdio.h>
#include <stdlib.h>
#pragma comment(lib,"ws2_32.lib")
void Help( void );
int main( int argc, char *argv[])
{
WORD wVersion = MAKEWORD(2, 0);
WSADATA wsaData;
//sockaddr_in结构体
struct sockaddr_in sin ;
int iFromPort;
int iToPort;
int iNowPort;
char *cHost;
HOSTENT *host_entry;
char *host_name;
char host_address[256];
SOCKET s;
int iOpenPort = 0;
int port[256], i;
if (argc != 4)
{
Help();
return -1;
}
iFromPort = atoi (argv[2]);
iToPort = atoi (argv[3]);
host_name = argv[1];
if (iFromPort > iToPort || iFromPort < 0 || iFromPort > 65535 || iToPort < 0 || iToPort > 65535)
{
printf ( "起始端口不能大于结束端口,且范围为:1--65535 " );
return 0;
}
if ( WSAStartup(wVersion , &wsaData) )
{
printf ( "初始化失败!" );
return -1;
}
// 根据用户输入的域名来获取服务器主机信息
host_entry = gethostbyname(host_name);
if (host_entry != 0)
{
wsprintf( host_address, "%d.%d.%d.%d" ,
(host_entry->h_addr_list[0][0] & 0x00ff),
(host_entry->h_addr_list[0][1] & 0x00ff),
(host_entry->h_addr_list[0][2] & 0x00ff),
(host_entry->h_addr_list[0][3] & 0x00ff)
);
printf ( "\n主机名称:%s 主机地址:%s \n" , host_name, host_address);
}
cHost = host_address;
printf ( "======= 开始扫描 ======= \n" );
for (iNowPort = iFromPort; iNowPort <= iToPort; iNowPort++)
{
s = socket(AF_INET, SOCK_STREAM, 0);
if (s == INVALID_SOCKET)
{
printf ( "create socket() failed!" );
WSACleanup();
}
sin .sin_family = AF_INET;
sin .sin_port = htons(iNowPort);
sin .sin_addr.S_un.S_addr = inet_addr(cHost);
if (connect(s, ( struct sockaddr *)& sin , sizeof ( sin )) == SOCKET_ERROR)
{
printf ( "%s -> %d:未开放 \n" , cHost, iNowPort);
closesocket(s);
}
else
{
printf ( "%s -> %d:开放 \n" , cHost, iNowPort);
port[iOpenPort] = iNowPort;
iOpenPort ++;
closesocket(s);
}
}
printf ( "======= 扫描结果 ======= \n" );
printf ( "主机:%s 扫描到%d个端口,分别是: \n" , host_name, iOpenPort);
for ( i = 0; i < iOpenPort; i++)
{
printf ( "%d " , port[i]);
}
//关闭socket
closesocket(s);
WSACleanup();
return 0;
}
//帮助函数
void Help()
{
printf ( "Usage: \n" );
printf ( "Port.exe <TargetIP> <BeginPort> <EndPort> \n" );
printf ( "Example: \n" );
printf ( "scan.exe 127.0.0.1 100 200 " );
}

函数列表

一、基本Socket函数

accept()响应连接请求,返回一个连接socket,原来的socket返回监听状态 bind()把一个socket绑定在某个端口上 CloseSocket()关闭套接字 Connect()连接 GetPeerName()得到连接在指定套接口上的对等通信方的名字 GetSockName()得到指定套接口上当前的名字 GetSockopt()得到与制定套接口相关的属性选项 htonl()把32位的数字从主机字节顺序转换到网络字节顺序 htons()把16位的数字从主机字节顺序转换到网络字节顺序 inet_addr()把一个Internet标准的点分十进制地址转换成Internet地址数值 inet_ntoa()把Internet地址转换成点分十进制的字符串 ioctlsocket()为套接字提供控制 listen()监听套接字上连接请求的到来 ntohl()把32位的数字从网咯字节顺序转换为主机字节顺序 ntons()把16位的数字从网咯字节顺序转换为主机字节顺序 recv()从已经连接的套接字接受数据 recvfrom()从已连接的或没有连接的套接口接受数据 select()执行同步IO多路复用 send()从已连接的套几口发送数据 sendto()从已连接的或没有连接的套接口发送数据 setsockopt()设置与指定套接口相关的属性选项 shutdown()关闭一部分全双工的链接 socket()创建并返回一个socket

二、获取信息

Gethostbyaddr()根据网络地址得到对应的名字(会有多个)和地址 Gethostbyname()根据主机名得到对应的名字(会有多个)和地址 gethostname()得到本机主机名 getservbyname()根据服务的名字得到对应的服务名和端口号 getservbyport()根据端口号得到对应的服务名和端口号 getprotobyname()根据协议名得到对应的协议名和数值 getprotobynumber()根据端口号得到对应的协议名和数值

三、DLL操作

WSAStartup初始化底层的windows Sockets DLL WSACleanup从底层的Sockets DLL中撤销注册 WSAAsyncSelectSelect函数的异步版本 WSAIsBlocking确定底层的winsock DLL是否在该线程已经被一个调用阻塞 WSACancelBlockingCall取消未完成的阻塞的API调用 WSASetBlockingHook为底层的windows sockets实现设置阻塞钩子 WSASetLastError设置下一次的WSAGetLastError返回的错误信息 WSAGetLastError得到最近的一个windows sockets API调用错误的详细信息 WSAUnhookBlockingHook恢复原始的阻塞钩子 WSACancelAsyncRequest取消一个未完成的 WSAAsyncGetXByY 函数的实例

文章索引

  • 1
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值