基础概念

协议:约定好的数据格式,约定好的数据的解析方式

在cmd中输入ipcongfig可以查看IP配置

image-20240716223213882

ip地址:当我们使用计算机或者其他网络设备进行互联网通信时,每一个设备都需要一个唯一的标识符来进行通信。这个标识符被称为 IP 地址。IP(Internet Protocol)地址是一个由32位二进制表示的数字,通常被分为四个数字组成的点分十进制的形式表示,例如:192.168.1.1。

端口

protocol port(协议端口)

操作系统中通信的端点,与IP地址及通信协议关联,用来获取相应的网络服务。

(eg.IP地址就像银行的地址,而端口就是银行处理不同业务的窗口)

在网络技术中,端口(Port)大致有两种意思:

一是物理意义上的端口,比如,ADSL Modem、集线器、交换机、路由器用于连接其他网络设备的接口,如RJ-45端口、SC端口等等。

二是逻辑意义上的端口,一般是指TCP/IP协议中的端口

公网

公网是相对于内网而言的。内网上网的计算机得到的IP地址是Internet上的保留地址;而公网上网的计算机得到的IP地址是因特网的公用地址,是非保留的地址。公网的计算机和因特网上的其他计算机可随意互相访问。

局域网

局域网(Local Area NetworkLAN)是指在一个较小的地理范围(如一所学校)内,将各种计算机、外部设备和数据库系统等通过双绞线、同轴电缆等连接介质互相连接起来n组成资源和信息共享的计算机互联网络。主要特点如下:

  1. 为一个单位所拥有,且地理范围和站点数目均有限。
  2. 所有站点共享较高的总带宽(即较高的数据传输速率)
  3. 较低的时延和较低的误码率。
  4. 各站为平等关系而非主从关系。
  5. 能进行广播和组播。

udp

1
2
3
4
5
CS-client server

BS-browser server

p2p-point 2 point

socket

image-20240717160850773

参数

1
[in] af

地址系列规范。 地址系列的可能值在 Winsock2.h 头文件中定义。

在为 Windows Vista 及更高版本发布的Windows SDK中,头文件的组织方式已更改,地址系列的可能值在 Ws2def.h 头文件中定义。 请注意, Ws2def.h 头文件会自动包含在 Winsock2.h 中,永远不应直接使用。

当前支持的值是 AF_INET 或 AF_INET6,它们是 IPv4 和 IPv6 的 Internet 地址系列格式。 用于 NetBIOS 的地址系列 (AF_NETBIOS 的其他选项,例如,如果安装了地址系列的 Windows 套接字服务提供商,则支持) 。 请注意,AF_地址系列和PF_协议系列常量的值 (相同,例如 ,AF_INETPF_INET) ,因此可以使用任一常量。

下表列出了地址系列的常见值,尽管许多其他值是可能的。

展开表

Af 含义
AF_UNSPEC0 地址系列未指定。
AF_INET2 Internet 协议版本 4 (IPv4) 地址系列。
AF_IPX6 IPX/SPX 地址系列。 仅当安装了 NWLink IPX/SPX NetBIOS 兼容传输协议时,才支持此地址系列。Windows Vista 及更高版本不支持此地址系列。
AF_APPLETALK16 AppleTalk 地址系列。 仅当安装了 AppleTalk 协议时,才支持此地址系列。Windows Vista 及更高版本不支持此地址系列。
AF_NETBIOS17 NetBIOS 地址系列。 仅当安装了适用于 NetBIOS 的 Windows 套接字提供程序时,才支持此地址系列。32 位版本的 Windows 支持 NetBIOS 的 Windows 套接字提供程序。 默认情况下,此提供程序安装在 32 位版本的 Windows 上。64 位版本的 Windows 不支持 NetBIOS 的 Windows 套接字提供程序,包括 Windows 7、Windows Server 2008、Windows Vista、Windows Server 2003 或 Windows XP。适用于 NetBIOS 的 Windows 套接字提供程序仅支持 类型 参数设置为 SOCK_DGRAM的套接字。适用于 NetBIOS 的 Windows 套接字提供程序与NetBIOS编程接口不直接相关。 Windows Vista、Windows Server 2008 及更高版本不支持 NetBIOS 编程接口。
AF_INET623 Internet 协议版本 6 (IPv6) 地址系列。
AF_IRDA26 IrDA (IrDA) 地址系列。仅当计算机安装了红外端口和驱动程序时,才支持此地址系列。
AF_BTH32 蓝牙地址系列。如果计算机安装了蓝牙适配器和驱动程序,则 SP2 或更高版本的 Windows XP 支持此地址系列。
1
[in] type

