网上第一本以TXT格式的VC++深入详解孙鑫的书.全文全以TXT格式,并每一章节都分了目录,清晰易读
源代码在线查看: 14.4 基于tcp的网络应用程序的编写及服务端程序.txt
14.4 基于 TCP的网络应用
14.4.1 服务器端程序
首先,利用 VC++集成开发环境新建一个 Win32 Console Application类型的应用程序,程序取
名为: TCPSrv,并在应用程序创建向导的第 1步选择: An empty project选项,以创建一个空的
工程。
然后利用【文件\新建】菜单命令,为 TCPSrv工程添加一个 C++源文件: TcpSrv.cpp, 并按照上
面讲述的基于 TCP(面向连接〕的 socket编程中的服务器端程序流程,编写具体的实现代码,结
果如例 14-1所示。
在使用之前须链接库函数:工程->设置->Link->输入ws2_32.lib,OK!
#include
#include
void main() {
//加载套接字库
WORD wVersionRequested;
WSADATA wsaData;
ìnt err;
wVersionRequested = MAKEWORD( 1 , 1 );
err = WSAStartup( wVersionRequested, &wsaData );
if ( err != 0 ) {
return;
if ( LOBYTE( wsaData.wVersion ) != 1 ||
HIBYTE( wsaData.wVersion ) != 1 ) {
WSACleanup( );
return;
//创建用于监听的套接字
SOCKET sockSrv=socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN addrSrv;
addrSrv.sin_addr.S_un.S_addr=htonl(INADDR_ANY) ;
addrSrv.sin_family=AF_INET;
addrSrv.sin_port=htons(6000) ;
//绑起套接字
bind(sockSrv, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
//将套接字设为监听模式,准备接收客户请求
listen(sockSrv, 5);
SOCKADDR_IN addrClient;
int len=sizeof(SOCKADDR);
while(1)
//等待客户请求到来
SOCKET sockConn=accept(sockSrv, (SOCKADDR*)&addrClient , &len);
char sendBuf[100];
sprintf (sendBuf, "Welcome %s to http://www.sunxin.org" , inet_ntoa(addrClient.sin_addr));
11发送数据
send(sockConn , sendBuf , strlen(sendBuf)+1 , 0) ;
char recvBuf[lOOl ;
11接收数据
char recvBuf[100];
recv (sockConn, recvBuf , 100 , 0) ;
11打印接收的数据
printf ("%S\n " , recvBuf) ;
11关闭套接字
closesocket(sockConn) ;
E 小技巧:如果程序俐的缩进比较混乱,可以先将需要调整的代码全部选
中,然后同时按下 ALT和 F8键,代码就会变得整齐了。
在 TcpSrv.cpp源文件,以及本章随后的几个示例程序中,因为需要使用 WinSock库中的函数,
因此需要包含头文件: Winsock.h,程序中还使用了 C语言中标准输入和输出函数,因此还
需要包含标准输入输出头文件: stdio.h.因此在如例 14-1所示代码中,首先在该源文件开始位置
添加 语句:
#include
#include < stdio.h>
'
接着,编写一个 main函数。在此函数中,首先定义了一个 WORD类型的变量 :
wVersionRequested,用来保存 WinSock库的版本号,接着调用 MAKEWORD宏创建一个包含了请求
版本号的 WORD值,之后,调用 WSAStartup函数加载套接字库,如果其返回值不等于 0,则程序
退出。接下来,判断 wsaData. wVersion的低字节和高字节是否都等于1,如果不是我们所请求
的版本,那么调用 WSACleanup函数,终止对 Winsock库的使用并返回。
加载套接字库之后,就可以按照上面讲述的基于 TCP (面向连接)的 socket编程中的服务器端程
序流程来编写实现代码:
(1)创建套接字 (socket)。
利用 socket函数创建套接字。首先定义了一个 SOCKET类型的变量: sockSrv,用来接收 socket
函数返回的套接字。对 socket函数来说,第一个参数只能是 AF_INET (或
PF_INET);本例是基于 TCP协议的网络程序,需要创建的是流式套接宇,因此将 socket
函数的第二个参数设置为 SOCK STREAM;将其第三个参数指定为 0,这样的话,该函数
将根据地址格式和套接字类别,自动选择一个合适的协议。
①将套接宇绑定到一个本地地址和端口上 ( bind )。
在创建了套接字之后,应该将这个套接字绑定到本地的某个地址和端口上。这时就需要调用 bind
函数。在绑定之前,定义了一个 SOCKADDR IN类型的变量: addrSrv,然后对该地址结构体变量
中的成员进行赋值。首先对该变量的 sin_addr.S_un.S_addr成员赋值,该成员需要 u_long类型。
前面己经提过,在 SOCKADDR_IN结构体中,除了 sa_family成员以外,其他成员都是按网络字节
顺序表示的。因此为了将 INADDR_ANY值转换为网
络字节顺序,需要使用 htonl函数,该函数将一个 u_long类型的值从主机字节顺序转换为
TCP_IP的网络字节顺序。要注意的是,这里不作转换也是可以的,因为 INADDR_ANY这个宏的值就
是 0,所以转换为网络字节顺序之后其值还是 0,但这里仍然建议要作这个转换,主要是表明这
里使用的是网络字节顺序,同时给程序员也作一个提醒:接下来,代码指定 sin_fanùly成员的值,
该字段只能指定为 AF_INET;最后是指定端口 :sm-PORT字段的值,前面已经提过,为所编写的网
络程序指定端口号时,要使用 1024以上的端口号,本例使用 6000这个端口号。同时要注意,这
里需要的是网络字节顺序,并且因为端口号是两个字节的,所以.需要调用 htons函数来转换转
换。读者一定要注意 htons与 htonl这两个函数之间的区别,如果利用 htons将一个 int或 long
型数值转换为网络字节顺序,可能会丢失数据。
接下来,调用 bind函数把套接字 sockSrv绑定到本地地址和指定的端口号上。该函数的第一个
参数就是将要绑定的套接字:第二个参数需要一个指针,可以用取地址符来实现,并且 addrSrv
变量是 SOCKADDR IN结构体类型,而这里需要的是 SOCKADDR*类型,所以需要进行强制类型转换。
注意=对 SOCKADDR类型来说,其大写和小写是一样的,如果我们在其上单击鼠标右键,选择【 00
To Defmition Of SOCKADDR】菜单项即可定位于该类型的定义处,其定义代码如下所示 (位于
WinSock2 .h文件中):
typedef struct sockaddr SOCKADDR;
而 bind函数的第三个参数是指定地址结构的大小,可以利用 sizeof操作符来获取。
3将套接字设为监昕模式 ( listen ),准备接收客户请求。
绑定之后,接下来应该调用 listen函数,将己绑定的套接字设置为监昕模式。该函数
第一个参数就是将要设置的套接字,第二个参数是等待连接队列的最大长度,本例将此参数设置
为 5.
④等待客户请求到来:当请求到来后,接受连接请求,返回一个新的对应于此次连接的套接字
(accept)。
接下来,需要调用 accept函数等待并接受客户的连接请求。此时,需要定义一个地址结构体
SOCKADDR IN的变量(本程序中即 addrC lient变量),用来接收客户端的地址信息。另外,因为
对 accept函数的第三个参数来说,在调用这个函数之前必须为它赋予一个初始值,就是
SOCKADDR_IN结构体的长度,否则调用会失败。所以,上述代码定义了一个 int类型的变量: len,
并将其初始化为 SOCKADDR_剧结构体的长度。
因为作为服务器端,它需要不断地等待客户端的连接请求的到来,所以,程序接着进入一个循环,
而且是一个死循环,让服务器端程序能够不断地运行。在此循环中,第一步就是调用 accept函
数等待并接受客户的连接请求,其中第一个参数是处于监昕状态的套接字:第二个参数利用
addrClient变量接收客户端的地址信息。当客户端连接请求到来时,该函数接受该请求,建立连
接,同时它将返回一个相对于当前这个新连接的一个套接字描述符,保存于 sockConn变量中,
然后利用这个套接字就可以与客户端进行通信了,而我们先前的套接字仍继续监昕客户端的连接
请求。
5用返回的套接字和客户端进行通信 ( sendlrecv)。此时,可以利用 send函数向客户端发送数
据。为了发送数据,首先定义了一个字符数
组,并将客户端的地址进行格式化处理后放到这个数组中。在格式化客户端的地址时,使用了
inet_ntoa函数,该函数接受一个in addr结构体类型的参数并返回一个以点分十进制格式表示的
IP地址字符串,而SOCKADDRIN结构体中的sinaddr成员是in addr类型的,正好可以作为参数传递。
接下来就可以调用send函数向客户端发送数据了,注意这个函数使用的套接字,这里需要使用已
建17-连接的那个套接字: sockConn,而不是用于监昕的那个套接字: addrSrv 0 另外,发送数
据的长度可以用strlen函数获得,但上述程序在发送数据时,多发送了一个字节,主要是为了让
接收端在接收数据后,可以在该数据字符串之后增加一个"\0。"结尾标志。
在发送完数据之后,还可以从客户端接收数据,这可以使用recv函数,应注意,该函数的第一个
参数也应该是建立连接之后的那个套接字: sockConn。并且定义一个字符数组: recvBuf,用来
保存接收的数据。
当接收到数据之后,利用 printf函数将其显示出来。上述代码调用 printf函数时在接收到的数
据之后加上一个 "\n"字符,这将打印一个换行符。
⑥返回,等待另一客户请求。
当前通信完成之后,需要调用closesocket函数关闭己建立连接的套接字,释放为该套接宇分配
的资源。然后进入下一个循环,等待另一客户请求的到来。
⑦关闭套接字。本例是一个死循环,如果不是一个死循环的话,这时在closesocket函数调用之
后还需要关闭监听套接字,并调用WSACleanup函数终止对套接字库的使用。
因为本程序使用了WinSock库中的函数,按照、动态链接库的使用方法之一,这时还需要为程序
链接相应的.lib文件,关于原因将在后面的动态链接库一章会详细讲解。这里,需要为本程序链
接相应的库文件: ws2_32.lib.方法是选择【Project\Setting...】菜单项,并在弹出的Project
Setting对话框上选择Link选项卡,然后在Objectl1ibrary modules编辑框中添加ws232.lib文件
(如图 14.6所示)。读者应注意,输入的库文件与前面的库文件之间一定要有一个空格。
图 14.6添加ws2_32.lib文件的链接最后利用Build命令,生成TCPSrv执行程序。