网络编程的一些理论

2018-01-12 10:01:58 浏览数 (2)

参考自《VC 深入详解》

  这是我在看书时记录下来的东西。

 注:下面的Socket其实都应该是socket

第14章网络编程

Socket是连接应用程序与网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定与驱动程序建立关系。

此后,应用程序给Socket的数据,由Socket交给驱动程序向网络上发送出去。

计算机从网络上收到与该Socket绑定的IP地址和端口号相关的数据后,由驱动程序交给Socket,应用程序便可从该Socket中提取接收到的数据。

14.1 计算机网络基本知识。

1,最简单的网络形式是由两台计算机组成,就酱

2,网络上主机间通信需要知道另一主机的名字。在Internet上用一个称为IP地址(4个字节)的整数来标识网络设备。

在Internet上,两台主机要通信,双方必须遵守约定的规则,称为协议。

计算机中运行着很多网络通信程序(迅雷、酷狗、浏览器等),要怎么区分呢?端口号:标识在计算机上运行的每一个网络通信程序。所以,指定IP外,还要指定端口号。

IP地址相当于总机号码,而端口号相当于分机号码。

14.1.1 IP地址

IP网络中每台主机必须有唯一的IP地址

IP地址是一个逻辑地址

因特网上的IP地址具有全球唯一性

32位,4字节。常用点分十进制表示,例如:192.168.0.1(每个字节用十进制整数来表示) 

注:127.0.0.1 称为是回送地址,指本地机,一般用来测试使用

14.1.2 协议  P533

为进行网络中的数据交换而建立的规则,标准或约定

不同层具有各自不同的协议。

14.1.5 ISO/OSI 七层参考模型

1,ISO国际标准化组织提出来OSI七层参考模型,将网络的不同功能划分为7层。如下图

从低到高各层的功能分别如下所述:

(1) 物理层:提供二进制传输,确定在通信信道上如何传输比特流

(2) 数据链路层:提供介质访问,加强物理层的传输功能,建立一条无差错的传输线路

(3) 网络层:提供IP寻址和路由。因为在网络上数据可以经由多条线路到达目的地,网络层负责找出最佳的传输线路

(4) 传输层:为源主机到目的端主机提供可靠的数据传输服务,隔离网络的上下层协议,使得网络应用与下层协议无关

(5) 会话层:在两个相互通信的应用程序之间建立、组织和协调其相互之间的通信

(6) 表示层:处理被传送数据的表示问题,即信息的语法和语义。如有必要,可使用一种通用的数据表示格式,在多种数据表示之间进行切换。

(7) 应用层:为用户的网络应用程序提供网络通信的服务

应注意一下几点:

(1) OSI七层参考模型并不是物理实体存在这七层,这只是一个功能的划分,是一个抽象的网络参考模型

(2) 在进行一个网络通信时,每一层为本次通信提供本层的服务。通信实体的对等层之间不允许直接通信

(3) 各层之间是严格单向依赖

(4) 上层使用下层提供的服务 – Service user

(5) 下层向上层提供服务 – Service provider

2,通信时数据传输的过程:在两个通信实体进行通信时,应用层所发出的数据经过表示层、会话层、传输层、网络层、数据链路层、最终到达物理层,在该层通过物理线路传输给另外一个实体的物理层。

然后,数据再依次向上传递,传递给另一个实体的应用层。

3,对等层通信的实质就是: 对等层实体之间虚拟通信。下层向上层提供服务,实际通信在最底层完成。

干货:对等通信peer-to-peer communication,为了使数据分组从源传送到目的地,源端OSI模型的每一层都必须与目的端的对等层进行通信,这种通信方式称为对等层通信。 在这一过程中,每一层的协议在对等层之间交换信息,该信息成为协议数据单元(PDU)。 简单来理解就是这样,假设主机A要和主机B通信。A的应用层只能与B的应用层交换信息,而不能与B的其他层(比如传输层)交换信息。

4,OSI7层参考模型中的应用层、传输层和网络层所用的协议:

(1) 应用层:远程登录协议Telnet,文件传输协议FTP(下载文件),超文本传输协议HTTP(浏览网页),

域名服务DNS(网址就是域名),简单邮件传输协议SMTP(发送邮件),邮局协议POP3(收取邮件)。

