资源描述
socket编程用法随着计算机网络化的深入,计算机网络编程在程序设计的过程中变得日益重要。由于C+语言对底层操作的优越性,许多文章都曾经介绍过用VC+进行Socket编程的方法。但由于都是直接利用动态连接库wsock32.dll进行操作,实现比较繁琐。其实,VC+的MFC类库中提供了CAsyncSocket这样一个套接字类,用他来实现Socket编程,是非常方便的。本文将用一个Echo例程来介绍CAsyncSocket类的用法。一客户端-1.创建一个DialogBased项目:CSockClient。2设计对话框-去掉Ok和Cancle两个按钮,增加ID_Connect(连接)、ID_Send(发送)、ID_Exit(关闭)按钮,增加ListBox控件IDC_LISTMSG和Edit控件IDC_EDITMSG,并按下表在ClassWizard中为CCSockClientDIg类添加变量。ControlIDTypeMemberIDC_EDITMSGCEditm_MSGIDC_LISTMSGClistBoxm_MSGS-3.CAsyncSocket类用DoCallBack函数处理MFC消息,当一个网络事件发生时,DoCallBack函数按网络事件类型:FD_READ、FD_WRITE、FD_ACCEPT、FD_CONNECT分别调用OnReceive、OnSend、OnAccept、OnConnect函数。由于MFC把这些事件处理函数定义为虚函数,所以要生成一个新的C+类,以重载这些函数,做法如下:以Public方式继承CAsyncSocket类,生成新类MySock;为MySock类添加虚函数OnReceive、OnConnect、OnSend4.在MySock.ccp中添加以下代码#includeCSockClient.h#includeCSockClientDlg.h-5.在MySock.h中添加以下代码public:BOOLm_bConnected;/是否连接UINTm_nLength;/消息长度charm_szBuffer4096;/消息缓冲区6在MySock.ccp中重载各函数MySock:MySock()m_nLength=0;memset(m_szBuffer,0,sizeof(m_szBuffer);m_bConnected=FALSE;MySock:MySock()/关闭套接字if(m_hSocket!=INVALID_SOCKET)Close();voidMySock:OnReceive(intnErrorCode)m_nLength=Receive(m_szBuffer,sizeof(m_szBuffer),0);/下面两行代码用来获取对话框指针CCSockClientApp*pApp=(CCSockClientApp*)AfxGetApp();CCSockClientDlg*pDlg=(CCSockClientDlg*)pApp-m_pMainWnd;pDlg-m_MSGS.InsertString(0,m_szBuffer);memset(m_szBuffer,0,sizeof(m_szBuffer);CAsyncSocket:OnReceive(nErrorCode);voidMySock:OnSend(intnErrorCode)Send(m_szBuffer,m_nLength,0);m_nLength=0;memset(m_szBuffer,0,sizeof(m_szBuffer);/继续提请一个“读”的网络事件,接收Server消息AsyncSelect(FD_READ);CAsyncSocket:OnSend(nErrorCode);voidMySock:OnConnect(intnErrorCode)if(nErrorCode=0)m_bConnected=TRUE;CCSockClientApp*pApp=(CCSockClientApp*)AfxGetApp();CCSockClientDlg*pDlg=(CCSockClientDlg*)pApp-m_pMainWnd;memcpy(m_szBuffer,Connectedto,13);strncat(m_szBuffer,pDlg-m_szServerAdr,sizeof(pDlg-m_szServerAdr);pDlg-m_MSGS.InsertString(0,m_szBuffer);AsyncSelect(FD_READ);/提请一个“读”的网络事件,准备接收CAsyncSocket:OnConnect(nErrorCode);-7.新建对话框IDD_Addr,用来输入IP地址和Port;生成新类CAddrDIg。增加两个Edit控件:IDC_Addr、IDC_Port按下表在ClassWizard中为CAddrDIg类添加变量。ControIIDTypeMemberIDC_AddrCStringm_AddrIDC_PortIntm_Port8.在CSockClientDlg.ccp中添加代码#includeAddrDlg.hprotected:intTryCount;MySockm_clientSocket;UINTm_szPort;public:charm_szServerAdr256;-9.双击IDD_CSOCKCLIENT_DIALOG对话框中的“连接”按钮,添加以下代码voidCCSockClientDlg:OnConnect()m_clientSocket.ShutDown(2);m_clientSocket.m_hSocket=INVALID_SOCKET;m_clientSocket.m_bConnected=FALSE;CAddrDlgm_Dlg;/默认端口1088m_Dlg.m_Port=1088;if(m_Dlg.DoModal()=IDOK&!m_Dlg.m_Addr.IsEmpty()memcpy(m_szServerAdr,m_Dlg.m_Addr,sizeof(m_szServerAdr);m_szPort=m_Dlg.m_Port;建立计时器,每1秒尝试连接一次,直到连上或TryCount10SetTimer(1,1000,NULL);TryCount=0;10添加Windows消息WM_TIMER响应函数OnTimervoidCCSockClientDlg:OnTimer(UINTnIDEvent)if(m_clientSocket.m_hSocket=INVALID_SOCKET)BOOLbFlag=m_clientSocket.Create(0,SOCK_STREAM,FD_CONNECT);if(!bFlag)AfxMessageBox(SocketError!);m_clientSocket.Close();PostQuitMessage(0);return;m_clientSocket.Connect(m_szServerAdr,m_szPort);TryCount+;if(TryCount=10|m_clientSocket.m_bConnected)KillTimer(1);if(TryCount=10)AfxMessageBox(ConnectFailed!);return;CDialog:OnTimer(nIDEvent);11双击IDD_CSOCKCLIENT_DIALOG对话框中的“发送”按钮,添加以下代码voidCCSockClientDlg:OnSend()if(m_clientSocket.m_bConnected)m_clientSocket.m_nLength=m_MSG.GetWindowText(m_clientSocket.m_szBuffer,sizeof(m_clientSocket.m_szBuffer);m_clientSocket.AsyncSelect(FD_WRITE);m_MSG.SetWindowText();12双击IDD_CSOCKCLIENT_DIALOG对话框中的“关闭”按钮,添加以下代码voidCCSockClientDlg:OnExit()/关闭Socketm_clientSocket.ShutDown(2);/关闭对话框EndDialog(0);12.运行此项目,连接时输入主机名或IP均可,CAsyncSocket类会自动处理。二服务端Server端的编程与Client端的类似,下面主要介绍他的Listen及Accept函数1.建立一个CNewSocket类,重载CAsyncSocket类的OnReceive、OnSend函数,如何进行信息的显示和发送可以参考Client程序。本例中采用将收到信息原封不动发回的方法来实现Echo功能,代码如下CNewSocket:OnReceive(intnErrorCOde)m_nLength=Receive(m_szBuffer,sizeof(m_szBuffer),0);/直接转发消息AsyncSelect(FD_WRITE);CNewSocket:OnSend(intnErrorCode)Send(m_szBuffer,m_nLength,0);2.建立一个CMyServerSocket类,重载CAsyncSocket类的OnAccept函数代码如下在MyServerSocket.h中声明变量public::CNewSocket*m_pSocket;voidCMyServerSocket:OnAccept(intnErrorCode)/侦听到连接请求,调用Accept函数CNewSocket*pSocket=newCNewSocket();if(Accept(*pSocket)pSocket-AsyncSelect(FD_READ)m_pSocket=pSocket;elsedeletepSocket;3为对话框添加一个“侦听”按钮,添加如下代码在CsockServerDlg.ccp中声明变量public:CMyServerSocketm_srvrSocket;voidCCSockServerDlg:OnListen()if(m_srvrSocket.m_hSocket=INVALID_SOCKET)BOOLbFlag=m_srvrSocket.Create(UserPort,SOCK_STREAM,FD_ACCEPT);if(!bFlag)AfxMessageBox(“SocketError!”);M_srvrSocket.Close();PostQuitMessage(0);Return;/“侦听”成功,等待连接请求if(!m_srvrSocket。Listen(1)intnErrorCode=m_srvrSocket.GetLastError()if(nError!=WSAEWOULDBLOCK)AfxMessageBox(“SocketError!”);M_srvrSocket.Close();PostQuitMessage(0);Return;4. 目前程序只能实现Echo功能,将信息原封不动的转发,若能将Accept中由CNewSocket*pSocket=newCNewSocket();得到的Socket指针存入一个CList或一个数组中,便像Client端那样,对所有的连接进行读写控制。三总结CAsyncSocket类为我们使用Socket提供了极大方便。建立Socket的WSAStartup过程和bind过程被简化成为Create过程,IP地址类型转换、主机名和IP地址转换的过程中许多复杂的变量类型都被简化成字符串和整数操作,特别是CAsyncSocket类的异步特点,完全可以替代繁琐的线程操作。MFC提供了大量的类库,我们若能灵活的使用他们,便会大大提高编程的效率。、TCP/IP体系结构与特点1、TCP/IP体系结构TCP/IP协议实际上就是在物理网上的一组完整的网络协议。其中TCP是提供传输层服务,而IP则是提供网络层服务。TCP/IP包括以下协议:(结构如图1.1)IP:网间协议(InternetProtocol)负责主机间数据的路由和网络上数据的存储。同时为ICMP,TCP,UDP提供分组发送服务。用户进程通常不需要涉及这一层。ARP:地址解析协议(AddressResolutionProtocol)此协议将网络地址映射到硬件地址。RARP:反向地址解析协议(ReverseAddressResolutionProtocol)此协议将硬件地址映射到网络地址ICMP:网间报文控制协议(InternetControlMessageProtocol)此协议处理信关和主机的差错和传送控制。TCP:传送控制协议(TransmissionControlProtocol)这是一种提供给用户进程的可靠的全双工字节流面向连接的协议。它要为用户进程提供虚电路服务,并为数据可靠传输建立检查。(注:大多数网络用户程序使用TCP)UDP:用户数据报协议(UserDatagramProtocol)这是提供给用户进程的无连接协议,用于传送数据而不执行正确性检查。FTP:文件传输协议(FileTransferProtocol)允许用户以文件操作的方式(文件的增、删、改、查、传送等)与另一主机相互通信SMTP:简单邮件传送协议(SimpleMailTransferProtocol)SMTP协议为系统之间传送电子邮件。TELNET:终端协议(TelnetTerminalProcotol)允许用户以虚终端方式访问远程主机HTTP:超文本传输协议(HypertextTransferProcotol)TFTP:简单文件传输协议(TrivialFileTransferProtocol)2、TCP/IP特点TCP/IP协议的核心部分是传输层协议(TCP、UDP),网络层协议(IP)和物理接口层,这三层通常是在操作系统内核中实现。因此用户一般不涉及。编程时,编程界面有两种形式:一、是由内核心直接提供的系统调用;二、使用以库函数方式提供的各种函数。前者为核内实现,后者为核外实现。用户服务要通过核外的应用程序才能实现,所以要使用套接字(socket)来实现。图1.2是TCP/IP协议核心与应用程序关系图。(图1.2)二、专用术语1、套接字套接字是网络的基本构件。它是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连听进程。套接字存在通信区域(通信区域又称地址簇)中。套接字只与同一区域中的套接字交换数据(跨区域时,需要执行某和转换进程才能实现)。WINDOWS中的套接字只支持一个域网际域。套接字具有类型。WINDOWSSOCKET1.1版本支持两种套接字:流套接字(SOCK_STREAM)和数据报套接字(SOCK_DGRAM)2、WINDOWSSOCKETS实现一个WINDOWSSOCKETS实现是指实现了WINDOWSSOCKETS规范所描述的全部功能的一套软件。一般通过DLL文件来实现3、阻塞处理例程阻塞处理例程(blockinghook,阻塞钩子)是WINDOWSSOCKETS实现为了支持阻塞套接字函数调用而提供的一种机制。4、多址广播(multicast,多点传送或组播)是一种一对多的传输方式,传输发起者通过一次传输就将信息传送到一组接收者,与单点传送(unicast)和广播(Broadcast)相对应。一、客户机/服务器模式在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式(Client/Servermodel)。该模式的建立基于以下两点:1、非对等作用;2、通信完全是异步的。客户机/服务器模式在操作过程中采取的是主动请示方式:首先服务器方要先启动,并根据请示提供相应服务:(过程如下)1、打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收客户请求。2、等待客户请求到达该端口。3、接收到重复服务请求,处理该请求并发送应答信号。4、返回第二步,等待另一客户请求5、关闭服务器。客户方:1、打开一通信通道,并连接到服务器所在主机的特定端口。2、向服务器发送服务请求报文,等待并接收应答;继续提出请求3、请求结束后关闭通信通道并终止。二、基本套接字为了更好说明套接字编程原理,给出几个基本的套接字,在以后的篇幅中会给出更详细的使用说明。1、创建套接字socket()功能:使用前创建一个新的套接字格式:SOCKETPASCALFARsocket(intaf,inttype,intprocotol);参数:af:通信发生的区域type:要建立的套接字类型procotol:使用的特定协议2、指定本地地址bind()功能:将套接字地址与所创建的套接字号联系起来。格式:intPASCALFARbind(SOCKETs,conststructsockaddrFAR*name,intnamelen);参数:s:是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。其它:没有错误,bind()返回0,否则SOCKET_ERROR地址结构说明:structsockaddr_inshortsin_family;/AF_INETu_shortsin_port;/16位端口号,网络字节顺序structin_addrsin_addr;/32位IP地址,网络字节顺序charsin_zero8;保留3、建立套接字连接connect()和accept()功能:共同完成连接工作格式:intPASCALFARconnect(SOCKETs,conststructsockaddrFAR*name,intnamelen);SOCKETPASCALFARaccept(SOCKETs,structsockaddrFAR*name,intFAR*addrlen);参数:同上4、监听连接listen()功能:用于面向连接服务器,表明它愿意接收连接。格式:intPASCALFARlisten(SOCKETs,intbacklog);5、数据传输send()与recv()功能:数据的发送与接收格式:intPASCALFARsend(SOCKETs,constcharFAR*buf,intlen,intflags);intPASCALFARrecv(SOCKETs,constcharFAR*buf,intlen,intflags);参数:buf:指向存有传输数据的缓冲区的指针。6、多路复用select()功能:用来检测一个或多个套接字状态。格式:intPASCALFARselect(intnfds,fd_setFAR*readfds,fd_setFAR*writefds,fd_setFAR*exceptfds,conststructtimevalFAR*timeout);参数:readfds:指向要做读检测的指针writefds:指向要做写检测的指针exceptfds:指向要检测是否出错的指针timeout:最大等待时间7、关闭套接字closesocket()功能:关闭套接字s格式:BOOLPASCALFARclosesocket(SOCKETs);三、典型过程图2.1面向连接的套接字的系统调用时序图口客户方2.2无连接协议的套接字调用时序图2.3面向连接的应用程序流程图WindowsSocket1.1程序设计一、简介WindowsSockets是从BerkeleySockets扩展而来的,其在继承BerkeleySockets的基础上,又进行了新的扩充。这些扩充主要是提供了一些异步函数,并增加了符合WINDOWS消息驱动特性的网络事件异步选择机制。WindowsSockets由两部分组成:开发组件和运行组件。开发组件:WindowsSockets实现文档、应用程序接口(API)引入库和一些头文件。运行组件:WindowsSockets应用程序接口的动态链接库(WINSOCK.DLL)。二、主要扩充说明1、异步选择机制:WindowsSockets的异步选择函数提供了消息机制的网络事件选择,当使用它登记网络事件发生时,应用程序相应窗口函数将收到一个消息,消息中指示了发生的网络事件,以及与事件相关的一些信息。WindowsSockets提供了一个异步选择函数WSAAsyncSelect(),用它来注册应用程序感兴趣的网络事件,当这些事件发生时,应用程序相应的窗口函数将收到一个消息。函数结构如下:intPASCALFARWSAAsyncSelect(SOCKETs,HWNDhWnd,unsignedintwMsg,longlEvent);参数说明:hWnd:窗口句柄wMsg:需要发送的消息lEvent:事件(以下为事件的内容)值:含义:FD_READ期望在套接字上收到数据(即读准备好)时接到通知FD_WRITE期望在套接字上可发送数据(即写准备好)时接到通知FD_OOB期望在套接字上有带外数据到达时接到通知FD_ACCEPT期望在套接字上有外来连接时接到通知FDCONNECT期望在套接字连接建立完成时接到通知FD_CLOSE期望在套接字关闭时接到通知例如:我们要在套接字读准备好或写准备好时接到通知,语句如下:rc=WSAAsyncSelect(s,hWnd,wMsg,FD_READ|FD_WRITE);如果我们需要注销对套接字网络事件的消息发送,只要将lEvent设置为02、异步请求函数在BerkeleySockets中请求服务是阻塞的,WINDOWSSICKETS除了支持这一类函数外,还增加了相应的异步请求函数(WSAAsyncGetXByY();)。3、阻塞处理方法WindowsSockets为了实现当一个应用程序的套接字调用处于阻塞时,能够放弃CPU让其它应用程序运行,它在调用处于阻塞时便进入一个叫“HOOK”的例程,此例程负责接收和分配WINDOWS消息,使得其它应用程序仍然能够接收到自己的消息并取得控制权。WINDOWS是非抢先的多任务环境,即若一个程序不主动放弃其控制权,别的程序就不能执行。因此在设计WindowsSockets程序时,尽管系统支持阻塞操作,但还是反对程序员使用该操作。但由于SUN公司下的BerkeleySockets的套接字默认操作是阻塞的,WINDOWS作为移植的SOCKETS也不可避免对这个操作支持。在WindowsSockets实现中,对于不能立即完成的阻塞操作做如下处理:DLL初始化循环操作。在循环中,它发送任何WINDOWS消息,并检查这个WindowsSockets调用是否完成,在必要时,它可以放弃CPU让其它应用程序执行(当然使用超线程的CPU就不会有这个麻烦了A_A)。我们可以调用WSACancelBlockingCall()函数取消此阻塞操作。在WindowsSockets中,有一个默认的阻塞处理例程BlockingHook()简单地获取并发送WINDOWS消息。如果要对复杂程序进行处理,WindowsSockets中还有WSASetBlockingHook()提供用户安装自己的阻塞处理例程能力;与该函数相对应的则是SWAUnhookBlockingHook(),它用于删除先前安装的任何阻塞处理例程,并重新安装默认的处理例程。请注意,设计自己的阻塞处理例程时,除了函数WSACancelBlockingHook()之外,它不能使用其它的WindowsSocketsAPI函数。在处理例程中调用WSACancelBlockingHook()函数将取消处于阻塞的操作,它将结束阻塞循环。4、出错处理WindowsSockets为了和以后多线程环境(WINDOWS/UNIX)兼容,它提供了两个出错处理函数来获取和设置当前线程的最近错误号。(WSAGetLastEror()和WSASetLastError()5、启动与终止使用函数WSAStartup()和WSACleanup()启动和终止套接字。来自网络实例staticvoid*listenconnect(void*param)intsockfd=socket(PF_UNIX,SOCK_SEQPACKET,0);/创建本地套接字fcntl(sockfd,F_SETFL,O_NONBLOCK);sockaddr_unmyaddr;memset(&myaddr,0,sizeof(myaddr);myaddr.sun_family=PF_UNIX;strcpy(myaddr.sun_path,/brainaire_nvr/trans_serv/socket);/套接字的名字,任意取myaddr.sun_path0=0;bind(sockfd,(structsockaddr*)&myaddr,sizeof(myaddr);/将套接字与它的名字绑定listen(sockfd,50);fd_setwatchset,listenset,clientset;FD_ZERO(&clientset);FD_ZERO(&watchset);FD_ZERO(&listenset);FD_SET(sockfd,&listenset);intmax_listen=sockfd+1;intmax_client=0;while(!process_exit)watchset=listenset;structtimevaltv;tv.tv_sec=0;tv.tv_usec=1000;if(select(max_listen,&watchset,NULL,NULL,&tv)0&FD_ISSET(sockfd,&watchset)structsockaddraddr;socklen_tlen;intclientfd=accept(sockfd,&addr,&len);if(clientfd=max_client)max_client=clientfd+1;FD_SET(clientfd,&clientset);watchset=clientset;tv.tv_sec=0;tv.tv_usec=1000;intret=select(max_client,&watchset,NULL,NULL,&tv);if(ret0)for(intclientfd=max_client-1;clientfd0;clientfd-)intflags=0;charbuffBUFSIZ;intlen;if(FD_ISSET(clientfd,&watchset)len=recv(clientfd,buff,sizeof(buff),flags);if(len=0)FD_CLR(clientfd,&clientset);fprintf(stderr,nvradminclientsocket%dclosed.n,clientfd);close(clientfd);if(clientfd=max_client-1)max_client-;elseboolmonitorworking=false;bufflen=0;intmonitorID=atoi(buff);if(monitorID!=0)MyRW_Lockmylock(&monitorlock);#ifTHREAD_NUMlist&monitors=monitorslistsmonitorID%THREAD_NUM;#endiffor(list:iteratorintr=monitors.begin();intr!=monitors.end();intr+)MonitorDaemonm_daemon=*intr;if(m_daemon.monitor=NULL)break;if(m_daemon.monitor-Id()=monitorID)fprintf(stderr,nvradminStartTransmittingRealPlayDatatoclientsocket%dn,clientfd);monitorworking=(0=m_daemon.monitor-StartTransData(clientfd);elsefprintf(stderr,nvradminReceivedMsgs:len%d,content:%sn,len,buff);if(!monitorworking)FD_CLR(clientfd,&clientset);fprintf(stderr,nvradminCannotStartTranmitting,clientSocket%dClosed.n,clientfd);close(clientfd);if(clientfd=max_client-1)max_client-;if(ret0)perror(SelectConnectedSockets);usleep(0);close(sockfd);return0;
展开阅读全文