Winsock程序设计初步之 Winsock编程原理
本课程主要讲Windows中TCP/IP编程接口Winsock,版本为1.1。高版本的Winsock实际与1.1版相差不多,主要是进行了一些扩充,如可超越TCP/IP协议直接用socket来实现IPX、NETBIOS等其它通信协议。
这叙述方便在本文的其余部分中提到的Winsock指的就是Winsock1.1。
通过Winsock可实现点对点或广播通信程序,实际这两者之间的区别不大,编程时其程序流程所用代码几乎相同,不同的地方在于目标地址选择的不同。本课程中所举实例为点对点的形式,并以客户/服务器形式来构建通过Winsock进行通信的点对点通信,并对通信过程的两点分别命名为Server和Client。
为更清楚的说明出Winsock的结构原理,下面以电信局的普通电话服务为比较对象进行说明:
1、电信局提供电话服务类似版主们这的Server,普通电话用户类似版主们这的Client。
2、首先电信局必须建立一个电话总机。这就如果版主们必须在Server端建立一个Socket(套接字),这一步通过调用socket()函数实现。
3、电信局必须给电话总机分配一个号码,以便使用户要拨找该号码得到电话服务,同时接入该电信局的用户必须知道该总机的号码。同样,版主也在Server端也要为这一套接字指定一port(端口),并且要连接该Server的Client必须知道该端口。这一步通过调用bind()函数实现。
4、接下来电信局必须使总机开通并使总机能够高效地监听用户拨号,如果电信局所提供服务的用户数太多,你会发现拨打电信局总机老是忙音,通常电信局内部会使该总机对应的电话号码连到好几个负责交换的处理中心,在一个处理中心忙于处理当前的某个用户时,新到用户可自动转到一下处理中心得到服务。同样版主们的Server端也要使自己的套接口设置成监听状态,这是通用listen()函数实现的,listen()的第二个参数是等待队列数,就如同你可以指定电信局的建立几个负责交换的处理中心。
5、用户知道了电信局的总机号后就可以进行拨打请求得到服务。在Winsock的世界里做为Client端是要先用socket()函数建立一个套接字,然后调connect()函数进行连接。当然和电话一样,如果等待队列数满了、与Server的线路不通或是Server没有提供此项服务时,连接就不会成功。
5、电信局的总机接受了这用户拨打的电话后负责接通用户的线路,而总机本身则再回到等待的状态。Server也是一样,调用accept()函数进入监听处理过程,Server端的代码即在中处暂停,一旦Server端接到申请后系统会建立一个新的套接字来对此连接做服务,而原先的套接字则再回到监听等待的状态。
6、当你电话挂完了,你就可以挂上电话,彼此间也就离线了。Client和Server间的套接字的关闭也是如此;这个关闭离线的动作,可由Client端或Server端劝嬷骰方先关闭。有些电话查询系统不也是如此吗?关闭套接字的函数为
closesocket()。
从以上情况可以看出在服务器端建立一个套接字,并进入实际的监听步骤的过程如下:socket()->bind()->listen()->accept()
那么在accept()完了后,版主们说在Server端将生成一个新的套接字,然后Server将继续进入accept()状态,版主们该如何用这个新的套接字来进行与Client端的通信呢,这就用到了recv()函数,而Client端则是通过send()函数来向服务器发信息的。
在客户端也是采取类似的过程,其调用Winsock的过程如下:
socket()->connect()->send()
首先建立一个socket,然后用connect()函数将其与Server端的socket连接,连接成功后调用send()发送信息。
//A simplest web server
//Written by Shen zhiliang for learning Winsock & HTTP
file://[email protected]
#include "winsock.h"
#include "stdio.h"
#include "conio.h"
#include "io.h"
#define BUFLEN 2048
#define DEFPATH ("C://frontpage webs//content//")
#define DEFFILE ("INDEX.HTM")
#define HTTPHEAD ("HTTP/1.0 200 OK/010Date: Monday, 04-Jan-99 17:06:17 GMT/x0aServer: HTTP-Server/1.0/x0a MIME-version: 1.0/x0a")
#define MIMEHTML ("Content-type: text/html/x0a Last-modified: Friday, 26-Sep-97 09:36:54 GMT/x0a")
#define MIMEGIF ("Content-type: /image/gif/x0a Last-modified: Friday, 26-Sep-97 09:36:54 GMT/x0a")
#define MIMEJPEG ("Content-type: /image/jpeg/x0a Last-modified: Friday, 26-Sep-97 09:36:54 GMT/x0a")
#define MIMEPAIN ("Content-type: text/pain/x0a Last-modified: Friday, 26-Sep-97 09:36:54 GMT/x0a")
#define HTMLHEAD ("<html><body>/x0a")
#define HTMLTAIL ("/n</body></html>")
void P(char * a)
{
printf("Error in : %s/n",a);
}
/*
char * httphead(FILE * fp)
{
struct tm *newtime;
time_t aclock;
time( &aclock );
newtime = localtime( &aclock );
printf( "The current date and time are: %s", asctime( newtime ) );
}
*/
void HtmlError(SOCKET s,unsigned int code,char * msg)
{
char tmp[1024];
sprintf(tmp,"Error code: %d %s",code,msg);
send(s,HTTPHEAD,strlen(HTTPHEAD),0);
send(s,HTMLHEAD,strlen(HTMLHEAD),0);
send(s,tmp,strlen(tmp),0);
send(s,HTMLTAIL,strlen(HTMLTAIL),0);
}
void SendHtmlFile(SOCKET s,char * filename)
{
FILE * fp;
char * tmp;
char fullname[512];
char buf[1024];
int i;
strcpy(fullname,DEFPATH);
if(strlen(filename)==0||(strlen(filename)==1&&filename[0]=='/'))
strcat(fullname,DEFFILE);
else
{
do{
tmp=strchr(filename,'/');
if(tmp!=NULL)
tmp[0]='//';
}while(tmp!=NULL);
if(filename[0]=='//')
strcat(fullname,&filename[1]);
else
strcat(fullname,filename);
}
FILE * fpt=fopen("recv.dat","a+b");
fwrite(fullname,sizeof(char),strlen(fullname),fpt);
fclose(fpt);
fp=fopen(fullname,"rb");
if(fp==NULL)
{
char msg[512];
if(filename[0]=='//')
filename[0]='/';
sprintf(msg," URI no found: %s",filename);
HtmlError(s,404,msg);
return;
}
send(s,HTTPHEAD,strlen(HTTPHEAD),0);
if(stricmp(&filename[strlen(filename)-4],".GIF")==0)
send(s,MIMEGIF,strlen(MIMEGIF),0);
else if(stricmp(&filename[strlen(filename)-4],".JPG")==0|| stricmp(&filename[strlen(filename)-5],".JPEG")==0)
send(s,MIMEJPEG,strlen(MIMEJPEG),0);
else if(stricmp(&filename[strlen(filename)-4],".HTM")==0|| stricmp(&filename[strlen(filename)-5],".HTML")==0)
{
send(s,MIMEHTML,strlen(MIMEHTML),0);
}
long fs=_filelength(_fileno(fp));
buf[0]=0;
sprintf(buf,"Content-length: %ld/x0a/x0a",fs);
send(s,buf,strlen(buf),0);
for(i=0;!feof(fp);i++)
{
buf[i]=fgetc(fp);
if(i>=1023||feof(fp))
{
send(s,buf,i,0);
i=0;
}
}
fclose(fp);
}
void SocketError(char * call)
{
fprintf(stderr," WinSock Error# function: %s, error code:%ld/n",call,WSAGetLastError());
}
main(int argc,char ** argv)
{
int iRes,iPort,iTmp;
SOCKET s,rs;
SOCKADDR_IN sin,rsin;
WSADATA wsad;
WORD wVersionReq;
char recvBuf[BUFLEN];
if(argc<2)
{
fprintf(stderr,"Usage: WebServer 999/n/t999 - Port number for this server.");
return -1;
}
iPort=0;
iPort=atoi(argv[1]);
if(iPort<=0)
{
fprintf(stderr,"must specify a port number");
return -1;
}
wVersionReq=MAKEWORD(1,1);
iRes=WSAStartup(wVersionReq,&wsad);
if(iRes!=0)
{
SocketError("WSAStartup()");
return -1;
}
s=socket(PF_INET,SOCK_STREAM,0);
if(s==INVALID_SOCKET)
{
SocketError("socket()");
return -1;
}
sin.sin_family=PF_INET;
sin.sin_port=htons(iPort);
sin.sin_addr.s_addr=INADDR_ANY;
iTmp=sizeof(sin);
if(bind(s,(LPSOCKADDR)&sin,iTmp)==SOCKET_ERROR)
{
SocketError("bind()");
closesocket(s);
WSACleanup();
return -1;
}
if(listen(s,1)==SOCKET_ERROR)
{
SocketError("listen()");
closesocket(s);
WSACleanup();
return -1;
}
fprintf(stderr,"WebServer Running....../n");
iTmp=sizeof(rsin);
rs=0;
while(1)
{
if(_kbhit()!=0)
{
if(_getch()!=27)
break;
if(rc!=0)
closesocket(rs);
closesocket(s);
WSACleanup();
fprintf(stderr,"WebServer Stopped....../n");
return 0;
}
rs=accept(s,(LPSOCKADDR)&rsin,&iTmp);
if(rs==INVALID_SOCKET)
{
SocketError("accept()");
closesocket(s);
WSACleanup();
return -1;
}
iRes=recv(rs,recvBuf,BUFLEN,0);
printf("RECEIVED DATA: /n---------------------------------/n%s/n---------------------------------/n",recvBuf);
if(iRes==SOCKET_ERROR)
{
SocketError("recv()");
closesocket(rs);
closesocket(s);
WSACleanup();
return -1;
}
char * sRes;
sRes=strstr(recvBuf,"GET");
if(sRes!=NULL&&(sRes-recvBuf)<3)
sRes=strchr(recvBuf,'/');
if(sRes!=NULL)
{
char * sRes1;
sRes1=strchr(sRes,'/r');
if(strchr(sRes,' ')<sRes1)
sRes1=strchr(sRes,' ');
if(sRes1!=NULL&&(sRes1-sRes)<256)
{
char tmp[256];
strncpy(tmp,sRes,(sRes1-sRes));
tmp[sRes1-sRes]=0;
int i;
for(i=strlen(tmp)-1;(tmp[i]==' '||tmp[i]=='/t')&&i>=0;i--)
tmp[i]=0;
for(i=0;tmp[i]==' '||tmp[i]=='/t';i++);
SendHtmlFile(rs,&tmp[i]);
}
}
else
{
HtmlError(rs,202,"Bad request");
}
closesocket(rs);
}
return 0;
}
//A simplest web client
//Written by Shen zhiliang for learning Winsock & HTTP
//[email protected]
//1998.7.29
#include "winsock.h"
#include "stdio.h"
#define BUFLEN 4096
void SocketError(char * call)
{
fprintf(stderr," WinSock Error# function: %s, error code:%ld/n",call,WSAGetLastError());
}
main(int argc,char ** argv)
{
int iRes,iPort,iTmp;
SOCKET s,rs;
SOCKADDR_IN sin,rsin;
WSADATA wsad;
WORD wVersionReq;
char recvBuf[BUFLEN];
if(argc<4)
{
fprintf(stderr,"Usage: sockserver ip port message/n");
return -1;
}
if(inet_addr(argv[1])==INADDR_NONE)
{
fprintf(stderr,"Error ip gaving/n");
return -1;
}
iPort=0;
iPort=atoi(argv[2]);
sin.sin_addr.s_addr=inet_addr(argv[1]);
sin.sin_family=PF_INET;
sin.sin_port=htons(iPort);
if(iPort<=0)
{
fprintf(stderr,"must specify a number for port/n");
return -1;
}
wVersionReq=MAKEWORD(1,1);
iRes=WSAStartup(wVersionReq,&wsad);
if(iRes!=0)
{
SocketError("WSAStartup()");
return -1;
}
s=socket(PF_INET,SOCK_STREAM,0);
if(s==INVALID_SOCKET)
{
SocketError("socket()");
return -1;
}
iTmp=sizeof(sin);
fprintf(stderr,"WinSock Client Start....../n");
if(connect(s,(LPSOCKADDR)&sin,iTmp)==SOCKET_ERROR)
{
SocketError("connect()");
closesocket(s);
WSACleanup();
return -1;
}
strcpy(recvBuf,argv[3]);
strcat(recvBuf,"/r/n/r/n");
iRes=send(s,recvBuf,strlen(recvBuf),0);
if(iRes==SOCKET_ERROR)
{
SocketError("send()");
closesocket(s);
WSACleanup();
return -1;
}
printf("Sent Data:/n------------------/n%s/n------------------/n",recvBuf);
FILE * fp=fopen("send.dat","a+b");
if(fp==NULL)
return -1;
iRes=recv(s,recvBuf,BUFLEN,0);
while(iRes!=SOCKET_ERROR&&iRes!=0)
{
printf("Received Data:/n------------------/n%s/n------------------/n",recvBuf);
fwrite(recvBuf,sizeof(char),iRes,fp);
iRes=recv(s,recvBuf,BUFLEN,0);
}
fclose(fp);
closesocket(s);
WSACleanup();
return 0;
}
Winsock函数用法说明
WSAStartup()
连结应用程序与Winsock.DLL 的第一个函数。
格 式:
int WSAStartup( WORD wVersionRequested,LPWSADATA lpWSAData )
参 数:
wVersionRequested 欲使用的 Windows Sockets API 版本
lpWSAData 指向 WSADATA 资料的指标
传回值:
成功 - 0
失败 - WSASYSNOTREADY / WSAVERNOTSUPPORTED / WSAEINVAL
说明:
此函数「必须」是应用程序呼叫到 Windows Sockets DLL 函数中的第一个函数呼叫成功后,才可以再呼叫其他 Windows Sockets DLL 的函数。此函数亦让使用者可以指定要使用的 Windows Sockets API 版本,及获取设计者的一些信息。
socket()
建立Socket。
格 式:
SOCKET socket( int af, int type, int protocol )
参 数:
af 目前只提供 PF_INET(AF_INET)
type Socket 的型态 (SOCK_STREAM、SOCK_DGRAM)
protocol 通讯协定(如果使用者不指定则设为0)
传回值:
成功 - Socket 的识别码
失败 - INVALID_SOCKET(呼叫 WSAGetLastError() 可得知原因)
说明:
此函数用来建立一 Socket,并为此 Socket 建立其所使用的资源。Socket 的型态可为 Stream Socket 或 Datagram Socket。
bind()
指定 Socket 的 Local 地址 (Address)。
格 式:
int bind( SOCKET s, const struct sockaddr FAR *name,int namelen );
参 数:
s Socket的识别码
name Socket的地址值
namelen name的长度
传回值:
成功 - 0
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明:
此一函数是指定 Local 地址及 Port 给某一未定名之 Socket。使用者若不在意地址或 Port 的值,那麽他可以设定地址为 INADDR_ANY,及 Port 为 0;那么Windows Sockets 会自动将其设定适当之地址及 Port (1024 到 5000之间的值),使用者可以在此 Socket 真正连接完成后,呼叫 getsockname() 来获知其被设定的值。
bind() 函数要指定地址及 port,这个地址必须是执行这个程序所在机器的 IP地址,所以如果读者在设计程序时可以将地址设定为 INADDR_ANY,这样Winsock 系统会自动将机器正确的地址填入。如果您要让程序只能在某台机器上执行的话,那么就将地址设定为该台机器的 IP 地址。由於此端是 Server 端,所以版主们一定要指定一个 port 号码给这个 socket。
listen()
设定 Socket 为监听状态,准备被连接。
格 式:
int listen( SOCKET s, int backlog );
参 数:
s Socket 的识别码
backlog 未真正完成连接前(尚未呼叫 accept 前)彼端的连接要求的最大个数
传回值:
成功 - 0
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明:
使用者可利用此函数来设定 Socket 进入监听状态,并设定最多可有多少个在未真正完成连接前的彼端的连接要求。(目前最大值限制为 5, 最小值为1)
connect()
要求连接某一 TCP Socket 到指定的对方。
格 式:
int connect( SOCKET s, const struct sockaddr FAR *name, int namelen );
参 数:
s Socket 的识别码
name 此 Socket 想要连接的对方地址
namelen name的长度
传回值:
成功 - 0
失败 - SOCKET_ERROR (呼叫WSAGetLastError()可得知原因)
说明:
此函数用来向对方要求建立连接。若是指定的对方地址为 0 的话,会传回错误值。当连接建立完成后,使用者即可利用此一 Socket 来做传送或接收资料之用了。
accept()
接受某一 Socket 的连接要求,以完成 Stream Socket 的连接。
格 式:
SOCKET accept(SCOKET s, SOCKADDR *addr,int FAR *addrlen )
参 数:
s Socket的识别码
addr 存放来连接的彼端的地址
addrlen addr的长度
传回值:
成功 - 新的Socket识别码
失败 - INVALID_SOCKET (呼叫 WSAGetLastError() 可得知原因)
说明:
Server 端的应用程序呼叫此一函数来接受 Client 端要求的 Socket 连接动作请求。
closesocket()
关闭某一Socket。
格 式:
int closesocket( SOCKET s );
参 数:
s Socket 的识别码
传回值:
成功 - 0
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明:
此一函数是用来关闭某一 Socket 。
WSACleanup()
结束 Windows Sockets DLL 的使用。
格 式:
int WSACleanup( void );
参 数: 无
传回值:
成功 - 0
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明:
当应用程序不再需要使用Windows Sockets DLL 时,须呼叫此一函数来注销使用,以便释放其占用的资源。
send()
使用连接式(connected)的 Socket 传送资料。
格 式:
int send( SOCKET s, const char FAR *buf, int len, int flags );
参 数:
s Socket 的识别码
buf 存放要传送的资料的暂存区
len buf 的长度
flags 此函数被呼叫的方式
传回值:
成功 - 送出的资料长度
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明:
此函数用于将信息从本端通过socket发送到远程端。
recv()
自 Socket 接收资料。
格 式:
int recv( SOCKET s, char FAR *buf, int len, int flags );
参 数:
s Socket 的识别码
buf 存放接收到的资料的暂存区
len buf 的长度
flags 此函数被呼叫的方式
传回值:
成功 - 接收到的资料长度 (若对方 Socket 已关闭,则为 0)
失败 - SOCKET_ERROR (呼叫 WSAGetLastError() 可得知原因)
说明:
此函数用来自连接式的 Datagram Socket 或 Stream Socket 接收资料。对 Stream Socket 言,版主们可以接收到目前 input buffer 内有效的资料,但其数量不超过 len 的大小。
WSAStartup()
连结应用程序与 Windows Sockets DLL 的第一个函数。
格 式:
int WSAStartup( WORD wVersionRequested,LPWSADATA lpWSAData );
参 数:
wVersionRequested 可使用的 Windows Sockets API 最高版本
lpWSAData 指向 WSADATA 资料的指标
传回值:
成功 - 0
失败 - WSASYSNOTREADY / WSAVERNOTSUPPORTED / WSAEINVAL
说明:
此函数「必须」是应用程序呼叫到 Windows Sockets DLL 函数中的第一个,也唯有此函数呼叫成功后,才可以再呼叫其他 Windows Sockets DLL 的函数。此函数亦让使用者可以指定要使用的 Windows Sockets API 版本,及获取设计者的一些信息。