C++ mfc版本socket通讯源码分享


这是上个学期网络通讯的课程设计所做的作业,C++语言中的MFC写的。


后面将更新C#版本的基于socket的聊天室程序。废话不多说,直接上源码:








服务端代码:


// sFileDlg.cpp : 实现文件
 //
 #include <winsock2.h>
 #include "stdafx.h"
 #include "sFile.h"
 #include "sFileDlg.h"
 #include "afxdialogex.h"
 #pragma comment(lib, "WS2_32.lib")

 #ifdef _DEBUG
 #define new DEBUG_NEW
 #endif


    UINT server_thd(LPVOID p); //声明线程函数
 SOCKET listen_sock;//定义一个全局的监听soket
 SOCKET sock;//定义一个soket
 CString showeditmsg; //定义全局的显示消息的字符串

 
 
 // CsFileDlg 对话框
 CsFileDlg::CsFileDlg(CWnd* pParent /*=NULL*/)
 : CDialogEx(CsFileDlg::IDD, pParent)
{
 m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}

 void CsFileDlg::DoDataExchange(CDataExchange* pDX)
{
 CDialogEx::DoDataExchange(pDX);
}

 BEGIN_MESSAGE_MAP(CsFileDlg, CDialogEx)
  ON_WM_PAINT()
  ON_WM_QUERYDRAGICON()
  ON_BN_CLICKED(IDC_BtnSend, &CsFileDlg::OnBnClickedBtnsend)
  ON_BN_CLICKED(IDC_BUTTON2, &CsFileDlg::OnBnClickedButton2)
 END_MESSAGE_MAP()


 // CsFileDlg 消息处理程序

 BOOL CsFileDlg::OnInitDialog()
 {
  CDialogEx::OnInitDialog();

  // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
  //  执行此操作
  SetIcon(m_hIcon, TRUE);   // 设置大图标
  SetIcon(m_hIcon, FALSE);  // 设置小图标

  // TODO: 在此添加额外的初始化代码
   show_edit = (CEdit *)GetDlgItem(IDC_EDIT1);
   send_edit = (CEdit *)GetDlgItem(IDC_EDIT2);
   port_edit = (CEdit *)GetDlgItem(IDC_EDIT3);
   star_btn =  (CButton *)GetDlgItem(IDC_BUTTON2);
   showmsg_edit =  (CEdit *)GetDlgItem(IDC_EDIT4);
  return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
 }


 

 // 如果向对话框添加最小化按钮,则需要下面的代码
 //  来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
 //  这将由框架自动完成。

 void CsFileDlg::OnPaint()
 {
  if (IsIconic())
  {
   CPaintDC dc(this); // 用于绘制的设备上下文

   SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

   // 使图标在工作区矩形中居中
   int cxIcon = GetSystemMetrics(SM_CXICON);
   int cyIcon = GetSystemMetrics(SM_CYICON);
   CRect rect;
   GetClientRect(&rect);
   int x = (rect.Width() - cxIcon + 1) / 2;
   int y = (rect.Height() - cyIcon + 1) / 2;

   // 绘制图标
   dc.DrawIcon(x, y, m_hIcon);
  }
  else
  {
   CDialogEx::OnPaint();
  }
 }

 //当用户拖动最小化窗口时系统调用此函数取得光标
 //显示。
 HCURSOR CsFileDlg::OnQueryDragIcon()
 {
  return static_cast<HCURSOR>(m_hIcon);
 }


 //添加函数update() 更新消息数据
 void CsFileDlg::update(CString s)
 {
  showeditmsg += s;
  show_edit->SetWindowText(showeditmsg);
 }


 //开启服务器按钮响应事件
 void CsFileDlg::OnBnClickedButton2()
 {
  // TODO: 在此添加控件通知处理程序代码
  WSADATA wsData; 
   WSAStartup(MAKEWORD(2,2), &wsData);

   char name[80];
   CString IP;
   CString P;
   hostent* pHost;
   gethostname(name,sizeof(name));//获得主机名
   pHost = gethostbyname(name);//获得主机结构
   IP = inet_ntoa(*(struct in_addr *)pHost->h_addr_list[1]);//获取主机ip地址
   showmsg_edit->SetWindowText("绑定IP地址:" + IP);
   AfxBeginThread(&server_thd,0); //开启新线程处理
 }



 //发送按钮响应事件
 void CsFileDlg::OnBnClickedBtnsend()
 {
   // TODO: 在此添加控件通知处理程序代码
  CString s;
  char * msg;
  send_edit->GetWindowText(s);
  msg = (char*)s.GetBuffer(s.GetLength()); //获取发送的数据的缓冲区的指针

  if(s == "")
   {
        MessageBox("请输入信息");
   }
   else if(send(sock, msg, strlen(msg), 0) == SOCKET_ERROR) //发送数据,并判断是否成功
   {
     update("发送失败\r\n");
   }
   else
   {
     update("我:" + s + "\r\n");//消息上屏
     send_edit->SetWindowText("");//清空输入
     send_edit->SetFocus();//并重获焦点
   }
 }


 
   //添加线程函数server_thd():
   UINT server_thd(LPVOID p)
  {
    WSADATA wsaData;
    WORD wVersion;
    wVersion = MAKEWORD(2,2);
    WSAStartup(wVersion,&wsaData);

    SOCKADDR_IN local_addr;
    SOCKADDR_IN client_addr;
    int iaddrSize = sizeof(SOCKADDR_IN);
    int res;
    char msg[1024];
    CString port;
    CsFileDlg * dlg = (CsFileDlg *)AfxGetApp()->GetMainWnd(); //得到应用程序活动主窗口的指针
    dlg->port_edit->GetWindowText(port);//获得端口地址
    if(port=="")
    {
     AfxMessageBox("请输入端口号");
     return 0;
    }
    //为local_addr赋值,创建soket
    local_addr.sin_family = AF_INET;
    local_addr.sin_port = htons(atoi(port)); //atoi, 把字符串转换成整型数
    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
              //创建监听listen_sock
    if( (listen_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET )
    {
     dlg->update("创建监听失败\r\n");
     return 0;
    }
    //绑定
    if( bind(listen_sock, (struct sockaddr*) &local_addr, sizeof(SOCKADDR_IN)) )
    {
    dlg->update("绑定错误,换一个端口试试吧~\r\n");
    return 0;
    }

    listen(listen_sock, 1); //开始监听,允许最大监听数为1
    dlg->star_btn->EnableWindow(FALSE);  //开启服务器按钮灰化
    dlg->showmsg_edit->ShowWindow(SW_SHOW); //绑定消息edit显示
    dlg->update("已成功开启....\r\n");

    //接受连接请求
    if( (sock = accept(listen_sock, (struct sockaddr *)&client_addr, &iaddrSize)) == INVALID_SOCKET)
    {
    dlg->update("接受连接请求失败\r\n");
    return 0;
    }
    else
    {
     CString port;
     port.Format("%d", int(ntohs(client_addr.sin_port)));
     dlg->update( "已连接来自:" + CString(inet_ntoa(client_addr.sin_addr)) + "  端口:" +
      port+"\r\n");
    }

     ////////////接收数据
    while(1)
    {
 
       if( (res = recv(sock, msg, 1024, 0)) == -1 ) //接收数据,判断是否接收成功
       {
        dlg->update("失去连接\r\n");
        dlg->star_btn->EnableWindow(TRUE);
        break;
       }
       else
       {
        dlg->update("客户端:" + (CString(msg)).Mid(0,res)+"\r\n");
       }
   
    }
     return 0;
   }





  客户端代码:


// cFileDlg.cpp : 实现文件
 //
 #include <winsock2.h>
 #include "stdafx.h"
 #include "cFile.h"
 #include "cFileDlg.h"
 #include "afxdialogex.h"
 #pragma comment(lib, "WS2_32.lib")

 #ifdef _DEBUG
 #define new DEBUG_NEW
 #endif


 SOCKET sock; //创建全局的soket
 UINT recv_thd(LPVOID p); //声明线程函数
 CString edit1msg; // 定义全局的显示消息的字符串


 // CcFileDlg 对话框

 CcFileDlg::CcFileDlg(CWnd* pParent /*=NULL*/)
  : CDialogEx(CcFileDlg::IDD, pParent)
 {
  m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
 }

 void CcFileDlg::DoDataExchange(CDataExchange* pDX)
 {
  CDialogEx::DoDataExchange(pDX);
 }

 BEGIN_MESSAGE_MAP(CcFileDlg, CDialogEx)
  ON_WM_PAINT()
  ON_WM_QUERYDRAGICON()
  ON_BN_CLICKED(IDC_SEND, &CcFileDlg::OnBnClickedSend)
  ON_BN_CLICKED(IDC_btnConnect, &CcFileDlg::OnBnClickedbtnconnect)
 END_MESSAGE_MAP()


 // CcFileDlg 消息处理程序

 BOOL CcFileDlg::OnInitDialog()
 {
   CDialogEx::OnInitDialog();

   // 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
   //  执行此操作
   SetIcon(m_hIcon, TRUE);   // 设置大图标
   SetIcon(m_hIcon, FALSE);  // 设置小图标

   // TODO: 在此添加额外的初始化代码
   //初始化

    edit1 = (CEdit *)GetDlgItem(IDC_EDIT1);
    send_edit = (CEdit *)GetDlgItem(IDC_EDIT2);
    btnconn = (CButton *)GetDlgItem(IDC_btnConnect);
    ip_edit = (CEdit *)GetDlgItem(IDC_EDIT3);
    port_edit = (CEdit *)GetDlgItem(IDC_EDIT4);
    ip_edit->SetWindowText("127.0.0.1");
  return TRUE;  // 除非将焦点设置到控件,否则返回 TRUE
 }


 // 如果向对话框添加最小化按钮,则需要下面的代码
 //  来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
 //  这将由框架自动完成。

 void CcFileDlg::OnPaint()
 {
  if (IsIconic())
  {
   CPaintDC dc(this); // 用于绘制的设备上下文

   SendMessage(WM_ICONERASEBKGND, reinterpret_cast<WPARAM>(dc.GetSafeHdc()), 0);

   // 使图标在工作区矩形中居中
   int cxIcon = GetSystemMetrics(SM_CXICON);
   int cyIcon = GetSystemMetrics(SM_CYICON);
   CRect rect;
   GetClientRect(&rect);
   int x = (rect.Width() - cxIcon + 1) / 2;
   int y = (rect.Height() - cyIcon + 1) / 2;

   // 绘制图标
   dc.DrawIcon(x, y, m_hIcon);
  }
  else
  {
   CDialogEx::OnPaint();
  }
 }

 //当用户拖动最小化窗口时系统调用此函数取得光标
 //显示。
 HCURSOR CcFileDlg::OnQueryDragIcon()
 {
  return static_cast<HCURSOR>(m_hIcon);
 }




      //更新显示窗口数据
 void CcFileDlg::update(CString s) 
 {
  edit1msg += s;
  edit1->SetWindowText(edit1msg);
 }


     //链接,点连接按钮执行事件
 void CcFileDlg::OnBnClickedbtnconnect()
 {
  // TODO: 在此添加控件通知处理程序代码

  WSADATA wsaData;
  SOCKADDR_IN server_addr;


   WORD wVersion;
   wVersion = MAKEWORD(2,2);
   WSAStartup(wVersion,&wsaData);//使用Socket的程序在使用Socket之前必须调用WSAStartup函数。
   //该函数的第一个参数指明程序请求使用的Socket版本,其中高位字节指明副版本、低位字节指明主版本;
   //操作系统利用第二个参数返回请求的Socket的版本信息。
  //当一个应用程序调用WSAStartup函数时,操作系统根据请求的Socket版本来搜索相应的Socket库,
  //然后绑定找到的Socket库到该应用程序中。以后应用程序就可以调用所请求的Socket库中的其它Socket函数了。
  //该函数执行成功后返回0

   CString ip;
   CString port;
   ip_edit->GetWindowText(ip);//取得服务器的IP地址
   port_edit->GetWindowText(port);//取得服务器的端口

   if(port=="") //判断端口是否为空
   {
    AfxMessageBox("请输入端口号");
    btnconn->EnableWindow(TRUE);
    return;
   }

   //为server_addr赋值 ,创建soket
   server_addr.sin_addr.s_addr = inet_addr((LPCSTR)ip);
   server_addr.sin_family = AF_INET;
   server_addr.sin_port = htons(atoi(port));

   if( (sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == INVALID_SOCKET) //判断soket是否创建成功
   {
     update("创建Soket错误!\r\n");
     btnconn->EnableWindow(TRUE);
     return;
   }
   if( connect(sock, (struct sockaddr *) &server_addr, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)//判断链接是否成功
   {
     update("连接失败\r\n");
     btnconn->EnableWindow(TRUE);
     return;
   }
   else
   {
     update("连接成功\r\n");
     AfxBeginThread(&recv_thd, 0); //开启线程处理
     btnconn->EnableWindow(FALSE);//连接按钮变灰
   }
 }


//发送消息按钮处理事件
  void CcFileDlg::OnBnClickedSend()
   {

   CString s;
   char * msg;
   send_edit->GetWindowText(s);
   msg = (char*)s.GetBuffer(s.GetLength()); //获取发送的数据的缓冲区的指针

   if(send(sock, msg, strlen(msg), 0) == SOCKET_ERROR) //判断发送是否成功
   {
     update("发送失败\r\n");
   }
   else if(s == "")   //判断发送的数据是否为空
   {
        MessageBox("请输入信息");
   }
   else
   {
     update("我:"+ s+"\r\n");//消息上屏
     send_edit->SetWindowText("");//清空输入
     send_edit->SetFocus(); //重获焦点
   }
 }


//添加线程函数,接收数据
  UINT recv_thd(LPVOID p)
 {
  int res;
  char msg[1024];
  CString s;
  CcFileDlg * dlg = (CcFileDlg *) AfxGetApp()->GetMainWnd(); //得到应用程序活动主窗口的指针
  dlg->update("开始聊天吧~\r\n");
 
  while(1)
  {
        if( (res = recv(sock, msg, 1024, 0)) == -1) //接收数据,判断是否接收成功
    {
     dlg->update("失去连接~\r\n");
     dlg->btnconn->EnableWindow(TRUE);
     break;
    }
    else
    {
         dlg->update("服务器:" + (CString(msg)).Mid(0,res)+ "\r\n");
    }
  }
  return 0;
 }





                         作者QQ:575674261

            

                         

                        .NET学习交流群 :324087998  (更多实战项目源码分享)




完整代码打包:  http://pan.baidu.com/s/1sj0KarN







本科生产实习做的 客户端实现的详细清单: 1、进入程序的时候光标默认在昵称栏中,回车的默认是“发送”按钮,“断开”按钮默认为不可用 2、用户必须先连接上服务器才能使用聊天室,否则点击“发送”按钮将提示“你还没有登录”,服务器的IP可以进行选择或者输入,默认为本机环回 3、用户不能使用已经使用的昵称重复登录,昵称不能为空,但可以又空格 4、登录成功后“连接”按钮,昵称框,服务器框 都变为不可用,“断开”可用,消息框将显示“xxx刚刚进入了聊天室”,并在用户列表显示当前已登录的用户列表。断开后变化还原 5、说话对象默认为“所有人”,对所有人不能使用“悄悄话”功能 6、双击用户列表的某一行,或者直接在“对象”下拉菜单中选择说话的对象 7、“悄悄话”只有自己和对象可以看见,否则所有人都能看到 8、每一则消息的最大长度为468个字节,约234个汉字(含符号) 9、“清屏”按钮可以清空消息显示框的内容,并将“对象”菜单还原到“所有人” 10、消息显示框滚动条可以自动保持在显示内容的最底端,用户可以任意对消息框中的信息进行拖选和复制 11、所有聊天记录将自动保存在程序目录下的“昵称.txt”文件中 12、“断开”按钮可以不用退出程序而退出聊天事,用户选择“断开”或者关闭程序时,其他用户将提示“xxx刚刚离开了聊天室” 13、服务器踢出某用户,或者被关闭时将提示“被管理员踢出聊天室(或服务器关闭)” 14、服务器显示时能够识别说话对象是否为“你” 15、不能对自己说话 16、不能发空信息 17、每次发送信息后以及通过双击用户列表选中某个用户时,光标将默认处于“消息”框中以方便发送 18、当对某个人说话,而这个人退出时,提示“对象错误” 服务器端实现的详细清单(暂时只支持32个用户) 1、自动启动服务 2、用户登录后显示用户列表 3、可以选中某个用户将其踢出聊天室 4、对需要显示的信息添加时间后缀 5、对用户登录请求进行响应,不允许同名用户重复登录 6、对用户聊天数据进行转发 7、当用户列表发生 变化时,广播用户列表报文(目前的办法只实现了小于32个用户的情况)
里面包含聊天室的客户端和服务器端的源文件和一份完整的设计报告。 一、 系统概要 本系统能实现基于VC++的网络聊天室系统。有单独的客户端、服务器端。 服务器应用程序能够接受来自客户端的广播,然后向客户端发送本机的IP与服务端口,让客户端接入到服务器进行聊天,检测用户名是否合法(重复),服务器责接收来自客户端的聊天信息,并根据用户的需求发送给指定的人或所有人,能够给出上线下线提示。客户端能够发出连接请求,能编辑发送信息,可以指定发给单人或所有人,能显示聊天人数,上线下线用户等。 二、 通信规范的制定 服务请求规范: 服务器端: (1) 创建一个UDP的套接字,接受来自客户端的广播请求,当请求报文内容为“REQUEST FOR IP ADDRESS AND SERVERPORT”时,接受请求,给客户端发送本服务器TCP聊天室的端口号。 (2) 创建一个主要的TCP协议的套接字负责客户端TCP连接 ,处理它的连接请求事件。 (3)在主要的TCP连接协议的套接字里面再创建TCP套接字保存到动态数组里,在主要的套接字接受请求后 ,就用这些套接字和客户端发送和接受数据。 客户端: (1) 当用户按“连接”按钮时,创建UDP协议套接字,给本地计算机发广播,广播内容为“REQUEST FOR IP ADDRESS AND SERVERPORT”。 (2)当收到服务器端的回应,收到服务器发来的端口号后,关闭UDP连接。根据服务器的IP地址和端口号重新创建TCP连接。 故我思考:客户端一定要知道服务器的一个端口,我假设它知道服务器UDP服务的端口,通过发广播给服务器的UDP服务套接字,然后等待该套接字发回服务器TCP聊天室服务的端口号,IP地址用ReceiveForom也苛刻得到。 通信规范 通信规范的制定主要跟老师给出的差不多,并做了一小点增加: (增加验证用户名是否与聊天室已有用户重复,在服务器给客户端的消息中,增加标志0) ① TCP/IP数据通信 --- “聊天”消息传输格式 客户机 - 服务器 (1)传输“用户名” STX+1+用户名+ETX (2) 悄悄话 STX+2+用户名+”,”+内容+ETX (3) 对所有人说 STX+3+内容+ETX 服务器- 客户机 (0)请求用户名与在线用户名重复 //改进 STX+0+用户名+EXT (1)首次传输在线用户名 STX+1+用户名+ETX (2)传输新到用户名 STX+2+用户名+ETX (3)传输离线用户名 STX+3+用户名+ETX (4)传输聊天数据 STX+4+内容+ETX (注:STX为CHR(2),ETX 为CHR(3)) 三、 主要模块的设计分析 四、 系统运行效果 (要求有屏幕截图) 五、 心得与体会
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值