Windows网络编程初步

时间:2022-06-13 00:18:12

1. OSI/RM和TCP/IP

国际标准化组织(ISO)和国际电报电话咨询委员会(CCITT)制定OSI/RM

OSI/RM:Open SystemInterconnect/Recommended Model

Windows网络编程初步

图1OSI/RM和TCP/IP体系结构模型


TCP: Transmission Control Protocol 传输控制协议是一种面向连接的、可靠的、基于字节流的传输层通信协议

Internet Protocol网际协议:IP不提供可靠的传输服务,它不提供端到端的或(路由)结点到(路由)结点的确认,对数据没有差错控制,它只使用报头的校验码,它不提供重发和流量控制。如果出错可以通过ICMP报告,ICMPIP模块中实现。



Windows网络编程初步

图2  TCP/IP协议栈

TD-SCDMA:Time Division-Synchronous Code Division Multiple Access(时分同步码分多址)中国制定的一个3G通信标准。

2.套接字

2.1套接字编程接口的起源与发展

2.2套接字的含义与分类

2.3套接字接口的位置及实现方式


端口号port:16位无符号二进制整数0~65535。

进程网络地址:三元组(传输协议、主机IP地址、端口号)

一对通信进程:五元组(传输层协议、本地主机IP、本地传输层端口号、远端主机IP、远端传输层端口号)


套接字:TCP用主机的IP地址加上主机上的端口号作为TCP连接的端点,这种端点就叫做套接字(socket)或插口。套接字用(IP地址:端口号)表示。

套接字是网络通信过程中端点的抽象表示,包含进行网络通信必需的五种信息(五元组):连接使用的协议,本地主机的IP地址,本地进程的协议端口,远地主机的IP地址,远地进程的协议端口。


关键词:网络应用程序编程接口-套接字、系统调用、Berkeley Sockets、Windows Sockets、

套接字的含义、套接字的分类、套接字接口的位置及实现方式


套接字本质上是一种应用程序调用操作系的网络通信功能的系统调用接口。


套接字的分类:

1)     流式套接字(Stream Socket

2)     数据报套接字(Datagram SocketTCP

3)     原始套接字(Raw SocketUDP


Windows网络编程初步

Windows网络编程初步

Windows网络编程初步

3.网络编程的不同层次


Windows网络编程初步

4.WinSock编程初步


Windows网络编程初步

应用程序调用WinSock API函数实现相互之间的通信,而WinSock API函数又利用下层的Windows操作系统中的网络通信协议和相关的系统调用实现其通信功能。WinSock与网络应用程序和操作系统中的网络通信协议软件之间的关系见图3.1。



4.1DLL常识


Windows网络编程初步

4.2WinSock编程初步示例


表1常用协议及协议号


协议名

协议号

IP

0

ICMP

1

TCP

6

UDP

17


WinSock提供了getprotobyname()和getprotobyname()来完成协议名字和编号之间的转换

以下代码实现:显示因特网所有协议的名称以及相应的协议号



#include "stdafx.h"
//1.包含WinSock头我文件:#include"WinSock2.h"
#include"WinSock2.h"
#include<iostream>
//2.链接WinSock导入库:#pragma comment(lib,"ws2_32.lib")
#pragma comment(lib,"ws2_32.lib")
using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
//3.加载WinSock动态链接库
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "加载WinSock DLL失败!\n";
		return 0;
	}
	struct protoent* pProto;
	for (int i = 1; i < 256; i++)
	{
		if ((pProto = getprotobynumber(i)) != NULL)
		{
			cout << "协议号:" << pProto->p_name << endl;
			cout << "协议号:" << pProto->p_proto << endl;
		}
	}
//4.注销WinSock动态链接库
	WSACleanup();
	system("pause");
	return 0;
}

在VC中使用WinSock,以下四步必不可少:

1.     包含WinSock头我文件:#include"WinSock2.h"

2.     链接WinSock导入库:#pragma comment(lib,"ws2_32.lib")

3.     加载WinSock动态链接库

4.     注销WinSock动态链接库


Windows网络编程初步

Windows网络编程初步

Windows网络编程初步

4.3WinSock网络地址表示

4.4地址结构

4.4.1 in_addr结构



用于存储一个IPv4地址。

//IPv4 Internet address

//This is an 'on-wire' format structure.

//