新套接字的类型规范。

套接字类型的可能值在 Winsock2.h 头文件中定义。

下表列出了 Windows 套接字 2 支持 的类型 参数的可能值:

展开表

类型 含义
SOCK_STREAM1 一种套接字类型,它通过 OOB 数据传输机制提供排序的、可靠的双向、基于连接的字节流。 此套接字类型对 Internet 地址系列 (AF_INET 或AF_INET6) 使用传输控制协议 (TCP) 。
SOCK_DGRAM2 支持数据报的套接字类型,这些数据报是固定 (通常较小) 最大长度的无连接、不可靠的缓冲区。 此套接字类型对 Internet 地址系列 (AF_INET 或AF_INET6) 使用用户数据报协议 (UDP) 。
SOCK_RAW3 一种套接字类型,它提供允许应用程序操作下一层协议标头的原始套接字。 若要操作 IPv4 标头,必须在套接字上设置 IP_HDRINCL套接字选项。 若要操作 IPv6 标头,必须在套接字上设置 IPV6_HDRINCL 套接字选项。
SOCK_RDM4 提供可靠消息数据报的套接字类型。 此类型的一个示例是 Windows 中的实用常规多播 (PGM) 多播协议实现,通常称为可靠多播编程。仅当安装了可靠多播协议时,才支持此 类型 值。
SOCK_SEQPACKET5 提供基于数据报的伪流数据包的套接字类型。

在 Windows 套接字 2 中,引入了新的套接字类型。 应用程序可以通过 WSAEnumProtocols 函数动态发现每个可用传输协议的属性。 因此,应用程序可以确定地址系列的可能的套接字类型和协议选项,并在指定此参数时使用此信息。 Winsock2.hWs2def.h 头文件中的套接字类型定义将随着新的套接字类型、地址系列和协议的定义而定期更新。

在 Windows 套接字 1.1 中,唯一可能的套接字类型是 SOCK_DGRAMSOCK_STREAM

1
[in] protocol

要使用的协议。 协议参数的可能选项特定于指定的地址系列和套接字类型。 协议的可能值在 Winsock2.hWsrm.h 头文件中定义。

在 Windows Vista 及更高版本发布的Windows SDK中,头文件的组织方式已更改,此参数可以是 Ws2def.h 头文件中定义的 IPPROTO 枚举类型中的值之一。 请注意, Ws2def.h 头文件会自动包含在 Winsock2.h 中,永远不应直接使用。

如果指定值 0,则调用方不希望指定协议,服务提供商将选择要使用的 协议

af 参数AF_INET或AF_INET6且类型为SOCK_RAW时,为协议指定的值在 IPv6 或 IPv4 数据包标头的协议字段中设置。

下表列出了 协议 的常见值,尽管许多其他值是可能的。

展开表

protocol 含义
IPPROTO_ICMP1 Internet 控制消息协议 (ICMP) 。 当 af 参数 AF_UNSPECAF_INETAF_INET6类型 参数 SOCK_RAW 或未指定时,此值可能为 。Windows XP 及更高版本支持此 协议 值。
IPPROTO_IGMP2 Internet 组管理协议 (IGMP) 。 当 af 参数 AF_UNSPECAF_INETAF_INET6类型 参数 SOCK_RAW 或未指定时,此值可能为 。Windows XP 及更高版本支持此 协议 值。
BTHPROTO_RFCOMM3 蓝牙射频通信 (蓝牙 RFCOMM) 协议。 当 af 参数AF_BTH且类型参数SOCK_STREAM时,这是一个可能的值。具有 SP2 或更高版本的 Windows XP 支持此 协议 值。
IPPROTO_TCP6 传输控制协议 (TCP) 。 当 af 参数AF_INETAF_INET6类型参数SOCK_STREAM时,这是一个可能的值。
IPPROTO_UDP17 用户数据报协议 (UDP) 。 当 af 参数AF_INETAF_INET6类型参数SOCK_DGRAM时,这是一个可能的值。
IPPROTO_ICMPV658 Internet 控制消息协议版本 6 (ICMPv6) 。 当 af 参数 AF_UNSPECAF_INETAF_INET6类型 参数 SOCK_RAW 或未指定时,此值可能为 。Windows XP 及更高版本支持此 协议 值。
IPPROTO_RM113 可靠多播的 PGM 协议。 当 af 参数AF_INET且类型参数SOCK_RDM时,这是一个可能的值。 在 Windows Vista 及更高版本发布的Windows SDK中,此协议也称为IPPROTO_PGM。仅当安装了可靠多播 协议 时,才支持此协议值。

