《VC++深入详解–学习笔记》(14)网络编程

Filed Under (VC++ 学习笔记) by panmaoru on 18-12-2009

网络程序的实现可以有多种方式,Windows Socket就是其中的一种比较简单的实现方式。Socket是连接应用程序和网络驱动程序的桥梁,Socket在应用程序中创建,通过绑定操作和应用程序建立连接关系。

14.1计算机网络的基本知识

计算机网络是相互连接的独立自主的计算机的集合,最初建立的目的是实现资源的共享。其中比较重要的概念是协议,其目的是为了进行网络中的数据交换而建立的规则、标准或约定。不同的层有不同的协议。

14.1.1ISO/OSI七层参考模型

OSI(Open System Interconnection)参考模型将网络的不同功能分成了七层。由高到底的分层及其功能分别如下:

  1. 应用层:为用户的网络应用程序提供网络通信服务;
  2. 表示层:处理被传输数据的表示问题,即信息的语法和语义;
  3. 会话层:在两个相互通信的应用程序之间建立连接,组织和协调之间的通信;
  4. 传输层:为源短主机和目的端主机提供可靠的传输服务,隔离网络的上下层协议,使得网络和上下层协议无关;
  5. 网络层:提供IP寻址和路由,负责找到最佳的数据传输路线;
  6. 数据链路层:提供介质访问,加强物理层的传输功能;
  7. 物理层:提供二进制传输,确定在通信信道上如何传输比特流。

七层参考模型知识一个功能上的划分,不是物理上的划分,各层之间是严格的单向依赖,通信实体的对等层之间不容许出现直接通信,下次向上层提供服务,上层使用下层提供的服务。

14.1.2数据封装

一台计算机向另外一台计算机发送数据,必须将该数据打包,打包的过程被称为数据封装。由应用层向下,逐步为数据进行封装,在头部或尾部或同时添加本层的头部或尾部。当数据包到达目标计算机时,再从物理层开始,逐步去除头部或尾部,完成拆分。

14.1.3TCP/IP模型

OSI的七层参考模型比较复杂,通常采用TCP/IP模型,该模型有四个层次与OSI七层模型的对应关系为:

  1. 应用层:对应应用层+表示层+会话层;
  2. 传输层:对应传输层;
  3. 网络层:对应网络层;
  4. 网络接口层:对应数据链路层+物理层。

14.1.4端口

为了标识通信实体中进行通信的进程,TCP/IP提供了协议端口的概念。端口是一种抽象的软解结构,应用程序通过系统调用和某端口建立连接后,传输层传给该端口的数据都会被相应得应用程序接收,相依进程发送给传输层的数据也通过该端口输出。(端口存在与传输层?)

端口的范围是0~65355,1024以下的端口号通常都被系统应用程序占用。如http的80,ftp的21.

14.1.9套接字Socket的引入

Socket最早出现在UNIX系统中,后来被引入Windows。Windows Socket只支持一个通信区域:网际域,这个域被使用网际协议簇通信的进程使用。

14.2Windows Socket的实现

套接字分为三种类型,其中主要使用的由2种:

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

数据报式套接字SOCK_DGRAM:提供无连接服务,数据包独立发送,基于UDP协议;

14.2.1基于TCPsocket编程

基于TCP的socket编程的服务器端的流程如下:

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

示例代码如下:

#include <Winsock2.h>
#include <stdio.h>
void 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;
	}
	//create socket
	SOCKET socksrv=socket(AF_INET,SOCK_STREAM,0);
	SOCKADDR_IN addsrcv;
	addsrcv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	addsrcv.sin_family=AF_INET;
	addsrcv.sin_port=htons(6000);

	//bind
	bind(socksrv,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));
	//listen
	listen(socksrv,5);

	SOCKADDR_IN addrClient;
	int len=sizeof(SOCKADDR);

	while(1)
	{
		//wait for client
		SOCKET sockConn=accept(socksrv,(SOCKADDR*)&addrClient,&len);
		char buf[100];
		sprintf(buf,"welcome %s to www.colsir.com",inet_ntoa(addrClient.sin_addr));
		//send
		send(sockConn,buf,strlen(buf)+1,0);
		char recvbuf[100];
		recv(sockConn,recvbuf,100,0);
		printf("%s\n",recvbuf);
		closesocket(sockConn);
	}
}

基于TCP的socket编程的客户端端的流程如下:

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

示例代码如下:

#include <Winsock2.h>
#include <stdio.h>
void 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;
	}
	//create socket
	SOCKET sockclient=socket(AF_INET,SOCK_STREAM,0);
	SOCKADDR_IN addsrcv;
	addsrcv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
	addsrcv.sin_family=AF_INET;
	addsrcv.sin_port=htons(6000);

	//connect
	connect(sockclient,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));

	//recv
	char recvbuf[100];
	recv(sockclient,recvbuf,100,0);
	printf("%s\n",recvbuf);
	//send
	send(sockclient,"send message",strlen("send message")+1,0);
	closesocket(sockclient);
	WSACleanup();
}

14.2.2基于UDPsocket编程

基于UDP的socket编程的服务器端的流程如下:

  1. 创建套接字socket;
  2. 将套接字绑定到一个本地地址和端口上bind;
  3. 等待接收数据,接收完毕之后可以选择返回,等待再一次接收recvfrom;
  4. 关闭套接字。

示例代码如下:

#include <Winsock2.h>
#include <stdio.h>
void 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;
	}
	//create socket
	SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM/*UDP*/,0);
	SOCKADDR_IN addsrcv;
	addsrcv.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
	addsrcv.sin_family=AF_INET;
	addsrcv.sin_port=htons(6000);

	//bind
	bind(sockSrv,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));

	//recv
	while(1)
	{
		SOCKADDR_IN addServer;
		int len=sizeof(SOCKADDR);
		char recvbuf[100];
		recvfrom(sockSrv,recvbuf,100,0,(SOCKADDR*)&addServer,&len);
		printf("%s\n",recvbuf);
	}

	closesocket(sockSrv);
	WSACleanup();
}

基于UDP的socket编程的客户端端的流程如下:

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

示例代码如下:

#include <Winsock2.h>
#include <stdio.h>
void 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;
	}
	//create socket
	SOCKET sockclient=socket(AF_INET,SOCK_DGRAM,0);
	SOCKADDR_IN addsrcv;
	addsrcv.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
	addsrcv.sin_family=AF_INET;
	addsrcv.sin_port=htons(6000);

	sendto(sockclient,"hello",strlen("hello")+1,0,(SOCKADDR*)&addsrcv,sizeof(SOCKADDR));

	closesocket(sockclient);
	WSACleanup();
}

14.3小结

看完了书本上的介绍,把代码照着书本抄了一遍,对于socket编程有了一个初步的认识。真正完成能实用的应用程序要有很远的路。过程中遇到的问题做个简单的小结:

  1. 因为本程序使用了winsock库中的函数,按照动态链接库的使用规则,需要为程序链接相应的lib文件:ws2_32.lib;
  2. 在创建TCP和UDP套接字的时候需要注意类型的区别,二者分别对应:SOCKET sockSrv=socket(AF_INET,SOCK_STREAM/*TCP*/,0);和SOCKET sockSrv=socket(AF_INET,SOCK_DGRAM/*UDP*/,0);
  3. 换行符为”\n”,经常不小心就给写成了”/n”

Comments (1)

  1. 俺下一步准备了解高度封装的CSocket类,看上去比较有范儿

Post a comment