本例是参考孙鑫vc++教程写的一个聊天程序,用到的基本知识是SOCKET跟多线程。
1.基于socket网络编程
socket编程基本上可以分为2类,面向连接的(TCP模式)和面向无连接的(UDP模式),TCP与UDP最大的区别就是TCP相对来说可靠的,保证所传送的内容达到接收端,而UDP不能确保所传送的内容到达接收端,就跟邮寄信件一样,UDP只负责将东西放在邮箱里面,不保证收件人一定能看到,而TCP是确保收件人签收的。当然,TCP这种模式是以牺牲时间为代价的,UDP的模式能够保证较高的实时性。本例是想编写一个简单的聊天工具,不一定要保证用户发的每一句话对方都接收到了,因而采用的是UDP模式。事实上,在VC里的SOCKET编程步骤都是固定的,有点类似于面向结构化编程。
基于SOCKET的TCP编程流程:
(1)加载套接字库,AfxSocketInit(实际上是封装了WSAStartup)
(2)创建socket关键字,建立服务器端(客户端)IP和端口号(通常为1024以上)等
(3-1)服务器端绑定bind套接字,并将套接字设置为监听listen模式,通过accept接收客户发送的内容
(3-2)客户端向服务器发送连接connect请求
(4)进行通信,用send/recv函数进行接收对方发送的内容
基于SOCKET的UDP编程流程:
(1)加载套接字库,AfxSocketInit(实际上是封装了WSAStartup)
(2)创建socket关键字,建立服务器端(客户端)IP和端口号(通常为1024以上)等
(3)服务器端绑定bind套接字
(4)进行通信,用sendto/recvfrom函数进行接收对方发送的内容
SOCKET在里面其实充当的一个中介的作用,信息都通过socket来传递。
2.多线程
多线程最大的优势是可以多段代码可以在时间片内进行切换运行不同代码。而聊天程序中,接收消息这个显然需要一个线程来维护,单有消息到来的时候会激活,没有消息到来的时候,是阻塞的。在VC里面线程的创建有多种方法,我们可以采用常用的API函数CreateThread,当有多个线程运行的时候,常会出现的一个问题是,在这个时间片结束的时候,该函数并没有执行完,从而导致输出的结果不正确,可以采用互斥createmutex的方式来保证这个时间片执行完函数之后才跳出。
基于对话框的聊天程序代码:
1.加载套接字库:
在InitInstance函数中
if (!AfxSocketInit())
{
AfxMessageBox(L"load socket lib error!");
return FALSE;
}
2.初始化套接字
BOOL CChatDlg::InitSocket()
{
m_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (INVALID_SOCKET == m_socket)
{
MessageBox(L"套接字创建失败!");
return FALSE;
}
//创建套接字
SOCKADDR_IN addrSock;
addrSock.sin_family = AF_INET;
addrSock.sin_port = htons(6000);
addrSock.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
int retval = bind(m_socket, (SOCKADDR*)&addrSock, sizeof(SOCKADDR));
if (SOCKET_ERROR == retval)
{
closesocket(m_socket);
MessageBox(L"绑定失败!");
return FALSE;
}
return TRUE;
}
//线程的创建
RecvParam* pRecvParam = new RecvParam;
pRecvParam->sock = m_socket;
pRecvParam->hwnd = m_hWnd;
pRecvParam->hMutex = m_hMutex;
HANDLE hThread = ::CreateThread(NULL, 0, RecvFunc, (LPVOID)pRecvParam, 0 , 0);
CloseHandle(hThread);
线程接收函数
DWORD WINAPI RecvFunc(LPVOID lpParam)// 线程函数 实现数据接收
{
SOCKET sock = ((RecvParam*)lpParam)->sock;
HWND hwnd = ((RecvParam*)lpParam)->hwnd;
HANDLE hMutex = ((RecvParam*)lpParam)->hMutex;
delete lpParam;
SOCKADDR_IN addrFrom;
int len = sizeof(SOCKADDR);
char recvBuf[200];
char tempBuf[300];
int retval;
while(TRUE)
{
WaitForSingleObject(hMutex,INFINITE);
retval = recvfrom(sock, recvBuf, 200, 0,(SOCKADDR*)&addrFrom, &len);
if (SOCKET_ERROR == retval)
{
break;
}
sprintf_s(tempBuf, ("%s 说: %s"), inet_ntoa(addrFrom.sin_addr), recvBuf);
::PostMessage(hwnd, WM_RECVDATA,(WPARAM)tempBuf, NULL);
ReleaseMutex(hMutex);
}
return 0;
}
4. 消息的发送与接收显示
void CChatDlg::OnBnClickedButtonSend()
{
// TODO: Add your control notification handler code here
//获取对方IP
CIPAddressCtrl* pIPAddress = ((CIPAddressCtrl*)GetDlgItem(IDC_IPADDRESS_SEND));
if (pIPAddress->IsBlank())
{
MessageBox(L"请先输入对方IP地址!");
return;
}
DWORD dwIP;
pIPAddress->GetAddress(dwIP);
SOCKADDR_IN addrTo;
addrTo.sin_family = AF_INET;
addrTo.sin_port = htons(6000);
addrTo.sin_addr.S_un.S_addr = htonl(dwIP);
CString strSend;
GetDlgItemText(IDC_Edit_Send, strSend);
int len =WideCharToMultiByte(CP_ACP,0,strSend,-1,NULL,0,NULL,NULL);
char *ptxtTemp =new char[len +1];
WideCharToMultiByte(CP_ACP,0,strSend,-1,ptxtTemp,len,NULL,NULL );
//发送数据
sendto(m_socket, ptxtTemp , strlen(ptxtTemp) + 1, 0, (SOCKADDR*)&addrTo, sizeof(SOCKADDR));
delete[] ptxtTemp;
if (m_chatHeight++ >= 12)
{
m_chatContent.SetWindowText(L"");
m_chatHeight = 0;
}
//发送我说的内容
WaitForSingleObject(m_hMutex, INFINITE);
CString strTemp("");
m_chatContent.GetWindowText(strTemp);
CString strMyContent = L"我说:" + strSend;
if (strTemp != "")
{
strTemp += "\r\n";
}
strTemp += strMyContent;
m_chatContent.SetWindowText(strTemp);
ReleaseMutex(m_hMutex);
SetDlgItemText(IDC_Edit_Send, L"");
}
代码可能粘贴不完整,运行结果图: