基于Windows Socket的安全通信(C++实现,附源码)

原创作品,转载请注明出自xelz's blog

博客地址:http://mingcn.cnblogs.com/

本文地址:http://mingcn.cnblogs.com/archive/2010/11/01/socket_c.html

 

被标题吸引到的大牛们原谅我吧,我只是菜鸟一只...

 

言归正传,还是那个课程设计,续昨天的AES加密(http://mingcn.cnblogs.com/archive/2010/10/31/aes_c.html

今天完成剩下的Socket通信部分(泪奔中...C++小白,MFC那是大白o(╯□╰)o,控件怎么用都不知道)

本来想退而求其次写个Console Application,后来想着最后一次课程设计了,上点心,咬咬牙,开始...

 

界面(软肋啊,随便搜了点源码,就弄了个Simple Dialog)

图示:红色为控件的ID, 蓝色为映射的变量名

 

下面要添加Socket通信功能了

先了解一下Socket的相关函数原型

 

//加载套接字库
int PASCAL FAR WSAStartup(WORD wVersionRequired, LPWSADATA lpWSAData);
//释放套接字库资源
int PASCAL FAR WSACleanup(void);
//创建套接字
SOCKET PASCAL FAR socket (int af, int type, int protocol);
//关闭套接字
int PASCAL FAR closesocket (SOCKET s);
//绑定一个IP地址和端口
int PASCAL FAR bind (SOCKET s, const struct sockaddr FAR *addr, int namelen);
//将套接字置为监听状态
int PASCAL FAR listen (SOCKET s, int backlog);
//接受客户端连接请求,并返回新创建的套接字
SOCKET PASCAL FAR accept (SOCKET s, struct sockaddr FAR *addr, int FAR *addrlen);
//尝试将本地套接字连接至服务器
int PASCAL FAR connect (SOCKET s, const struct sockaddr FAR *name, int namelen);
//发送数据
int PASCAL FAR send (SOCKET s, const char FAR * buf, int len, int flags);
//接收数据
int PASCAL FAR recv (SOCKET s, char FAR * buf, int len, int flags);

 

使用Socket的程序在使用Socket之前必须调用WSAStartup函数来绑定Socket库

在Constructor中添加如下代码

 

	int error;
	WORD wVersionRequested;
	WSADATA wsaData;
	wVersionRequested = MAKEWORD(2, 1);//加载2.1版本的Socket库
	if(error = WSAStartup(wVersionRequested, &wsaData))
	{
		AfxMessageBox("Link Socket Library Failed!");
		exit(0);
	}

应用程序完成对Socket的使用后应当调用WSACleanup函数来释放Socket库占用的系统资源

 

在析构函数冲添加如下代码

 

WSACleanup();

 

 

Socket通信流程

实现安全通信,应采用面向连接的TCP/IP协议来保证连接的可靠性

面向连接的套接字的系统调用时序图

 

添加成员变量及初始化

 

//服务器端:
  SOCKET Listener,toClient;//用于监听的套接字和连接至客户端的套接字(只是为了实现通信模型,所以不考虑多客户端)
  bool listening, connected;//指示监听和连接的状态
  AES aes;//加密/解密模块

CTestSocketServerDlg::CTestSocketServerDlg(CWnd* pParent):
	CDialog(CTestSocketServerDlg::IDD, pParent), 
	aes((unsigned char *)"0123456789abcdef"),
	listening(false),
	connected(false)
{
	//Constructor of Server
}

//客户端:
  SOCKET toServer;//连接至服务器端的套接字
  bool connected;//指示连接状态
  AES aes;//加密/解密模块

CTestSocketClientDlg::CTestSocketClientDlg(CWnd* pParent):
	CDialog(CTestSocketClientDlg::IDD, pParent), 
	aes((unsigned char *)"0123456789abcdef"),
	connected(false)
{
	//Constructor of Client
}

 

为“Start/Stop”按钮注册单击事件处理服务器端初始化及关闭操作

 

void CTestSocketServerDlg::OnBtnStart() 
{
	if(connected || listening)//若正在监听或已连接则关闭服务器
	{
		connected = false;
		listening = false;
		closesocket(toClient);
		closesocket(Listener);
		m_chat += "Socket Server Stopped!\r\n";
		UpdateData(false);
		return;
	}

	UpdateData(true);
	//创建监听Socket
	struct protoent *ppe;
	ppe = getprotobyname("tcp");
	if((Listener = socket(PF_INET, SOCK_STREAM, ppe->p_proto)) == INVALID_SOCKET)
	{
		m_chat += "Initialize Socket Listener Failed!\r\n";
		UpdateData(false);
		return;
	}

	//绑定IP及端口
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(m_port);
	saddr.sin_addr.s_addr = htonl(INADDR_ANY);
	if(bind(Listener, (struct sockaddr *)&saddr, sizeof(saddr)))
	{
		m_chat += "Bind to IPEndPoint Failed! (Port in use?)\r\n";
		UpdateData(false);
		return;
	}
	//开始监听,队列长度1(不考虑多客户端)
	if(listen(Listener, 1))
	{
		m_chat += "Listen Failed!\r\n";
		UpdateData(false);
		return;
	}
	m_chat += "Socket Server Started!\r\n";
	UpdateData(false);

	listening = true;
	AfxBeginThread(Wait4Client, this);//另起线程等待客户端连接
}

 

接收来自客户端的连接请求

 

UINT Wait4Client(LPVOID pParam)
{
	CTestSocketServerDlg * c = (CTestSocketServerDlg *) pParam;
	struct sockaddr_in caddr;
	int caddrlen = sizeof(caddr);
	c->toClient = accept(c->Listener, (struct sockaddr *)&caddr, &caddrlen);

	if(c->toClient == INVALID_SOCKET)//异常处理
	{
		if(!c->listening)return 0;//服务器端主动关闭,则直接退出
		c->m_chat += "Connect Failed!\r\n";
		c->UpdateData(false);
		return -1;
	} 
	else 
	{
		c->connected = true;//连接建立,另起线程用于接收信息
		AfxBeginThread(ReceiveMessage, c);
		c->m_chat += "Client: ";
		c->m_chat += inet_ntoa(caddr.sin_addr);
		c->m_chat += " Connected!\r\n";
		c->m_ip = inet_ntoa(caddr.sin_addr);
		c->UpdateData(false);
	}
	return 0;
}

 

客户端只需要创建Socket并尝试与服务器连接

为“Connect/Disconnect”按钮注册单击事件

 

void CTestSocketClientDlg::OnBtnConnect() 
{
	if(connected)//如果已连接,则断开
	{
		connected = false;
		closesocket(toServer);
		m_chat += "Disconnect to Server!\r\n";
		UpdateData(false);
		return;
	}

	UpdateData(true);
	//创建Socket
	struct protoent *ppe;
	ppe = getprotobyname("tcp");
	if((toServer = socket(PF_INET, SOCK_STREAM, ppe->p_proto)) == INVALID_SOCKET)
	{
		m_chat += "Initialize Socket Listener Failed!\r\n";
		UpdateData(false);
		return;
	}
	//尝试连接服务器
	struct sockaddr_in saddr;
	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(m_port);
	saddr.sin_addr.s_addr = inet_addr(m_ip);
	if(connect(toServer, (struct sockaddr *)&saddr, sizeof(saddr)))
	{
		m_chat += "Connect Failed!\r\n";
		UpdateData(false);
		return;
	}
	m_chat += "Server: ";
	m_chat += inet_ntoa(saddr.sin_addr);
	m_chat += " Connected!\r\n";
	connected = true;
	UpdateData(false);
	AfxBeginThread(ReceiveMessage, this);//连接建立,另起线程用于接收信息
}

 

用于循环接收信息的线程

 

UINT ReceiveMessage(LPVOID pParam)
{
	CTestSocketServerDlg * c = (CTestSocketServerDlg *) pParam;
	char buffer[1024];
	int error;//记录recv函数返回值,即接收的字节数,也作异常代码
	while(error = recv(c->toClient, buffer, 1024, 0))
	{
		if(error == 0 || error == SOCKET_ERROR)break;
		c->PrintData("Received Data", (unsigned char*)buffer, error);
		c->aes.InvCipher((void *)buffer, error);//解密,恢复明文
		c->PrintData("Unencrypted Data", (unsigned char*)buffer, error);
		c->m_chat += "Client:";
		c->m_chat += buffer;
		c->m_chat += "\r\n";
		c->UpdateData(false);
	}
	c->m_ip = "Not Connected...";
	c->UpdateData(false);
	if(!c->connected)return 0;//服务器端主动关闭,直接返回
	closesocket(c->toClient);
	c->connected = false;
	c->m_chat += "Client Disconnected...\r\n";
	c->UpdateData(false);
	AfxBeginThread(Wait4Client, c);
	return 0;
}

 

为“Send”按钮注册单击事件,处理数据的加密发送

 

void CTestSocketServerDlg::OnBtnSend() 
{
	if(!connected)return;
	UpdateData(true);
	if(m_message == "")return;
	
	int len = m_message.GetLength()+1 >= 1024 ? 1024 : m_message.GetLength()+1;
	len = len%16 ? len+16-len%16 : len;
	char buffer[1024];
	strcpy(buffer,m_message.GetBuffer(0));//将message拷贝至buffer数组中
	m_message.ReleaseBuffer();
	PrintData("Input Data", (unsigned char*)buffer, len);
	aes.Cipher((void *)buffer);//对数据进行加密
	if(send(toClient, buffer, len, 0) == SOCKET_ERROR)//发送密文
	{
		m_chat += "Send Failed!(Socket Exception?)\r\n";
		UpdateData(false);
		return;
	}
	PrintData("Encrypted Data", (unsigned char*)buffer, len);
	m_chat += "Server:" + m_message + "\r\n";
	m_message ="";
	UpdateData(false);
}

 

发送和接收的时候都用到了一个函数PrintData,用于将明文或密文以16进制输出以便作演示

 

void CTestSocketServerDlg::PrintData(char* title, unsigned char* buffer, int length)
{
	int i;
	CString temp("");
	m_chat += "(";
	m_chat += title;
	m_chat += ":";
	for(i=0; i<length; i++)
	{
		temp.Format("%s%X ",*(buffer+i)>15?"":"0",*(buffer+i));
		m_chat += temp;
	}
	m_chat += ")\r\n";
}

 

贴出的代码都是服务器端的,客户端代码类似,最大区别就是类名不同,不做赘述

 

运行效果

 

最后惯例附上源码   Safe_Socket

posted @ 2010-11-01 03:47  xelz  阅读(14177)  评论(8编辑  收藏  举报