MFC网络编程1——网络基础及套接字

一、关于网络的部分概念

1 URL(网址)及URL的解析

 网址:学名叫URL(Uniform Resource Locator),统一资源定位符。URL的各种形式如下图所示。
在这里插入图片描述
 尽管URL有各种不同的写法,但他们有一个共同点,就是URL开头的文字,即“http:”“ftp:”“file:”"mailto:"这部分文字标识浏览器应当使用的访问方法。比如当访问Web服务器时应当使用HTTP协议,而访问FTP服务器时应当使用FTP协议。因此,我们可以把这部分理解为访问时使用的协议类型。

2 URL的解析

 浏览器先要解析URL,从而生成发送给Web服务器的请求消息。以下我们以访问Web服务器为例进行讲解,假定URL为http://www.lab.glasscom.com/dir1/file1.html ,经过浏览器解析之后,可以看到拆分结果为:Web服务器名称www.lab.glasscom.com,以及文件的路径名/dir1/file1.html
在这里插入图片描述
在这里插入图片描述

3 域名及域名解析

 一般来说,域名包括顶级域名、一级域名、子域名组成,其格式为www.A.B.C.com,句点划分了域名的层次结构,越靠右的域(如com)的层次越高。假设DNS服务器和域一一对应(实际上比较复杂),则上层域的DNS服务器保存着下级服务器的信息,因此可以一层一层顺藤摸瓜地查找下去。
在这里插入图片描述
 对地址为 http://www.lab.glasscom.com/dir1/file1.html 的URL来说,其中的www.lab.glasscom.com就是域名。
 当我们在浏览器输入一个域名后,需要进行域名解析,也就是通过操作系统Socket库中的解析器向最近的DNS服务器查询域名对应的IP地址,获得此地址后,我们才能同该服务器进行通信。

3 IP及子网掩码

 互联网和公司内部的局域网都是基于TCP/IP的思路来设计的,也就是由一些小的子网,通过路由器连接起来组成一个大的网络。这里的子网可以理解为用集线器连接起来的几台计算机,我们将它看作一个单位,成为子网。将子网通过路由器连接起来,就形成了一个网络。在网络中,所有的设备都会被分配一个地址。这个地址就相当于现实中某条路上的“XX号XX室”。其中“号”对应的号码是分配给整个子网的,而“室”对应的号码室分配给子网中的计算机的,这就是网络中的地址。“号”对应的号码称为网络号,“室”对应的号码称为主机号,这个地址的整体称为IP地址。
 准确的说,IP地址是连接在Internet中的每一台主机(或 路由器)的全球唯一的标识符。实际的IP地址是一串32比特的数字,按照8比特为一组分成4组,分别用十进制标识然后再用原点隔开。但是这样我们就无法区分哪些是网络号,哪些是主机号。为此,我们附加了子网掩码。
在这里插入图片描述
 子网掩码为1的部分表示网络号,子网掩码为0的部分表示主机号,如下图所示。
在这里插入图片描述

4 什么是Web服务器

  Web服务器一般指的是“网站服务器”,也称为WWW(Word Wide Web)服务器,是某种驻留在因特网上的计算机程序,可以向请求终端提供服务,主要功能时存储、处理和传递网页给“客户”,传递内容一般是HTML文档、图像、样式表或脚本等,也可以放置网站文件以供浏览或下载。严格来说Web服务器只负责处理Http协议请求,发送静态页面的内容。

5 HTTP的基本概念

 在浏览器解析完URL之后,就会使用HTTP协议访问Web服务器。那么HTTP协议到底是什么?
在这里插入图片描述
 HTTP 协议定义了客户端和服务器之间交互的消息内容和步骤,其基本思路非常简单。首先,客户端会向服务器发送请求消息。请求消息中包含的内容是“对什么”和“进行怎样的操作”两个部分。其中相当于“对什么”的部分称为 URI。一般来说,URI 的内容是一个存放网页数据的文件名或者是一个 CGI 程序的文件名,例如“/dir1/file1.html” “/dir1/program1.cgi”等。不过,URI 不仅限于此,也可以直接使用“http:”开头的 URL 来作为 URI。换句话说就是,这里可以写各种访问目标,而这些访问目标统称为 URI。
 相当于接下来“进行怎样的操作”的部分称为方法。方法表示需要让Web 服务器完成怎样的工作,其中典型的例子包括读取 URI 表示的数据、将客户端输入的数据发送给 URI 表示的程序等。下表列举了主要的方法,通过这张表大家应该能够理解通过方法可以执行怎样的操作。
