练好网络编程基本功——实现TCP/IP通信

编程爱好者

千里之行,始于足下。要学好网络编程,必须掌握一定的网络编程基础知识,本部分内容简单介绍了Winsock与网络套接字,并通过一个TCP/IP例子让你了解怎样创建服务器与客户端,客户端是怎样与服务器连接的,还有服务器与客户端间的数据传输是怎样实现的。使用的编程工具是Visual C++.Net2003。

了解:Winsock

Windows Socket(简称Winsock)是一种用于网络数据通信的标准API。使用Winsock,应用程序可以通过网络协议(如TCP/IP)建立通信。Winsock有Winsock1和Winsock2两个版本(现在多数操作系统都支持Winsock2,Windows95与Windows CE只支持Winsock1)。两个版本的函数很容易区分,Winsock2的函数一般都以WSA为前缀(以下函数例外:WSAStartup、WSACleanup、WSARecvEx、WSAGetLastError四个函数在两个版本的Winsock中函数名都是一样)。

使用Winsock1编写应用程序,必须把winsock.h包含在应用程序中,并且必须使用wsock32.lib库。使用Winsock2编写应用程序,必须把winsock2.h包含在应用程序中,并且必须使用ws2_32.lib库。

实例学习:TCP/IP通信程序

通过TCP/IP协议创建的网络应用程序是一种面向连接的网络通信程序。使用TCP通信,在源计算机和目标计算机之间会建立起一个虚拟的连接。TCP保证数据传输的无误,为了要使数据传输无误,就要进行数据校验。在传输文件时,为了保证文件的完整,就必需使用面向连接的通信。

服务器端

笔者先给出使用TCP/IP网络协议的服务器端的源代码。创建TCP/IP服务器的主要步骤如图1所示。

19-g14-1-1.jpg
图1

建立一个“Win32控制台项目”,应用程序类型选“Windows应用程序”,附加选项选“空项目”。

添加一个C++文件,然后按菜单“项目→属性”,出现TCPServer对话框。在左边选中“链接器”下的“输入”,然后在右边的“附加依赖项”加入“WS2_32.lib”。

如果你觉得这样加lib库很麻烦,也可以在程序的开头添加以下语句:

#pragma comment(link, "WS2_32.lib")

下面是源代码:

#include <winsock2.h>

#include <iostream>

using namespace std;

const int BUFFER_LEN = 2048;

void main(void)

{

WSADATA wsaData;

SOCKET ServerSocket;

SOCKET ClientSocket;

SOCKADDR_IN ServerAddr;

SOCKADDR_IN ClientAddr;

int Port = 5555;

char buffer[BUFFER_LEN];

//初始化Winsock

WSAStartup( MAKEWORD( 2, 2 ), &wsaData );

//创建套接字

ServerSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

//将套接字绑定到一个已知的地址上

ServerAddr.sin_family = AF_INET;

ServerAddr.sin_port = htons( Port );

ServerAddr.sin_addr.s_addr = htonl( INADDR_ANY );

bind( ServerSocket, ( SOCKADDR* )&ServerAddr, sizeof( ServerAddr ) );

//监听

listen( ServerSocket, 5 );

//接受连接

int iClentAddrLen = sizeof( ClientAddr );

ClientSocket = accept( ServerSocket, ( SOCKADDR* )&ClientAddr, &iClentAddrL

en );

//接收数据

while( recv( ClientSocket, buffer, BUFFER_LEN, 0 ) )

cout<<buffer<

//关闭套接字

closesocket( ServerSocket );

closesocket( ClientSocket );

//释放Winsock分配的资源

WSACleanup();

}

即使上面的程序是很短,但你会发现有许多函数都不懂。不要紧,下面让我们分析主要部分的代码:

1.初始化与释放Winsock

初始化Winsock即加载Winsock库,每个Winsock应用程序都必须加载合适的Winsock库。这一步通过WSAStartup函数实现:

int WSAStartup(

WORD wVersionRequested,

LPWSADATA lpWSAData

);

● wVersionRequested——用于指定准备加载的WinSock库的版本。高位字节指定所需WinSock库的次版本,而低位字节则是主版本。可使用宏MAKEWORD(x,y),x是高位字节,y是低位字节。如MAKEW

ORD(2,2)表示2.2版本。

● lpWSAData——WSADATA结构的指针。

在使用完Winsock之后,要把Winsock分配的资源释放。这一步用WSACleanup函数完成:

int WSACleanup(void);

2.创建套接字

“套接字”一词是从英文单词“socket”得来,而“socket”的原意是“插座”。为什么网络套接字要用“插座”这一词呢?大家可以想像一下,插座是把电器与电源连接起来的端口,因此可以认为网络套接字是网络连接的端口。

用于创建套接字的函数是socket(Winsock2版本用的是WSASocket):

SOCKET socket(

int af,

int type,

int protocol

);

● af——协议的地址族

● type——协议的套接字类型

● protocol——指定套接字的使用协议。对于TCP,应该设为IPPROTO_TCP;对于UDP,应该设为IPPROTO_UDP。

3.监听

把套接字绑定到已知的地址后,接下来是要将套接字置入监听模式,指示套按字等候客户端的连接。监听这一步骤由listen函数完成。在使用套接字调用了监听函数后,程序将在关闭该套接字前一直处于监听状态。

int listen(

SOCKET s,

int backlog

);

● s ——被绑定的服务器端套接字

● backlog——可同时接受的连接请求数量,因为极可能出现同时几个客户端请求连接,所以这个参数显得尤其重要。一般设为5即可。

客户端

客户端比服务器端要简单得多,它不用像服务器端要进行绑定,监听与接受连接。创建TCP/IP客户端的主要步骤如图2所示。

19-g14-1-4.jpg
图2

客户端的示例源代码如下:

#include <winsock2.h>

#include <iostream>

using namespace std;

const int BUFFER_LEN = 2048;

void main(void)

{

WSADATA wsaData;

SOCKET ClientSocket;

SOCKADDR_IN ServerAddr;

int Port = 5555;

char buffer[BUFFER_LEN];

//初始化Winsock

WSAStartup( MAKEWORD(2,2), &wsaData );

//创建套接字

ClientSocket = socket( AF_INET, SOCK_STREAM, IPPROTO_TCP );

//连接到服务器

ServerAddr.sin_family = AF_INET;

ServerAddr.sin_port = htons( Port );

ServerAddr.sin_addr.s_addr = inet_add

r( "192.168.1.72" );

connect( ClientSocket, ( SOCKADDR* )&ServerAddr, sizeof( ServerAddr ) );

int sl = -1;

while( sl != 0 )

{ cout<<"1. 发送信息"<<endl;

cout<<"0. 退出"<

cin>>sl;

if( sl == 1 )

{ cin>>buffer;

//发送数据

send( ClientSocket, buffer, BUFFER_LEN, 0 ); } }

//关闭套接字

closesocket( ClientSocket );

//释放Winsock分配的资源

WSACleanup(); }

通过上面的代码,你会发现客户端在创建套接字后,就可以通过SOCKADDR_I

N结构给出服务器端的IP地址与端口号,然后用connect函数与服务器进行连接。

保障数据完整:错误检查

Winsock调用失败时最常见的返回值是SOCKET_ERROR。上面的程序为了结构清晰,我并没有进行错误检查。

(1)int WSAGetLastError(void);

发生错误之后调用这个函数,返回的是所发生的错误的整数代码。

(2)WSASetLastError

可以手动设置WSAGetLastError获取的错误代码。

(3)演示代码

if( WSACleanup() == SOCKET_ERROR )

printf( "WSACleanup failed with error %d \n", WSAGetLastError() );