C语言Windows网络编程


〇、网络编程错误码

错误码
错误码错误原因
10045参考的对象种类不支持尝试的操作
10046协议家族尚未配置到系统中或没有它的存在迹象
10047使用了与请求的协议不兼容的地址
10048通常每个套接字地址 (协议/网络地址/端口)只允许使用一次
10049在其上下文中,该请求的地址无效
10050套接字操作遇到了一个已死的网络
10051向一个无法连接的网络尝试了一个套接字操作
10052当该操作在进行中,由于保持活动的操作检测到一个故障,该连接中断
10053您的主机中的软件放弃了一个已建立的连接
10054远程主机强迫关闭了一个现有的连接
10055由于系统缓冲区空间不足或列队已满,不能执行套接字上的操作
10056在一个已经连接的套接字上做了一个连接请求
10057由于套接字没有连接并且 (当使用一个 sendto 调用发送数据报套接字时) 没有提供地址,发送或接收数据的请求没有被接受
10058由于以前的关闭调用,套接字在那个方向已经关闭,发送或接收数据的请求没有被接受
10059对某个内核对象的引用过多
10060由于连接方在一段时间后没有正确的答复或连接的主机没有反应,连接尝试失败
10061不能做任何连接,因为目标机器积极地拒绝它
10062无法翻译名称
10063名称组件或名称太长
10064由于目标主机坏了,套接字操作失败
10065套接字操作尝试一个无法连接的主机
10066不能删除目录,除非它是空的
10067一个 Windows 套接字操作可能在可以同时使用的应用程序数目上有限制
10068超过限额
10069超过磁盘限额
10070文件句柄引用不再有效
10071项目在本地不可用
10091因为它使用提供网络服务的系统目前无效,WSAStartup 目前不能正常工作
10092不支持请求的 Windows 套接字版本
10093应用程序没有调用 WSAStartup,或者 WSAStartup 失败
10101由 WSARecv 或 WSARecvFrom 返回表示远程方面已经开始了关闭步骤
10102WSALookupServiceNext 不能返回更多的结果
10103在处理这个调用时,就开始调用 WSALookupServiceEnd该调用被删除
10104过程调用无效
10105请求的服务提供程序无效
10106没有加载或初始化请求的服务提供程序
10107从来不应失败的系统调用失败了
10108没有已知的此服务在指定的名称空间中找不这个服务
10109找不到指定的类别
10110WSALookupServiceNext 不能返回更多的结果
10111在处理这个调用时,就开始调用 WSALookupServiceEnd该调用被删除
10112由于被拒绝,数据查询失败
11001不知道这样的主机
11002这是在主机名解析时常出现的暂时错误,并且意味着本地服务器没有从权威服务器上收到响应
11003在数据寻找中出现一个不可恢复的错误
11004请求的名称有效并且是在数据库中找到,但是它没有相关的正确的数据
11005至少到达了一个保留
11006至少到达了一个路径
11007没有发送方
11008没有接受方
11009保留已经确认
11010错误是由于资源不足造成
11011由于管理原因被拒绝 无效凭据
11012未知或有冲突类型
11013某一部分的 filterspec 或 providerspecific 缓冲区有问题
11014flowspec 的某部分有问题
11015一般性 QOS 错误
11016在流程规格中发现一个无效的或不可识别的服务类型
11017在 QOS 结构中发现一个无效的或不一致的流程规格
11018无效的 QOS 提供程序特定缓冲区
11019使用了无效的 QOS 筛选器样式
11020使用了无效的 QOS 筛选器类型
11021FLOWDESCRIPTOR 中指定的 QOS FILTERSPEC 数量不正确

一、基本概念

1 计算机网络和TCPIP协议

五层结构:

2 网络通信模型

C/S:

B/S:

P2P:

3 网络数据传输

字节顺序:

  • 大端顺序:高字节存在内存低地址。

  • 小端顺序:低字节存在内存低地址。

  • 例如:对于0x12345678 大端: 地址0 [ 0x12 , 0x34 , 0x56 , 0x78 ] 地址31 小端: 地址0 [ 0x78 , 0x56 , 0x34 , 0x12 ] 地址31

  • 一般情况下:网络传输用大端模式,内存存储用小端模式。(例如基于x86平台的PC机)

对齐:C语言结构体存储会自动对齐。

编码:Base64是网络上最常见的用于传输8比特方式之一,可用于在HTTP环境下传递较长的标识信息。

