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 版本,及獲取設計者的一些信息。