在这里插入图片描述
 HTTP 消息中还有一些用来表示附加信息的头字段。客户端向 Web 服务器发送数据时,会先发送头字段,然后再发送数据。收到请求消息之后,Web 服务器会对其中的内容进行解析,通过 URI和方法来判断“对什么”“进行怎样的操作”,并根据这些要求来完成自己的工作,然后将结果存放在响应消息中。在响应消息的开头有一个状态码,它用来表示操作的执行结果是成功还是发生了错误。当我们访问 Web 服务器时,遇到找不到的文件就会显示出 404 Not Found 的错误信息,其实这就是状态码。状态码后面就是头字段和网页数据。响应消息会被发送回客户端,客户端收到之后,浏览器会从消息中读出所需的数据并显示在屏幕上。到这里,HTTP 的整个工作就完成了。

6 Socket库概念

 Socket库是操作系统提供的一个库,包含的程序组件可以让其他的程序调用操作系统的网络功能。如域名解析器就是库中的一个组件。
 当浏览器和 Web 服务器通信时、通过 DNS 服务器查询 IP 地址时,都需要使用 Socket 库中的程序组件。
 使用 Socket 库来收发数据的操作过程如下图所示 。简单来说,收发数据的两台计算机之间连接了一条数据通道,数据沿着这条通道流动,最终到达目的地。我们可以把数据通道想象成一条管道,将数据从一端送入管道,数据就会到达管道的另一端然后被取出。数据可以从任何一端被送入管道,数据的流动是双向的。不过,这并不是说现实中真的有这么一条管道,只是为了帮助大家理解数据收发操作的全貌。
在这里插入图片描述

7 协议栈

 协议栈是指操作系统中的网络控制软件,协议栈的内部结构为:
在这里插入图片描述
 图中最上面的部分是网络应用程序,也就是浏览器、电子邮件客户端、Web 服务器、电子邮件服务器等程序,它们会将收发数据等工作委派给下层的部分来完成。当然,除了浏览器之外,其他应用程序在网络上收发数据的操作也都是类似上面这样的,也就是说,尽管不同的应用程序收发的数据内容不同,但收发数据的操作是共通的。
 应用程序的下面是 Socket 库,其中包括解析器,解析器用来向 DNS服务器发出查询。再下面就是操作系统内部了,其中包括协议栈。协议栈的上半部分有两块,分别是负责用 TCP 协议收发数据的部分和负责用 UDP 协议收发数据的部分,它们会接受应用程序的委托执行收发数据的操作。
 TCP 和UDP是面向传输层的两种协议;像浏览器、邮件等一般的应用程序都是使用 TCP 收发数据的,而像 DNS 查询等收发较短的控制数据的时候则使用 UDP。
 总的来说,上层向下层发出指令,下层执行上层指令;应用程序调用Socket库,Socket库调用了协议栈,协议栈又调用了网卡驱动程序,最终将消息发送出去。

8 Socket库收发数据基本步骤

 利用socket收发数据的操作分为若干个阶段,可以大致总结为以下 4 个。
  1.创建套接字(创建套接字阶段)
  2.将管道连接到服务器端的套接字上(连接阶段)
  3.收发数据(通信阶段)
  4.断开管道并删除套接字(断开阶段)

客户端和服务器之间收发数据操作的情形
在这里插入图片描述

二、基于TCP的网络应用程序

 基于TCP(面向连接)的socket编程的服务器端程序流程如下:

  1. 创建套接字(socket)
  2. 将套接字绑定到一个本地地址和端口上(bind)
  3. 将套接字设为监听模式,准备接受客户请求(listen)
  4. 等待客户请求到来;当请求到来时,接受连接请求,返回一个新的对应于此次连接的套接字(accept)。
  5. 用返回的套接字和客户端进行通信(send/recv).
  6. 返回,等待另一客户请求。
  7. 关闭套接字

 基于TCP(面向连接)的socket编程的客户端程序流程如下:

  1. 创建套接字(socket).
  2. 向服务器发出连接请求(connect).
  3. 和服务器进行通信(send/recv).
  4. 关闭套接字。

 服务器端的实现为