返回值

如果未发生错误, 套接字 将返回引用新套接字的描述符。 否则,将返回值 INVALID_SOCKET,并且可以通过调用WSAGetLastError来检索特定的错误代码。

bind

image-20240717162813098

参数

1
[in] s

标识未绑定套接字的描述符。

1
[in] name

指向要分配给绑定套接字 的本地地址 的 sockaddr 结构的指针。

1
[in] namelen

name 参数指向的值的长度(以字节为单位)。

返回值

如果未发生错误, 绑定 将返回零。 否则,它将返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

sockaddr

image-20240717162829215

WSAStartup

image-20240717163612217

参数

1
[in] wVersionRequested

调用方可以使用的最高版本的 Windows 套接字规范。 高序字节指定次要版本号;低序字节指定主版本号。

1
[out] lpWSAData

指向 WSADATA数据结构的指针,用于接收 Windows 套接字实现的详细信息。

返回值

如果成功, WSAStartup 函数返回零。 否则,它将返回下面列出的错误代码之一。

WSAStartup 函数直接返回此函数的返回值中的扩展错误代码。 不需要调用 WSAGetLastError函数,也不应使用。

展开表

recvfrom

image-20240717165945812

参数

1
[in] s

标识绑定套接字的描述符。

1
[out] buf

传入数据的缓冲区。

1
[in] len

buf 参数指向的缓冲区的长度(以字节为单位)。

1
[in] flags

一组选项,用于修改函数调用的行为,超出为关联套接字指定的选项。 有关更多详细信息,请参阅下面的“备注”。

1
[out] from

指向sockaddr结构中的缓冲区的可选指针,该缓冲区将在返回时保存源地址。

1
[in, out, optional] fromlen

指向 from 参数指向的缓冲区大小(以字节为单位)的可选指针。

返回值

如果未发生错误, recvfrom 将返回收到的字节数。 如果连接已正常关闭,则返回值为零。 否则,将返回值 SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。

服务器

  1. 创建socket,指明使用udp协议

    1
    2
    SOCK_STREAM - 数据流
    SOCK_DGRAM - 数据报
  2. 绑定端口

  3. 收发数据

  4. 关闭

新建控制台程序

image-20240717160425708

image-20240717163642942

将大端序转换成小段序输入

image-20240717165215460

将ip地址转换成十六进制小端序输入

image-20240717165451224

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
// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return;
}

SOCKET sockServer = socket(AF_INET, SOCK_DGRAM , IPPROTO_UDP);
if (sockServer == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
//127.0.0.1-回环地址
sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(0x7788);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;
int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "bind 失败" << endl;
return 0;
}

char szBuff[MAXBYTE];
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
nRet = recvfrom(sockServer, szBuff, sizeof(szBuff), 0, (sockaddr*)&siFrom, &nSizeofSi);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recvfrom失败" << endl;
}
else
{
printf("from %s %d recv: %s", inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port), szBuff);
}
char szSend[] = { "recv OK" };
nRet = sendto(sockServer, szSend, sizeof(szSend), 0, (sockaddr*)&siFrom, sizeof(siFrom));
if (nRet == SOCKET_ERROR)
{
cout << "sendto 失败" << endl;
}

cout << "Hello World!\n";
}

sendto

image-20240717174356520

参数

1
[in] s

