VC++ 网络编程总结(一)

时间:2022-05-14 19:36:02

1、套接字编程原理

        一个完整的网间通信进程需要由两个进程组成,并且只能用同一种高层协议。也就是说,不可能通信的一段用TCP,而另一端用UDP。一个完整的网络信息需要一个五元组来标识:协议、本地地址、本地端口号、远端地址、远端端口号。

1.1Client/Server通信模型

        在客户端/服务器模式中我们将请求服务的一方成为客户,将提供某种服务的一方称为服务器(Server)

       一个服务程序通常在一个众所周知的地址监听对服务的请求,也就是说服务进程一直处于休眠状态,直到一个客户对这个服务的地址提出了连接请求。在这个时刻,服务程序被“惊醒”,并且为客户提供服务----对客户的请求做出适当的反应。虽然基于链接的服务是设计客户机/服务器应用程序时的标准,但有些服务也是可以通过无连接的接口提供的。

         客户及/服务器的请求/响应过程示意图如下所示:

                 VC++ 网络编程总结(一)

1.2Windows Sockets规范

        Windows Sockets规范从90年代的1.0版本开始,经过不断的完善和发展,目前已经有了Windows Sockets 2版本。值得注意的是,Microsoft的MFC库现在只支持Windows Sockets1版本,不支持WindowsSockets2版本。

        MFC提供了两个类用以封装Windows Sockets API。一个是CAsyncSocket类,它主要是提供给那些具有一定网络编程经验,希望同时拥有Socket API编程的灵活性和类库编程便利性的开发者。另一个是CSocket类,它有CAsyncSocket类派生,它具有更高的抽象化,致力于简化网络编程所需的操作。

1.3套接字

1.3.1套接字定义

      套接字是一个通信终结点,它是Sockets应用程序用来在网络上发送或接收数据包的对象。套接字具有类型,与正在运行的进程相关联,并且可以有名称。目前,套接字一般只与使用网际协议组的同一“通信域”中德其他套接字交换数据。使用套接字的应用程序间通信模型如图:

                VC++ 网络编程总结(一)

1.3.2分类

   1.3.2.1流式套接字

       流式套接字提供没有记录边界的数据流,即字节流。字节流能确保以正确的顺序无重复地被送达。

VC++ 网络编程总结(一)

1.3.2.2数据报套接字

        数据报套接字支持面向记录的数据流,但不能确保能被送达,也无法确保按照发送顺序或不重复。

       VC++ 网络编程总结(一)

     “有序”指数据包按发送的顺序送达。“不重复”指一个特定的数据包只能获取一次。这两种套接字都是双向,是可以同时在两个方向上(全双工)进行通信的数据流.

       注意:在某些网络协议下(如XNS),流可以面向记录,即作为记录流而非字节流。但在更常用的TCP/IP协议下,流为字节流,Win Sockets提供与基础协议无关的抽象化级别。

1.3.3套接字的作用

      套接字的作用非常大,至少在下面三种通信上下文中如此:

  • 客户端/服务器模型
  • 对等网络方案,如聊天应用程序
  • 通过让接受应用程序将消息解释为函数调用来进行远程过程调用

1.3.4 端口与地址

     在网络上,一个套接字的标识主要借助于地址和端口来描述。

     套接字的地址指该套接字所在计算机的网络地址,可以为域名或IP地址的形式。通常创建套接字时不必指明网络地址,只有在拥有多个网络地址的机器时,才需要显式指定的一个网络地址。

      同一机器上可以运行多个网络应用程序,每个应用程序都有自己的套接字用以进行网络通信,此时如果只有地址表示套接字,则当一个通信包到达机器时,将无法确定究竟是哪个应用程序的套接字需要接收此信息。由此增加了端口的概念,以协助区分同一机器上不同应用程序的套接字。

      段开口用于标识进程,同一机器上不同的网络应用程序各有不同的端口,这样,通过“网络地址+端口号”的标识方法,便唯一标识了机器上的应用程序了。

实例应用程序:

======客户端

#include < WINSOCK2.H>
#pragma comment( lib, "WS2_32" )
#include < stdio.h> int main()
{
printf( "------------------------\n| 客户端 |\n|---------------------------------------\n" );
//------①加载动态链接库winsock DLL-----------
printf( "|加载等待中.... " );
WSADATA wsaData;
WORD wVersionRequested= MAKEWORD( 2 ,2 );
if ( WSAStartup( wVersionRequested,& wsaData)!= 0 )
{
printf( "|WSAStartup Failed\n" );
printf( "|WSAStartup Error=%d\n" , WSAGetLastError());
}
else
{
printf( "加载Winsock 库成功 |\n" );
}
printf( "|---------------------------------------\n" );
//-------②创建用于监听的流式套接口s,使用socket()-----------------
SOCKET s= socket( AF_INET, SOCK_STREAM, IPPROTO_TCP);
if ( s== INVALID_SOCKET)
{
printf( "|Failed socket\n" );
printf( "|socket Error=%d\n" , WSAGetLastError());
}
else
printf( "|已创建用于监听的套接口,套接口号:[%u]\n" , s);
printf( "|---------------------------------------\n" );
//------③本地地址bind(可以不做这部分,如果不绑定,系统将自动分配)--------
/*struct sockaddr_in Cadd;
Cadd.sin_family=AF_INET;
Cadd.sin_port=htons(4444);
Cadd.sin_addr.S_un.S_addr=htonl(INADDR_ANY);
if (bind(s,(sockaddr*)&Cadd,sizeof(Cadd))==SOCKET_ERROR)
{
printf("|Failed bind()/n");
}*/
//-------填写要连接的服务器地址信息---------
struct sockaddr_in Sadd;
Sadd.sin_family= AF_INET;
Sadd.sin_port= htons( 5555 );
Sadd.sin_addr.S_un.S_addr= inet_addr( "127.0.0.1" );
//--------④将套接口s与远程主机相连--------------
if ( connect( s,( sockaddr*)& Sadd, sizeof ( Sadd))== INVALID_SOCKET)
{
printf( "|Failed connect()\n" );
printf( "|connect Error=%d\n" , WSAGetLastError());
}
else
{
//####################开始发接数据########################
printf( "|连接成功,可以开始发送接收数据了!\n" );
printf( "|服务器IP地址:[%s]\n 端口号:[%u]\n" , inet_ntoa( Sadd.sin_addr), ntohs( Sadd.sin_port));
//####################结束发接数据########################
}
//--------------⑤关闭套接字s,终止对动态链接库的访问----------
closesocket( s);
printf( "|---------------------------------------\n" );
printf( "|连接完毕\n" );
WSACleanup();
return 0 ;
}

========服务端

#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32") int main(int argc, char* argv[])
{
printf("---------\n| 服务端 |\n-----------");
//----------1、加载动态链接库Winsock Dll---------
WORD wVersionRequested = MAKEWORD(2,2);
WSADATA wsaData;
if(WSAStartup(wVersionRequested,&wsaData) != 0)
{
printf("WSAStartup Failed \n");
printf("WSAStartup Error = %d \n",WSAGetLastError());
}
else
{
printf("加载WinSocket成功 \n");
printf("调用者希望使用的WinSocket版本号=%x\n",wsaData.wVersion);
printf("加载的WinSock库支持最高WinSock版本号=%x \n",wsaData.wHighVersion);
printf("wWinSock库的说明字符串=%s \n",&wsaData.szDescription[0]);
printf("系统状态或配置信息的说明字符串=%s\n",&wsaData.szSystemStatus[0]);
}
//----------2、创建监听的流式套接口---------- SOCKET s=socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
if(s == INVALID_SOCKET)
{
printf("| Failed socket\n");
printf("| Socket Error = %d\n",WSAGetLastError());
}
else
printf("毅创建用于监听的套接口,套接口号:[%u]\n",s); //----3、绑定使用bind()
struct sockaddr_in Sadd;
Sadd.sin_family = AF_INET;
Sadd.sin_port = htons(5555);
Sadd.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
if(bind(s,(sockaddr*)&Sadd,sizeof(Sadd)) == SOCKET_ERROR)
{
printf("Failed bind()\n");
printf("Bind Error = %d\n",WSAGetLastError());
}
else
{
printf("绑定成功\n");
printf("本地Ip地址:[%s],本地端口号:[%u]\n",inet_ntoa(Sadd.sin_addr),ntohs(Sadd.sin_port));
}
//---------------④监听状态-------------
if ( listen( s, 2 )== SOCKET_ERROR)
{
printf( "Failed listen()\n" );
printf( "listen Error=%d\n" , WSAGetLastError());
}
//----------------⑤循环接受客户的连接请求---------------------------
struct sockaddr_in Cadd;
int caddLen= sizeof ( Cadd);
SOCKET c;
while ( TRUE )
{
printf( "|----------------------------\n" );
printf( "|进入监听状态.....\n|--------------------------------|\n" );
c= accept( s,( sockaddr*)& Cadd,& caddLen);
if ( c== INVALID_SOCKET)
{
printf( "|Failed accept()\n" );
printf( "|accept Error=%d\n" , WSAGetLastError());
}
else
printf( "|可以在套接口[%u]上发送接收数据了!\n" , c);
//#########################开始发送、接收###################### 注意都要在新套接口c上进行
//#########################结束发送、接收######################
closesocket( c);
printf( "|与主机IP地址是:[%s]\n|端口号是:[%u]的连接完毕\n" , inet_ntoa( Cadd.sin_addr), ntohs( Cadd.sin_port));
char xx;
printf( "|-------------------------------------\n需要退出吗?(Y\n)" );
scanf( "%c" ,& xx);
if ( xx== 'Y' || xx== 'y' )
{
break ;
}
}
closesocket( s);
WSACleanup();
return 0;
}

运行结果:

VC++ 网络编程总结(一)VC++ 网络编程总结(一)