(二)服务器端网络通讯功能的实现
在此采用的是可靠的有连接的流式套接字,并且采用了多线程和异步通知机制能有效避免一些函数如accept()等的阻塞会引起整个程序的阻塞。由于套接字编程方面的书籍资料非常丰富,对其进行网络编程做了很详细的描述,故本文在此只针对一些关键部分做简要说明,有关套接字网络编程的详细内容请参阅相关资料。采用流式套接字的服务器端的主要设计流程可以归结为以下几步:
1. 创建套接字
sock=socket(AF_INET,SOCK_STREAM,0); |
该函数的第一个参数用于指定地址族,在Windows下仅支持AF_INET(TCP/IP地址);第二个参数用于描述套接字的类型,对于流式套接字提供有SOCK_STREAM;最后一个参数指定套接字使用的协议,一般为0。该函数的返回值保存了新套接字的句柄,在程序退出前可以用closesocket()函数来将其释放。
2. 绑定套接字
服务器方一旦获取了一个新的套接字后应通过bind()将该套接字与本机上的一个端口相关联。此时需要预先对一个指向包含有本机IP地址和端口信息的sockaddr_in结构填充一些必要的信息,如本地端口号和本地主机地址等。然后就可经过bind()将服务器进程在网络上标识出来。需要注意的是由于1024以内的埠号都是保留的端口号因此如无特别需要一般不能将sockin.sin_port的端口号设置为1024以内的值:
…… sockin.sin_family=AF_INET; sockin.sin_addr.s_addr=0; sockin.sin_port=htons(USERPORT); bind(sock,(LPSOCKADDR)&sockin,sizeof(sockin)); …… |
3. 侦听套接字
4. 等待客户机的连接
这里需要通过accept()调用等待接收客户端的连接以完成连接的建立,由于该函数在没有客户端进行申请连接之前会处于阻塞状态,因此如果采取通常的单线程模式会导致整个程序一直处于阻塞状态而不能响应其他的外界消息,因此为该部分代码单独开辟一个线程,这样阻塞将被限制在该线程内而不会影响到程序整体。
AfxBeginThread(Server,NULL);//创建一个新的线程 …… UINT Server(LPVOID lpVoid)//线程的处理函数 { //获取当前视类的指针,以确保访问的是当前的实例对象。 CNetServerView* pView=((CNetServerView*)( (CFrameWnd*)AfxGetApp()->m_pMainWnd)->GetActiveView()); while(pView->nNumConns<1)//当前的连接者个数 { int nLen=sizeof(SOCKADDR); pView->newskt= accept(pView->sock, (LPSOCKADDR)& pView->sockin,(LPINT)& nLen); WSAAsyncSelect(pView->newskt, pView->m_hWnd,WM_SOCKET_MSG,FD_CLOSE); pView->nNumConns++; } return 1; } |
这里在accept ()后使用了WSAAsyncSelect()异步选择函数。对于网络事件的响应最好采取异步选择机制,只有采取这种方式才可以在由网络对方所引起的不可预知的网络事件发生时能马上在进程中做出及时的响应处理,而在没有网络事件到达时则可以处理其他事件,这种效率是很高的,而且完全符合Windows所标榜的消息触发原则。WSAAsyncSelect()函数便是实现网络事件异步选择的核心函数。通过第四个参数FD_CLOSE注册了应用程序感兴取的网络事件是网络断开,当客户方端开连接时该事件会被检测到,同时会发出由第三个参数指定的自定义消息WM_SOCKET_MSG。
5. 发送/接收
当客户机同服务器建立好连接后就可以通过send()/recv()函数进行发送和接收数据了,对于本程序只需在监测到有拨号连接事件发生时向客户机发送通知消息即可:
char buffer[1]={'a'}; send(newskt,buffer,1,0);//向客户机发送字符a,表示现在服务器正在拨号。 |
6. 关闭套接字
在全部通讯完成之后,在退出程序之前需要调用closesocket();函数把创建的套接字关闭。
(三)客户机端的程序设计
客户机的编程要相对简单许多,全部通讯过程只需以下四步:
1. 创建套接字 2. 建立连接 3. 发送/接收 4. 关闭套接字
具体实现过程同服务器编程基本类似,只是由于需要接收数据,因此待监测的网络事件为FD_CLOSE和FD_READ,在消息响应函数中可以通过对消息参数的低位字节进行判断而区分出具体发生是何种网络事件,并对其做出响应的反应。下面结合部分主要实现代码对实现过程进行解释:
…… m_ServIP=SERVERIP; //指定服务器的IP地址 m_Port=htons(USERPORT); //指定服务器的端口号 if((IPaddr=inet_addr(m_ServIP))==INADDR_NONE) //转换成网络地址 return FALSE; else { sock=socket(AF_INET,SOCK_STREAM,0); //创建套接字 sockin.sin_family=AF_INET; //填充结构 sockin.sin_addr.S_un.S_addr=IPaddr; sockin.sin_port=m_Port; connect(sock,(LPSOCKADDR)&sockin,sizeof(sockin)); //建立连接 //设定异步选择事件 WSAAsyncSelect(sock,m_hWnd,WM_SOCKET_MSG,FD_CLOSE|FD_READ); //在这里可以通过震铃、弹出对话框等方式通知客户已经连上服务器 } ……
//网络事件的消息处理函数 int message=lParam & 0x0000FFFF;//取消息参数的低位 switch(message) //判断发生的是何种网络事件 { case FD_READ: //读事件 AfxBeginThread(Read,NULL); break; case FD_CLOSE: //服务器关闭事件 …… break; }
|
在读事件的消息处理过程中,单独为读处理过程开辟了一个线程,在该线程中接收从服务器发送过来的信息,并通过震铃、弹出对话框等方式通知客户端现在服务器正在拨号:
…… int a=recv(pView->sock,cDataBuffer,1,0); //接收从服务器发送来的消息 if(a>0) AfxMessageBox("拨号连接已启动!"); //通知用户 …… |
 
说明:本教程来源互联网或网友上传或出版商,仅为学习研究或媒体推广,wanshiok.com不保证资料的完整性。
2/2 首页 上一页 1 2 |