简析Windows C语言网络编程技术与方法
2011-04-03韩玉坤邵国强
韩玉坤,邵国强,杨 红
(大庆师范学院 计算机科学与信息技术学院,大庆 黑龙江 163712)
0 引言
网络程序是对速度要求高、对硬件操作较多的程序,利用Windows C编程方式编写的程序源代码量虽然较大,但可执行代码效率高,大多数网络应用程序是用Windows C编程方式开发的。用Windows C进行网络应用编程,最核心的内容就是使用套接字(socket)函数。
1 套接字(socket)概念
1.1 套接字
套接字是支持TCP/IP网络通信的基本操作单元。可以将套接字看作是不同主机间的进程进行双向通信的端点,在一个双方可以通信的套接字实例中,既保存了本机的IP地址和端口,也保存了对方的通信采用的协议等信息。应用程序在使用TCP/IP协议进行通信之前,必须向操作系统申请创建一个套接字用于通信。如果申请成功,系统返回一个短整型数作为描述符,以标识该socket。应用程序在调用函数进行网络数据传输时,就将这个socket描述符作为参数,而不必在每次传输数据时都指明远程目标细节。
1.2 Winsock
为了方便网络编程,Microsoft在90年代初,联合了其他几家公司共同制定的一套WINDOWS下的网络编程接口Winsock,它是通过C语言的动态链接库方式提供的,主要由winsock.h头文件和动态链接库winsock.dll组成。用Visual C++6.0编译Windows C程序,使用Winsock API函数时,首先要把wsock32.lib添加到它的库模块中。添加的方法有两种:①直接添加到库模块中,步骤:工程-设置-link选项卡-在对象/库模块文本框中添加wsock32.lib;②用预处理命令:#pragma comment(lib,”wsock32.lib”)。
1.3 套接字工作原理
要通过互联网进行通信,至少需要一对套接字,其中一个运行于客户机端,称之为ClientSocket;另一个运行于服务器端,称之为ServerSocket。根据连接启动的方式以及本地套接字要连接的目标,套接字之间的连接过程可以分为三个步骤:服务器监听,客户端请求,连接确认。
所谓服务器监听,指服务器端套接字并不定位具体的客户端套接字,而是处于等待连接的状态,实时监控网络状态,所谓客户端请求,指由客户端的套接字提出连接请求,要连接的目标是服务器端的套接字。为此,客户端的套接字指出服务器端套接字的地址和端口号,然后向服务器端套接字提出连接请求;所谓连接确认,指当服务器端套接字监听到或者说接收到客户端套接字的连接请求,就响应客户端套接字的请求,连接就建立好了。
2 Windows下Socket函数的使用
在Windows下进行网络编程主要使用Winsock提供的API函数。
1)初始化DLL库WSAStartup()函数和释放DLL库WSAClenaup()函数。不管是客户端还是服务器,开发Windows C网络应用程序时,必须先用WSAStartup()函数加载Winsock动态库(DLL)。只有该函数在调用成功之后才能使用其它的Winsock网络操作函数。函数原型为:int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData),其中,参数WVersionRequired:<输入>表示欲使用的Winsock版本,这是一个WORD类型的整数,它的高位字节定义的是次版本号,低位字节定义的是主版本号。参数LpWSAData:<输出>是一个指向WSADATA资料的指针。当Windows C网络程序在完成对请求的Winsock库的使用后,要调用WSACleanup函数来解除与Winsock库的绑定并且释放Winsock库所占用的系统资源。
2)创建套接字socket()和关闭套接字closesocket()函数。应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段。函数原型为:SOCKET PASCAL FAR socket(int af, int type, int protocol),其中,参数af指定通信发生的区域,Windows中用AF_INET;参数type 描述要建立的套接字的类型;参数protocol说明该套接字使用的特定协议,如果不希望特别指定使用的协议,即使用默认的连接模式,closesocket()关闭套接字s,并释放分配给该套接字的资源;如果s涉及一个打开的TCP连接,则该连接被释放。closesocket()的调用格式为:BOOL PASCAL FAR closesocket(SOCKET s),其中,参数s为待关闭的套接字描述符。如果没有错误发生,closesocket()返回0;否则返回值SOCKET_ERROR。
3)指定本地地址bind()。bind()将套接字地址(包括本地主机地址和本地端口地址)与所创建的套接字号联系起来,即将名字赋予套接字,以指定本地半相关。函数原型为:int PASCAL FAR bind(SOCKET s, const struct sockaddr FAR *name, int namelen),其中,参数s是由socket()调用返回的,并且作为连接的套接字描述符(套接字号);参数name 是赋予给套接字s的本地地址(名字),其长度可变,结构随通信域的不同而不同;namelen表明了name的长度。如果没有错误发生,bind()返回0;否则返回值SOCKET_ERROR。
4)监听连接listen()、建立套接字连接accept()和connect()函数。listen()用于服务器,表明它愿意接收连接。listen()需在accept()之前调用,其调用格式为:int PASCAL FAR listen(SOCKET s, int backlog),其中,参数s标识一个本地已建立、尚未连接的套接字号,服务器从它上面接收请求。backlog表示请求连接队列的最大长度,用于限制排队请求的个数。如果没有错误发生,listen()返回0;否则返回SOCKET_ERROR。
accept()和connect()这两个函数用于完成一个完整相关的建立。accept()用于服务器,在调用过listen ()之后,它使服务器等待来自某客户进程的实际连接。函数原型为:SOCKET PASCAL FAR accept(SOCKET s, struct sockaddr FAR*addr, int FAR*addrlen),其中,参数s为本地套接字描述符;addr 指向客户端套接字地址结构的指针,用来接收连接实体的地址,addr的格式由套接字创建时建立的地址族决定;addrlen 为客户端套接字地址的长度(字节数)。如果没有错误发生,accept()返回一个SOCKET类型的值,表示接收到的套接字的描述符,否则返回值INVALID_SOCKET。
connect()用于客户端,它使客户端与服务器之间建立连接。函数原型为:int PASCAL FAR connect(SOCKET s, const struct sockaddr FAR*name, int namelen)。其中,参数s是欲建立连接的本地套接字描述符;参数name说明对方套接字地址结构的指针;对方套接字地址长度由namelen说明。如果没有错误发生,connect()返回0,否则返回值SOCKET_ERROR。
5)数据传输send()和recv()。当一个连接建立以后,就可以传输数据了。常用的系统调用函数有send()和recv()。send()调用用于参数s指定的已连接的数据包或流套接字上发送输出数据,格式为int PASCAL FAR send(SOCKET s, const char FAR*buf, int len, int flags),其中,参数s为已连接的本地套接字描述符;buf 指向存有发送数据的缓冲区的指针,其长度由len 指定;flags 指定传输控制方式。如果没有错误发生,send()返回所有发送的字节数;否则返回SOCKET_ERROR。recv()调用用于参数s指定的已连接的数据包或流套接字上接收输入数据,格式为:int PASCAL FAR recv(SOCKET s, char FAR*buf, int len, int flags),其中,参数s 为已连接的套接字描述符;buf指向接收输入数据缓冲区的指针,其长度由len 指定;flags 指定传输控制方式。如果没有错误发生,recv()返回所有接收的字节数;如果连接被关闭,返回0;否则返回SOCKET_ERROR。
6)输入/输出多路复用select()。select()调用用于检测一个或多个套接字的状态。对每一个套接字来说,该调用可以请求读、写或错误状态方面的信息。请求给定状态的套接字集合由fd_set结构指示。在返回时,此结构被更新,以反映那些满足特定条件的套接字的子集。同时,select()调用返回满足条件的套接字的数目,其调用格式为:int PASCAL FAR select(int nfds, fd_set FAR*readfds, fd_set FAR*writefds,{fd_set} FAR*exceptfds, const struct timeval FAR*timeout),其中,参数nfds指明被检查的套接字描述符的值域,此变量一般被忽略;参数readfds指向要进行读检测的套接字描述符集合的指针,调用者希望从中读取数据,参数writefds 指向要进行写检测的套接字描述符集合的指针;exceptfds指向要检测是否出错的套接字描述符集合的指针;timeout指向select()函数等待的最大时间,如果设为NULL则为阻塞操作。select()返回包含在fd_set结构中已准备好的套接字描述符的总数目,若发生错误则返回SOCKET_ERROR。
3 Windows C网络编程实例
为了更直观说明C/S模式下的通信,下面给出一个面向TCP协议简单的控制台网络应用程序实例。
实例:当服务器端程序启动后,服务器端马上进入侦听状态,这时当客户端程序启动后,客户端立即与服务器建立了连接。此时,客户端就可与服务器进行通信了。当一端发送出”bye”信息时结束通信。
//服务器端Server.cpp
#include
#include
#include
#pragma comment(lib,"ws2_32.lib")
#define PORT 3333
int main()
{WSADATA wsa;
if(WSAStartup(MAKEWORD(2,2),&wsa) != 0)//初始化套接字DLL
{printf("套接字初始化失败! ");return(-1); }
SOCKET serverSock;
//创建套接字
if((serverSock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP)) == INVALID_SOCKET)
{printf("创建套接字失败! "); return(-1);}
struct sockaddr_in sserverAddr;
memset(&sserverAddr,0,sizeof(sockaddr_in));
sserverAddr.sin_family = AF_INET;
sserverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
sserverAddr.sin_port = htons(PORT);
…
}
4 结束语
网络应用编程是近年来非常流行的编程技术,它不仅要求具备熟练的编程功底,而且还要求有坚实的网络知识基础,在程序设计思路上也与传统编程不同。通过文中给出的简单实例,能够得出用Windows C编写网络应用程序的一个模式。因此,掌握网络编程技术,就要深入研究和熟练掌握Windows下的 Socket函数的用法,才能更好地进行网络应用程序的开发。
[参考文献]
[1] 王艳平.Windows网络与通信程序设计[M].北京:人民邮电出版社,2009.
[2] 侯俊杰.深入浅出Visual C++ 6程序设计导学[M].北京:清华大学出版社,2002.