// TCPSrv.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//
#include <Winsock2.h>
#include <iostream>
#include <ws2tcpip.h>

//#define _WINSOCK_DEPRECATED_NO_WARNINGS
#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) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		return EXIT_SUCCESS;
	}

	/* Confirm that the WinSock DLL supports 2.2.*/
	/* Note that if the DLL supports versions greater    */
	/* than 2.2 in addition to 2.2, it will still return */
	/* 2.2 in wVersion since that is the version we      */
	/* requested.                                        */

	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		WSACleanup();
		return EXIT_SUCCESS;
	}


	//第一步:创建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

	//第二步:将套接字绑定到本地地址和端口上
	SOCKADDR_IN addSrv;
	//使用网络字节序,可以将IP地址指定为INADDR_ANY,允许套接字向任何分配给
	//本地机器的IP地址发送或接受数据。
	//如果我们想让套接字使用多个IP中的一个地址,就必须指定实际地址,这需要用到
	//inet_addr函数来实现
	addSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addSrv.sin_family = AF_INET;
	addSrv.sin_port = htons(6000);	
	bind(sockSrv, (SOCKADDR*)&addSrv, sizeof(SOCKADDR));//绑定套接字

	//第三步:将套接字设为监听模式,准备接受客户请求,
	//第二个参数是指等待连接队列的最大长度,比如说此时等待连接队列是5,那么下一个连接请求会被拒绝
	listen(sockSrv, 5);

	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	while (true)
	{
		//第四步:等待客户连接到来
		//accept 函数提取套接字 s 上挂起连接队列上的第一个连接。然后,它会创建一个新的套接字,并将其句柄并将其返回。
		//新创建的套接字是将处理实际连接的套接字;它具有与套接字相同的属性,包括使用 WSAAsyncSelect 或 WSAEventSelect 函数注册的异步事件。
		SOCKET sockConn = accept(sockSrv, (SOCKADDR*)&addrClient, &len);
		char sendBuf[100];
		sprintf_s(sendBuf, "Welcome %s to http://www.sunxin.org", inet_ntoa(addrClient.sin_addr));

		//第五步:用返回的套接字和客户端通信
		//发送数据,为了让接收端在接受数据以后,可以在该数据字符串之后增加一个“\0”结尾标志
		send(sockConn, sendBuf, strlen(sendBuf) + 1, 0);
		char recvBuf[100];
		//接收数据
		recv(sockConn, recvBuf, 100, 0);

		//打印接受到的数据
		printf("%s\n", recvBuf);
		//第六步:关闭套接字,此时程序进入到下一循环
		closesocket(sockConn);
	}
}

 客户端的实现如下

// TCPClient.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <Winsock2.h>
#include <iostream>
#include <ws2tcpip.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 EXIT_SUCCESS;
	}

	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		WSACleanup();
		return EXIT_SUCCESS;
	}

	//第一步:创建套接字
	SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);

	//第二部:向服务器发送连接请求
	SOCKADDR_IN addrSrv;
	//地址127.0.0.1 是本地回路地址。主机环回是指地址为 127.0.0.1 的任何数据包
	//都不应该离开计算机(主机),发送它——而不是被发送到本地网络或互联网,它只是被自己“环回”,
	//并且发送数据包的计算机成为接收者。
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	connect(sockClient, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//第三步:和服务器进行通信
	//接受数据
	char recvBuf[100];
	recv(sockClient, recvBuf, 100, 0);
	printf("%s\n", recvBuf);
	//发送数据
	send(sockClient, "This is lisi", strlen("This is lisi") + 1, 0);

	//第四步:关闭套接字
	closesocket(sockClient);

	//释放资源
	WSACleanup();
	getchar();
}

三、基于UDP的网络应用程序

 基于UDP(面向无连接)的socket编程的服务器端程序流程如下:

  1. 创建套接字(socket)
  2. 将套接字绑定到一个本地地址和端口上(bind)
  3. 等待接受数据(recvfrom)
  4. 关闭套接字

基于UDP(面向无连接)的socket编程的客户端程序流程如下:

  1. 创建套接字(socket).
  2. 向服务器发送数据(sendto).
  3. 关闭套接字。

  注意,在基于UDP的套接字编程时,利用的是sendto和recvfrom两个函数;而基于TCP的套接字编程时,发送和接受分别使用send和recv函数。
  服务器端的程序实现如下:

