(一.)首先在对软件需求有所了解后就开始学习网络知识了,以前看过孙新的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>
两个头文件,其它的都很简单了。