参考自《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,对等层通信的实质就是: 对等层实体之间虚拟通信。下层向上层提供服务,实际通信在最底层完成。
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");