// UDPSrv.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <Winsock2.h>
#include <iostream>
#include <ws2tcpip.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 EXIT_SUCCESS;
	}

	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		WSACleanup();
		return EXIT_SUCCESS;
	}

	//第一步:创建套接字
	SOCKET sockSrv = socket(AF_INET, SOCK_DGRAM, 0);

	//第二步:将套接字绑定到一个本地地址和端口上
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);
	bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//第三步:等待接收数据
	SOCKADDR_IN addrClient;
	int len = sizeof(SOCKADDR);
	char recvBuf[100];
	recvfrom(sockSrv, recvBuf, 100, 0, (SOCKADDR*)&addrClient, &len);
	printf("%s\n", recvBuf);

	//第四步:关闭套接字
	closesocket(sockSrv);

	WSACleanup();
	getchar();
}

 客户端的程序实现如下:

// UDPClient.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <Winsock2.h>
#include <iostream>
#include <ws2tcpip.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 EXIT_SUCCESS;
	}

	if (LOBYTE(wsaData.wVersion) != 2 ||
		HIBYTE(wsaData.wVersion) != 2) {
		/* Tell the user that we could not find a usable */
		/* WinSock DLL.                                  */
		WSACleanup();
		return EXIT_SUCCESS;
	}

	//第一步:创建套接字
	SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(6000);

	//第二步:向服务器发送数据(sendto)
	sendto(sockClient, "Hell0", strlen("Hello") + 1, 0, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));

	//第三步:关闭套接字
	closesocket(sockClient);

	WSACleanup();
	getchar();
}

 下一节,我们将学习异步套接字的使用,欢迎移步至:
MFC网络编程2——异步套接字

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
精通Windows Sockets网络开发——基于Visual C++实现 目 录 第1篇网络开发基础篇 第1章准备开发环境 1.1windows sockets开发概述 1.1.1网络程序开发应用 1.1.2网络程序结构——c/s、b/s 1.1.3网络程序通信基础——网络协议 1.1.4网络程序通信技术——windows sockets介绍 1.2连接网络 1.2.1tcp/ip设置 1.2.2tcp/ip是否工作正常 1.2.3系统与网络适配器间的通信 1.2.4默认网关 1.2.5ping其他计算机ip地址 1.3创建应用程序 1.3.1控制台程序 1.3.2mfc应用程序 1.4调试两个应用程序 1.4.1启动两个工程 1.4.2将一个工程加入到另一个工程空间 1.5配置开发环境 1.6小结 第2章tcp/ip简介 2.1开放系统互连参考模型 2.2tcp/ip协议概述 2.2.1tcp/ip模型 2.2.2udp 2.2.3tcp 2.2.4端口 2.3小结 第3章windows sockets基础 3.1windows sockets 3.1.1应用程序与windows sockets的关系 3.1.2套接字 3.2协议特征 3.2.1面向连接与面向无连接 3.2.2可靠性与次序性 3.2.3面向消息 3.2.4部分消息 3.2.5从容关闭 3.2.6路由选择 3.2.7广播数据 3.3ip定址 3.3.1ip定址 3.3.2字节顺序问题 3.4基本tcp套接字编程 3.4.1wsastartup()函数 3.4.2socket()函数 3.4.3bind()函数 3.4.4listen()函数 3.4.5accept()函数 3.4.6recv()函数 3.4.7send()函数 3.4.8closesocket()函数 3.4.9shutdown()函数 3.4.10connect()函数 3.5tcp示例程序 3.5.1服务器实现 3.5.2客户端实现 3.6基本udp套接字编程 3.6.1recvfrom()函数 3.6.2sendto()函数 3.7udp示例程序 3.7.1服务器实现 3.7.2客户端实现 3.8套接字选项41 3.8.1getsockopt()函数 3.8.2setsockopt()函数 3.8.3sol_socket选项级别 3.9小结 第2篇visual c++网络模式开发篇 第4章阻塞模式开发 第5章非阻塞模式开发 第6章select模型开发 第7章wsaasyncselect模型开发 第8章wsaeventselect模型开发 第9章重叠i/o模型开发 第10章完成端口模型开发

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

_Santiago

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值