Vc++ 网络编程

这一小节介绍网络编程。首先我们介绍一下计算机网络的基本知识,然后着重介绍一下Windows Socket程序的编写。
首先,介绍几个基本概念。什么是计算机网络?它是相互连接的独立自主的计算机的集合。它们是如何通信的呢,需要一个东西来表明我要跟哪个计算机进行通信,在网络上,为每个计算机分配了一个“IP”地址,通过地址来找到想要通信的计算机。具体的通信是计算机的某个程序实现的,一台计算机可能同时有多个程序在使用网络。为了区分它们,为每个程序提供了一个“端口号”来标识自己。具体通信时所发送的内容,成为协议,它规定了我们发送的格式:除了发送的内容以外,还包括这些东西是谁发送的,要发给谁,总共有多长等等。而信息具体是如何从一台主机,发送到另一台主机,则会有很大的不确定性:
不同的通信媒介:是通过有线传输的,还是无线网络?
不同的操作系统:Unix、Windows
不同的应用环境:移动、固定
不同的业务类型:对时延的要求、对差错控制的要求。
等等,使得实际中的相互通信的网络异常复杂,如何解决这个问题呢?国际标准化组织(ISO)提出了OSI(Open System Interconnected)七层参考模型,将网络按照不同的功能划分为7层:

应用层
表示层
会话层
传输层
网络层

数据链路层

物理层


它们的功能简述如下:
物理层:提供二进制传输,确定在通信信道上如何传输比特流。一条物理信道上所能传送的信息的最快速度是有限制的,不是我们想传送多块就能传送多快的。为了对抗复杂的传输环境(这一点在无线通信中尤其明显),物理层通常要使用非常复杂的调制、编码技术,来在在一定差错容忍度的前提下,尽可能的多发送。
数据链路层:提供介质访问,增强物理层的传输功能,建立一条无差错的传输线路。比如对于收到物理层发送过来的数据,需要通过确认请求或者简单的差错控制编码(比如奇偶效验)来判断这一帧数据是否有错误,如果错误了,则通知发送发重新发送。
网络层:IP寻址和路由。因为网络上的数据可以经由多条路线到达目的地,所以其中要考虑路由算法、拥塞控制等问题。
传输层:为源端主机到目的端主机提供可靠的数据传输服务,隔离网络的上下层协议,是得网络应用于下层协议无关。也就是应用程序与应用程序之间的连接。
会话层:两个相互通信的应用进程之间建立、组织和协调其相互之间的通信。比如电影里使用对讲机时,一句话说完后总要加一句“over”。
表示层:被传送的数据如何表示。
应用层:用户所提供的服务。
要注意,这7层模型是功能上的划分,并不是具体一定要有这七层。
下面介绍一下应用层、传输层和网络层的常见协议(这是笔试题中常考的):
应用层协议:远程登录协议(Telnet)、文件传输协议(FTP)、超文本传输协议(HTTP)、域名服务(DNS)、简单哟见传输协议(SMTP)、邮局协议(POP3)等。
传输层:传输控制协议(TCP)、用户数据报协议(UDP)。
这两个协议值得仔细说说。
TCP协议是面向连接的,也就是说,在双方通信之前,已经安排好了一条通信线路(不管它具体是什么)共他俩使用,别人不能使用,等他俩通信结束后,需要释放这条线路。TCP是通过3次握手建立的:
1.客户端给服务器发送SYN(syn = j)包,进入SYN_SEND状态。
2.服务器接收到SYN包,确认客户的SYN(ack = j+1),同时自己也发送一个SYN包(syn = k),把它俩都发送出去,服务器进入SYN_RECV状态。
3.客户端收到服务器的SYN+ACK包,向服务器发送ACK(ack = k+1)。客户端和服务器都进入ESTABLISHED状态。此时,连接已经建立完毕,可以发送消息了。
上面只是正常的建立连接过程,其中的任何一步都有可能失败,至于失败以后的操作,这里就不细说了。
下面再看看UDP协议:这是一种无连接的、不可靠的协议。这意味着可能向对方发送的消息时对方无法接到。或者,向一个根本不存在的IP地址或者端口发送消息。既然是不可靠的,为什么还要使用它呢?因为UDP协议不需要建立连接,没有数据重传,所以实时性较高。比如我们看视频时,一两个像素的错误我们根本不会发觉。
网络层:网际协议(IP),Internet互联网报文控制协议(ICMP)、Internet组管理协议IGMP。


由于7层模型在使用起来很不方便,实际中应用更广泛的是TCP/IP模型:


应用层
传输层
网络层

网络接口层


它与OSI七层模型的对应关系如下:网络接口层对应于物理层和数据链路层,网络层对应于网络层,传输层对应于传输层,应用层对应于会话层、表示层、应用层。
在TCP/IP网络应用中,通信的两个进程间相互作用的主要模式是客户机、服务器模式:客户向服务器发送请求,服务器收到请求后,提供相应的服务。为什么这么设计呢?首先,建立网络的原因是因为网络中软硬件资源、运算能力和信息分布不均,需要共享,从而拥有资源多的主机提供服务,资源少的客户请求服务。其次,网间进程通信完全是异步的,相互通信的进程间即不存在父子关系,又不存在共享的内存缓冲区,需要一种机制为希望通信的进程间建立联系,为二者的数据交换提供同步。
它们的通信过程如下:
服务器先运行:
1.打开一个通信通道并告知本地主机,他愿意在某一地址和端口上接收客户的请求。
2.等待客户请求到达端口。
3.接收到重复服务请求没处理该请求并发送应答信号。收到并发服务的请求,要激活一个新的进程或者线程来处理这个客户请求。新的进程或者线程处理此客户的请求,并不需要对其他请求作出相应。等服务完成后,关闭新进程与客户的通信链路,并终止。
4.返回第二步,等待另一请求。
5.关闭服务器。


客户端:
1.打开一个通信通道并连接到服务器所在的主机的特定端口。
2.向服务器发送服务请求报文,等待接收应答,继续提出请求。
3.请求结束后关闭通信通道并终止。


讲了这么多,我们可以隐约感觉到,网络编程是一件很麻烦的事情,为了方便的开发应用软件程序,美国伯克利大学在UNIX上推出了一种应用程序访问通信协议的操作系统套接字(socket),是得程序员可以很方便的访问TCP/IP协议,从而开发各种网络应用程序。后来,socket又引入到windows操作系统。我们先介绍与之相关的函数,然后给出几个例子:
1.使用WSAStartup进行版本协商。
int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData);
其中wVersionRequested指明了版本号。高字节是副版本,低字节是主版本,可以通过MAEWORD宏来获得。lpWSAData是用来返回值的,这个函数会把它加载的版本信息填到这个结构里面。具体的使用方法可以参照MSDN给出的例子。
2.使用WSACleanup()来释放为应用程序分配的资源,与WSAStartup相对应。
3.使用socket函数来创建套接字:
SOCKET socket(  int af, int type,int protocol  );
af参数是地址族,对于TCP/IP协议,它只能是AF_INET。
type指明了socket的类型,对于1.1版本的socket,他只接受两种类型:SOCK_STREAM、SOCK_DGRAM
protocol指明了与特定地址家族相关的协议,如果为0,则根据地址格式和套接字类别,自动选择一个合适的协议。
如果函数调用成功,则会返回一个SOCK数据类型的套接字描述符:如果调用失败,则返回一个INVALID_SOCKET值。
4.使用bind函数将套接字绑定到本地的某个地址和端口上:
int bind(  SOCKET s, const struct sockaddr FAR *name,int namelen);
s指定要绑定的套接字,name是一个指向sockaddr结构体类型的指针,这个结构体表明了本地信息。
struct sockaddr 
{
  u_short    sa_family;
  char       sa_data[14];
}; 
由于这个结构是为所有地址族准备的,所以不同的协议会有一定的区别,所以用第三个参数指明结构的长度。
再回过头来看第二个参数,第一成员指明了地址族,第二个成员是14个字节的内存区域,对于不同的协议,有不同的内容。对于TCP/IP协议,使用sockaddr_in 结构来替换sockaddr结构:
struct sockaddr_in 
{
        short   sin_family;
        u_short sin_port;
        struct  in_addr sin_addr;
        char    sin_zero[8];
};


sin_family指明了地址族,应使用AF_INET;sin_port指明了端口号;sin_addr指明了主机的IP地址;sin_zero则只是为了填充字节,是得sockaddr_in 与sockaddr长度相同。其中sin_addr又是一个结构体,定义如下:
typedef struct in_addr 
{
union 
{
struct
{
unsigned char s_b1,s_b2,s_b3,s_b4;
} S_un_b;
struct 
{
unsigned short s_w1,s_w2;
} S_un_w;
unsigned long S_addr;
} S_un;
} IN_ADDR


这个结构其实是一个联合体,通常我们都是将点分十进制的IP地址转换为u_long类型,并赋值给S_addr成员。
一般情况下,我们可以使用INADDR_ANY允许套接字向任何分配本地机器的IP地址发送或者接收数据。这个参数的实际意义在于,一般情况下,一台主机只有一个IP地址,但如果主机有两个网卡,那么他会有两个IP地址,如果我们只想使用其中的一个供套接字使用,我们可以使用inet_addr函数将本地的IP地址(点分十进制字符串形式),转化为unsigned long并赋给S_addr;与之相反的转化为inet_ntoa函数,将S_addr转化为点分十进制,供打印输出使用。
5.listen函数将指定的套接字设置为监听模式。
int listen(  SOCKET s, int backlog  );
s 为指定的套接字;backlog为等待队列的最大长度。
6.使用accept函数来接受客户端的连接请求
SOCKET accept(  SOCKET s,  struct sockaddr FAR *addr,  int FAR *addrlen);
s为已被设置为监听模式的套接字;addr为指向客户端的sockaddr的地址,通过函数来获取值;addrlen为地址长度。
6.通过send函数发送消息
int send(SOCKET s,const char FAR *buf,int len,int flags);
s为已建立连接的套接字,buf为要发送消息的地址,len为消息长度,flags一般设为0即可。
7.通过recv函数获取消息:
int recv(SOCKET s,char FAR *buf,int len,int flags);
s为已建立连接的套接字,buf用来保存接收的数据,len表示缓冲区的长度,flags一般填0即可。
8.使用connect函数与特定的套接字连接
int connect(  SOCKET s,const struct sockaddr FAR *name,int namelen);
s为即将建立连接的那个套接字;name指定了服务器端的地址信息,s为地址信息长度。
9.使用recvfrom接受消息
int recvfrom(SOCKET s,char FAR* buf,int len,int flags,struct sockaddr FAR *from,int FAR *fromlen);
s为准备接受数据的套接字,buf为接收数据的缓冲区,len为缓冲区的长度,flag一般填0,from指针用来存储发送方的地址信息,fromlen为地址的长度。
10.使用sendto向一个特定的目的方发送数据。
int sendto(  SOCKET s,const char FAR *buf,int len,int flags,const struct sockaddr FAR *to,int tolen );
s为套接字,buf为发送的数据的地址,len为数据的长度,flags一般为0,to指针指向目标套接字的地址,tolen为地址的长度
11.字节序的转换函数。
首先先搞清楚什么是字节序。一般情况下,我们使用电脑都是低位在前、高位在后的,这被称为小端字节序;而网络传输时使用的是低位在前,高位在后的大端字节序。如果不进行转化一个16为数据0X1234就被网络认为是0X3412了。转换的函数有两个:
u_short htons(  u_short hostshort  );将一个16位数转换为网络字节序
u_long htonl(  u_long hostlong  );将一个32位数转换为网络字节序


