WinsockAPI文档格式.docx
《WinsockAPI文档格式.docx》由会员分享,可在线阅读,更多相关《WinsockAPI文档格式.docx(20页珍藏版)》请在冰豆网上搜索。
}WSADATA,FAR*LPWSADATA;
在应用程序关闭套接字连接后,还需要调用WSACleanup函数终止对Winsock库的使用,并释放资源,函数声明如下:
intWSACleanup(void);
三、Winsock编程模型
不论是流套接字还是数据报套接字编程,一般都采用客户端/服务器模式,其运行原理基本类似。
数据报套接字的编程模型如图一所示。
流套接字的编程模型如图二所示。
图一数据报套接字编程模型图二流套接字编程模型
流套接字的服务进程和客户端进程在通信前必须创建各自的套接字并建立连接,然后才能实现数据传输。
具体编程步骤如下:
(1)服务器进程创建套接字。
(2)将本地地址绑定到套接字上以标识该套接字。
(3)将套接字置入监听模式并准备接收连接请求。
(4)客户端进程调用socket函数创建客户端套接字。
(5)客户端进程向服务进程发出连接请求。
(6)数据传输。
(7)关闭套接字。
服务器进程总是先于客户进程启动,调用socket创建一个流套接字,该函数声明如下:
SOCKETsocket(intaf,inttype,intprotocol);
◇af:
指定网络地址族,一般为AF_INET。
◇type:
指定套接字类型,可选的取值如下:
△SOCK_STREAM流套接字。
△SOCK_DGRAM数据报套接字。
◇protocol:
指定网络协议,一般为0,表示默认的TCP/IP协议。
成功创建了Socket之后,就应该选定通信的对象。
调用bind()函数可以将本地地址绑定到套接字上,该函数声明如下:
intbind(SOCKETs,conststructsockaddtFAR*name,intnamelen);
◇s:
指定一个未绑定的套接字句柄,用于等待客户进程的连接。
◇name:
指向sockaddr结构对象的指针。
◇namelen:
指定sockaddr结构的长度。
其中sockddr结构随选择的协议的不同而变化,因此常用的是sockaddr_in结构,用来标识TCP/IP协议下的地址,该结构定义如下:
structsockaddr_in{
shortsin_family;
//指定地址族,一般为AF_INET
u_shortsin_port;
//指定端口号
structin_addrsin_addr;
//指定IP地址
charsin_zero[8];
//填充位
};
其中IP地址结构in_addr的定义如下:
structin_addr{
union{
struct{u_chars_b1,s_b2,s_b3,s_b4;
}S_un_b;
struct{u_shorts_w1,s_w2;
}S_un_w;
u_longS_addr;
}S_un;
绑定成功后,调用listen函数用于设置套接字的等待连接状态,该函数声明如下:
intlisten(SOCKETs,intbacklog);
指定一个已绑定未连接的套接字句柄。
◇backlog:
指定正在等待连接的队列的最大长度,可取1~5。
进入监听状态后,通过调用accept函数使套接字做好接受客户连接的准备,该函数声明如下:
SOCKETaccept(SOCKETs,structsockaddrFAR*addr,intFAR*addrlen);
指定处于监听状态的套接字句柄。
◇addr:
指定一个有效的SOCKADDR_IN结构地址。
◇addrlen:
指定SOCKADDR_IN结构的长度。
accept函数返回后,addr变量中会包含请求连接的客户IP地址,并返回一个新的套接字句柄,对应于已经接受的那个客户端连接。
而原来的监听套接字仍处于监听状态。
客户进程调用connect函数可以主动提出连接请求,该函数声明如下:
intconnect(SOCKETs,conststructsockaddrFAR*name,intnamelen);
指定一个未连接的套接字句柄。
指定服务进程的IP地址信息,针对TCP协议。
指定name参数的长度。
当服务器进程接受连接请求后,将生成一个新的套接字,并向各客户进程返回接受信号。
一旦客户进程收到来自服务器的接受信号,表示建立连接,即可进行数据传输了。
调用send函数用于发送数据,调用recv函数用于接受数据,函数声明如下:
intsend(SOCKETs,constcharFAR*buf,intlen,intflags);
intrecv(SOCKETs,constcharFAR*buf,intlen,intflags);
参数说明
指定已建立连接的套接字。
◇buf:
发送或接受数据缓冲区。
◇len:
指定数据缓冲区的长度。
◇flags:
标志,一般为0。
通信结束,必须关掉连接以释放套接字占用的资源。
调用closesocket函数用于关闭套接字,该函数声明如下:
Intclosesocket(SOCKETs);
为了保证套接字正常关闭,一般在调用closesocket之前先调用shutdown函数中断连接。
该函数声明如下:
intshutdown(SOCKETs,inthow);
指定要中断的套接字句柄。
◇how:
指定将禁止的操作,可选的取值如下:
△SD_RECEIVE禁止调用接受函数。
△SD_SEND禁止调用发送函数。
△SD_BOTH取消收发操作。
四、WinsockI/O模型
Winsock套接字在两种模式下执行I/O操作,即阻塞和非阻塞。
默认情况下,套接字为阻塞模式。
下面的代码演示了创建一个套接字,并将其设置为非阻塞模式的过程:
SOCKETs;
//套接字句柄
unsignedlongcmd;
//指令参数
intnStatus;
//返回值
s=socket(AF_INET,SOCK_STREAM,0);
//创建流套接字
nStatus=ioctlsocket(s,FIOBIO,&
cmd);
//设置为非阻塞模式
将一个套接字设置为非阻塞模式之后,WinsockAPI调用会立即返回。
Winsock提供了几种不同的套接字I/O模型,如选择(Select)、异步选择(WSAAsyncSelect)、事件选择(WSAEventSelect)和重叠(Overlapped)等。
1.WSAAsyncSelect模型
利用异步选择模型,应用程序可以在一个套接字上接收以Windows消息为基础的网络事件通知。
该模型的实现方法是通过调用WSAAsyncSelect函数自动将套接字设置为非阻塞模式,并注册一个或多个网络事件,提供一个消息通知的窗口句柄。
当注册的网络事件发生时,对应的窗口将接收到一个基于消息的通知。
WSAAsyncSelect函数声明如下:
intWSAAsyncSelect(SOCKETs,HWNDhWnd,unsignedintwMsg,longIEwent);
指定需要事件通知的套接句柄。
◇hWnd:
指定接收消息的窗口句柄。
◇wMsg:
指定发送的消息。
◇IEvent:
指定网络事件集合,可以是以下取值的和:
△FD_READ想要接收读准备好的通知。
△FD_WRITE想要接收写准备好的通知。
△FD_OOB想要接收带外数据到达的通知。
△FD_ACCEPT想要接收连接准备好的通知。
△FD_CONNECT想要接收已经连接的通知。
△FD_CLOSE想要接收套接字关闭的通知。
如果要取消所有的通知,则将IEvent参数设置为0即可。
2.WSAEventSelect模型
与异步选择模型相似,利用事件选择模型应用程序可以在一个或多个套接字上接收以事件为基础的网络事件通知,并且它支持的网络事件与异步选择模型一样。
它与WSAAsyncSelect模型最主要的区别在于,网络事件会被发送到一个事件对象句柄,而不是一个窗口句柄。
首先需要调用WSACreateEvent函数创建事件对象来接收网络事件,该函数声明如下:
WSAEVENTWSACreateEvent(void);
返回的事件对象具有两种工作状态:
有信号和无信号。
接着调用WSAEventSelect函数将所创建的事件对象与套接字关联起来,并注册网络事件,该函数声明如下:
IntWSAEventSelect(SOCKETs,WSAEVENThEventObject,
LongINetworkEvents);
指定需要事件通知的套接字句柄。
◇hEventObject:
指定事件对象句柄。
◇InetworkEvent:
指定网络事件集合。
在完成一个I/O操作之后应用程序需要调用WSAResetEvent函数重置该事件对象,函数声明如下:
BOOLWSAResetEvent(WSAEVENThEvent);
◇hEvent:
用于指定事件对象句柄。
一个套接字与一个事件对象句柄关联起来之后,应用程序就可以通过WSAWaitForMultipleEvents函数等待网络事件来触发事件句柄的工作状态,进行I/O操作,该函数声明如下:
DWORDWSAWaitForMultipleEvents(DWORDcEvents,
constWSAEVENTFAR*IphEvents,
BOOLfWaitAll,DWORDdwTimeOUT,BOOLfAlertable);
◇cEvent:
指定事件对象句柄数组的元素的个数。
、
◇IphEvents:
指向一个事件对象句柄数组的指针。
◇fWaitAll:
指定是否等待所有事件对象同时有信号。
◇dwTimeOUT:
指定超时等待时间。
◇fAlertable:
指定当系统将I/O例程放入队列时,函数是否返回。
五、应用实例:
基于WinsockAPI调用的聊天室
1、服务器端应用程序设计
(1)首先建立一个空的工作空间E1701。
(2)在空间E1701里建立一个对话框工程Server作为服务端。
对话框设计如图三所示。
图三服务器端窗口设计
(3)在类CServerDlg中给显示聊天内容和发送聊天内容的文本框添加变量
//DialogData
//{{AFX_DATA(CServerDlg)
enum{IDD=IDD_SERVER_DIALOG};
CEditm_Show;
//聊天记录显示控件对象
CStringm_strShow;
//聊天记录字符串
CStringm_strMsg;
//聊天内容字符串
//}}AFX_DATA
(4)在服务器端要保存客户端的socket连接,故引入链表支持,在ServerDlg.h中添加代码:
#include<
afxtempl.h>
typedefCList<
SOCKET,SOCKET&
>
SOCKET_ARRAY;
(5)要调用WinsockAPI必须包含winsock.h头文件和动态链接库winsock.dll。
在StdAfx.h文件中添加如下代码:
winsock.h>
#pragmacomment(lib,"
wsock32.lib"
)
(6)自定义消息,并添加消息处理函数。
在Server.h中添加:
#defineWM_SERVERMSG(WM_USER+100)
//Generatedmessagemapfunctions
//{{AFX_MSG(CServerDlg)
afx_msglongOnServerMsg(WPARAMwParam,LPARAMlParam);
//}}AFX_MSG
(7)初始化服务器窗口,创建服务器端socket。
BOOLCServerDlg:
:
OnInitDialog()
{
CDialog:
OnInitDialog();
//TODO:
Addextrainitializationhere
try
{
WSADATAwsaData;
//WSADATA结构对象
WORDwVersionRequested=MAKEWORD(2,0);
//指定Winsock版本为2.0
WSAStartup(wVersionRequested,&
wsaData);
//启动Winsock
m_hSocket=socket(AF_INET,SOCK_STREAM,0);
//创建流套接字
UINTlen=WSAGetLastError();
//获取错误代码
if(len!
=0)
throwlen;
//抛出异常错误
m_saList.RemoveAll();
//清空套接字列表
WSAAsyncSelect(m_hSocket,
this->
m_hWnd,//接收消息的窗口为对话框
WM_SERVERMSG,//指定消息
FD_ACCEPT|FD_READ|FD_WRITE|FD_CLOSE);
//指定事件
m_uPort=8080;
//设置端口号
//设置套接字地址结构对象
m_addr.sin_family=AF_INET;
m_addr.sin_addr.S_un.S_addr=INADDR_ANY;
m_addr.sin_port=htons(m_uPort);
bind(m_hSocket,(LPSOCKADDR)&
m_addr,sizeof(m_addr));
//绑定套接字
listen(m_hSocket,3);
//进入监听状态
len=WSAGetLastError();
m_strShow=_T("
服务器启动成功……"
);
}
catch(UINT&
error)
switch(error)
{
caseWSANOTINITIALISED:
MessageBox("
创建套接字失败!
"
m_strShow=_T("
break;
caseWSAEINVAL:
监听端口已被占用!
服务器启动失败"
default:
}
}
UpdateData(FALSE);
//更新显示
returnTRUE;
}
(8)修改OnDestroy()函数
voidCServerDlg:
OnDestroy()
OnDestroy();
WSAAsyncSelect(m_hSocket,this->
m_hWnd,0,0);
//取消异步选择模式
WSACleanup();
//清理Winsock
(9)编写发送按钮函数
OnButton1()
UpdateData(TRUE);
m_strShow+=_T("
\r\n"
//回车、换行
m_strShow+=m_strMsg;
//添加聊天内容
for(inti=0;
i<
m_saList.GetCount();
i++)
{//向每个客户端发送聊天内容
s=m_saList.GetAt(m_saList.FindIndex(i));
intt=send(s,m_strMsg.GetBuffer(0),m_strMsg.GetLength(),0);
if(t<
0)
closesocket(s);
m_saList.RemoveAt(m_saList.FindIndex(i));
m_strMsg.Empty();
m_Show.LineScroll(m_Show.GetLineCount());
//跟踪滚动条的位置
(10)编写消息处理函数
longCServerDlg:
OnServerMsg(WPARAMwParam,LPARAMlParam)
SOCKETsocket,s;
inti,j;
charbuf[1024],name[6],buf1[1024];
intlen;
switch(lParam)
caseFD_ACCEPT:
socket=accept(m_hSocket,NULL,NULL);
for(i=0;
s=m_saList.GetAt(m_saList.FindIndex(i));
UintToChar(socket,name);
buf[0]=NULL;
strcat(buf,"
游客"
strcat(buf,name);
进入聊天室"
len=send(s,buf,strlen(buf),0);
if(len<
{
closesocket(s);
m_saList.RemoveAt(m_saList.FindIndex(i));
}
m_strShow+="
;
UintToChar(socket,name);
m_strShow+="
m_strShow+=name;
UpdateData(FALSE);
m_saList.AddHead(socket);
return0;
caseFD_READ:
length=m_saList.GetCount();
length;
if(s==wParam)
len=recv(s,buf,1024,0);
UintToChar(wParam,name);
buf[len]=NULL;
buf1[0]='
\0'
strcat(buf1,"
strcat(buf1,name);
说:
strcat(buf1,buf);
for(j=0;
j<
j++)
{
s=m_saList.GetAt(m_saList.FindIndex(j));
if(s!
=wParam)
{
len=send(s,buf1,strlen(buf1),0);
if(len<
{
closesocket(s);
m_saList.RemoveAt(m_saList.FindIndex(j));
}
}
}
len=WSAGetLastError();
len=WSAENOTCONN;
m_strShow+="
m_strShow+=buf1;
UpdateData(FALSE);