VC学习笔记11尝试互联网

来源:互联网 发布:win10 python环境搭建 编辑:程序博客网 时间:2024/05/16 07:59
下面以聊天室为例,来解析网络编程代码

聊天软件由服务器端和客户端两部分组成,先来说下服务器端

在VC++6.0环境下,建立MFC对话框工程,我这里的工程名为hlw
在对话框类CHlwDlg所在文件下先加入
#include "winsock2.h"   
#pragma comment (lib,"ws2_32.lib")
#define MAXNUM 10
第一个是需要的头文件,下面调入静态链接库,后面的宏定义是最大连接数
下面再该类的声名中加入几个变量
public:
void TranslateData(); //声明事件响应函数
SOCKET m_SockServer;  //用在服务器端的监听套接字
SOCKET m_Clients[MAXNUM];  //定义套接字数组用于为每个客户端进行连接
SOCKET m_SockClient;  //定义用于客户端的临时套接字变量
int m_ConnectNum;  //表示连接个数
CString m_IP;  //ip地址
UINT m_Port;  //端口号

那么SOCKET是一个什么数据类型呢?
typedef u_int           SOCKET; //这个是原定义unsigned int
在网络协议中我们用套接字来唯一标识网络中的进程,这也是个ID

当然,在网络编程中必须先启动套接字
在CHlwApp类中的InitInstance()响应中加入启动函数
WSADATA wsd;  //启用winsock
WSAStartup(MAKEWORD(2,2),&wsd);  //启动套接字
在应用结束方法ExitInstance()中加入关闭套接字函数
WSACleanup();   //清除套接字

下面我们建立两个编辑框,并且为他们关联两个变量
m_editip   //ip地址编辑
m_editport  //端口编辑

添加一个设置按钮

下面是响应函数
void CHlwDlg::OnOK()
{
    m_editip.GetWindowText(m_IP);   //将ip编辑窗口上的内容给m_IP
    CString strport;  //定义临时字符串储存变量
    m_editport.GetWindowText(strport);  //将端口编辑窗ko上的内容给strport
    if(m_IP.IsEmpty()||strport.IsEmpty())   //如果为空弹出错误信息
    {
        MessageBox("设置错误","错误");return;
    }
    m_Port=atoi(strport);  //转换成int
    sockaddr_in serveraddr;  //定义套接字地址信息变量
    serveraddr.sin_family=AF_INET;   //下面进行设置
    serveraddr.sin_addr.S_un.S_addr=inet_addr(m_IP);  //配置IP这里的输入为字符串类型,所以要转化成一个特定的量
    serveraddr.sin_port=htons(m_Port);  //设置端口,这边输入是int型,所以用htons转换
    if(bind(m_SockServer,(sockaddr*)&serveraddr,sizeof(serveraddr)))  //绑定套接字,用服务器端的地址信息
    {
        MessageBox("绑定地址失败"); return;
    }
    listen(m_SockServer,20);  //监听客户端建立连接的套接字
}
当点击设置按钮,就会配置好服务器的IP和端口。
IP是每台电脑的标识,端口是某台电脑某个应用程序的标识。
启动监听后,该程序就会响应网络中的信息
那么如何响应

在对话框创建过程中

BOOL CHlwDlg::OnInitDialog()
{
    CDialog::OnInitDialog();
    ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX);
    ASSERT(IDM_ABOUTBOX < 0xF000);
    CMenu* pSysMenu = GetSystemMenu(FALSE);
    if (pSysMenu != NULL)
    {
        CString strAboutMenu;
        strAboutMenu.LoadString(IDS_ABOUTBOX);
        if (!strAboutMenu.IsEmpty())
        {
            pSysMenu->AppendMenu(MF_SEPARATOR);
            pSysMenu->AppendMenu(MF_STRING, IDM_ABOUTBOX, strAboutMenu);
        }
    }
    SetIcon(m_hIcon, TRUE);            // Set big icon
    SetIcon(m_hIcon, FALSE);        // Set small icon
    
    // 下面是加上的内容
    m_SockServer=socket(AF_INET,SOCK_STREAM,0);  //创建套接字
    WSAAsyncSelect(m_SockServer,m_hWnd,WM_USER+1,FD_WRITE|FD_READ|FD_ACCEPT);
    //关联套接字中的FD_WRITE|FD_READ|FD_ACCEPT信息到消息WM_USER+1中(前面有定义)
    m_ConnectNum=0;  //连接数为0
    for(int i=0;i<MAXNUM;i++)  //初始化
        m_Clients[i]=0;
    return TRUE;  
}

该过程用自定义响应WM_USER+1来处理每次监听到网络中的信息
#define WM_USER                         0x0400
可以看到WM_USER是已经被系统定义过的
我们在这处地方加上响应声明:
BEGIN_MESSAGE_MAP(CHlwDlg, CDialog)
    ON_WM_SYSCOMMAND()
    ON_WM_PAINT()
    ON_WM_QUERYDRAGICON()

    //下面这个是加入的
    ON_MESSAGE(WM_USER+1,TranslateData)  //用TranslateData作为WM_USER+1的响应函数