4 编程概念

接口功能:

  • 基本功能:
    • 分配用于通信的本地资源。
    • 指定本地与远程通信端点。
    • (客户端)启动连接。
    • (服务器)等待连接到来。
    • 发送或接收数据。
    • 判断数据何时到来。
    • 从容终止连接。
  • 辅助功能:
    • 产生紧急数据。
    • 处理到来的紧急数据。
    • 处理来自远程端点的连接终止。
    • 异常终止通信。
    • 处理错误条件或连接异常终止。
    • 连接结束后释放本地资源。

UNIX中的基本I/O功能:

  • UNIX中的基本I/O功能
  • 内核通过文件描述符引用打开的文件,通常通过open函数或者create函数返回文件描述符。

5 套接字

协议:

  • PF_INET:IPv4协议簇
  • PF_INET6:IPv6协议簇​
  • PF_IPX:IPX/SPX协议簇
  • PF_NETBIOS:NetBIOS协议簇

套接字类型:

  • SOCK_STREAM:TCP协议
  • SOCK_DGRAM:UDP协议
  • SOCK_RAW:IP协议

端点地址:

  • AF_INET:IPv4地址族
  • AF_INET6:IPv6地址族
  • AF_IPX:IPX/SPX地址族
  • AF_NETBIOS:NetBIOS地址族
  • AF_UNSPEC 则意味着函数返回的是适用于指定主机名和服务名且适合任何协议族的地址。

通信过程:

  1. 建立一个Socket。
  2. 配置Socket。
  3. 连接Socket。(可选)
  4. 通过Socket发送数据。
  5. 通过Socket接收数据。
  6. 关闭Socket。

6 多线程编程

使用:

  1. 创建线程函数。
  2. 创建线程。
  3. 激活线程。
  4. 结束线程。

二、函数

1 头文件

// VS
#define _CRT_SECURE_NO_WARNINGS
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdlib.h>
//连接到WinSock2对应的文件:Ws2_32.lib, Mswsock.lib, Advapi32.lib
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")

2 网络数据函数

对齐:#pragma pack([show]|[push|pop][,identifier],n);

  • #pragma pack用法

  • #pragma pack()设置的是默认对齐模数。

  • show:可选参数,显示当前对齐的模板,以警告消息的形式显示。

  • push:对齐模数n压栈,未指定n则压入当前对齐模数。push一般带n。

  • pop:n出栈。不带n设置出栈数为对齐模数,设置n则会出栈并设置n为对齐模数。pop一般不带n。

  • identifier:编译器执行这条执行时会从栈顶向下顺序查找匹配的identifier,找到identifier相同的这个数之后将从栈顶到identifier,包括找到identifier全部pop弹出, 若没有找到则不进行任何操作。

  • n:对齐模数。

// 网络与主机类型转化
// 网络变主机
u_short WSAPPI ntohs(__in u_short netshort); // u_short
u_long WSAPPI ntohl(__in u_long netlong); // u_long

// 主机变网络
u_short WSAPPI htons( __in u_short hostshort); // u_short
u_long WSAPPI htonl( __in u_long hostlong); // u_long


// 数据校验实例
// 数据校验,传入数据包头和数据包头长
u_short in_cksum(u_short* pchBuffer,int iSize)
{
u_long ulCksum = 0;
while (iSize > 1)
{
ulCksum += *pchBuffer++;
iSize -= sizeof(u_short);
}
if (iSize)
ulCksum += *(UCHAR*)pchBuffer;
ulCksum = (ulCksum >> 16) + (ulCksum & 0xffff);
ulCksum += (ulCksum >> 16);
return (USHORT)(~ulCksum);
}

3 Windows套接字函数

3.1 WSAStartup()

Windows Sockets DLL的初始化。

// 定义:
int WSAStartup( __in WORD wVersionRequested , __out LPWSADATA lpWSAData );

// wVersionRequested[in]:一个WORD(双字节)型数值,在最高版本的Windows Sockets支持调用者使用,高阶字节指定小版本(修订本)号,低位字节指定主版本号。
// lpWSAData[out]:指向WSADATA数据结构的指针,用来接收Windows Sockets实现的细节。
// 成功返回0,失败返回错误码。

// 使用:
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2,2),&wsaData);
if (iResult != 0)
{
printf("WSAStartup failed with error: %d\n",iResult);
return -1;
}

3.2 WSACleanup()

Windows Sockets DLL的释放。

每一次WSAStartup()必须有一个对应的WSACleanup()。

// 定义
int WSACleanup(void);
// 成功返回0,失败返回错误码。

// 使用
int iResult;
iResult = WSACleanup();
if (iResult != 0)
{
printf("WSAStartup failed with error: %d\n",iResult);
return -1;
}

3.3 IPv4地址结构

使用时应该把 sockaddr_in强转为sockaddr传递。

// 地址结构
struct sockaddr
{
ushort sa_family ; // 地址族标识符
char sa_data[14] ; // 存储地址
}

// 常用的IPv4地址结构
typedef 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;
}IN_ADDR,*PIN_ADDR,FAR* LPIN_ADDR;

// 用于填写IP和端口
struct sockaddr_in
{
short sin_family; // 地址族
u_short sin_port; // 端口号
struct in_addr sin_addr; // IPv4地址(使用为 hints.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");)
char sin_zero[8]; // 保留
};

3.4 IPv6地址结构

使用时应该把 sockaddr_in6强转为sockaddr传递。

// 常用的IPv6地址结构
typedef struct in6_addr
{
union
{
u_char Byte[16];
u_short Word[8];
}u;
}IN6_ADDR,*PIN6_ADDR,FAR* LPIN6_ADDR;

// 用于填写IPv6和端口
struct sockaddr_in6
{
short sin6_family; // 地址族
u_short sin6_port; // 端口号
u_long sin6_flowinfo; // 流信息
struct in6_addr sin6_addr; // IPv6地址
char sin6_scope_id; // Scope标识
};

addrinfo

WinSock2增加的一个以链表形式保存地址信息的函数。

// getaddrinfo()获得地址
typedef struct addrinfo
{
int ai_flags; // getaddrinfo函数的调用选项
int ai_family; // 地址族
int ai_socktype; // 套接字类型
int ai_protocol; // 协议
size_t ai_addrlen; // ai_addr指向的sockaddr结构的缓冲区字节长度
char* ai_canonname; // 主机的正规名称
struct sockaddr* ai_addr; // 以sockaddr结构描述的地址信息
struct addrinfo* ai_next; // 指向下一个addrinfo结构
}ADDRINFOA,*PADDRINFOA;

// 获得地址
int getaddrinfo
(
const char* hostname, // 主机名(无填NULL)
const char* service, // 端口号
const struct addrinfo* hints, // 输入类型结构体
struct addrinfo** result // 返回获得地址的结构体
);

3.5 IPv4点分与无符号整形转化

// IP->u_long
unsigned long inet_addr( __in const char* cp);
// u_long->IP
char *FAR inet_ntoa( __in struct in_addr in);

3.6 套接字选项

套接字选项

套接口

// 获取套接字选项
int getsockopt
(
__in socket s, // 一个标识套接口的描述字
__in int level, // 选项定义的层次。支持的层次仅有SOL_SOCKET和IPPROTO_TCP。
__in int optname, // 需获取的套接口选项。
__out char* optval, // 指针,指向存放所获得选项值的缓冲区。
__inout int* optlen, // 指针,指向optval缓冲区的长度值。
);

// 设置套接字选项
int setsockopt
(
__in socket s, // 一个标识套接口的描述字
__in int level, // 选项定义的层次。支持的层次仅有SOL_SOCKET和IPPROTO_TCP。
__in int optname, // 需获取的套接口选项。
__in const char* optval, // 指针,指向存放所获得选项值的缓冲区。
__in int optlen, // 指向optval缓冲区的长度值。
);

// 例子
// 超时设置
int time = 1000; // 毫秒
int r = setsockopt(s,SOL_SOCKET,SO_RCVTIMEO,(const char*)&time,sizeof(time));
// 广播
unsigned long time = 1;
setsockopt(s,SOL_SOCKET,SO_BROADCAST,(const char*)&time,sizeof(time));

imgimg

3.7 I/O控制命令

int WSAAPI WSAIoctl())

// 控制套接字I/O模式
int ioctlsocket
(
__in int s, // 套接字句柄
__in long cmd, // 指示在套接字上要执行的命令
__in_out u_long * argp // 指向cmd的指针
);

// 例
// 设置网卡混杂模式
u_long optival = 1;
isoctlsocket(s,SIO_RCVALL,&optival);

img

3.8 补充

WSAGetLastError():获得上次失败操作的错误码。

shutdown()):禁止某些操作。

4 TCP函数

4.1 TCP通信过程

// 服务器进程
// Windows Sockets DLL初始化,协商版本号
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建套接字,指定使用TCP通信
socket();
// 指定本地地址和通信端口
getaddrinfo();bind();
// 等待客户连接请求
listen();
// 进行数据传输
accept();send();recv();
// 关闭套接字
shutdown();closesocket();
//结束对Windows Sockets DLL的使用,释放资源
WSACleanup();

//客户端进程
// Windows Sockets DLL初始化,协商版本号
WSAStartup(MAKEWORD(2, 2), &wsaData);
// 创建套接字,指定使用TCP通信
socket();
// 指定服务器地址和通信端口
getaddrinfo();
// 向服务器发送连接请求
connect();
// 进行数据传输
send();recv();
// 关闭套接字
shutdown();closesocket();
// 结束对Windows Sockets DLL的使用,释放资源
WSACleanup();

4.2 套接字

socket WSASocket()):创建套接字。

TCP参数:socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

// 创建套接字
socket WSAAPI socket
(
__in int af, // 确定套接字通信地址族(例:AF_INET)
__in int type, // 套接字类型(例:SOCK_STREAM)
__in int protocol, // 指定通信协议(例:IPPROTO_TCP)
) ;
// 非负值表示成功,-1表示失败,可以用WSAGetLastError()获得失败码。
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET)
{
printf("socket failed with error: %d\n", WSAGetLastError());
WSACleanup();
return 1;
}

// 关闭套接字
int closesocket
(
__in SOCKET s // 套接字
);
// 非负值表示成功,-1表示失败,可以用WSAGetLastError()获得失败码。
closesocket(ConnectSocket);

4.3 获取地址

getaddrinfo():获得主机地址。

gethostbyname():依据主机名获得IP地址。

// 将套接字与本地地址关联(服务器端)
int bind
(
__in SOCKET s, // 套接字
__in const struct sockaddr* name, // 地址参数,被声明为一个指向sockaddr结构的指针
__in int namelen // 地址结构大小
)
// 地址或端口填0将由操作系统自动选择
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR)
{
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
}

// 获得套接字关联的本地端点地址
int getsockname
(
__in SOCKET s, // 套接字
__in struct sockaddr* name, // 地址参数,被声明为一个指向sockaddr结构的指针
__in int *namelen // 地址结构大小
)
// 该函数用于对本地套接字进行bind()或connect()后获得这个套接字的地址信息。

// 获得套接字关联的本地端点地址
int getsockname
(
__in SOCKET s, // 套接字
__out struct sockaddr* name, // 地址参数,被声明为一个指向sockaddr结构的指针
__inout int* namelen // 地址结构大小
)
// 该函数用于获得已连接套接字的对等方端点地址,获得的端点地址保存在name中返回。

4.4 连接请求

socket_listen里面第二个参数backlog的用处

// 连接服务器
int connect
(
__in SOCKET s, // 套接字
__in const struct sockaddr* name, // 地址参数,被声明为一个指向sockaddr结构的指针
__in int namelen // 地址结构大小
);
iResult = connect(ConnectSocket,ptr->ai_addr,(int)ptr->ai_addrlen);
if(iResult == SOCKET_ERROR)
{
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}

// 监听等待TCP连接
int listen
(
__in SOCKET s, // 套接字
__in int backlog, // 等待握手队列长度
)
iResult = listen(ListenSocket, SOMAXCONN);
if(iResult == SOCKET_ERROR)
{
printf("listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}

// 用于从已完成连接的连接请求队列中返回一个已完成连接的客户请求
SOECKT accept
(
__in SOCEKT s, // 已绑定并设置为“监听”状态的套接字(该套接字不可以用来通信)
__out struct sockaddr* addr, // 被声明为一个指向sockaddr结构的指针(客户端IP,不需要输入NULL)
__inout int *addrlen // 地址结构长度
)
// 该函数会返回另一个用于数据传输的套接字,被称为已连接套接字,负责与本次连接的客户通信。
ClientSocket = accept(ListenSocket,NULL,NULL);
if(ClientSocket == INVALID_SOCKET)
{
printf("accept failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
}

4.5 传输数据

// 发送数据
int send
(
__in SOCKET s, // 已连接套接字
__in const char* buf, // 要发送的字节序列
__in int len, // 发送的字节数
__in int flags, // 改变套接字调用的默认行为方式。默认为0。MSG_DONTROUTE(不经过本地的路由机制),MSG_OOB(带外数据)
);
// 该函数返回实际发送了的字节总数
// 在流式套接字中服务器调用accept()/客户端调用connect()函数完成了地址绑定,所以不用地址
iResult = send(ConnectSocket,sendbuf,(int)strlen(sendbuf),0);
if(iResult == SOCKET_ERROR)
{
printf("send failed with error: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
}

// 接收数据
int recv
(
__in SOCKET s, // 已连接套接字
__out char* buf, // 接受缓冲区
__in int len, // 接受缓冲区的字节长度
__in int flags // 改变套接字调用的默认行为方式。默认为0。MSG_DONTROUTE(不经过本地的路由机制),MSG_OOB(带外数据)
)
// 该函数返回实际接收了的字节总数。
iResult = recv(ConnectSocket,recvbuf,recvbuflen,0);
if(iResult > 0)
printf("Bytes received: %d\n", iResult);
else if(iResult == 0)
printf("Connection closed\n");
else
printf("recv failed with error: %d\n",WSAGetLastError());

5 UDP函数

5.1 UDP通信过程

UDP连接模式:

  • connect() 连接,不同于TCP三次握手,仅仅是绑定IP和port,不会产生网络活动
  • connect() 重新绑定套接字可以更新IP和port
  • 再次调用connect() 时,若之前把套接字地址结构设置成AF_UNSPEC,后续的send()和recv()将出错

UDP参数:socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);

// 服务器进程
// Windows Sockets DLL初始化,协商版本号
WSAStartup(MAKEWORD(2, 2), &wsaData);
// (不同于TCP)创建套接字,指定使用UDP通信
socket();
// 指定本地地址和通信端口
getaddrinfo();bind();
// (不同于TCP)等待客户连接请求
// (不同于TCP)进行数据传输
recvfrom();sendto();
// 关闭套接字
shutdown();closesocket();
// 结束对Windows Sockets DLL的使用,释放资源
WSACleanup();

// 客户端进程
// Windows Sockets DLL初始化,协商版本号
WSAStartup(MAKEWORD(2, 2), &wsaData);
// (不同于TCP)创建套接字,指定使用UDP通信
socket();
// 指定服务器地址和通信端口
getaddrinfo();
// (不同于TCP)向服务器发送连接请求
// (不同于TCP)进行数据传输
recvfrom();sendto();
// 关闭套接字
shutdown();closesocket();
// 结束对Windows Sockets DLL的使用,释放资源
WSACleanup();

5.2 传输数据

// 发送数据
int sendto
(
__in SOCKET s, // 已连接套接字
__in const char* buf, // 要发送的字节序列
__in int len, // 发送的字节数
__in int flags, // 改变套接字调用的默认行为方式。默认为0。MSG_DONTROUTE(不经过本地的路由机制),MSG_OOB(带外数据)
__in const struct sockaddr* to, // 目的地址
__in int tolen // 地址结构长度
);
// 该函数返回实际发送了的字节总数
// 在数据报套接字中则需要输入地址
iResult = sendto(ConnectLessSocket,sendbuf,(int)strlen(sendbuf),0,result->ai_addr,(int)result->ai_addrlen);
if(iResult == SOCKET_ERROR)
{
printf("sendto failed with error: %d\n", WSAGetLastError());
closesocket(ConnectLessSocket);
WSACleanup();
return 1;
}

// 接收数据
int recvfrom
(
__in SOCKET s, // 已连接套接字
__out char* buf, // 接受缓冲区
__in int len, // 接受缓冲区的字节长度
__in int flags // 改变套接字调用的默认行为方式。默认为0。MSG_DONTROUTE(不经过本地的路由机制),MSG_OOB(带外数据)
__out struct sockaddr* from, // 源地址
__in_outopt_ int *fromlen // 地址结构长度
)
// 该函数返回实际接收了的字节总数
iResult = recvfrom(ConnectLessSocket,recvbuf, recvbuflen,0,NULL,NULL);
if(iResult > 0)
printf("Bytes received: %d\n", iResult);
else if(iResult == 0)
printf("Connection closed\n");
else
printf("recv failed with error: %d\n", WSAGetLastError());

6 原始套接字函数

使用场景:

  • 作用于网络层
  • 发送和接受ICMP IGMP等包。
  • 发送和接受内核不处理其协议字段的IPv4数据包。实现非常规协议OSPF。
  • 控制IPv4首部,伪造IP地址。

IP参数:

  • 端口号填0 ,网络层无端口的概念。

  • socket (AF_INET , SOCK_RAW , IPPROTO_ICMP);

准备工作:

  • 选项 IP_HDRINCL

  • 开启可以对IP首部进行控制。关闭则只能对下一协议首部和协议数据控制。

本地地址关联——bind()(不常见)

远端地址关联——connect()(不常见)

常用协议协议名 协议
IPPROTO_IP 0 IP协议
IPPROTO_ICMP 1 ICMP协议
IPPROTO_IGMP 2 IGMP协议
IPPROTO_RFCOMM 3 PFCOMM协议
IPPROTO_IPv4 4 IPv4协议
IPPROTO_TCP 6 TCP协议
IPPROTO_UDP 17 UDP协议
IPPROTO_IPv6 41 IPv6协议
IPPROTO_ICMPv6 58 ICMPv6协议
IPPROTO_RAW 255 RAW协议
// 设置网卡混杂模式
u_long optival = 1;
isoctlsocket(s,SIO_RCVALL, &optival);

7 多线程函数

7.1 线程函数

终止线程的方式:

  1. 线程函数返回(最好)。
  2. 通过调用ExitThread函数,线程将自行撤销。
  3. 同一个进程的另一个线程调用TerminateThread函数。
  4. 包含线程的进程终止。
// 线程创建
// 线程函数
DWORD WINAPI ThreadFunc(LPVOID lpvThreadParm);
// 线程创建
HANDLE CreateThread
(
LPSECURITY_ATTRIBUTES lpThreadAttributes, // NULL
SIZE_T dwStackSize, // 0
LPTHREAD_START_ROUTINE lpStartAddress, // 上面线程名 ThreadFunc
LPVOID lpParameter, // 上面线程参数 lpvThreadParm
DWORD dwCreationFlags, // 0
LPDWORD lpThreadId // 0
);
// 线程创建成功,返回线程的句柄,失败,返回值是-1。
// 同样的线程函数只需要初始化一个线程函数名,每次调用返回不同的句柄以此来区分线程。


// 线程操作
// 1.暂停线程
DWORD SuspendThread(HANDLE hThread);
// hThread:要暂停线程的句柄,一般要在线程外执行

// 2.恢复线程我在Ubuntu中使用C进行网络编程。我现在有一个问题,请问有可能是什么原因?我的服务器主进程创建了两个子进程A和B。我先创建了A再创建了B。我的B可以send给A,但是我的A无法send给B。我可以在A中找到B的描述符,但是访问不到描述符的资源。请问该怎么办。
DWORD ResumeThread(HANDLE hThread);
// hThread:要恢复线程的句柄,一般要在线程外执行(恢复后线程输入的参数会出错)

// 3.使线程睡眠
VOID Sleep(DWORD dwMilliseconds);
// dwMilliseconds:线程睡眠的时间,单位是毫秒,要在线程内执行


// 线程退出
// 线程内退出
VOID ExitThread(DWORD dwExitCode);
// dwExitCode:线程退出码,根据需要设置,一般设为0

// 线程外终止
BOOL TerminateThread(HANDLE hThread,DWORD dwExitCode);
// hThread:要终止的线程句柄
// dwExitCode:线程退出码,根据需要设置

7.2 线程通信

通过临界段通信:

  • 临界段:以原子方式执行关键代码段。
  • 关键代码段:是指一小段代码,同一个时刻,只能有一个线程具有访问权。
  • 多个线程访问同一个临界区的原则
    1. 一次最多只能一个线程停留在临界区内。
    2. 不能让一个线程无限地停留在临界区内,否则其它线程将不能进入该临界区。

通过事件通信:

  • 在Windows环境下,事件被理解为可以通过代码响应或处理的操作。
  • 事件主要用于标识一个操作是否已经完成。
  • 有两种不同类型的事件对象,一种是人工重置的事件,另一种是自动重置的事件。当人工重置的事件得到通知时,等待该事件的所有线程均变为可调度线程。当一个自动重置的事件得到通知时,等待该事件的线程中只有一个线程变为可调度线程。

事件对象属于内核对象,它包含三个主要内容:

  1. 一个使用计数。
  2. 一个布尔值,指明该事件是自动复位事件(false),还是人工复位事件(true)。
  3. 一个布尔值,指明该事件是已通知状态(true),还是未通知状态(false) 。

事件实例

// 临界段的使用
// 1.首先定义一个临界段对象(通常全局变量)
CRITICAL_SECTION cs;
// 2.在main函数中临界段对象初始化
InitializeCriticalSection(&cs);
// 3.在要执行关键代码进入临界段
EnterCriticalSection(&cs);
// 4.在执行完关键代码离开临界段
LeaveCriticalSection(&cs);
// 5.释放临界段对象
DeleteCriticalSection(&cs);
// 事件的使用
// 1.创建事件
HANDLE WINAPI CreateEvent
(
LPSECURITY_ATTRIBUTES lpEventAttributes,
BOOL bManualReset,
BOOL bInitialState,
LPCTSTR lpName
);
// lpEventAttributes:NULL
// bManualReset:告诉系统是创建一个人工重置的事件(TRUE)还是创建一个自动重置的事件(FALSE)。常设置TRUE;
// bInitialState:用于指明该事件是要初始化为已通知状态(TRUE)还是未通知状态(FALSE)。常设置为FALSE;
// lpName:事件对象的名字,是一个字符串,名字对大小写敏感。
// 自动重置: SetEvent之后, 事件自动重置为未触发状态。
// 手动重置 : SetEvent之后, 需要调用ResetEvent事件才置为未触发状态。
// 区别 : 当一个手动重置事件被触发的时候, 正在等待该事件的所有线程都变为可调度状态; 当一个自动重置事件被触发的时候, 只有一个正在等待该事件的线程会变为可调度状态.系统并不会保证会调度其中的哪个线程, 剩下的线程将继续等待.这样, 可以在在每个线程函数返回之前调用SetEvent。


// 2.将事件改为已通知状态
BOOL WINAPI SetEvent(HANDLE hEvent);
// 3.将该事件改为未通知状态
BOOL WINAPI ResetEvent(HANDLE hEvent);
// 4.等待单个事件变为通知态
DWORD WaitForSingleObject(HANDLE hObject,DWORD dwMilliseconds);
// hObject:事件的句柄
// dwMilliseconds:指定要等待的毫秒数。如设为零,表示立即返回。如指定常数INFINITE,则可根据实际情况无限等待下去。

// 5.等待一组事件变为通知态
DWORD WaitForMultipleObjects
(
DWORD nCount,
const HANDLE* lpHandles,
BOO bWaitAll,
DWORD dwMilliseconds
);
// nCount:监控对象的个数
// lpHandles:一组对象句柄的指针
// bWaitAll:是否等待全部符合条件,如果为TRUE,表示除非事件都变成通知态,否则就一直等待下去;如果FALSE,表示只要有一个事件都变成通知态,就立即返回
// dwMilliseconds:指定要等待的毫秒数。如设为零,表示立即返回。如指定常数INFINITE,则可根据实际情况无限等待下去

三、拓展处理

1 TCP

1.1 TCP的流传输控制

1.2 面向连接程序的可靠性保护

2 UDP

2.1 可靠性

2.2 并发性


四、I/O操作

1 阻塞I/O模型

通信过程

  1. 初始化
  2. recv()
  3. return:
    • 0 关闭
    • >0 请求—>重复
    • <0 处理错误

2 非阻塞I/O模型

通信过程:

  1. 初始化
  2. recv()
  3. return :
    • 0:关闭
    • >0:请求—>重复
    • <0:WSAEWOULD重复 | 其他 处理错误

设置非阻塞:函数在第二章。

// 设置非阻塞
int iMode = 1;
int iResult = ioctlsocket(s,FIONBIO,(u_long*)&iMode);

3 I/O复用模型

通信过程:等待事件

// 事件结构:数量,套接字(FD_SETSIZE = 64)
typedef struct fd_set
{
u_int fd_count;
SOCKET fd_array[FD_SETSIZE];
}fd_set;
in select
(
__in int nfds,
__int_out fd_set* readfds,
__int_out fd_set* writefds,
__int_out fd_set* execptfds,
__in const struct timeval* timeout
) ;
// nfds:为了与Berkeley套接字兼容保留的,执行时被忽略(填0)。
// readfds:指定一个套接字集合,可读性(连接请求,有数据可读,连接关闭、重置或终止)。
// writefds:指定一个套接字集合,可写性。
// exceptfds:指定一个套接字集合,异常状况。
// timeout:等待时间,阻塞模式设置NULL表示无限长。
// 函数返回发送网络事件的所有套接字数量综合,超时返回0,错误返回SOCKET_ERROR。
// 填入的套接字集合会只留下发生了网络事件的套接字。

// 从set中删除s
FD_CLR(s,*set);
// s if in set
FD_ISSET(s,*set);
// 从set中加入s
FD_SET(s,*set);
// 将set置空
FD_ZERO(*set);

4 基于消息的WSAAsyncSelect模型

5 基于事件的WSAEventSelect模型

通信过程:等待事件

// 创建事件对象(事件处于人工重置模式和未授信状态)
WSAEVENT WSACreateEvent(void);
// 设为未授信,成功返回TRUE
BOOL WSAReserEvent(__in WSAEVENT hEvent);
// 设为已授信,成功返回TRUE
BOOL WSAReserEvent(__in WSAEVENT hEvent);
// 关闭事件对象,成功返回TRUE
BOOL WSACloseEvent( __in WSAEVENT hEvent );

// 绑定监视事件到套接字和事件对象中(多个事件用 | 关联)
int WSAEventSelect
(
__int SOCKET s, // 套接字
__in WSAEVENT hEventObject, // 与网络事件集合关联的事件对象句柄
__in long lNetworkEvents // 感兴趣事件集合
) ;
// 成功返回0,失败返回SOCKET_ERROR

// 等待事件
DWORD WSAWaitForMultipleEvents
(
__in DWORD cEvents, // 指定参数lphEvents指向的数组中包含的事件对象句柄的数量
__in const WSAEVENT* lphEvents, // 指向事件对象句柄数组的指针
__in BOOL fWaitAll, // True:lphEvents事件全变为已授信才返回,False:有一个已授信就返回
__in DWORD dwTimeout, // 超时时间,WSA_INFINITE无限
__in BOOL fAlertable // 指定当完成例程在系统队列中排队等待执行时函数是否返回,TRUE返回时例程已经被执行,FALSE则说明未被执行。(事件模型中填FALSE)
);
// 返回值
// WSA_WAIT_TIMEOUT:超时
// WSA_WAIT_FAILED:失败,检查参数
// 返回索引(返回授信事件的索引),该值需要减去
// WSA_WAIT_EVENT_0才是正确索引(一般该宏值为0)

// 枚举事件
typedef struct _WSANETWORKEVENTS
{
long lNetworkEvents; // 已发生的网络事件
int iErrorCode[FD_MAX_EVENTS]; // 存事件的相关出错码
}WSANETWORKEVENTS,*LPWSANETWORKEVENTS;
int WSAEnumNetworkEvents
(
__in SOCKET s, // 套接字
__in WSAEVENT hEventObject, // 需复位的相应事件对象,指定了本函数会重置这个事件对象的状态
__out LPWSANETWORKEVENTS lpNetworkEvents // 一个WSANETWORKEVENTS结构的数组,每个元素记录了一个网络事件和相应的出错码
);
// 成功返回0,失败返回SOCKET_ERROR

6 重叠I/O模型

7 完成端口模型


五、WinPcap编程

1 Winpcap基础

2 环境配置

问题:

配置流程:

  1. C/C++ 常规:附加包含目录:WpdPack\Include
  2. 链接器 常规:附加库目录:WpdPack\Lib(64位系统继续选择:[ \64x ]
  3. 链接器 输入:附加依赖项目: Packet.lib;wpcap.lib;ws2_32.lib

3 wpcap.dll

4 Packet.dll


六、端口扫描

1 原理

2 工具

3 实现


七、网络安全传输

1 数据传输

主要功能:使用DES和RSA混合加密传输网络数据。客户端为数据发送端,服务器端为数据接收端。

客户端主要步骤:

  1. 接收公钥并返回确认。
  2. 生成DES的密钥。
  3. 用户输入发送数据。
  4. 使用DES密钥加密数据。
  5. 使用RSA公钥加密DES密钥。
  6. 将将被加密的密钥和被加密的数据一起发送给服务器。

服务器端主要步骤:

  1. 生成RSA的公钥私钥对。
  2. 发送公钥给客户端。
  3. 接收被加密的密钥和数据并返回确认。
  4. 使用私钥解密DES密钥。
  5. 使用DES密钥解密数据。
  6. 显示数据。

img


八、实例

my::全部实例