typedefstructin_addr {

        union {

                struct { UCHAR s_b1,s_b2,s_b3,s_b4; } S_un_b;

                struct { USHORT s_w1,s_w2; } S_un_w;

                ULONG S_addr;

        } S_un;

#defines_addr  S_un.S_addr /* can be used for most tcp & ip code */

#defines_host  S_un.S_un_b.s_b2    // host on imp

#defines_net   S_un.S_un_b.s_b1    // network

#defines_imp   S_un.S_un_w.s_w2    // imp

#defines_impnoS_un.S_un_b.s_b4    // imp #

#defines_lh    S_un.S_un_b.s_b3    // logical host

} IN_ADDR, *PIN_ADDR, FAR *LPIN_ADDR;

#endif


共用体类型的字段S_un,专门用来存储32位的IP地址。


4.4.2 sockaddr_in结构


该结构对IP地址和协议号进行了封装。



//
// IPv4 Socket address, Internet style
//

typedef struct sockaddr_in {

#if(_WIN32_WINNT < 0x0600)
    short   sin_family;	//地址族,IP协议地址对应的值为AF_INET
#else //(_WIN32_WINNT < 0x0600)
    ADDRESS_FAMILY sin_family;
#endif //(_WIN32_WINNT < 0x0600)

    USHORT sin_port;		//16位端口号
    IN_ADDR sin_addr;		//32位IP地址,使用网络字节顺序(大端序)
    CHAR sin_zero[8];
} SOCKADDR_IN, *PSOCKADDR_IN;


4.4.3 sockaddr结构


sockaddr为通用套接字地址结构。当使用TCP/IP时,该结构内容与sockaddr_in完全相同。



//
// Structure used to store most addresses.
//
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;

#ifndef __CSADDR_DEFINED__
#define __CSADDR_DEFINED__
/*
 * SockAddr Information
 */
typedef struct _SOCKET_ADDRESS {
    _Field_size_bytes_(iSockaddrLength) LPSOCKADDR lpSockaddr;

//  ESP: 791.
//    _When_(
//        lpSockaddr->sa_family == AF_INET,
//        _Field_range_(>=, sizeof(SOCKADDR_IN)))
//    _When_(
//        lpSockaddr->sa_family == AF_INET6,
//        _Field_range_(>=, sizeof(SOCKADDR_IN6)))
    INT iSockaddrLength;
} SOCKET_ADDRESS, *PSOCKET_ADDRESS, *LPSOCKET_ADDRESS;


//sockaddr_in结构与sockaddr结构存储内容完全一致。因此:
	struct sockaddr a;
	struct sockaddr_in* p;
	p = (sockaddr_in*)&a;
	p->sin_family = AF_INET;
	p->sin_port = 54321;
p->sin_addr.S_un.S_addr = inet_addr("192.168.1.1");

以上三个地址结构体的内存大小:

sizeof(in_addr)=4

sizeof(sockaddr_in)=16

sizeof(sockaddr)=16


(考虑结构体内存对齐)


4.5 IP地址转换函数

4.5.1 inet_addr函数


unsigned long inet_addr(constchar* cp);

cp指向点分十进制字符串,返回无符号长整型32位二进制的网络字节顺序的IP地址

4.5.2 inet_aton函数

将参数cp指向的点分十进制表示的IP地址字符串转换为32位无符号长整型数,并存放于参数inp指向的in_addr结构变量中。

int inet_aton(constchar* cp, structin_addr* inp);


4.5.3 inet_ntoa函数

将一个包含在in_addr结构变量中的长整型IP地址转换为点分点分十进制形式。

char* inet_ntoa(structin_addr in);

函数调用成功则返回一个字符指针,该指针指向一个char型缓冲区,该缓冲区保存有由参数in的值转换而来的点分十进制表示的IP地址字符串。调用失败则返回NULL指针。


4.6 WinSock错误处理

int WSAGetLastError(void);

函数返回上一次WinSoc函数调用错误时返回的错误码。



4.7网络字节顺序

计算机在存储多字节数据时存在大端顺序(Big Endian)和小端顺序(Little Endian)。

对于字符编码,编码标准中明确规定了字节顺序。但对于整数数据则不存在类似的规定。



字节的高位与低位:

举个例子,int a = 0x12345678 ; 那么左边12就是高位字节,右边的78就是低位字节,从左到右,由高到低。int b=0x1234:12是高字节,34是低字节。

地址的高端与低端

0x00000001

0x00000002

0x00000003

0x00000004

从上倒下,由低到高,地址值小的为低端地址,地址值大的为高端地址。

 

Bit-endian 如此存放(按原来顺序存储,书写顺序)

0x00000001           -- 12

0x00000002           -- 34

0x00000003           -- 56

0x00000004           -- 78

 

Little-endian 如此存放(高字节存高地址,低字节存低地址)

0x00000001           -- 78

0x00000002           -- 56

0x00000003           -- 34

0x00000004           -- 12



4.7.1 htons函数

该函数将一个16位的无符号短整型数据由主机字节顺序转换为网络字节顺序。

u_short htons(u_short hostshort);



4.7.2 ntohs函数

该函数将一个16位的无符号短整型数据由网络字节顺序转换为主机字节顺序返回。

u_short ntohs(u_short netshort);


4.7.3 htonl函数

该函数将一个32位的无符号长整型数据由主机字节顺序转换为网络字节顺序返回。

u_long htonl(u_long hostlong);


4.7.4 ntohl函数

该函数将一个32位的无符号长整型数据由网络字节顺序转换为主机字节顺序并返回。

u_long ntohl(u_long netlong);



#include "stdafx.h"
#include<iostream>
#include"WinSock2.h"
#pragma comment(lib,"ws2_32.lib")

using namespace std;

int _tmain(int argc, _TCHAR* argv[])
{
	WSADATA wsaData;
	WORD wVersionRequested = MAKEWORD(2, 2);
	if (WSAStartup(wVersionRequested, &wsaData) != 0)
	{
		cout << "加载WinSock DLL失败!\n";
		return 0;
	}

	u_short x, y = 0x1234;
	x = htons(y);
	cout << "主机字节顺序:" << hex << y << "\t对应的网络字节顺序:" << x << endl;
	u_long a, b = 0x1122ABCD;
	a = htonl(b);
	cout << "主机字节顺序:" << hex << b << "\t对应的网络字节顺序:" << a << endl;

	WSACleanup();
	system("pause");
	return 0;
}

/*

主机字节顺序:1234      对应的网络字节顺序:3412
主机字节顺序:1122abcd   对应的网络字节顺序:cdab2211
*/

4.7.5以上四个函数的WinSock版本

int WSAHtons(SOCKET s, u_short hostshort, u_short* lpnetshort);

WSAHtons将一个16位的无符号短整型数据由主机字节顺序转换为网络字节顺序。

 

    int WSANtohs(SOCKET s, u_short netshort, u_short* lphostshort);

    int WSAHtonl(SOCKET s, u_long hostlong, u_long* lpnetlong);

int WSANtohl(SOCKET s, u_long netlong, u_long* lphostlong);


4.8网络配置信息查询

4.8.1主机名字与IP地址查询



int gethostname(char* name, int namelen);
	struct hostent* gethostbyname(const char* name);
	struct hostent* gethostbyaddr(const char* addr, int len, int type);

WinSock提供的异步版本:
	HANDLE WSAAsyncGetHostByName(
		HWND hWnd,
		unsigned int wMsg,
		const char FAR* name,
		const char FAR* buf,
		int buflen
		);
	HANDLE WSAAsyncGetHostByAddr(
		HWND hWnd,
		unsigned int wMsg,
		const char FAR* addr,
		int len,
		int type,
		const char FAR* buf,
		int buflen);

4.8.2服务查询

struct servent* getservbyname(const char* name, const char* proto);

	struct  servent {
		char    FAR * s_name;           /* official service name */
		char    FAR * FAR * s_aliases;  /* alias list */
		#ifdef _WIN64
		char    FAR * s_proto;          /* protocol to use */
		short   s_port;                 /* port # */
		#else
		short   s_port;                 /* port # */
		char    FAR * s_proto;          /* protocol to use */
		#endif
	};

	struct servent* getservbyport(int port, const char* proto);
WinSock提供的异步版本:

	HANDLE WSAAsyncGetServByName(
		HWND hWnd,
		unsigned int wMsg,
		const char FAR* name,
		const char FAR* proto,
		char FAR* buf,
		int buflen);

	HANDLE WSAAsyncGetServByPort(
		HWND hWnd,
		unsigned int wMsg,
		int port,
		const char* proto,
		char* buf,
		int buflen);

 

4.8.3协议查询

	struct protoent* getprotobyname(const char* name);

	struct  protoent {
		char    FAR * p_name;           /* official protocol name */
		char    FAR * FAR * p_aliases;  /* alias list */
		short   p_proto;                /* protocol # */
	};

	struct protoent* getprotobynumber(int number);
WinSock提供的异步版本:
	HANDLE WSAAsyncGetProtoByName(
		HWND hWnd,
		unsigned int wMsg,
		const char* name,
		char* buf,
		int buflen);

	HANDLE PASCAL FAR WSAAsyncGetProtoByNumber(
		HWND hWnd,
		unsigned int wMsg,
		int number,
		char FAR* buf,
		int buflen);



4.8.3协议查询 4.8.3协议查询