抢答器程序的总结

 抢答器软件的编写我用了一段时间了,大概20来天吧,这几天我基本上其它的什么都没干,专写这个软件了,还好最终编写出来了,这是我写的第一个稍微大点的程序,也是我独立编写的第一个网络程序,由于没有参考资料,所以整个程序的编写都是在一无所知的情况下一步一步的查阅资料,一步一步实现的,过程的确有点艰难,但让我长了不少知识,为以后编写网络程序打下了一定的基础,现在基本完工有两天了,应该总结总结了。

     (一.)首先在对软件需求有所了解后就开始学习网络知识了,以前看过孙新的VC视频,将过C++网络编程,现在忘得差不多了,于是就重新看了一遍,了解了一下VC网络编程的基本知识,了解了WINSOCKET编程技术,了解了TCP,UDP通信的基本步骤:

TCP:
Server :                     
1.建立服务器套接字和服务器地址SOCKET(套接字类型) ,SOCKADDR_IN
2.将服务器套接字和服务器地址绑定 BIND(套接字,地址)
3.开始监听 LISTEN(套接字)
当有客户端连接时:
4.创建连接套接字 CONN= ACCEPT(监听套接字,客户端地址):每个连接套接字对应着其客户端地址
5.SEND(连接套接字,SENDBUF)
6.RECV(连接套接字,RECVBUF)

Client:
1.建立客户端套接字和(服务器)地址---SOCKET(套接字类型) ,SOCKADDR_IN
2.连接服务器 CONNECT(客户端套接字,服务器地址)
3.RECV(客户端套接字,RECVBUF)
4.SEND(客户端套接字,SENDBUF)

=====================================================================================

UDP:
SERVER:(先RECVFROM记录客户端地址,后SENDTO)
1.建立服务器套接字和服务器地址SOCKET(套接字类型) ,SOCKADDR_IN
2.将服务器套接字和服务器地址绑定 BIND(套接字,地址)
3.RECVFORM(服务器套接字,RECVBUF,客户端地址)
4.SENDTO(服务器套接字,SENDBUF,客户端地址)
CLIENT:
1.建立客户端套接字和(服务器)地址---SOCKET(套接字类型) ,SOCKADDR_IN
2.SENDTO(客户端套接字,SENDBUF,服务器地址)
3.RECVFROM(客户端套接字,RECVBUF,服务器地址)

======================================================================================

        (二)确定软件的界面,我先找了几个网络程序,对其结构进行了研究,最后发现一个网络聊天程序的界面作的不错,所以决定模仿该聊天程序的界面,察看其代码发现:该程序是单文档结构,但以前我有个问题是如果是单文档结构,那么怎么在界面上添加控件呢?这又不能像对话框结构那样直接把空间拖进去就行了,于是我对其代码进行详细研究,发现其视图类和文档类不是从CView 和CDocument继承而来的,而是分别从CRichEditView 和CRichEditDoc继承而来的,而且还有一个从CRichEditCntrItem类继承的类。然后我就自己建立了一个工程,在向导的最后一步改变VIEW类的父类为CRichEditView ,然后发现DOC类也自动继承自CRichEditDoc了,完成后发现CRichEditCntrItem的继承类也自动生成了。在查阅资料后得知,CRichEditView 的功能正如其名,有丰富的编辑功能,能够改变在其视图内输出文本的字体,大小,颜色等,这正是我想要的。这样基本框架就产生了。

        然后就该在试图内添加控件了,在研究聊天室的程序后发现它用的是CDialogBar,就是能够在试图上面添加一个对话框,而且可以拖动,其方法是在CMainFrame里添加CDialogBar类,生成两个对象,然后再在CMainFrame的OnCreate()函数里设置其属性。方法是:

       首先先设计两个对话框,分别是发送对话框和在线用户对话框,然后生成类时,将其添加都已有的VIEW类里面,而不是生成新的对话框类,这样利于以后的具体操作,然后再CDialogBar创建的时候与各自的对话框相关联,这样,视图的对话框就生成了,在这过程过曾经出现过一个问题:就是当你在对话框上添加按钮后,当程序运行时,该按钮时不可用的,到后来我才发现只有当你为按钮添加功能函数后才是可用的,其它空间同理。虽是个小问题,但也着实耗费了我不少精力,后来发现这其实和在工具栏上添加按钮一个道理,只添加按钮,是不可用的,而在为其添加处理函数后就可用了。

      到此为止,程序的框架就完工了,下一步就开始具体的代码实现了。

     (三)功能的实现

     孙新VC视频里的网络编程都是用Socket()函数生成套结字对象,然后再进行SEND和RECV,这都是API级别的,而我查阅了几个应用程序,方法都不是这样的,都是直接继承一个CSocket对象,然后用CSocket类的函数进行操作。但是基本步骤还是相同的。所以我也要自己定义一个继承自CSocket类的套结字类进行通信,令一个问题是在该程序中是否要用到多线程,是否要进行异步套结字通信,这是孙新视频里面所讲到的。到后来自己的多次试探后发现,在这个程序中是没有必要的,所以我的做法是:

      自定义一个套结字类,继承自CSocket,然后重载其OnReceive()函数即可,这个函数就是在该程序受到消息的处理函数。而发送函数就不用重载了,直接用该类的SendTo()函数即可。(当然以上步骤现在看起来很简单,当时可是让我费尽了脑汁,经过多次实验才得知的)

     (四)UDP协议的实现

      这是该程序最重要的部分了,当然只要一个消息会处理了,其它的消息也就水到渠成了。所以我开始做的就是传输一个数,不要别的,只要客户端发送一个整数,服务器端能够接受就行,但是这对我来说也有很大的难度,一开始传输整数,程序老出错误,而又找不到哪里错了,此时,是我非常困难的时期之一,后来回宿舍思考,突然想到,网络程序需要初始化,然后我就在APP的InitInstance()里添加了AfxSocketInit()进行初始化,然后模仿了VC知识库的一个实例,发送了一个结构体,最终经过多次实验,还是能够通信了,可喜可贺,这可是该程序中具有里程碑意义的一步。

    后来发现,当传输结构体时不能有CString类的变长类型,否则无法传输,其实这也可以理解,程序是无法知道你的字符串类是从哪里开始,哪里结束的,所以要在发送信息中包括字符串的长度,要程序知道你发送消息的每一个字节的意义。以下是我所用的两种类型的消息:

      一。结构体

 void CFasonDlg::OnSend()
   yuan1.x=m_x;
  yuan1.y=m_y;
  yuan1.r=m_r;
  p=&yuan1;
  CDSocket m_hSocket;
  m_hSocket.Create(2330,SOCK_DGRAM);
  m_hSocket.SendTo( p,sizeof(yuan1),3550,"127.0.0.1");//用结构体发送。
