基于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