MFC的CSocket编程,利用CSocket实现一个基于TCP实现一个QQ聊天程序。
/ 服务端 start ///
先讲讲服务端,一切先从服务端开始:
首先就是要使用AfxSocketInit初始化winsocket,
- //初始化winSock库,成功则返回非0否则返回0
- WSAData wsData;
- if(!AfxSocketInit(&wsData))
- {
- AfxMessageBox(_T("Socket 库初始化出错!"));
- return false;
- }
//初始化winSock库,成功则返回非0否则返回0
WSAData wsData;
if(!AfxSocketInit(&wsData))
{
AfxMessageBox(_T("Socket 库初始化出错!"));
return false;
}
m_iSocket 是一个 CServerSocket*的 指针 ,CServerSocket类是一个我们自己的类我会在后面给出相应代码,他继承于CSocket类。
- //创建服务器端Socket、采用TCP
- m_iSocket = new CServerSocket();
- if(!m_iSocket)
- {
- AfxMessageBox(_T("动态创建服务器套接字出错!"));
- return false;
- }
//创建服务器端Socket、采用TCP
m_iSocket = new CServerSocket();
if(!m_iSocket)
{
AfxMessageBox(_T("动态创建服务器套接字出错!"));
return false;
}
实例socket好了,就要创建套接字了。。这里的端口要和客户端连接的端口一致,不然就连接不上。而且,这个端口,要和服务器的其他软件端口不能冲突,怎么去判断冲突,可以自己谷歌一下,很简单的。我这里就直接写死了,这个端口一般不会被占用的。。
- //端口使用8989
- if(!m_iSocket->Create(8989))
- {
- AfxMessageBox(_T("创建套接字错误!"));
- m_iSocket->Close();
- return false;
- }
//端口使用8989
if(!m_iSocket->Create(8989))
{
AfxMessageBox(_T("创建套接字错误!"));
m_iSocket->Close();
return false;
}
创建好了就要,开始监听这个端口了。这个是一般的,socket必须建立的几个过程。。
- if(!m_iSocket->Listen())
- {
- AfxMessageBox(_T("监听失败!"));
- m_iSocket->Close();
- return false;
- }
if(!m_iSocket->Listen())
{
AfxMessageBox(_T("监听失败!"));
m_iSocket->Close();
return false;
}
走完上面的几个步骤,这样,服务端,就能和客户端接受和发送消息了。大家可能会很奇怪,上面那个怎么没有绑定端口,直接listen了。。。这个我那个简单socket里有介绍,因为,Create 方法已经包含了 Bind 方法,如果是以 Create 方法创建socket的前提下不能再调用 Bind ,要不一定出错。
然后重载ExitInstance,退出时对进行清理
- int CNetChatServerApp::ExitInstance()
- {
- if(m_iSocket)
- {
- delete m_iSocket;
- m_iSocket = NULL;
- }
- return CWinApp::ExitInstance();
- }
int CNetChatServerApp::ExitInstance()
{
if(m_iSocket)
{
delete m_iSocket;
m_iSocket = NULL;
}
return CWinApp::ExitInstance();
}
我再去看看上面用到的CServerSocket类,这个是用来,服务端接收消息用的。开启了监听这里的OnAccept()方法就会一直被循环调用。这个方法其实是重写CSocket类的OnAccept()方法。只要socket开启了监听,OnAccept就会被循环调用,我那个简单的socket实例,是开启一个while进行死循环来达到这个目的。大家不要介意,我也是新手。这里OnAccept方法为什么能一直被循环执行,我到现在也没弄明白,如果有高手知道请告诉我下。但是我知道,这里就是如果服务器有收到消息就会调用这里,提示ClientSocket接受消息。
- void CServerSocket::OnAccept(int nErrorCode)
- {
- //接受到一个连接请求
- CClientSocket* theClientSock(0);
- //初始化在初始化里把m_listSockets赋值到m_pList里
- theClientSock = new CClientSocket(&m_listSockets);
- if(!theClientSock)
- {
- AfxMessageBox(_T("内存不足,客户连接服务器失败!"));
- return;
- }
- Accept(*theClientSock); //接受
- //加入list中便于管理,这个很关键
- m_listSockets.AddTail(theClientSock);
- CSocket::OnAccept(nErrorCode);
- }
void CServerSocket::OnAccept(int nErrorCode)
{
//接受到一个连接请求
CClientSocket* theClientSock(0);
//初始化在初始化里把m_listSockets赋值到m_pList里
theClientSock = new CClientSocket(&m_listSockets);
if(!theClientSock)
{
AfxMessageBox(_T("内存不足,客户连接服务器失败!"));
return;
}
Accept(*theClientSock); //接受
//加入list中便于管理,这个很关键
m_listSockets.AddTail(theClientSock);
CSocket::OnAccept(nErrorCode);
}
OnAccept收到消息,就会实例CClientSocket类,这里其实主要是,服务端发送消息和接受消息的主要部分。也是服务端,最核心的部分。OnAccept收到消息后,就会通知CClientSocket来接受消息。注意了,CserverSocket是接收消息而CClientSocket是接收消息。接收,接受还是有区别的。这个关系我们要理解清楚。这个是我自己的理解,不时候是否有错误。还请高手赐教。。我学c++最多不过半个月,有些东西,都真是靠自己的理解。下面的接受消息OnReceive方法怎么调用的,我也有点模糊,这个方法好像是重写Socket的。就有数据来,他就会自动调用。m_listSockets.AddTail(theClientSock);这个很关键,m_listSockets是CPtrList类型,我对这个也还不太了解,经过我一些认识,这个是存放socket连接,成功一个就会加入这个,是一个链表。用来存放所有连接到服务器的socket连接的,这个后面会经常用到。
下面的HEADER是一个结构体,定义如下
- typedef struct tagHeader{
- int type ;//协议类型
- int nContentLen; //将要发送内容的长度
- char to_user[20];
- char from_user[20];
- }HEADER ,*LPHEADER;
typedef struct tagHeader{
int type ;//协议类型
int nContentLen; //将要发送内容的长度
char to_user[20];
char from_user[20];
}HEADER ,*LPHEADER;
这个结构体,要和客户端保持一致,不然我担心会有问题。就算没有问题,我估计转换也麻烦。尽量保持一直吧,这个也算是一种协议吧。客户端传输的时候,也传递这样的结构体。下面的方法,具体就看看备注吧。我在备注里讲解了。但是注意的是,我们客户端发送消息,是一次发送2个消息,先发送一个头部消息,这个头部消息是一个结构体,是服务端和客户端一种自定义的协议。这样的好处是,能节约资源,并且提前知道内容的长度进行申请内存空间。能先知道对应的消息类型,然后再进行转换和读取。这个头部接受好了,然后再去接受正式的数据。这个,可能你去看看。服务端可能会更容易了解。
- void CClientSocket::OnReceive(int nErrorCode)
- {
- //有消息接收
- //先得到信息头
- HEADER head; //定义客户端发送的过来的一样的结构体
- int nlen = sizeof HEADER; //计算结构体大小
- char *pHead = NULL; //用于接受的结构体
- pHead = new char[nlen]; //申请和结构体一样大小的内存空间
- if(!pHead)
- {
- TRACE0("CClientSocket::OnReceive 内存不足!");
- return;
- }
- memset(pHead,0, sizeof(char)*nlen ); //初始化
- Receive(pHead,nlen); //收到内容,并赋值到pHead中,指定接受的空间大小
- //以下是将接收大结构体进行强制转换成我们的结构体,
- head.type = ((LPHEADER)pHead)->type;
- head.nContentLen = ((LPHEADER)pHead)->nContentLen;
- //head.to_user 是char[]类型,如果不进行初始化,可能会有乱码出现
- memset(head.to_user,0,sizeof(head.to_user));
- //讲接受的数据转换过后并赋值到head.to_user,以下同
- strcpy(head.to_user,((LPHEADER)pHead)->to_user);
- memset(head.from_user,0,sizeof(head.from_user));
- strcpy(head.from_user,((LPHEADER)pHead)->from_user);
- delete pHead; //使用完毕,指针变量的清除
- pHead = NULL;
- //再次接收,这次是接受正式数据内容
- //这个就是,头部接受到的内容长度,这样能对应的申请内容空间
- pHead = new char[head.nContentLen];