如何在windows环境下实现socket编程
Monday, 19. March 2007, 11:17:27
1. Let's be ready to start using sockets(only connern this when you are in MS windows circumstance)
在启动socket前,windows有一个与众不同的步骤(这步怪异的步骤常常为unix的粉丝所不齿),就是要先初始化socket,这一步的主要目的是加载与socket代理有关的dll版本文件(一般我们使用2.0的版本),所以如果你使用unix或者linux或者其它非microsoft的产品,可以省略这一步。
WSAStartup();
就是这个怪异的函数。与此函数相对的是:
WSACleanup();
函数。这个函数用来清除由于调用WSAStartup()所占据的系统资源。
而WSACleanup()只要在程序不再使用网络链接时调用就可以了,一般在程序中止时调用,但是考虑到windows的鲁棒性,在程序中止时windows会自动清除一切占用资源,这一api有时遗漏也不是很严重,但是不推荐这么做。
2. Now let's establish a socket agent.
socket();
这是用来建立socket代理的api,在unix下这个函数返回一个int型的标示符,但是在windows环境下则返回一个SOCKET结构体(注意必须大写)。
在继续往下讨论时,我们碰到了一个问题,在网络编程时,通信的双方在最简单的情况下也要分为服务器(server)和客户(customer),并且服务器与客户处理的是不同的过程,接下去本文将先从服务器的角度来介绍,然后再介绍客户方的内容。
3. If I'm the server, which port should I listen to.
端口是TCP/IP中一个很重要的概念,它主要用来防止多个网络通信程序彼此相互妨碍,所以不同的网络功能程序一般使用不同的端口。根据行业标准,1024(含)以下的端口都是保留的,用户自己最好不要把自己的应用程序定在这些端口上。
在建立了socket代理后,我们还需要让代理知道,它负责哪个端口的网络通信任务。而赋予代理这个任务的api就是:
bind();
函数。
4. I'm stupid, so tell me what should I do.
在固定了代理所负责的端口后,接着就必须让它开始工作,就像一个售货员一样,代理也必须等到有数据请求才开始工作,所以它必须一直看着自己负责的端口是否有请求到来,负责这一功能的api就是:
listen();
注意,要调用listen()时必须要先调用bind(),否则代理根本就不知道自己要listen哪个端口。就像之前的一系列步骤一样,socket编程是有次序的来调用api,不可随意乱跳。
5. Hey, someone is calling me and I want to give a reply.
在通过listen()发现有客户方请求数据时,如果想对其作出接收请求的应答,那么就调用:
accept();
什么,你问我如果不想理睬这个请求怎么办?Oh, easy. You just ignore it.
如果接收了请求,那么accept()将会返回一个全新的SOCKET型的标示,也就是说服务器通过这个新产生的标示符与客户进行通信,也就是通过这个标示符来实现与客户的收发数据。在编程时,一般有两种方法处理这时出现的问题。一是停止程序监听端口,使用另一个标识符与客户开始进行通信,等此服务结束后在重新来监听端口。二是可以另外打开一个进程来处理这个请求服务。一般人们会使用方法二,因为其高效,但是方法一方便安全。
6. Let my mouth speak or let my ear can hear something.
send();
使用来发送数据。而
recv();
则是接收数据。
根据TCP/IP协议,任何发送数据过程只是尽力发送,并不保证发送数据一定成功或者一定将全部数据发送出去,所以send()只是返回发送数据的个数。send()则是尽力接收,当其返回值是0时,标示对方关闭了连接。
7. I'm the customer. Hey, I wanna something from you.
讲完了服务器方,现在来看看客户的行为。作为客户在取得服务前必须要现告诉服务器你想取得它得服务。
connect();
就能很好得实现这个功能。通过connect()的返回值就能了解连接请求是否得到承认。然后选择下一步的行为。
8. Goodbye, my client.
在socket代理使用结束后需要使用:
closesocket();
将socket代理关闭。
9. Everything should go to its ending(only in windows).
正如在第一点提到的,当不再使用网络通信时,windows还要多一步就是释放其所占的资源,就是使用api:
WSACleanup();
综上所述,我们可以把最基本的服务过程和客户过程分别以如下所示:
********************************************************************************
服务过程:___________________________________客户过程:_____________________
初始化WSACleanup() --> ______________________WSACleanup() -->
建立socket代理socket() --> __________________建立socket代理socket() -->
绑定端口bind() --> __________________________empty
侦听端口listen() --> ________________________empty
接收连接请求accept() --> _____________________发送连接请求connect() -->
发送/接收数据send()/recv() --> _______________接收/发送数据recv()/send() -->
关闭socket代理closesocket() --> _____________关闭socket代理closesocket() -->
中止winsocket,释放资源WSACleanup() __________中止winsocket,释放资源WSACleanup()
*********************************************************************************
以上就是全部的基本socket函数了,但是在实际使用中我们常常还会需要一些辅助函数,比如获得IP地址等,下面就一一介绍。
1. htons():把机器字序转换做网络字序。这一函数与硬件有关,这里只要记住如果你不清楚自己机器的字序方式,就使用这个函数吧。这里指出Intel的处理器芯片一般和网络字序相反,所以一般都必须使用这个函数。
2. inet_addr():将IP地址转换做一串unsigned long型的数。注意这个函数内的参数无需变换字序了,其内置了变换。在windows下:
inet_ntoa();
是一个与其功能一样的函数。
3. gethostbyname():通过DNS服务器获得IP地址。
以上就将基本的常用的函数都介绍完了,下面通过一个向http服务器请求网页的程序来演示一下其基本的应用方法。
为了使程序更通用话,这里设计了通用的函数以方便编程。
/*------------------------------------------------------------------------
Procedure: StartSocket ID:1
Purpose: 打开winsocket的DLL文件,这个是在windows下特有的,在
Unix下就不需要这步了。
Input:
Output: 函数执行成功则返回1。
Errors: 无法调用DLL则返回-1。
------------------------------------------------------------------------*/
int StartSocket(void)
{
WSADATA WSAData;
if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0){
//printf("Socket init fail!\n");
return -1;
}
return 1;
}
/*------------------------------------------------------------------------
Procedure: CreateSocket ID:1
Purpose: 创建socket代理。
Input: socket结构的一个结构体指针。
Output: 1。
Errors: -1。
------------------------------------------------------------------------*/
int CreateSocket(SOCKET *sock)
{
*sock=socket(AF_INET,SOCK_STREAM,0);
if(*sock==INVALID_SOCKET){
//printf("Socket create fail!\n");
return -1;
}
return 1;
}
这里传递指针是因为SOCKET结构sock在以后还要多次用到,考虑到通用性,将其地址作为参数传递。
/*------------------------------------------------------------------------
Procedure: GetIP ID:1
Purpose: 通过URL结构中的Host名获得相对应的IP地址。
Input: 存放URL结构的结构体,socket专用的sockaddr_in结构体指针。
Output: 1。
Errors: -1。
------------------------------------------------------------------------*/
int GetIP(PtrUrlStrData PtrUrlData,SOCKADDR_IN *PtrServerAddr)
{
struct hostent *PtrHost;
PtrHost=gethostbyname(PtrUrlData->Host);
if(PtrHost==NULL){
//puts("Host name wrong.");
return -1;
}
PtrServerAddr->sin_family=AF_INET;
PtrServerAddr->sin_port=htons(PtrUrlData->Port);
PtrServerAddr->sin_addr.s_addr=inet_addr(inet_ntoa(*((struct in_addr *)PtrHost->h_addr)));
memset(&PtrServerAddr->sin_zero[0],0,8);
return 1;
}
UrlStrData这个结构体采用如下定义:
typedef struct _Url_Str_Data { //a structure for URL
char Protocol[8];
char Host[256];
unsigned short Port;
char Path[1024];
char FileName[256];
} UrlStrData, *PtrUrlStrData;
/*------------------------------------------------------------------------
Procedure: CallServer ID:1
Purpose: 联络服务器方。等待服务器方的accept()回应。
Input: socket结构的结构体指针,sockaddr_in型的指针。
Output: 1
Errors: -1
------------------------------------------------------------------------*/
int CallServer(SOCKET *sock,SOCKADDR_IN *PtrServerAddr)
{
if(CreateSocket(sock)==-1){
//puts("Cannot create socket.");
return -1;
}
if(connect(*sock,(struct sockaddr *)PtrServerAddr,sizeof(SOCKADDR_IN))==SOCKET_ERROR){
//printf("Connect fail.\n");
closesocket(*sock);
return -1;
}
return 1;
}
/*------------------------------------------------------------------------
Procedure: TCPSendAll ID:1
Purpose: 发送所有要求被发送的数据。
这里不用send是因为这个函数只是尽力发送数据,所以要改
进一下。
Input: socket结构体指针,需要被发送的数据指针。
Output: 1。
Errors: -1。
------------------------------------------------------------------------*/
int TCPSendAll(SOCKET *sock,char *data)
{
int length=0;
int TotalSended=0;
int LengthAll=strlen(data);
int bytesleft=LengthAll;
while(TotalSended<LengthAll){
length=send(*sock,data+TotalSended,bytesleft,0);
if(length==SOCKET_ERROR){
printf("send data error!\n");
//closesocket(sock);
//WSACleanup();
return -1;
}
TotalSended+=length;
bytesleft-=length;
}
return 1;
}
/*------------------------------------------------------------------------
Procedure: MakeCommand ID:1
Purpose: 制作发送给服务器方的命令。此处是构建http命令。
Input: 指向存放命令的数据指针,存放URL结构的结构体。
Output:
Errors:
------------------------------------------------------------------------*/
void MakeCommand(char *CommandBox,UrlStrData UrlData)
{
char *Ptr1="GET /";
char *Ptr6=" HTTP/1.1\r\nAccept: */*\r\nAccept-Language: zh-cn\r\n";
char *Ptr3="User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\r\n";
char *Ptr4="Host: ";
char *Ptr5="Connection: close\r\n\r\n";
sprintf(CommandBox,"%s%s%s%s",Ptr1,UrlData.Path,UrlData.FileName,Ptr6,Ptr3);
int offset=strlen(CommandBox);
char UnShortToStr[18];
sprintf(CommandBox+offset,"%s%s:%s\r\n",Ptr4,UrlData.Host,itoa((int)(short)UrlData.Port,UnShortToStr,10));
offset=strlen(CommandBox);
sprintf(CommandBox+offset,"%s",Ptr5);
return ;
}
O.K.有了上面的函数,编程起来就方便了,程序清单如下:
//**************************************************
#include <winsock.h>
#include <windows.h>
#include <stdheaders.h>
#define Default_Port 80
#define Default_Connect_Time 10 //define the time we try to connect
#define Max_CommandLen 1000 //define the max length of command we send to a http server
#define Default_Txt_Len 1048576 //假设网页默认长度1M,注意这是一个演示程序,当网页>1M程序出错
typedef struct _Url_Str_Data { //a structure for URL
char Protocol[8];
char Host[256];
unsigned short Port;
char Path[1024];
char FileName[256];
} UrlStrData, *PtrUrlStrData;
char SaveBox[Default_Txt_Len];
//-----------
char *GetHttpFiles(UrlStrData UrlData);
int GetIP(PtrUrlStrData PtrUrlData,SOCKADDR_IN *PtrServerAddr);
int StartSocket(void);
int CreateSocket(SOCKET *sock);
int CallServer(SOCKET *sock,SOCKADDR_IN *PtrServerAddr);
int TCPSendAll(SOCKET *sock,char *data);
void MakeCommand(char *CommandBox,UrlStrData UrlData);
void main(void)
{
UrlStrData UrlData;
strcpy(UrlData.Host,"auto.163.com");
strcpy(UrlData.Path,"\0");
UrlData.Port=Default_Port;
strcpy(UrlData.FileName,"\0");
char *p;
p=GetHttpFiles(UrlData);
printf("%s",p);
free(p);
return ;
}
char *GetHttpFiles(UrlStrData UrlData)
{
SOCKET sock;
SOCKADDR_IN ServerAddr;
if(StartSocket()==-1)
return NULL;
if(GetIP(&UrlData,&ServerAddr)==-1){
WSACleanup();
return NULL;
}
int ServerExist=1; //1表示对方服务器存在
for(int i=0;i<Default_Connect_Time;i++){
if(CallServer(&sock,&ServerAddr)==1){
//puts("Connect OK.");
break;
}
if(i==(Default_Connect_Time-1))
ServerExist=0;
}
if(ServerExist==0){
//puts("Server may not start yet.");
WSACleanup();
return NULL;
}
char SendCommand[Max_CommandLen];
memset(SendCommand,0,Max_CommandLen);
MakeCommand(SendCommand,UrlData);
if(TCPSendAll(&sock,SendCommand)==-1){
//puts("wrong");
closesocket(sock);
WSACleanup();
return NULL;
}
int result=1;
//char SaveBox[Default_Txt_Len];
memset(SaveBox,0,Default_Txt_Len);
char buf[2];
memset(buf,0,2);
while(result>0){
result = recv(sock,buf,1,0);
//printf("%d",result);
//printf("%s",buf);
strcat(SaveBox,buf);
memset(buf,0,2);
}
closesocket(sock);
WSACleanup();
return SaveBox;
}
int StartSocket(void)
{
WSADATA WSAData;
if(WSAStartup(MAKEWORD(2,2),&WSAData)!=0){
//printf("Socket init fail!\n");
return -1;
}
return 1;
}
int CreateSocket(SOCKET *sock)
{
*sock=socket(AF_INET,SOCK_STREAM,0);
if(*sock==INVALID_SOCKET){
//printf("Socket create fail!\n");
return -1;
}
return 1;
}
int GetIP(PtrUrlStrData PtrUrlData,SOCKADDR_IN *PtrServerAddr)
{
struct hostent *PtrHost;
PtrHost=gethostbyname(PtrUrlData->Host);
if(PtrHost==NULL){
//puts("Host name wrong.");
return -1;
}
PtrServerAddr->sin_family=AF_INET;
PtrServerAddr->sin_port=htons(PtrUrlData->Port);
PtrServerAddr->sin_addr.s_addr=inet_addr(inet_ntoa(*((struct in_addr *)PtrHost->h_addr)));
memset(&PtrServerAddr->sin_zero[0],0,8);
return 1;
}
int CallServer(SOCKET *sock,SOCKADDR_IN *PtrServerAddr)
{
if(CreateSocket(sock)==-1){
//puts("Cannot create socket.");
return -1;
}
if(connect(*sock,(struct sockaddr *)PtrServerAddr,sizeof(SOCKADDR_IN))==SOCKET_ERROR){
//printf("Connect fail.\n");
closesocket(*sock);
return -1;
}
return 1;
}
int TCPSendAll(SOCKET *sock,char *data)
{
int length=0;
int TotalSended=0;
int LengthAll=strlen(data);
int bytesleft=LengthAll;
while(TotalSended<LengthAll){
length=send(*sock,data+TotalSended,bytesleft,0);
if(length==SOCKET_ERROR){
printf("send data error!\n");
//closesocket(sock);
//WSACleanup();
return -1;
}
TotalSended+=length;
bytesleft-=length;
}
return 1;
}
void MakeCommand(char *CommandBox,UrlStrData UrlData)
{
char *Ptr1="GET /";
char *Ptr6=" HTTP/1.1\r\nAccept: */*\r\nAccept-Language: zh-cn\r\n";
char *Ptr3="User-Agent: Mozilla/4.0 (compatible; MSIE 6.0; Windows NT 5.1; SV1)\r\n";
char *Ptr4="Host: ";
char *Ptr5="Connection: close\r\n\r\n";
sprintf(CommandBox,"%s%s%s%s",Ptr1,UrlData.Path,UrlData.FileName,Ptr6,Ptr3);
int offset=strlen(CommandBox);
char UnShortToStr[18];
sprintf(CommandBox+offset,"%s%s:%s\r\n",Ptr4,UrlData.Host,itoa((int)(short)UrlData.Port,UnShortToStr,10));
offset=strlen(CommandBox);
sprintf(CommandBox+offset,"%s",Ptr5);
return ;
}
这个程序将下载下的网页文件打印在屏幕上,可以复制下来另存为html文件就是我们常用的网页了。
如何在windows环境下实现socket编程.doc










