基于TCP协议实现服务器和客户端的通信程序

实习期间,项目要求学习MFC编程和SOCKET编程,先写了一个入门的小程序来熟悉一下。

服务器的界面图:

为了简化,将服务器的IP和Port固定为127.0.0.1和5000

基于TCP协议实现服务器和客户端的通信程序_第1张图片

【启动】按钮用来启动服务器

代码:

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



客户端的界面:

基于TCP协议实现服务器和客户端的通信程序_第2张图片

【连接】按钮用来连接服务器

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中,服务器选择相应的客户端发送消息

最后运行一下:

基于TCP协议实现服务器和客户端的通信程序_第3张图片

基于TCP协议实现服务器和客户端的通信程序_第4张图片

 


你可能感兴趣的:(MFC,Socket)