标识可能连接的 () 套接字的描述符。

1
[in] buf

指向包含要传输的数据的缓冲区的指针。

1
[in] len

buf 参数指向的数据的长度(以字节为单位)。

1
[in] flags

一组指定调用方式的标志。

1
[in] to

指向包含目标套接字地址的 sockaddr结构的可选指针。

1
[in] tolen

由 to 参数指向的地址的大小(以字节为单位)。

返回值

如果未发生错误, sendto 将返回发送的总字节数,这可能小于 len 指示的数量。 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError检索特定的错误代码。

客户端:

  1. 创建socket,指明使用udp协议

    1
    2
    SOCK_STREAM - 数据流
    SOCK_DGRAM - 数据报
  2. 收发数据

  3. 关闭

新建控制台应用

image-20240717172356697

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
// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockClient == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(0x7788);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;

char szSend[MAXBYTE] = { };
cin >> szSend;
int nRet = sendto(sockClient, szSend, sizeof(szSend), 0, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "sendto 失败" << endl;
}

char szBuff[MAXBYTE];
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
nRet = recvfrom(sockClient, szBuff, sizeof(szBuff), 0, (sockaddr*)&siFrom, &nSizeofSi);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recvfrom失败" << endl;
}
else
{
printf("from %s %d recv: %s \r\n", inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port), szBuff);
}
cout << "Hello World!\n";
}

Server成功收到消息

image-20240717180823760

Client成功收到Server的回复

image-20240717180736181

Client输出

image-20240717181608706

image-20240717181626510

Server输出

image-20240717181657835

image-20240717181726118

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
// Server.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

SOCKET sockServer = socket(AF_INET, SOCK_DGRAM , IPPROTO_UDP);
if (sockServer == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
//127.0.0.1-回环地址
sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(0x7788);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;
int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "bind 失败" << endl;
return 0;
}

while (true)
{
char szBuff[MAXBYTE];
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
nRet = recvfrom(sockServer, szBuff, sizeof(szBuff), 0, (sockaddr*)&siFrom, &nSizeofSi);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recvfrom失败" << endl;
}
else
{
printf("from %s %d recv: %s\r\n", inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port), szBuff);
}

char szSend[] = { "recv OK" };
nRet = sendto(sockServer, szSend, sizeof(szSend), 0, (sockaddr*)&siFrom, sizeof(siFrom));
if (nRet == SOCKET_ERROR)
{
cout << "sendto 失败" << endl;
}
}

cout << "Hello World!\n";
return 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
// Client.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <iostream>
using namespace std;
#define WIN32_LEAN_AND_MEAN//仅保留windows.h中的必要的头文件
//如果不加这一行则要把Winsock2.h放到windows.h前面,否则会因重定义而报错
#include <windows.h>
#include <Winsock2.h>
#pragma comment(lib,"Ws2_32.lib")

int main()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0)
{
return 0;
}

if (LOBYTE(wsaData.wVersion) != 2 ||
HIBYTE(wsaData.wVersion) != 2)
{
WSACleanup();
return 0;
}

SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
if (sockClient == INVALID_SOCKET)
{
cout << "socket 失败" << endl;
return 0;
}
sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(0x7788);
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//0x0100007f;

while (true)
{
char szSend[MAXBYTE] = { };
cin >> szSend;
int nRet = sendto(sockClient, szSend, sizeof(szSend), 0, (sockaddr*)&si, sizeof(si));
if (nRet == SOCKET_ERROR)
{
cout << "sendto 失败" << endl;
}

char szBuff[MAXBYTE];
sockaddr_in siFrom = {};
int nSizeofSi = sizeof(sockaddr_in);
nRet = recvfrom(sockClient, szBuff, sizeof(szBuff), 0, (sockaddr*)&siFrom, &nSizeofSi);
if (nRet == 0 || nRet == SOCKET_ERROR)
{
cout << "recvfrom失败" << endl;
}
else
{
printf("from %s %d recv: %s \r\n", inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port), szBuff);
}
}
cout << "Hello World!\n";
}

运行Client和Server后,在Client中输入消息,可以在Server中收到消息,同时Client可以收到Server的回复

image-20240717183157928