用Winsock实现语音全双工通信
副标题#e#
一、引言
Windows 95作为微机的操纵系统,已经完全融入了网络与通信成果,不只可以成立纯Windows 95情况下的“对等网络”,并且支持多种协议,如TCP/IP、IPX/SPX、NETBUI等。在TCP/IP协议组中,TPC是一种面向毗连的协义,为用户提供靠得住的、全双工的字节约处事,具有确认、流节制、多路复用和同步等成果,适于数据传输。UDP协议则是无毗连的,每个分组都携带完整的目标地点,各分组在系统中独立传送。它不能担保分组的先后顺序,不举办分组堕落的规复与重传,因此不担保传输的靠得住性,可是,它提供高传输效率的数据报处事,适于及时的语音、图像传输、广播动静等网络传输。
Winsock接口为历程间通信提供了一种新的手段,它不单能用于同一呆板中的历程之间通信,并且支持网络通信成果。跟着Windows 95的推出。Winsock已经被正式集成到了Windows系统中,同时包罗了16位和32位的编程接口。而Winsock的开拓东西也可以在Borland C++4.0、Visual C++2.0这些C编译器中找到,主要由一个名为winsock.h的头文件和动态毗连库winsock.dll或wsodk32.dll构成,这两种动态毗连库别离用于Win16和Win32的应用措施。
本文针对话音的全双工传输要求,回收UDP协议实现了及时网络通信。利用VisualC++2.0编译情况,其动态毗连库名为wsock32.dll。
二、主要函数的利用要点
通过成立双套接字,可以很利便地实现全双工网络通信。
1.套接字成立函数:
SOCKET socket(int family,int type,int protocol)
对付UDP协议,写为:
SOCKRET s;
s=socket(AF_INET,SOCK_DGRAM,0);
或s=socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP)
为了成立两个套接字,必需实现地点的反复绑定,即,当一个套接字已经绑定到某当地地点后,为了让另一个套接字反复利用该地点,必需为挪用bind()函数绑定第二个套接字之前,通过函数setsockopt()为该套接字配置SO_REUSEADDR套接字选项。通过函数getsockopt()可得到套接字选项配置状态。需要留意的是,两个套接字所对应的端标语不能沟通。
另外,还涉及到套接字缓冲区的配置问题,按划定,每个区的配置范畴是:不小于512个字节,大大于8k字节,按照需要,文中选用了4k字节。
#p#副标题#e#
2.套接字绑定函数
int bind(SOCKET s,struct sockaddr_in*name,int namelen)
s是适才建设好的套接字,name指向描写通讯工具的布局体的指针,namelen是该布局体的长度。该布局体中的分量包罗:IP地点(对应name.sin_addr.s_addr)、端标语(name.sin_port)、地点范例(name.sin_family,一般都赋成AF_INET,暗示是internet地点)。
(1)IP地点的填写要领:在全双工通信中,要把用户名对应的点分暗示法地点转换成32位长整数名目标IP地点,利用inet_addr()函数。
(2)端标语是用于暗示同一台计较机差异的历程(应用措施),其分派要领有两种:1)历程可以让系统为套接字自动分派一端标语,只要在挪用bind前将端标语指定为0即可。由系统自动分派的端标语位于1024~5000之间,而1~1023之间的任一TCP或UDP端口都是保存的,系统不答允任一历程利用保存端口,除非其有效用户ID是零(超等用户)。
(2)历程可为套接字指定一特定端口。这对付需要给套接字分派一众所端口的处事器是很有用的。指定范畴为1024和65536之间∩任意指定。
在本措施中,对两个套接字的端标语划定为2000和2001,前者对应发送套接字,后者对应吸收套接字。
端标语要从一个16位无标记数(u_short范例数)从主机字节顺序转换成网络字节顺序,利用
htons()函数。
按照以上两个函数,可以给出双套接字成立与绑定的措施片段;
//配置有关的全局变量
SOCKET sr,ss;
HPSTR sockBufferS,sockBufferR;
HANDLE hSendData,hReceiveData;
DWROD dwDataSize=1024*4;
struct sockaddr_in therel.there2;
#DEFINE LOCAL_HOST_ADDR 200.200.200.201
#DEFINE REMOTE_HOST-ADDR 200.200.200.202
#DEFINE LOCAL_HOST_PORT 2000
#DEFINE LOCAL_HOST_PORT 2001
//套接字成立函数
BOOL make_skt(HWND hwnd)
{
struct sockaddr_in here,here1;
ss=socket(AF_INET,SOCK_DGRAM,0);
sr=socket(AF_INET,SOCK_DGRAM,0);
if((ss==INVALID_SOCKET)||(sr==INVALID_SOCKET))
{
MessageBox(hwnd,“套接字成立失败!”,“”,MB_OK);
return(FALSE);
}
here.sin_family=AF_INET;
here.sin_addr.s_addr=inet_addr(LOCAL_HOST_ADDR);
here.sin_port=htons(LICAL_HOST_PORT);
//another socket
herel.sin_family=AF_INET;
herel.sin_addr.s_addr(LOCAL_HOST_ADDR);
herel.sin_port=htons(LOCAL_HOST_PORT1);
SocketBuffer();//套接字缓冲区的锁定配置
setsockopt(ss,SOL_SOCKET,SO_SNDBUF,(char FAR*)sockBufferS,dwDataSize);
if(bind(ss,(LPSOCKADDR)&here,sizeof(here)))
{
MessageBox(hwnd,“发送套接字绑定失败!”,“”,MB_OK);
return(FALSE);
}
setsockopt(sr SQL_SOCKET,SO_RCVBUF|SO_REUSEADDR,(char FAR*)
sockBufferR,dwDataSize);
if(bind(sr,(LPSOCKADDR)&here1,sizeof(here1)))
{
MessageBox(hwnd,“吸收套接字绑定失败!”,“”,MB_OK);
return(FALSE);
}
return(TRUE);
}
//套接字缓冲区配置
void sockBuffer(void)
{
hSendData=GlobalAlloc(GMEM_MOVEABLE|GMEM_SHARE,dwDataSize);
if(!hSendData)
{
MessageBox(hwnd,“发送套接字缓冲区定位失败!”,NULL,
MB_OK|MB_ICONEXCLAMATION);
return;
}
if((sockBufferS=GlobalLock(hSendData)==NULL)
{
MessageBox(hwnd,“发送套接字缓冲区锁定失败!”,NULL,
MB_OK|MB_ICONEXCLAMATION);
GlobalFree(hRecordData[0];
return;
}
hReceiveData=globalAlloc(GMEM_MOVEABLE|GMEM_SHARE,dwDataSize);
if(!hReceiveData)
{
MessageBox(hwnd,"“吸收套接字缓冲区定位败!”,NULL
MB_OK|MB_ICONEXCLAMATION);
return;
}
if((sockBufferT=Globallock(hReceiveData))=NULL)
MessageBox(hwnd,"发送套接字缓冲区锁定失败!”,NULL,
MB_OK|MB_ICONEXCLAMATION);
GlobalFree(hRecordData[0]);
return;
}
3.数据发送与吸收函数;
#p#分页标题#e#
int sendto(SOCKET s.char*buf,int len,int flags,struct sockaddr_in to,int
个中,参数flags一般取0。
tolen);
int recvfrom(SOCKET s.char*buf,int len,int flags,struct sockaddr_in
fron,int*fromlen)
recvfrom()函数实际上是读取sendto()函数发过来的一个数据包,当读到的数据字节少于划定吸收的数目时,就把数据全部吸收,并返回实际吸收到的字节数;当读到的数据多于划定值时,在数据报文方法下,多余的数据将被扬弃。而在流方法下,剩余的数据由下recvfrom()读出。为了发送和吸收数据,必需成立数据发送缓冲区和数据吸收缓冲区。划定:IP层的一个数据报最大不高出64K(含数据报头)。当缓冲区配置得过多、过大时,常因内存不足而导致套接字成立失败。在减小缓冲区后,该错误消失。颠末尝试,文中选用了4K字节。
另外,还应留意这两个函数中最后参数的写法,给sendto()的最后参数是一个整数值,而recvfrom()的则是指向一整数值的指针。
4.套接字封锁函数:closesocket(SOCKET s)
通讯竣事时,应封锁指定的套接字,以释与之相关的资源。
在封锁套接字时,应先对锁定的各类缓冲区加以释放。其措施片段为:
void CloseSocket(void)
{
GlobalUnlock(hSendData);
GlobalFree(hSenddata);
GlobalUnlock(hReceiveData);
GlobalFree(hReceiveDava);
if(WSAAysncSelect(ss,hwnd,0,0)=SOCKET_ERROR)
{
MessageBos(hwnd,“发送套接字封锁失败!”,“”,MB_OK);
return;
}
if(WSAAysncSelect(sr,hwnd,0,0)==SOCKET_ERROR)
{
MessageBox(hwnd,“吸收套接字封锁失败!”,“”,MB_OK);
return;
}
WSACleanup();
closesockent(ss);
closesockent(sr);
return;
}
}
三、Winsock的编程特点与异步选择机制
1 阻塞及其处理惩罚方法
在网络通讯中,由于网络拥挤或一次发送的数据量过大等原因,常常会产生互换的数据在短时间内不能传送完,收发数据的函数因此不能返回,这种现象叫做阻塞。Winsock对有大概阻塞的函数提供了两种处理惩罚方法:阻塞和非阻塞方法。在阻塞方法下,收发数据的函数在被挪用后一直要到传送完毕可能堕落才气返回。在阻塞期间,被阻的函数不会断挪用系统函数GetMessage()来保持动静轮回的正常举办。对付非阻塞方法,函数被挪用后当即返回,当传送完成后由Winsock给措施发一个事先约定好的动静。
在编程时,应只管利用非阻塞方法。因为在阻塞方法下,用户大概会长时间的期待进程中试图封锁措施,因为动静轮回还在起浸染,所以措施的窗口大概被封锁,这样当函数从Winsock的动态毗连库中返回时,主措施已经从内存中删除,这显然是极其危险的。
2 异步选择函数WSAAsyncSelect()的利用
Winsock通过WSAAsyncSelect()自动地配置套接字处于非阻塞方法。利用Windows Sockets实现Windows网络措施设计的要害就是它提供了对网络事件基于动静的异步存取,用于注册应用措施感乐趣的网络事件。它请求Windows Sockets DLL在检测到套接字上产生的网络事件时,向窗口发送一个动静。对UDP协议,这些网络事件主要为:
FD_READ 期望在套接字收到数据(即读筹备好)时吸收通知;
FD_WRITE 期望在套接字可发送数(即写筹备好)时吸收通知;
FD_CLOSE 期望在套接字封锁时接电通知
动静变量wParam指示产生网络事件的套接字,变量1Param的低字节描写产生的网络事件,高字包括错误码。如在窗口函数的动静轮回中均加一个分支:
#p#分页标题#e#
int ok=sizeof(SOCKADDR);
case wMsg;
switch(1Param)
{
case FD_READ:
//套接字上读数据
if(recvfrom(sr.lpPlayData[j],dwDataSize,0,(struct sockaddr FAR*)&there1,
(int FAR*)&ok)==SOCKET_ERROR0
{
MessageBox)hwnd,“数据吸收失败!”,“”,MB_OK);
return(FALSE);
}
case FD_WRITE:
//套接字上写数据
}
break;
在措施的体例中,应按照需要机动地将WSAAsyncSelect()函敏捷放在相应的动静轮回之中,其它说明可拜见文献[1]。另外,应该指出的是,以上措施片段中的动静框主要是为措施调试利便而配置的,而在正式产物中不再呈现。同时,凭据措施容错误设计,应成立一个专门的容错处理惩罚函数。措施中大概呈现的各类错误都将由该函数举办处理惩罚,依据错误的危害水平差异,成立几种差异的处理惩罚法子。这样,才气担保两边通话的顺利和靠得住。 四、结论
本文是多媒体网络传输项目标重要内容之一,今朝,团结硬件全双工语音卡等设备,已经乐成地实现了话音的全双工的通信。有关整个多媒体传输系统设计的内容,将有另文论述。
参考文献
1 蒋东兴,林鄂华Windows Sockets网络措施设计指南。清华大学出书社。1995,12
2 祝小翰 Winsock编程劈头,微电脑世界,1996,(8):54~60