实习期间,项目要求学习MFC编程和SOCKET编程,先写了一个入门的小程序来熟悉一下。
服务器的界面图:
为了简化,将服务器的IP和Port固定为127.0.0.1和5000
【启动】按钮用来启动服务器
代码:
void CServerDlg::OnBnClickedStartButton()
{
// TODO: 在此添加控件通知处理程序代码
if(0 != WSAStartup(MAKEWORD(2,2),&wsaData))
{
SetDlgItemText(IDC_STATE_EDIT,_T("打开WinSock失败!"));
return;
}
sListen = socket(AF_INET,SOCK_STREAM,0);
if(sListen == INVALID_SOCKET)
{
SetDlgItemText(IDC_STATE_EDIT,_T("创建监听套接字失败!"));
return;
}
serverAddr.sin_family = AF_INET;
// serverAddr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
serverAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
serverAddr.sin_port = htons(m_Port);
if(0 != bind(sListen,(const sockaddr*)&serverAddr,sizeof(serverAddr)))
{
SetDlgItemText(IDC_STATE_EDIT,_T("绑定IP和端口号失败!"));
return;
}
if(0 != listen(sListen,5))
{
SetDlgItemText(IDC_STATE_EDIT,_T("建立监听队列失败!"));
return;
}
SetDlgItemText(IDC_STATE_EDIT,_T("服务器启动成功"));
//创建线程,负责监听
RECVPARAM *pRecvParam = new RECVPARAM;
pRecvParam->sock = sListen;
pRecvParam->hwnd = m_hWnd;
pRecvParam->addr = serverAddr;
HANDLE hListen;
hListen = CreateThread(NULL,0,ListenProc,(LPVOID)pRecvParam,0,NULL);
CloseHandle(hListen);
// Sleep(4000);
}
这里使用了多线程来监听服务器端的请求,这是因为我们的程序运行是一个主线程在执行的,而SOCKET的accept()方法执行的时候会阻断线程,这样主线程会被阻断,程序会被卡住,只有当由客户来连接服务器时,主线程才会被释放出来。
ListenProc负责监听的方法:
DWORD WINAPI CServerDlg::ListenProc(LPVOID lpParameter)
{
SOCKET sock = ((RECVPARAM *)lpParameter)->sock;
HWND hwnd = ((RECVPARAM *)lpParameter)->hwnd;
while(1)
{
SOCKADDR_IN clientAddr;
int cLen = sizeof(SOCKADDR);
int error;
SOCKET Accept;
Accept = accept(sock,(struct sockaddr *)&clientAddr,&cLen);
if(INVALID_SOCKET == Accept)
{
error = WSAGetLastError();
break;
}
//创建线程,负责接收客户端发来的消息
RECVPARAM *lParam = new RECVPARAM;
lParam->sock = Accept;
lParam->hwnd = hwnd;
lParam->addr = clientAddr;
HANDLE hRecv;
hRecv = CreateThread(NULL,0,RecvProc,(LPVOID)lParam,0,NULL);
CloseHandle(hRecv);
::PostMessageA(hwnd,WM_LISTENDATA,0,(LPARAM)Accept);
}
return 0;
}
在监听的线程里,当服务器接受客户端的连接请求,使用accept()函数新建一个套接字与客户端的套接字相同,原来监听的套接字继续进入监听状态。同时继续创建一个线程用于接收连接的客户端发来的消息。RecvProc()负责接收客户端发来的消息
DWORD WINAPI CServerDlg::RecvProc(LPVOID lpParameter)
{
SOCKET sock = ((RECVPARAM *)lpParameter)->sock;
HWND hwnd = ((RECVPARAM *)lpParameter)->hwnd;
SOCKADDR_IN addrFrom = ((RECVPARAM *)lpParameter)->addr;
int iReceive; //发送和接收数据的长度
char rBuf[BUFFER_SIZE],tempBuf[BUFFER_SIZE]; //发送数据信息和接收数据信息
while(TRUE)
{
iReceive = recv(sock,rBuf,sizeof(rBuf),0);
if(SOCKET_ERROR == iReceive){
break;
}else if(iReceive == 0){
break;
}else{
sprintf(tempBuf,"receive: %s",rBuf);
::PostMessageA(hwnd,WM_RECVDATA,0,(LPARAM)tempBuf);
}
}
return 0;
}
【发送】按钮用来实现向客户端发送相应的消息
void CServerDlg::OnBnClickedSendButton()
{
// TODO: 在此添加控件通知处理程序代码
int iSend;
CString str;
GetDlgItemText(IDC_SEND_EDIT,str);
int len = str.GetLength();
char * p = new char[len];
for(int i=0;i
客户端的界面:
【连接】按钮用来连接服务器
void CClientDlg::OnBnClickedConnectButton()
{
// TODO: 在此添加控件通知处理程序代码
DWORD dwIP;
((CIPAddressCtrl *)GetDlgItem(IDC_IPADDRESS))->GetAddress(dwIP);
m_ServerPort = GetDlgItemInt(IDC_PORT_EDIT,0,1);
serverAddr.sin_family = AF_INET;
serverAddr.sin_port = htons(m_ServerPort);
serverAddr.sin_addr.S_un.S_addr = htonl(dwIP);
//打开Winsock
if(0 != WSAStartup(MAKEWORD(2,2),&wsaData))
{
SetDlgItemText(IDC_STATE_EDIT,_T("打开Winsock失败!"));
return;
}
//建立套接字
client = socket(AF_INET,SOCK_STREAM,0);
if(INVALID_SOCKET == client)
{
SetDlgItemText(IDC_STATE_EDIT,_T("创建套接字失败!"));
return;
}
//请求与服务器端建立连接
if(SOCKET_ERROR == connect(client,(const sockaddr *)&serverAddr,sizeof(serverAddr)))
{
SetDlgItemText(IDC_STATE_EDIT,_T("连接服务器失败!"));
return;
}
SetDlgItemText(IDC_STATE_EDIT,_T("连接服务器成功!"));
//创建线程,负责接收服务器发来的消息
RECVPARAM * pRecvParam = new RECVPARAM;
pRecvParam->sock = client;
pRecvParam->addr = serverAddr;
pRecvParam->hwnd = m_hWnd;
HANDLE hRecv;
hRecv = CreateThread(NULL,0,RecvProc,(LPVOID)pRecvParam,0,NULL);
CloseHandle(hRecv);
// Sleep(4000);
}
void CClientDlg::OnBnClickedDisconnectButton()
{
// TODO: 在此添加控件通知处理程序代码
//关闭Winsock
closesocket(client);
WSACleanup();
SetDlgItemText(IDC_STATE_EDIT,_T("连接已断开"));
}
接收和发送的实现与服务器端类似。
将一下具体实现中遇到的问题把,刚刚入门,多多学习:
1、因为是利用MFC进行实现的,同时没有调用MFC库中类,用CreateThread创建线程时,因为不允许全局变量和函数的使用,需要将线程函数作为类的成员方法设置为static
2、在线程中利用PostMessage,将一个消息放入到与创建线程的指定窗口相联系的消息队列里,用指定附加的的消息将信息传回到指定窗口
3、没有实现多个客户端连接的情况,考虑可以加一个List Box,将每一个连接的客户端都加入到List Box中,服务器选择相应的客户端发送消息
最后运行一下: