33、Windows API 网络通信

简介:     Socket就是系统提供的一种使用传输层的网络协议进行数据传输的程序设计接口规范,经过Socket封装的应用程序不再需要处理与TCP协议相关的内容。诸如连接时的三次握手、分包、包头解析、重传、滑动窗口等行为应用程序都不需关注,而只需要像使用系统中的其他I/O接口一样进行输入和输出操作即可。

    Socket就是系统提供的一种使用传输层的网络协议进行数据传输的程序设计接口规范,经过Socket封装的应用程序不再需要处理与TCP协议相关的内容。诸如连接时的三次握手、分包、包头解析、重传、滑动窗口等行为应用程序都不需关注,而只需要像使用系统中的其他I/O接口一样进行输入和输出操作即可。

IP HelperWindows系统与IP协议相关的配置和管理的重要接口,通过IP Helper可以实现一些在网络通信软件中常用的功能,比如获取本机IP网关,配置、获得统计信息、网卡数量和连接状态等。

Windows除了为网络通信程序设计提供Socket这样的传输层接口外,还提供了很多应用层协议接口,包括HttpSNMP等。此外还有DNSDHCP等常用网络协议。

1Socket通信

Socket套接字是使用传输程序协议(TCPUDP等)进行网络通信的程序设计接口。Socket程序分为服务端与客户端,服务端程序监听端口,等待客户端程序的连接;客户端程序发起连接,等待服务端的响应。客户端程序需要知道服务端程序的IP地址和监听端口。服务端与客户端在建立连接后,双方都可以发送和接收数据。

所有接口函数都由ws2_32.dll导出,相关数据类型、结构定义、函数声明等位于头文件winsock2.h中。

1)客户端

客户端的功能是连接到服务端。Socket的连接是由客户端发起的;客户端在连接服务端的指定端口时如果指定端口开放,而且服务端响应了客户端的连接请求,连接就会建立。客户端在连接前需知道服务端程序所监听的端口和服务端程序所在主机的IP地址。在连接建立后,需要得到Socket,然后就可以向服务端发送数据,或者从服务端接收数据。

1)客户端程序过程

一个Socket客户端程序的典型过程:

(1)客户端程序在运行后,首先需要使调用WSAStartup函数,确保进程加载socket应用程序所必须的环境和库文件,如Ws2_32.dll

(2)调用函数Socket创建SOCKET,在创建时需指定使用的网络协议、连接类型等。

(3)填充SOCKADDR结构,指定服务端的地址、端口等。

(4)调用connect函数连接到服务端。

(5)如果连接成功,就可以使用sendrecv函数发送和接收数据。

(6)在数据传输完成后,可调用closesocket函数关闭Socket

(7)调用WSACleanup函数释放资源。[2]

系统在实现TCP协议时都为数据的接收保留了缓存。协议收到数据包后,解包并将数据放入缓存中,直到recv函数将数据接收。如果recv函数长时间不接收数据,在协议的缓存存满之后,对方的send函数就不能再发送数据。

当调用recv函数后,如果协议栈缓存中的数据还没有recv接收缓存的大,那么缓存中的数据有多少recv函数接收多少,然后返回。如果当前协议缓存中没有数据,那么一旦有数据到达就接收到达的数据,然后返回。如果协议缓存中的数据大于recv接收缓存,那么recv将接收缓存填满之后返回。

2)服务端

服务端的功能在指定的端口上监听,等待客户端的连接。在连接建立后可使用sendrecv函数发送、接收数据。

一般情况下,Socket程序服务端过程如下:

(1)程序在运行后,首先需要使调用WSAStartup加载Ws2_32.dll

(2)调用函数socket创建用于监听的SOCKET,在创建时需指定使用的网络协议、连接类型等。

(3)调用bind函数将Socket绑定到网络地址和端口。

(4)调用listen函数开始监听。

(5)调用accept函数等待客户端连接。在客户端连接后,accept函数返回,得到连接Socket。在accept函数返回后,可立即再调用,以处理其他客户端的连接。

(6)得到连接Socket后,可调用sendrecv发送、接收数据。

(7)在数据传输完成后,可调用closesocket函数关闭Socket

(8)调用WSACleanup函数释放DLL

listen函数的功能是将Socket的状态设置为监听,以使客户端程序可以进行连接。accept函数的功能是接收客户端的连接,accept函数直到客户端有连接后才会返回。

3)相关API

Socket函数[3]

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

示例Socket通信

img_1c53668bcee393edac0d7b3b3daff1ae.gifimg_405b18b4b6584ae338e0f6ecaf736533.gifSocket通信
 
   
* client.c
**************************************/
/* 头文件 */
#include
< stdio.h >
#include
" winsock2.h "
/* 常量 */
#define RECV_BUFFER_SIZE 8192
#pragma comment(lib,"ws2_32.lib")
/* ************************************
* main
* 功能 socket通信客户端
*************************************
*/
void main( int argc, char * argv[])
{
// 变量定义
SOCKADDR_IN clientService; // 地址
SOCKET ConnectSocket; // socket
WSADATA wsaData; //
LPVOID recvbuf; // 接收缓存
int bytesSent;
int bytesRecv = 0 ;
char sendbuf[ 32 ] = " get information " ; // 默认发送的数据

// 初始化socket库, 保存ws2_32.dll已经加载
int iResult = WSAStartup(MAKEWORD( 2 , 2 ), & wsaData);
if (iResult != NO_ERROR)
printf(
" Error at WSAStartup()\n " );

// 创建socket
ConnectSocket = socket(AF_INET, // IPv4
SOCK_STREAM, // 顺序的、可靠的、基于连接的、双向的数据流通信
IPPROTO_TCP // 使用TCP协议
);
if (ConnectSocket == INVALID_SOCKET)
{
printf(
" Error at socket(): %ld\n " , WSAGetLastError());
WSACleanup();
return ;
}

// 设置服务端的通信协议、IP地址、端口
clientService.sin_family = AF_INET;
clientService.sin_addr.s_addr
= inet_addr( " 127.0.0.1 " );
clientService.sin_port
= htons( 10000 );

// 连接到服务端
if ( connect(
ConnectSocket,
// socket
(SOCKADDR * ) & clientService, // 地址
sizeof (clientService) // 地址的大小
) == SOCKET_ERROR)
{
printf(
" Failed to connect(%d)\n " ,WSAGetLastError() );
WSACleanup();
return ;
}
// 准备发送数据
// 如果输入参数是-d,那么发送的数据是“download file”否则是"get information"
if (argc == 2 && ( ! lstrcmp(argv[ 1 ], " -d " )))
{
lstrcpyn(sendbuf,
" download file " , 32 );
}
// 向服务端发送数据
bytesSent = send( ConnectSocket, // socket
sendbuf, // 发送的数据
lstrlen(sendbuf) + 1 , // 数据长度
0 ); // 无标志

if (bytesSent == SOCKET_ERROR)
{
printf(
" send error (%d)\n " , WSAGetLastError());
closesocket(ConnectSocket);
return ;
}
printf(
" Bytes Sent: %ld\n " , bytesSent );

// 准备接收数据
recvbuf = HeapAlloc(GetProcessHeap(), 0 , RECV_BUFFER_SIZE);
// 循环接收
while ( bytesRecv != SOCKET_ERROR )
{
// Sleep(50);
bytesRecv = recv( ConnectSocket, // socket
( char * )recvbuf, // 接收数据缓存
RECV_BUFFER_SIZE, // 缓存大小
0 ); // 无标志
if ( bytesRecv == 0 )
{
printf(
" Connection Closed.\n " );
break ;
}
// TODO,处理接收的数据,这里只简单的将收到的数据大小显示
printf( " Bytes Recv: %ld\n " , bytesRecv );
}
HeapFree(GetProcessHeap(),
0 , recvbuf);
WSACleanup();
return ;
}


* server.c
**************************************/
/* 头文件 */
#include
< winsock2.h >
#include
< ws2tcpip.h >
#include
< stdio.h >
/* 常量 */
#define DEFAULT_PORT "10000" // 端口
#define MAX_REQUEST 1024 // 接收数据的缓存大小
#define BUF_SIZE 4096 // 发送数据的缓存大小

#pragma comment(lib,"ws2_32.lib")
/* ************************************
* CommunicationThread
* 功能 用于接收和发送数据的线程
* 为每一个连接的客户端创建一个接收发送数据的线程,
* 可以使用多个客户端同时连接到服务端
* 参数 lpParameter,SOKCET
*************************************
*/
DWORD WINAPI CommunicationThread(
LPVOID lpParameter
)
{
DWORD dwTid
= GetCurrentThreadId();
// 获得参数sokcet
SOCKET socket = (SOCKET)lpParameter;
// 为接收数据分配空间
LPSTR szRequest = (LPSTR)HeapAlloc(GetProcessHeap(), 0 , MAX_REQUEST);
int iResult;
int bytesSent; // 用于保存send的返回值,实际发送的数据的大小

// 接收数据
iResult = recv(socket, // socket
szRequest, // 接收缓存
MAX_REQUEST, // 缓存大小
0 ); // 标志
if (iResult == 0 ) // 接收数据失败,连接已经关闭
{
printf(
" Connection closing...\n " );
HeapFree(GetProcessHeap(),
0 ,szRequest);
closesocket(socket);
return 1 ;
}
else if (iResult == SOCKET_ERROR) // 接收数据失败,socket错误
{
printf(
" recv failed: %d\n " , WSAGetLastError());
HeapFree(GetProcessHeap(),
0 ,szRequest);
closesocket(socket);
return 1 ;
}
else if (iResult > 0 ) // 接收数据成功
{
// 显示接收到的数据
printf( " \tCommunicationThread(%d)\tBytes received: %d\n " , dwTid, iResult);
printf(
" \tCommunicationThread(%d)\trequest string is (%s)\n " ,dwTid, szRequest);

// 如果接收到的数据是"download file"
if (lstrcmpi(szRequest, " download file " ) == 0 )
{
// 读取文件download.txt将发送
HANDLE hFile;
LPVOID lpReadBuf;
// 发送缓存
DWORD dwBytesRead;
DWORD dwFileSize;
DWORD dwSendFile
= 0 ;
hFile
= CreateFile( " download.txt " ,
GENERIC_READ,
FILE_SHARE_READ,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);

if (hFile == INVALID_HANDLE_VALUE)
{
printf(
" \tCommunicationThread\tCould not open file (error %d)\n " ,
GetLastError());
send(socket,
" error " , 6 , 0 );
closesocket(socket);
return 1 ;
}
// 分配发送数据缓存
lpReadBuf = HeapAlloc(GetProcessHeap(), 0 , BUF_SIZE);
// 获取文件大小
dwFileSize = GetFileSize(hFile, NULL);
// 循环发送
while ( 1 )
{
// 读文件到缓存
if ( ! ReadFile(hFile, lpReadBuf, BUF_SIZE, & dwBytesRead, NULL))
{
printf(
" \tCommunicationThread\tCould not read from file (error %d)\n " ,
GetLastError());
closesocket(socket);
CloseHandle(hFile);
return 1 ;
}
// 发送读取的文件数据
bytesSent = send(socket, ( const char * )lpReadBuf, dwBytesRead, 0 );
if ( bytesSent == SOCKET_ERROR)
{
printf(
" \tCommunicationThread\tsend error %d\n " ,
WSAGetLastError());
closesocket(socket);
CloseHandle(hFile);
return 1 ;
}
// 显示发送数据的大小
printf( " \tCommunicationThread(%d)\tsend %d bytes\n " , dwTid, bytesSent);
// 累加,已经发送的大小
dwSendFile += dwBytesRead;
// 如果所有文件数据都已经发送
if (dwSendFile == dwFileSize)
{
printf(
" \tCommunicationThread\tFile download ok\n " );
break ; // 退出循环
}
}
// 释放内存、关闭连接,关闭文件
HeapFree(GetProcessHeap(), 0 , lpReadBuf);
CloseHandle(hFile);
closesocket(socket);
}
// 如果接收到的数据是"get information"
else if (lstrcmpi(szRequest, " get information " ) == 0 )
{
// 发送数据
bytesSent = send(socket, // socket
" this is information " , // 数据
lstrlen( " this is information " ) + 1 , // 数据长度
0 ); // 标志
// 判断是否成功
if ( bytesSent == SOCKET_ERROR)
{
printf(
" \tCommunicationThread\tsend error %d\n " ,
WSAGetLastError());
closesocket(socket);
return 1 ;
}
printf(
" \tCommunicationThread(%d)\tsend %d bytes\n " ,dwTid, bytesSent);
}
else // 收到未知数据
{
printf (
" unreferenced request\n " );
}
}
// 释放接收数据缓存,关闭socket
HeapFree(GetProcessHeap(), 0 ,szRequest);
closesocket(socket);
return 0 ;
}

/* ************************************
* int __cdecl main(void)
* 功能 socket服务端
*************************************
*/
int __cdecl main( void )
{
WSADATA wsaData;
SOCKET ListenSocket
= INVALID_SOCKET; // 监听socket
SOCKET ClientSocket = INVALID_SOCKET; // 连接socket
struct addrinfo * result = NULL,
hints;
int iResult; // 保存返回结果

// 初始化Winsock,保证Ws2_32.dll已经加载
iResult = WSAStartup(MAKEWORD( 2 , 2 ), & wsaData);
if (iResult != 0 )
{
printf(
" WSAStartup failed: %d\n " , iResult);
return 1 ;
}
// 地址
ZeroMemory( & hints, sizeof (hints));
hints.ai_family
= AF_INET;
hints.ai_socktype
= SOCK_STREAM;
hints.ai_protocol
= IPPROTO_TCP;
hints.ai_flags
= AI_PASSIVE;

// 获取主机地址,保证网络协议可用等
iResult = getaddrinfo(NULL, // 本机
DEFAULT_PORT, // 端口
& hints, // 使用的网络协议,连接类型等
& result); // 结果
if ( iResult != 0 )
{
printf(
" getaddrinfo failed: %d\n " , iResult);
WSACleanup();
return 1 ;
}

// 创建socket,用于监听
ListenSocket = socket(
result
-> ai_family, // 网络协议,AF_INET,IPv4
result -> ai_socktype, // 类型,SOCK_STREAM
result -> ai_protocol); // 通信协议,TCP
if (ListenSocket == INVALID_SOCKET)
{
printf(
" socket failed: %ld\n " , WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1 ;
}
// 绑定到端口
iResult = bind( ListenSocket, result -> ai_addr, ( int )result -> ai_addrlen);
if (iResult == SOCKET_ERROR)
{
printf(
" bind failed: %d\n " , WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1 ;
}
printf(
" bind\n " );

freeaddrinfo(result);
// reuslt不再使用

// 开始监听
iResult = listen(ListenSocket, SOMAXCONN);
printf(
" start listen......\n " );
if (iResult == SOCKET_ERROR)
{
printf(
" listen failed: %d\n " , WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1 ;
}
while ( 1 )
{
// 接收客户端的连接,accept函数会等待,直到连接建立
printf( " ready to accept\n " );
ClientSocket
= accept(ListenSocket, NULL, NULL);
// accept函数返回,说明已经有客户端连接
// 返回连接socket
printf( " accept a connetion\n " );
if (ClientSocket == INVALID_SOCKET)
{
printf(
" accept failed: %d\n " , WSAGetLastError());
closesocket(ListenSocket);
break ; // 等待连接错误,退出循环
}
// 为每一个连接创建一个数据发送的接收线程,
// 使服务端又可以立即接收其他客户端的连接
if ( ! CreateThread(
NULL,
0 ,
CommunicationThread,
// 线程函数
(LPVOID)ClientSocket, // 将socket作为参数
0 ,
NULL))
{
printf(
" Create Thread error (%d) " , GetLastError());
break ;
}
}
// 循环退出,释放DLL。
WSACleanup();
return 0 ;
}

4)处理并发的客户端连接

    服务端在监听时需指定一个端口,这个端口可以接收多个客户端的连接。客户端在连接服务端时,本机的端口不需要指定,系统会自动选取一个当前不用的端口与服务端的固定端口连接。

    在同一时刻可以有多个不同主机上的客户端连接到服务端,一个主机上也可以同时有多个客户端连接到同一个服务端,但是在建立连接时,socket函数会为客户端分配不同的端口。客户端不会使用同一端口和同一服务端建立多次连接。

    服务端程序使用accept函数接收服务端的连接。因此如果服务端需要有与多个客户端连接时,在accept函数返回,一个连接已经建立后需要立即再调用accept函数,等待其他客户端的连接。如果客户端连接时,而服务端程序此时如果没有调用accept函数,那么连接不会成功建立。

    因此处理并发客户端连接的服务端必定是多线程的。监听程序最好使用单独的线程,而且为了对每个客户端的请求都能立即响应,每个与客户端的连接最好建立至少有一个线程处理数据的发送和接收。

进行网络数据的发送和接收不仅只有sendrecv函数可以使用,还可以使用senttorecvformWSARecvWSARecvEx等来进行异步连接。

2IP Helper[1,P482]

参考

[1] 精通Windows API 函数、接口、编程实例

[2] http://msdn.microsoft.com/en-us/library/ms741412%28VS.85%29.aspx

[3] http://msdn.microsoft.com/en-us/library/ms740506%28VS.85%29.aspx

目录
相关文章
|
1月前
|
弹性计算 负载均衡 网络协议
这种情况可能是由于阿里云的API服务出现了短暂的故障或者网络波动导致的
【2月更文挑战第20天】这种情况可能是由于阿里云的API服务出现了短暂的故障或者网络波动导致的
61 1
|
2月前
|
分布式计算 API Linux
通义千问API:找出两篇文章的不同
本章我们将介绍如何利用大模型开发一个文档比对小工具,我们将用这个工具来给互联网上两篇内容相近但版本不同的文档找找茬,并且我们提供了一种批处理文档比对的方案
|
3月前
|
缓存 网络协议 数据安全/隐私保护
[运维笔记] - (命令).Windows server常用网络相关命令总结
[运维笔记] - (命令).Windows server常用网络相关命令总结
185 0
|
3月前
|
消息中间件 网络协议 Unix
Posix API 与 网络协议栈 详细介绍
Posix API 与 网络协议栈 详细介绍
59 0
|
3月前
|
网络协议 API
2.2.3 Posix API与网络协议栈
2.2.3 Posix API与网络协议栈
|
3月前
|
缓存 网络协议 算法
Posix API与网络协议栈
Posix API与网络协议栈
|
3月前
|
API Python Windows
python3应用windows api对后台程序窗口及桌面截图并保存的方法
python3应用windows api对后台程序窗口及桌面截图并保存的方法
89 1
|
1月前
|
Linux iOS开发 MacOS
|
1月前
|
缓存 网络协议 Unix
Windows 命令提示符(CMD)操作(四):网络通信
Windows 命令提示符(CMD)操作(四):网络通信
55 0
|
2月前
|
存储 网络协议 安全
POSIX API与网络协议栈
POSIX API与网络协议栈
36 0