将WM_USER+1这个响应绑定到函数TranslateData中
下面来看下这个函数

void CHlwDlg::TranslateData()
{
    sockaddr_in serveraddr;  //定义套接字地址配置变量,用来配置所要发送给谁的信息
    char buffer[1024];   //定义接收缓冲区
    int len=sizeof(serveraddr);  
    int curlink=-1;  //保存当前的连接位置
    int num=-1;
    for(int i=0;i<MAXNUM;i++)  //轮流察看是否接收到信息
    {
        num=recv(m_Clients[i],buffer,1024,0);  //如果没有接收到数据,则是接收到客户端连接
        if(num!=-1)
        {
            curlink=i;   
            break;
        }
    }
    buffer[num]=0;
    if(num==-1)  //接收客户端连接,连接是在第一次
    {
        if(m_ConnectNum<MAXNUM)
        {
            m_Clients[m_ConnectNum]=accept(m_SockServer,(struct sockaddr*)&serveraddr,&len);  //接受接收到的套接字请求,保存
            m_ConnectNum++;   //连接数+1
        }
        return;
    }
    for(int j=0;j<m_ConnectNum;j++)  //对于除发送方外的其他客户端广播,这里只将接受到的数据直接转发
    {
        if(j!=curlink)
            send(m_Clients[j],buffer,num,0);
    }
}


下面是客户端的程序:

首先和服务端一样:
创建实例时:
int CKhdApp::ExitInstance()
{
    WSACleanup();  //释放套接字
    return CWinApp::ExitInstance();
}
退出实例加上
WSACleanup();  //释放套接字

在对话框上先加入三个编辑器
分别分配变量为:
m_ServerIP  //输入的服务器IP地址
m_ServerPort  //输入服务器的端口
m_NickName   //昵称
再加上一个登录按钮
后面,加入列表控件作为信息显示部分
为其分配变量为
m_MsgList
再在下面加入编辑器作为发送信息的临时储存内容
为其分配变量为
m_SendData

在对话框类的文件中先定义
#include "winsock2.h"
#pragma comment (lib,"ws2_32.lib")
#define CM_RECEIVE 1000    //这个表示一个ID号
然后再声明
void ReceiveInfo();  //用来处理网络响应
CString m_IP;  //ip地址
UINT m_Port;  //端口号
SOCKET m_SockClient;  //定义用于客户端的套接字变量

现在处理登录按钮
void CKhdDlg::OnButton1()
{
    sockaddr_in serveraddr;  //定义套接字信息
    CString strport;
    m_ServerPort.GetWindowText(strport);
    m_ServerIP.GetWindowText(m_IP);
    if(strport.IsEmpty()|m_IP.IsEmpty())
    {
        MessageBox("服务器或端口号错误");
        return;
    }
    m_SockClient=socket(AF_INET,SOCK_STREAM,0);  //这个应该是用来获取本地网络的信息吧
    m_Port=atoi(strport);
    serveraddr.sin_family=AF_INET;  //下面设置所连接服务器信息
    serveraddr.sin_port=htons(m_Port);
    serveraddr.sin_addr.S_un.S_addr=inet_addr(m_IP);
    if(connect(m_SockClient,(sockaddr*)&serveraddr,sizeof(serveraddr))!=0)  //连接
    {
        MessageBox("连接失败");
        return;
    }
    else
        MessageBox("连接成功");
    WSAAsyncSelect(m_SockClient,m_hWnd,CM_RECEIVE,FD_READ);  //建立网络事件通知机制
    CString strname,info;
    m_NickName.GetWindowText(strname);
    info.Format("%s----->%s",strname,"进入聊天室");
    send(m_SockClient,info.GetBuffer(0),info.GetLength(),0);  //发送到网络,当然,互联网上的信息都是广播
}

下面在事件声明下面加入
ON_MESSAGE(CM_RECEIVE,ReceiveInfo)  //将CM_RECEIVE事件的响应函数定义为ReceiveInfo()

下面是事件处理函数
void CKhdDlg::ReceiveInfo()
{
    char buffer[1024];
    int num=recv(m_SockClient,buffer,1024,0);
    buffer[num]=0;
    m_MsgList.AddString(buffer);  //列表控件插入字符
}

下面实现发送按钮
void CKhdDlg::OnButton2()
{
    CString strData,name,info;
    m_NickName.GetWindowText(name);
    m_SendData.GetWindowText(strData);
    if(!name.IsEmpty()&&!strData.IsEmpty())
    {
        info.Format("%s说:%s",name,strData);  //格式化字符串
        send(m_SockClient,info.GetBuffer(0),info.GetLength(),0);  //把字符串广播出去
        m_MsgList.AddString(info);
        m_SendData.SetWindowText("");
    }
}


当然,这边的客户端发送信息都是可以被任何网络上的设备监听到的,
这时就需要数据加密了。










0 0