首页 > 免费论文 > 计算机论文 > C语言实的串行通信接口程序
 

C语言实的串行通信接口程序

日期:2006-06-10 04:12:04  来源:
请您记住思索网的网址: http://www.4so.net  [加入收藏夹]

      摘要该文介绍了Sockets通信原理,从程序员角度着重讨论了WindowsSockets为支持异步通信对Sockets的功能扩充,并给出了应用WindowsSockets实现网络实时通信的一个程序实例。关
IBM公司于1994年4月推出的TCP/IPforDOSV2.1.1所提供的开发软件包Programmer'sToolKit不仅带有DOS下网络编程接口,而且提供了Windows下网络异步通信接口WINSOCK。
一、Socket网络编程原理Socket是BSDUNIX提供的网络应用编程接口,它采用客户机/服务器的通信机制,使网络客户机方和服务器方通过Socket实现网络之间的连接和数据交换。Socket提供了一系列的系统调用,使用这些系统调用可以实现TCP、UDP、ICMP和IP等多种网络协议之间的通信。
Socket有三种主要类型:streamsockets,datagramsockets和rawsockets。Streamsocket接口定义了一种可靠的面向连接的服务,它实现了无差错无重复的顺序数据传输。它通过内置的流量控制解决了数据的拥塞,应用程序可以发送任意长度的数据,将数据当作字节流。Datagramsocket接口定义了一种无连接的服务,数据通过相互独立的包进行传输,包的传输是无序的,并且不保证是否出错、丢失和重复。包长度是有限的(隐含长度为8192字节,最大长度可设为32768字节)。Rawsocket接口允许对低层协议如IP和ICMP的直接存取,它主要用于新的网络协议实现的测试等。
下面我们通过一个面向连接的传输发生的典型情况来说明socket网络通信的实现。
由图我们可以看出,客户机和服务器的关系不是对称的。服务器首先启动,然后在某一时间启动客户机与服务器建立连接。服务器和客户机开始都必须用调用socket()建立一个套接字(socket),然后服务器调用bind()将套接字与一个本地网络地址捆扎在一起,再用调用listen()使套接字处于一种被动的准备接收状态,同时规定它的请求队列长度,之后服务器就可以调用accept()来接收连接了。客户机在建立套接字之后,便可以通过调用connect()和服务器建立连接。连接建立后,客户机和服务器之间就可以通过连接发送和接收数据(调用read()和write())。最后,待数据传送结束,双方调用close()关闭套接字。
@@T8S10700.GIF;面向连接的协议实现的Socket调用图@@
二、WINSOCK对Socket的扩充
BSDSocket支持阻塞(blocking)和非阻塞(non-blocking)两种工作方式。在阻塞方式下,connect()、accept()、read()和recv()等调用在执行时都处于阻塞状态直到它成功或出错返回。在非阻塞方式下,这些调用是立即返回的,但是它们是否完成得靠查询才能知道。对于Windows这种非抢先多任务操作系统来说,这两种工作方式都是难以接受的,为此,WINSOCK在尽量与BSDSocket保持一致的前提下,又对它作了必要的扩充。
WINSOCK对BSDSocket的扩充主要是在基于消息、对网络事件的异步存取接口上。表1列出了WINSOCK扩充的函数功能。
从表1可以看出,WINSOCK的扩充功能可以分为如下几类。
(1)异步选择机制
异步选择函数WSAAsyncSelect()允许应用程序提名一个或多个感兴趣的网络事件,所有非阻塞的网络I/O例程(如send()和resv()),不管它是已经使用还是即将使用,都可作为WSAAsyncSelect()函数选择的候选。当被提名的网络事件发生时,Windows应用程序的窗口函数将收到一个消息,消息附带的参数指示被提名过的某一网络事件。
@@T8S10701.GIF;表1WINSOCK扩充函数功能@@
(2)异步请求例程
异步请求例程允许应用程序用异步方式获取请求的信息,如WSAAsyncGetXByY()类函数允许用户请求异步服务,这些功能在使用标准Berkeley函数时是阻塞的。函数WSACancelAsyncRequest()允许用户终止一个正在执行的异步请求。
(3)阻塞处理方法
WINSOCK在调用处于阻塞时进入一个叫“Hook”的例程,它负责处理Windows消息,使得Windows的消息循环能够继续。WINSOCK还提供了两个函数(WSASetBlockingHook()和WSAUnhookBlockingHook())让用户能够设置和取消自己的阻塞处理例程。另外,函数WSAIsBlocking()可以检测调用是否阻塞,函数WSACancelBlockingCall()可以取消一个阻塞的调用。
(4)出错处理
为了和以后的多线索环境(如WindowsNT)兼容,WINSOCK提供了两个出错处理函数WSAGetLastError()和WSASetLastError()来获取和设置本线索的最近错误号。
(5)启动与终止
WINSOCK的应用程序在使用上述WINSOCK函数前,必须先调用WSAStartup()函数对WindowsSocketsDLL进行初始化,以协商WINSOCK的版本支持,并分配必要的资源。在应用程序退出之前,应该先调用函数WSACleanup()终止对WindowsSocketsDLL的使用,并释放资源,以利下一次使用。
在这些函数中,实现Windows网络实时通信的关键是异步选择函数WSAAsyncSelect()的使用,其原型如下:intPASCALFARWSAAsyncSelect(SOCKETs,HWNDhWnd,unsignedintwMsg,longlEvent);它请求WindowsSocketsDLL在检测到在套接字s上发生的lEvent事件时,向窗口hWnd发送一个消息wMsg。它自动地设置套接字s处于非阻塞工作方式。参数lEvent由表2所列事件的一个或多个组成。
@@T8S10702.GIF;表2异步选择网络事件@@
例如,我们要在套接字s读准备好或写准备好时接到通知,可以使用下面的语句:
rc=WSAAsyncSelect(s,hWnd,wMsg,FD-READ|FD-WRITE);
当套接字s上被提名的一个网络事件发生时,窗口hWnd将收到消息wMsg,变量lParam的低字指示网络发生的事件,高字指示错误码。应用程序就可以通过这些信息来决定自己的下一步动作。
三、网络实时通信的实现
我们来设计一个简单的基于连接的点对点网络实时通信程序。服务器首先启动,它建立套接字之后等待客户机的连接;客户机在启动后,建立套接字,然后和服务器建立连接;连接建立后,客户机通过连接给服务器发送一段数据,服务器接收后又将它发送回来,客户机再发送,如此循环,直至用户命令客户机退出或网络出错;客户机关闭连接和套接字后退出,服务器在检测到连接关闭后,关闭套接字自动结束。
我们的实例是UNIX下基于BSDSocket的服务器程序和Windows下基于WINSOCK的客户机程序之间的通信。服务器在主机UNIX下直接运行,前台和后台均可;客户机在Windows下运行,带一个参数,即主机的名字。如winclientrs6000,rs6000是在HOSTS文件中已定义好的主机名。
我们先看客户机程序,首先定义几个宏、菜单资源和部分全局变量。
程序1:部分Windows程序源代码(宏、菜单和变量)
#defineUSERPORT3333/*用户定义端口号*/
#defineIDM-START101/*“启动”菜单项标志*/
#defineIDM-EXIT102/*“退出”菜单项标志*/
#defineUM-SOCKWM-USER+0x100/*用户定义网络消息*/
ClientMenuMENU/*客户机菜单*/
BEGIN
POPUP"&Server"
BEGIN
MENUITEM"&Start...",IDM-START
MENUITEM"&Stop",IDM-STOP
END
END
#include<winsock.h>/*必须包含winsock.h头文件*/
HANDLEhInst;
charserver-address={0};/*服务器地址缓冲区*/
charbuffer;/*接收发送缓冲区*/
charFAR*lpBuffer=&buffer;
SOCKETs=0;/*套接字*/
structsockaddr-indst-addr;/*目标地址*/
structhostent*hostaddr;/*主机地址*/
structhostenthostnm;
intcount=0;/*发送接收循环计数器*/
客户机程序的窗口主函数很简单,它在注册窗口类、建立窗口后,只是给主窗口函数发送一个用户消息,然后就进入Windows消息处理循环。
程序2:部分Windows程序源代码(窗口主函数)
intPASCALWinMain(HANDLEhInstance,HANDLEhPrevInstance,LPSTRlp
CmdL
ine,intnCmdShow)
{
HWNDhWnd;
MSGmsg;
lstrcpy((LPSTR)server-address,lpCmdLine);/*取主机名字*/
if(!hPrevInstance)
if(!InitApplication(hInstance))
return(FALSE);
hInst=hInstance;
hWnd=CreateWindow("ClientClass","WindowsECHOClient",
WS-OVERLAPPEDWINDOW,CW-USEDEFAULT,CW-USEDEFAULT,
CW-USEDEFAULT,CW-USEDEFAULT,
NULL,NULL,hInstance,NULL);
if(!hWnd)
return(FALSE);
ShowWindow(hWnd,nCmdShow);
UpdateWindow(hWnd);
/*给主窗口函数发送WM-USER消息*/
PostMessage(hWnd,WM-USER,(WPARAM)0,(LPARAM)0);
while(GetMessage(&msg,NULL,NULL,NULL)){
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return(msg.wParam);
}
主窗口函数ClientProc是程序的主要部分,它处理相关的消息:在接到消息WM-USER后,它调用函数WSAStartup()初始化WindowsSocketsDLL,并检查其版本号,然后通过主机名获取主机地址;在接到消息WM-COMMAND时,如果是命令IDM-START,则调用子程序Client()建立套接字,并试图和服务器建立连接,如果是命令IDM-STOP,则调用函数WSACleanup()终止WindowsSocketsDLL,并发出终止应用程序的消息;在接到消息UM-SOCK时,它根据参数lParam指示的网络事件,进行相应的操作,然后选择下一个期望的网络事件。
程序3:部分Windows程序源代码(主窗口函数)
longFARPASCAL
ClientProc(HWNDhWnd,unsignedmessage,UINTwParam,LONGlParam)
{
intlength,i;
WSADATAwsaData;/*描述WindowsSockets实现细节的数据结构*/
intStatus;
switch(message){
caseWM-USER:
Status=WSAStartup(0x101,&wsaData);
if(Status!=0){
AlertUser(hWnd,"WSAStartup()failed");
PostQuitMessage(0);
}
if(LOBYTE(wsaData.wVersion)!=1||HIBYTE(wsaData.wVersion)!=1)
{/*现在支持的版本是WINSOCK.DLL1.1*/
AlertUser(hWnd,"WSAStartup()Versionnotmatch");
WSACleanup();
PostQuitMessage(0);
}
hostaddr=gethostbyname(server-address);
if(hostaddr==NULL){
AlertUser(hWnd,"gethostbynameERROR");
WSACleanup();
PostQuitMessage(0);
}
memcpy(&hostnm,hostaddr,sizeof(structhostent));
break;
caseWM-COMMAND:
switch(wParam){
caseIDM-START:
if(!Client(hWnd)){
closesocket(s);
AlertUser(hWnd,"StartFailed");
}
break;
caseIDM-STOP:
WSACleanup();
PostQuitMessage(0);
break;
}
break;
caseUM-SOCK:
switch(lParam){
caseFD-CONNECT:/*网络事件:连接建立*/
if(!set-select(hWnd,FD-WRITE))/*选择:期望发送*/
closesocket(s);
break;
caseFD-READ:/*网络事件:读准备好*/
if(!receive-pkt(hWnd)){/*接收数据*/
AlertUser(hWnd,"ReceivePacketFailed");
closesocket(s);
break;
}
if(!set-select(hWnd,FD-WRITE))/*选择:期望发送*/
closesocket(s);
break;
caseFD-WRITE:/*网络事件:写准备好*/
for(i=0;i<1024;i++)
buffer=(char)'A'+i%26;
length=1024;
if(!(send-pkt(hWnd,length))){/*发送数据*/
AlertUser(hWnd,"PacketSendFailed");
closesocket(s);
break;
}
if(!set-select(hWnd,FD-READ))/*选择:期望接收*/
closesocket(s);
break;
caseFD-CLOSE:/*网络事件:连接关闭。操作:停止异步选择*/
if(WSAAsyncSelect(s,hWnd,0,0)==SOCKET-ERROR)
AlertUser(hWnd,"WSAAsyncSelectFailed");
AlertUser(hWnd,"Sockethasbeenclosed");
break;
default:/*网络出错则警告,其他事件忽略*/
if(WSAGETSELECTERROR(1Param)!=0){
AlertUser(hWnd,"SocketReportFailure");
closesocket(s);
break;
}
break;
}
break;
caseWM-DESTROY:
closesocket(s);/*关闭窗口前应该关闭套接字,并*/
WSACleanup();/*终止WindowsSocketsDLL*/
PostQuitMessage(0);
break;
default:
return(DefWindowProc(hWnd,message,
wParam,lParam));
}
return(NULL);
}
程序4:部分Windows程序源代码(子程序)
BOOLClient(HWNDhWnd)/*客户机子程序*/
{
if(!make-skt(hWnd))/*建立套接字*/
return(FALSE);
if(!set-select(hWnd,FD-CONNECT))/*设置异步连接*/
return(FALSE);
if(!connect-skt(hWnd))/*建立连接*/
return(FALSE);
return(TRUE);
}
BOOLreceive-pkt(HWNDhWnd)/*接收数据子程序*/
{
HDCdc;
intlength;
int11,12,13;
charlinel,line2,line3;
count++;/*循环计数器加1*/
if((length=recv(s,lpBuffer,1024,0))==SOCKET-ERROR)
return(FALSE);/*如果接收数据出错,则返回FALSE*/
if(length==0)/*接收数据长度为零,表示连接中断*/
return(FALSE);
if(dc=GetDC(hWnd)){/*接收数据成功,显示信息*/
11=wsprintf((LPSTR)line1,"TCPEchoClientNo.%d",count);
12=wsprintf((LPSTR)line2,"Received%dbytes",length);
13=wsprintf((LPSTR)line3,"Thoseare:%c,%c,%c,%c,%c,%c",
buffer,buffer,buffer,buffer,buffer,buffer
);
TextOut(dc,10,2,(LPSTR)linel,11);
TextOut(dc,10,22,(LPSTR)line2,12);
TextOut(dc,10,42,(LPSTR)line3,13);
ReleaseDC(hWnd,dc);
}
return(TRUE);
}
BOOLset-select(HWNDhWnd,longlEvent)/*异步选择子程序*/
{
if(WSAAsyncSelect(s,hWnd,UM-SOCK,lEvent)==SOCKET-ERROR){
AlertUser(hWnd,"WSAAsyncSelectFailed");
return(FALSE);
}
return(TRUE);
}
BOOLmake-skt(HWNDhWnd)/*建立套接字子程序*/
{
if((s=socket(AF-INET,sock-type,0))==INVALID-SOCKET){
AlertUser(hWnd,"SocketFailed");
return(FALSE);
}
return(TRUE);
}
BOOLconnect-skt(HWNDhWnd)/*建立连接子程序*/
{
memset((void*)&dst-addr,sizeof(dst-addr),0);
dst-addr.sin-family=AF-INET;
dst-addr.sin-port=htons(USERPORT);
dst-addr.sin-addr.s-addr=*((unsignedlong*)hostnm.h-addr-list);
if(connect(s,(structsockaddr*)&dst-addr,
sizeof(dst-addr))==SOCKET-ERROR){
AlertUser(hWnd,"ConnectFailed");
return(FALSE);
}
return(TRUE);
}
BOOLsend-pkt(HWNDhWnd,intlen)/*发送数据子程序*/
{
intlength;
if((length=send(s,lpBuffer,len,0))==SOCKET-ERROR)
return(FALSE);
elseif(length!=len){
AlertUser(hWnd,"SendLengthNOTMatch!");
return(FALSE);
}
return(TRUE);
}
我们用最简单的语句编制一个UNIX下基于BSDSOCKET的服务器程序,它在建立连接后,只负责将收到的数据发回去,在连接断开后,服务器关闭套接字返回。要编制在Windows下的服务器程序,可参照客户机程序,使用WINSOCK的异步选择机制。
程序5:UNIX下服务器程序源代码
/*TCP/IP必要的头文件*/
#include<sys/types.h>
#include<netinet/in.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#defineUSERPORT3333/*用户定义端口号,与客户机相同*/
#defineHOST-IP-ADDR"166.111.8.80"/*我们的主机地址*/
main(intargc,char**argv)
{
charbuf;/*bufferforsendingandreceivingdata*/
structsockaddr-inclient;/*clientaddressinformation*/
structsockaddr-inserver;/*serveraddressinformation*/
ints;/*socketforacceptingconnections*/
intns;/*socketconnectedtoclient*/
intnamelen;/*lengthofclientname*/
intpktlen;/*lengthofpacketreceivedorsended*/
if((s=socket(AF-INET,SOCK-STREAM,0))<0){
perror("Socket()");
return;
}
/*Bindthesockettotheserveraddress.*/
bzero((char*)&server,sizeof(server));
server.sin-len=sizeof(structsockaddr-in);
server.sin-family=AF-INET;
server.sin-port=htons(USERPORT);
server.sin-addr.s-addr=INADDR-ANY;
if(bind(s,(structsockaddr*)&server,sizeof(server))<0){
perror("Bind()");
return;
}
/*Listenforconnections.Specifythebacklogas1.*/
if(listen(s,1)!=0){
perror("Listen()");
return;
}
/*Acceptaconnection.*/
namelen=sizeof(client);
if((ns=accept(s,(structsockaddr*)&client,&namelen))==-1){
perror("Accept()");
return;
}
/*Receivethemessageonthenewlyconnectedsocket.*/
for(;;){
if((pktlen=recv(ns,buf,1024,0))<0){
perror("Recv()");
break;
}
elseif(pktlen==0){
printf("Recv():returnFAILED,connectionisshutdown!");
break;
}
elseprintf("Recv():returnSUCCESS,packetlength=%d",pktlen);
sleep(1);/*Sleep()1秒钟是为了减慢数据交换速度*/
/*Sendthemessagebacktotheclient.*/
if(send(ns,buf,pktlen,0)<0){
perror("Send()");
break;
}
elseprintf("Send():returnSUCCESS,packetlength=%d",pktlen);
}
close(ns);
close(s);
printf("Serverendedsuccessfully");
}
四、结束语
本文试图通过一个实例来说明如何使用WINSOCK实现Windows下网络实时通信。从上面的讨论可以看出,使用WINSOCK编制Windows下网络软件是比较方便的,WINSOCK提供的异步选择机制使Socket强大的网络编程功能能够在Windows下得到应用。相信随着INTERNET的推广,TCP/IP网络协议的广泛使用,使用WINSOCK编制Windows网络实时通信软件将会有一个大的发展。

参考文献
1MartinHall等.WindowsSockets-AnOpenInterfaceforNetworkProgrammingunderMicrosoftWindows.WINSOCKDocument,1993(6).2孙义等.UNIX环境下的网络程序设计.北京:希望公司,1991.3梁振军等.新编TCP/IP协议与计算机网络互联技术.北京:希望公司,1992.


 
 
热门信息
 
相关文章