(2) 传输层:传输控制协议TCP:面向连接的可靠的传输协议,通信时要通过三步握手以建立通信双方的连接。

用户数据报协议UDP:无连接、不可靠的传输协议。不需要建立连接,可能会丢失数据,实时性较高。

(3) 网络层:网络协议IP、Internet互联网控制报文协议ICMP,Internet组管理协议IGMP。

14.1.6 数据封装

1,往另外一台计算机发送数据,首先要将该数据打包打包的过程称为封装。

2,封装就是在数据前面加上特定的协议头部,例如:用TCP协议传时,数据到传输层时,就会加上TCP协议头,在到达网络层时,在其前面加上IP协议头。

3,OSI参考模型中,对等协议之间交换的信息单元统称为协议数据单元(PDU)。

4,为了提供服务,下层把上层的PDU作为本层的数据封装,然后加入本层的头部(有点还要加尾部,如数据链路层)。头部的数据中含有数据传输所需的控制信息。

14.1.7 TCP/IP模型

1,起源于美国国防部高级研究规划署的一项研究计划,现在。已经称为Internet上通信的工业标准。

2,OSI参考模型比较复杂,目前应用较多的是TCP/IP模型,该模型包含4个层次:  应用层   传输层   网络层   网络接口层

14.1.8 端口

1,端口是一种抽象的软件结构(包括一些数据结构和I/O缓冲区)。应用程序通过系统调用与某端口建立连接(binding)后,传输层传给该端口的数据都被相应的进程所接收。

相应进程发给传输层的数据都通过该端口输出。

2,端口用一个整数型标识符来表示,即端口号。(0 – 65535,我们在编写网络应用程序时,要为程序指定1024以上的端口号)。

3,端口号跟协议相关,TCP/IP传输层的两个协议TCP和UDP是完全独立的两个软件模块,因此各自的端口号也相互独立。

也就是说基于TCP和UDP协议的不同的网络应用程序,它们可以拥有相同的端口号。

14.1.9 套接字的引入

1,伯克利大学推出一种应用程序访问通信协议的操作系统调用套接字。Socket的出现,使程序员可以很方便的访问TCP/IP,从而开发各种网络应用的程序。

2,套接字存在于通信区域中。通信区域也叫地址族,是一个抽象的概念,主要用于将通过套接字通信的进程的共有特性综合在一起。套接字通常只于同一区域的套接字交换数据。

Windows Socket只支持一个通信区域:网际域(AF_INET),这个域被使用网际协议簇通信的进程使用。

14.1.10 网络字节顺序

1,小端模式:低位存在低地址。(低位先存)

2,大端模式:高位存在低地址。(高位先存)

3,基于Inter的CPU,我们常用的PC机采用的是小端模式,为了保证数据的正确性,在网络协议中需要指定网络字节顺序。

4,TCP/IP协议使用16位整数和32位整数的高位先存格式(大端模式)。

5,在网络中不同主机间进行通信时,要统一采用网络字节顺序。

14.1.11 客户机/服务器模式(C/S)

1,在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户机/服务器模式(客户向服务器提出请求,服务器收到请求后,提供相应的服务)。

2,客户机/服务器模式在操作过程中采取的是主动请求的方式。

首先服务器方要先启动,并根据请求提供相应的服务:

(1) 打开一个通信通道并告知本地主机,它愿意在某一地址和端口上接收客户请求。

(2) 等待客户请求到达该端口

(3) 接收到重复服务请求,处理该请求并发送应答信号。接收到并发服务请求,要激活一个新的进程(或线程)来处理这个客户请求。

新进程(线程)处理此客户请求,并不需要对其他请求做出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。

(4) 返回第二步

(5) 关闭服务器

而客户方:

(1) 打开一个通信通道,并连接到服务器所在主机的特定端口。

(2) 向服务器发送服务请求报文,等待并接收应答:继续提出请求,

(3) 请求结束后关闭通信通道并终止。

14.2 Windows Socket的实现

14.2.1 套接字的类型:

(1)流式套接字(SOCK_STREAM):提供面向连接、可靠的数据传输服务,数据无差错、无重复的发送,且按照发送顺序接收。基于TCP协议实现的