------------------------------------------------------------------------
void CDASocket::OnReceive(int nErrorCode)
char buff[256];
  int ret=0;
  ret=Receive(buff,256);
  if(ret==ERROR)
  {
    TRACE("ERROR!");
  }
  else
  m_pDoc->Presscessding(buff);
  class CAsyncSocket::OnReceive(nErrorCode);
-------------------------------------------------------------
Presscessding(char* lbuff)
{
  buff=(struct yuan*)lbuff;
  p.x=buff->x;
  p.y=buff->y;
  p.r=buff->r;
  p.color=buff->color;

     二。带字符串的消息

SEND:
--------------------------------------------------------------
CString str;
str += char(m_strUser.GetLength());
str += m_strUser;
str += char(m_strPass.GetLength());
str += m_strPass;
char* buf = str.GetBuffer(0);
ret = send(m_hSocket, buf, str.GetLength(), 0);

RECV:
---------------------------------------------------------------
char buff[256];
ret = recv(s, buff, 256, 0);
if(ret == 0 || ret == SOCKET_ERROR )
{
 TRACE("Recv data error: %d/n", WSAGetLastError());
 return ;
}
       
char* name = NULL;
char* pass = NULL;
int len = 0;
len = buff[0];
name = new char[len + 1];
for(int i = 0; i < len; i++)
 name[i] = buff[i+1];
int len2 = buff[len + 1];
pass = new char[len2 + 1];
for(i = 0; i < len2; i++)
 pass[i] = buff[i + 2 + len];
pass[len2] = '/0';
name[len] = '/0';

if(strcmp(name, "ware") != 0){
 str = _T("用户名不正确!");
 TRACE(_T("用户名不正确!/n"));
}
else{
 if(strcmp(pass, "11111") != 0){
  str = _T("用户密码不正确!");
  TRACE(_T("用户密码不正确!/n"));
}

以上只是我找的几个例子,此程序的几个协议都是模仿这两种方式编写的,主要是第二种。

   (五)实现广播

    当服务器向客户端发送消息时,有许多客户段,那怎么办呢?我首先想到的方法是当客户端登陆时,服务器记录客户段的地址信息,然后发送是,对各地址循环发送,这样不但有点麻烦,而且对抢答器来说不大合理,这样可能对各客户不公平,因为这不是严格意义上的同时发送,虽然差别也许很小,但是还是不好,后在我查阅资料发现UDP可以广播发送,可以对局域网里的客户段同时发送,这样就简单多了,方法是这样的:

 

在服务器端:

        m_socket =new  CCopSocket(this);
        m_socket->Create(6000,SOCK_DGRAM);
       

        BOOL     fBroadcast=TRUE;  
        m_socket->SetSockOpt(SO_BROADCAST,&fBroadcast,sizeof(BOOL),SOL_SOCKET);

 

       m_socket->SendTo(buf,str.GetLength(),5000,"255.255.255.255");//发送的IP是255.255.255.255

在客户段不用任何特殊的设置,跟普通接受完全一样。

   (六)问题数据库

    以前做过数据库的程序,这回就简单多了,记住要在STDAFX。H中添加:

   #include <odbcinst.h>//ODBC数据库API头文件
   #include <afxdb.h>

   两个头文件,其它的都很简单了。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章