1.程序说明
开发环境为VS2012,基于TCP连接的客户端与服务端的通信程序,服务端IP为本地网卡ip地址或127.0.0.1,默认端口为1234(在程序编写过程中连接端口要大于1000,否则容易与计算机中某些程序端口冲突导致无法通信)。
2.socket简介
MFC类库中,几乎封装了Windows Sockets的全部功能,在微软基础类库中有两个基础类:CAsyncSocket类封装了异步套接字(非阻塞模式)的基本功能;CSocket类派生于CAsyncSocket类,具有串行化功能(阻塞模式)。
CAsyncSocket类实现数据传输步骤如下:
(1)调用构造函数创建套接字对象;
(2)如果创建服务器套接字,则调用Bind()函数绑定本地IP和端口,然后调用Listen()函数监听客户端请求。如果请求到来,则调用Accept()函数响应该请求。如果创建客户端套接字,则直接调用函数Connect()连接服务器即可;
(3)调用Send()等功能函数进行数据传输与处理;
(4)关闭或销毁套接字对象。
CSocket类实现数据传输步骤如下:
(1)创建CSocket类对象;
(2)如果创建服务器端套接字,则调用Bind()函数绑定本地IP和端口,然后调用Listen()函数监听客户端请求。如果请求到来,则调用Accept()函数响应该请求。如果创建客户端套接字,则直接调用函数Connect()连接服务器即可;
(3)创建与CSocket类对象相关联的CSocketFile类对象;
(4)创建与CSocket类对象相关联的CArchive类对象;
(5)使用CArchive类对象在客户端和服务器之间进行数据传输;
(6)关闭或销毁CSocket类,CSocketFile类,CArchive类三个对象。
上述两种步骤均为使用微软类库进行开发流程,使用MFC通信开发调用MFC封装类库即可。
3.相关函数简介
(1)struct sockaddr_in
{
short sin_family;//指定地址家族即地址格式,默认为AF_INET,就是TCP/IP协议
unsigned short sin_port;//端口号
struct in_addr sin_addr;//ip地址
char sin_zero[8];//需要指定为0
}
(2)BOOL Bind(const SOCKADDR* lpSockAddr,int nSockAddrLen);
lpSockAddr指定将要绑定的服务器地址结构
nSockAddrLen地址结构的长度
(3)BOOL Listen(int nConnectionBacklog=5);
最大监听客户端数,默认为5
(4)BOOL Connect(const SOCKADDR* lpSockAddr,int nSockAddrLen);
lpSockAddr 服务器地址结构
nSockAddrLen 地址结构长度
(5)virtual int Send(const void* lpBuf,int nBufLen,int nFlags=0);
lpBuf 待发送数据地址
nBufLen 待发送数据大小
nFlags 发送标志
(6)virtual int Receive(const void* lpBuf,int nBufLen,int nFlags=0);
lpBuf 接收数据地址
nBufLen 接收数据大小
nFlags 标志
4.关键代码(1)服务器端代码
#define WM_SOCKET_SERVER WM_USER+1000
// CSocket_Connect_ServerDlg 对话框
class CSocket_Connect_ServerDlg : public CDialogEx
{
// 构造
public:
CSocket_Connect_ServerDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_SOCKET_CONNECT_SERVER_DIALOG };
SOCKET s,s1;//定义套接字句柄
sockaddr_in addr,add1;//定义套接字地址结构变量
int n;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
DECLARE_MESSAGE_MAP()
afx_msg LRESULT OnSocketServer(WPARAM wParam, LPARAM lParam);
public:
afx_msg void OnClickedButton1();
};
BOOL CSocket_Connect_ServerDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
n=0;
addr.sin_family=AF_INET;
addr.sin_port=htons(1234);
addr.sin_addr.S_un.S_addr=INADDR_ANY;
s=::socket(AF_INET,SOCK_STREAM,0);
::bind(s,(sockaddr*)&addr,sizeof(addr));
::listen(s,5);
GetDlgItem(IDC_EDIT1)->EnableWindow(FALSE);
GetDlgItem(IDC_STATIC)->SetWindowTextA("服务器监听已经启动!");
::WSAAsyncSelect(s,this->m_hWnd,WM_SOCKET_SERVER,FD_ACCEPT|FD_READ|FD_CONNECT);//设置异步套接字
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
afx_msg LRESULT CSocket_Connect_ServerDlg::OnSocketServer(WPARAM wParam, LPARAM lParam)
{
CString str13;
char cs[100] = {0};
//int err_log=listen(s,10);
//if (err_log != 0)
//{
// perror("listen");
// //close(s);
// exit(-1);
//}
switch (lParam)
{
case FD_ACCEPT: //连接事件
{
int lenth=sizeof(add1);
s1=::accept(s,(sockaddr*)&add1,&lenth);
n=n+1;
str13.Format("有%d客户已经连接上了\n",n);
str13+=::inet_ntoa(add1.sin_addr);
str13+="\r\n登录\r\n";
GetDlgItem(IDC_EDIT1)->SetWindowTextA(str13);
}
case FD_READ: //读取事件
{
CString num="";
::recv(s1,cs,100,0);
GetDlgItem(IDC_EDIT1)->GetWindowTextA(num);
num += "\r\n";
num += (LPTSTR)::inet_ntoa(add1.sin_addr);
num += "对您说:";
num += (LPTSTR)cs;
GetDlgItem(IDC_EDIT1)->SetWindowTextA(num);
}
default:
break;
}
return 0;
}
void CSocket_Connect_ServerDlg::OnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CString str="";
GetDlgItem(IDC_EDIT2)->GetWindowTextA(str);
if (str=="")
{
MessageBox("消息不能为空!");
}
else
{
if (::send(s1,str.GetBuffer(1),str.GetLength(),0)!= SOCKET_ERROR)
{
GetDlgItem(IDC_EDIT1)->GetWindowTextA(str);
str += "\r\n消息已经发送到客户端!\r\n";
GetDlgItem(IDC_EDIT1)->SetWindowTextA(str);
}
else
{
GetDlgItem(IDC_EDIT1)->SetWindowTextA("消息发送失败!\r\n");
}
}
}
#define WM_SOCKET WM_USER+100
// CSocket_ConnectDlg 对话框
class CSocket_ConnectDlg : public CDialogEx
{
// 构造
public:
CSocket_ConnectDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_SOCKET_CONNECT_DIALOG };
SOCKET s;
sockaddr_in addr;
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnSysCommand(UINT nID, LPARAM lParam);
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg LRESULT OnSocket(WPARAM wParam,LPARAM lParam);
DECLARE_MESSAGE_MAP()
public:
afx_msg void OnClickedButton1();
afx_msg void OnClickedButton2();
};
BOOL CSocket_ConnectDlg::OnInitDialog()
{
CDialogEx::OnInitDialog();
// 将“关于...”菜单项添加到系统菜单中。
// IDM_ABOUTBOX 必须在系统命令范围内。
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE);
if (pSysMenu != NULL)
{
BOOL bNameValid;
CString strAboutMenu;
bNameValid = strAboutMenu.LoadString(IDS_ABOUTBOX);
ASSERT(bNameValid);
if (!strAboutMenu.IsEmpty())
{
pSysMenu->AppendMenu(MF_SEPARATOR);
pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
}
}
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
this->GetDlgItem(IDC_EDIT3)->EnableWindow(FALSE);
this->GetDlgItem(IDC_EDIT4)->EnableWindow(FALSE);
this->GetDlgItem(IDC_BUTTON2)->EnableWindow(FALSE);
s=::socket(AF_INET,SOCK_STREAM,0); //创建套接字
::WSAAsyncSelect(s,this->m_hWnd,WM_SOCKET,FD_READ);//将套接字设置为异步模式
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CSocket_ConnectDlg::OnClickedButton1()
{
// TODO: 在此添加控件通知处理程序代码
CString str,str1;
int port;
GetDlgItem(IDC_EDIT1)->GetWindowTextA(str);
GetDlgItem(IDC_EDIT2)->GetWindowTextA(str1);
if (str==""||str1=="")
{
MessageBox("服务器地址或端口号不能为空");
}
else
{
port=atoi(str1.GetBuffer(1));//将端口字符串转换为数字
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=inet_addr(str.GetBuffer(1));
//转换服务器IP地址
addr.sin_port=ntohs(port);
GetDlgItem(IDC_EDIT3)->SetWindowTextA("正在连接服务器......\r\n");
//提示用户正在连接服务器
if (::connect(s,(sockaddr *)&addr,sizeof(addr)))
{
GetDlgItem(IDC_EDIT3)->GetWindowTextA(str);
str +="连接服务器成功!\r\n";
GetDlgItem(IDC_EDIT3)->SetWindowTextA(str);
GetDlgItem(IDC_EDIT4)->EnableWindow(TRUE);
GetDlgItem(IDC_BUTTON2)->EnableWindow(TRUE);
GetDlgItem(IDC_EDIT1)->EnableWindow(FALSE);
GetDlgItem(IDC_EDIT2)->EnableWindow(FALSE);
}
else
{
GetDlgItem(IDC_EDIT3)->GetWindowTextA(str);
str += "连接服务器失败!请重试\r\n";
GetDlgItem(IDC_EDIT3)->SetWindowTextA(str);
}
}
}
void CSocket_ConnectDlg::OnClickedButton2()
{
// TODO: 在此添加控件通知处理程序代码
CString str,str1;
GetDlgItem(IDC_EDIT4)->GetWindowTextA(str);
if (str=="")
{
GetDlgItem(IDC_EDIT3)->GetWindowTextA(str1);
str1 += "\r\n";
str1 += "消息不能为空\r\n";
GetDlgItem(IDC_EDIT3)->SetWindowTextA(str1);
}
else
{
::send(s,str.GetBuffer(1),str.GetLength(),0);
//发送消息到指定服务器
GetDlgItem(IDC_EDIT3)->GetWindowTextA(str1);
str1 += "\r\n";
str1 += str;
GetDlgItem(IDC_EDIT3)->SetWindowTextA(str1);//将发送内容输出到消息框
}
}
LRESULT CSocket_ConnectDlg::OnSocket(WPARAM wParam,LPARAM lParam)
{
char cs[100]=""; //定义数据缓冲区
if (lParam==FD_READ) //如果是套接字读取时间
{
CString num=""; //定义字符串变量
recv(s,cs,100,NULL); //接受数据
GetDlgItem(IDC_EDIT3)->GetWindowText(num);
num += "\r\n";
num += (LPTSTR)cs; //将接收到的数据转换为字符串
GetDlgItem(IDC_EDIT3)->SetWindowText(num);//设置输出消息内容
}
return 0;
}