(2)数据报套接字(SOCK_DGRAM):提供无连接服务,数据包以独立包形式发送,不提供无错保证,数据可能丢失或重复,接收顺序混乱。基于UDP协议实现的

(3)原始套接字(SOCK_RAW)

14.2.2 基于TCP的Socket编程

服务器端程序流程如下:

(1) 创建套接字(socket)

(2) 将套接字绑定到一个本地地址和端口上(bind)

(3) 将套接字设为监听模式,准备接收客户请求(listen)

(4) 等待客户机请求到来:当请求到来时,接收连接请求,返回一个新的对应于此次连接的套接字(accept)】

(5) 用返回的套接字和客户端进行通信(send/recv)

(6) 返回,等待另一个客户请求

(7) 关闭套接字

客户端程序流程如下:

(1) 创建套接字(socket)

(2) 向服务器发出连接请求(connect)

(3) 和服务器端进行通信(send/recv)

(4) 关闭套接字

服务器端,调用accept函数时,程序就会等待,等待客户端调用connect函数发出连接请求,然后服务器接收该请求,于是双方就建立了连接。

之后,服务器和客户端就可以通过recv/send进行通信了

客户端不要调用bind,因为服务器需要接收客户端的请求,所以必须告诉本地主机打算在哪个IP地址和哪个端口上等待客户请求,因此必须调用bind来实现这一功能。

客户端发起连接,服务器接收该请求后,在服务器就保存了客户端的IP地址和端口的信息,这样就可以利用所返回的套接字调用recv/send函数与客户端进行通信了。

14.2.3 基于UDP(面向无连接的)socket编程

1,服务器端也叫接收端,先启动的一端称为接收端,发送数据的一段称为发送端,也称客户端。(这个概念好像和Linux的有点不一样)

接收端程序的编写:

(1) 创建套接字(socket)

(2) 将套接字绑定到一个本地地址和端口上(bind)

(3) 等待接收数据(recvfrom) // 不是recv     linux这里也可以发送数据

(4) 关闭套接字

客户端程序的编写:

(1) 创建套接字(socket)

(2) 向服务器发送数据(sendto) // 不是send

(3) 关闭套接字

套接字相当于电话机,IP地址相当于总机号码,端口相当于分机

14.3 相关函数

14.3.1 WSAStartup函数(加载套接字库)

1,利用套接字编程时,第一步要加载套接字库。这个函数有两个功能:

(1) 加载套接字库

(2) 进行套接字库的版本协商,就是确定将使用的socket版本

2,每个WSAStartup成功调用(成功加载winsock动态库以后),在最后都会对应一个WSACleanUp调用,以便释放为该应用分配的资源。

   终止对Winsock动态库的使用。

14.3.2 socket函数:加载库之后,用这个函数创建套接字了

1,原型声明如下啊:

14.3.3 bind函数:创建套接字以后,要将该套接字绑定到本地的某个地址和端口上

1,原型

Int bind(SOCKET s, const struct socketaddr FAR *name, int namelen);

成功返回0,失败返回一个SOCKET_ERROR,错误信息可以在WSAGetLastError函数返回。

s :指定要绑定的套接字

name :指定了该套接字的本地地址信息,由于该地址结构是为所有的地址家族准备的,这个结构可能随所使用的网络协议不同而不同

namelen :指定该地址结构的长度

2,sockaddr结构

struct socketaddr

{

u_short sa_family; // 指定地址家族,对于TCP/IP协议的套接字,必须设置为 AF_INET

char sa_data[14];  // 仅仅表示要求一块内存分配区,起到占位的作用

};

3,在基于TCP/IP的socket编程过程中,可以用sockaddr_in结构替换sockaddr

struct socketaddr_in

{

short sin_family; // 地址族,对于IP地址,这个值一直是 AF_INET

unsigned short sin_port; // 指定将要分配给套接字的端口

struct in_addr sin_addr; // 套接字的主机IP地址

char sin_zero[8];// 填充数

};

4,sockaddr_in结构中的sin_addr成员类型是in_addr,这个实际上是一个联合(union),

  通常利用这个结构将一个点分十进制格式的IP地址转换为u_long类型。

14.3.4 inet_addr和inet_ntoa函数

