练好网络编程基本功——实现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所示。

建立一个“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(); } 即使上面的程序是很短,但你会发现有许多函数都不懂。不要紧,下面让我们分析主要部分的代码: 初始化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); “套接字”一词是从英文单词“socket”得来,而“socket”的原意是“插座”。为什么网络套接字要用“插座”这一词呢?大家可以想像一下,插座是把电器与电源连接起来的端口,因此可以认为网络套接字是网络连接的端口。 用于创建套接字的函数是socket(Winsock2版本用的是WSASocket): SOCKET socket( int af, int type, int protocol ); ● af——协议的地址族 ● type——协议的套接字类型 ● protocol——指定套接字的使用协议。对于TCP,应该设为IPPROTO_TCP;对于UDP,应该设为IPPROTO_UDP。 把套接字绑定到已知的地址后,接下来是要将套接字置入监听模式,指示套按字等候客户端的连接。监听这一步骤由listen函数完成。在使用套接字调用了监听函数后,程序将在关闭该套接字前一直处于监听状态。 int listen( SOCKET s, int backlog ); ● s ——被绑定的服务器端套接字 ● backlog——可同时接受的连接请求数量,因为极可能出现同时几个客户端请求连接,所以这个参数显得尤其重要。一般设为5即可。 客户端比服务器端要简单得多,它不用像服务器端要进行绑定,监听与接受连接。创建TCP/IP客户端的主要步骤如图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() );1.初始化与释放Winsock
2.创建套接字
3.监听
客户端

保障数据完整:错误检查