介绍完函数,我们就先举一个利用TCP协议编写的简单的网络通信的例子。我们先看一下基本步骤:
服务器端:
1.进行版本协商(WSAStartup)。
2.创建一个套接字(socket)。
3.将套接字设为监听状态(listen)。
4.接受客户端的发送请求(accept)。
5.发送或者接收数据(send/recv)。
6.关闭套接字(closesocket),一次通信结束。
7.转4.


客户端端:
1.进行版本协商(WSAStartup)。
2.创建一个套接字(socket)。
3.连接到服务器(connect)。
4.发送或者接收消息(send/recv)。
5.关闭套接字(closesocket)。
6.释放资源(WSACleanup)。
目录 (1)基本网络编程实例 Winsock实现网络聊天室【\chap1\ChatRoom(Winsock)】 CSocket实现聊天室【\chap1\ChatRoom(Csocket)】 (2)本地计算机网络编程实例 获取计算机的名称和IP地址【\chap2\Local】 获取计算机的子网掩码【\chap2\ Local】 获取计算机的DNS设置【\chap2\ Local】 获取计算机的网卡地址【\chap2\ Local】 获取计算机安装的协议【\chap2\ Local】 获取计算机提供的服务【\chap2\ Local】 获取计算机的所有网络资源【\chap2\ Local】 修改本地计算机的所有网络设置【\chap2\ Local】 获取计算机TCP/IP协议的所有信息【\chap2\ Local】 (3)局域网网络通信编程实例 获取网上邻居【\chap3\Neighbor】 lIP地址和计算机名之间的转换【\chap3\Neighbor】 l映射网络驱动器【\chap3\Neighbor】 l消息发送程序Net Send【\chap3\Neighbor】 l获取局域网内其他计算机的信息【\chap3\ NeighborInfo】 (4)IE编程实例 简单的浏览器的实现【\chap4\MyBrowser】 删除IE相关历史记录【\chap4\DelHistory】 将应用程序加入到IE工具栏【\chap4\AddToToolBar】 超级链接的实现【\chap4\HyperLink】 禁止IE的弹出窗口【\chap4\StopPopup】 禁止浏览某些网站【\chap4\StopTravel】 IE收藏夹【\chap4\ MyBrowser】 创建桌面快捷方式和活动桌面【\chap4\ShortCut】 (5)基本网络编程实例 点对点文件传输【\chap5\Transfer】 大型文件传输【\chap5\Transfer】 端口扫描程序【\chap5\ MyPortScanner】 Finger编程【\chap5\MyFinger】 Sniff编程【\chap5\MySniff】 Internet文件下载【\chap5\ InternetDownload】 (6)网络通信协议编程 FTP协议【\chap6\FTP】 Email协议【\chap6\Email】 ICMP协议【\chap6\ICMP】 RAS协议【\chap6\RAS】 TAPI协议【\chap6\TAPI】 Telnet协议【\chap6\Telnet】 HTTP协议 【\chap6\HTTP】 (7)Modem /串口通信编程 Modem编程【\chap7\Modem】 MSCOMM控件编程【\chap7\MSCOMM】 串口通信API编程【\chap7\MySerialCom】 (8)代理服务器编程实例 Socks 5协议编程【\chap8\Socks5】 HTTP代理服务器【\chap8\HTTP代理服务】 (9)高级网络通信编程实例 串口通信编程实例【\chap9\SerialPort】 网络流量监控【\chap9\NetTraffic】 网站下载【\chap9\ Snag】 网络五子棋系统【\chap9\FiveChess】 语音聊天【\chap9\ ChatRoom】 远程监控【\chap9\RemoteControl】
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值