1,将IP地址指定为INADDR_ANY,允许套接字向任何分配给本地机器的IP地址发送或接收数据。

2,每个机器只有一个IP,但有的机器有多个网卡,每个网卡都会有自己的IP地址。

3,如果想让套接字使用多个IP中的一个地址,就必须指定实际地址,可以用inet_addr函数来实现。

4,unsigned long inet_addr(const char FAR *cp);

 Cp:指定了以点分十进制格式表示的IP地址

5,char FAR *inet_ntoa(struct in_addr in);

这个函数会完成相反的转换,接收一个in_addr结构体类型的参数并返回一个以点分十进制表示的IP地址字符串

14.3.5 listen函数:将指定的套接字设置为监听模式

1,int listen(SOCKET s, int backlog);

s:套接字描述符

backlog:等待连接队列的最大长度

2,backlog是为了设置等待连接队列的最大长度,不是在一个端口上同时可以进行连接的数目。

(假如设为2,有3个请求同时来的时候,前两个会放到等待请求连接队列中,然后由应用程序一次为这些请求服务,第三个连接请求被拒绝了)

14.3.6 accept函数:接收客户端发送的连接请求

1,SOCKET accept(SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);

s:套接字描述符,这个套接字已经设置为监听状态

addr:指向一个缓冲区的指针,用来接收连接实体的地址(客户端连接时,保存这个客户端的Ip地址信息和端口信息)

addrlen:也是返回参数,返回包含地址信息的长度

14.3.7 send函数:向一个已经建立连接的套接字发送数据

1, int send(SOCKET s, const char FAR *buf, int len, int flags);

s:已经建立连接的套接字

buf:指向一个缓冲区,包含将要传递的数据

len:缓冲区的长度

flags:这个值将影响函数的行为,一般设为0

14.3.8 recv函数:从一个已连接的套接字接收数据

1, int recv(SOCKET s, const char FAR *buf, int len, int flags);

s:已经建立连接的套接字

buf:指向一个缓冲区,保存接收的数据

len:缓冲区的长度

flags:这个值将影响函数的行为,一般设为0

14.3.9 connect:与一个特定的套接字建立连接(客户端连接服务器)

Int connect(SOCKET s, const struct socketaddr FAR *name, int namelen);

s:即将在其上就建立连接是那个套接字

name:设定连接的服务器端地址信息

namelen:指定服务器端地址长度

14.3.10 recvfrom:接收一个数据报信息并保存源地址

1,int recvfrom(

SOCKET s,  // 准备接收数据的套接字

char FAR* buf,  // 指向一个缓冲区的指针,用来接收数据

int len, // 缓冲区的长度

int flags, // 与send函数的第四个参数类似

struct sockaddr FAR* from,  // 接收发送数据方的地址信息

int FAR* fromlen // 输入输出参数,函数调用之后,会通过这个参数返回一个值,该返回值是地址结构的大小

);

14.3.11 sendto:向一个特定的目的方发送数据

1,int sendto(

SOCKET s,  // 一个套接字描述符(可能已经建立连接)

char FAR* buf,  // 指向一个缓冲区的指针,包含将要发送的数据

int len, // 缓冲区的长度

int flags, // 与send函数的第四个参数类似

struct sockaddr FAR* to,  // 可选的指针,指定目标套接字的地址

int FAR* tolen // 参数to中指定的地址的长度

);

14.3.12 htons和htonl函数

1,u_shorts htons(u_short hostshort); // 把一个u_short类型的值从主机字节顺序转换成TCP/IP网络字节顺序

参数:一个以主机字节顺序表示的16位数值

2,u_long htonl(u_long hostshort); // 把一个u_long类型的值从主机字节顺序转换成TCP/IP网络字节顺序

参数:一个以主机字节顺序表示的32位数值

注意:当链接不到这个库的时候,可以这样进行显示加载(这个是我测试时遇到的问题)

#include<Winsock2.h>

// 显示加载这个库WS2_32.DLL

#pragma comment(lib, "WS2_32");

下面这张图很早以前找的,已经忘了原出处了,我觉得可以加深我们对网络编程的理解,就搬过来了。

如果有人知道原出处在哪麻烦告知一下。 

0 人点赞