MFC局域网的winsocket编程
1.功能简介
客户端:
- 与服务器建立连接
- 发送消息到服务器
- 断开与服务器的连接
服务器端:
- 接受客户端的请求
- 将某个客户端发来的消息广播到其他的在线客户端
2.winsocket模块
1.服务器端和客户端收发数据前准备工作:
服务器端应用程序(创建套接字socket,绑定bind,监听listen,接受用户请求accept)
客户端应用程序(根据服务器应用程序所在IP和PORT创建套接字socket,连接conn)
2.收发数据操作:
接收数据recv(SOCKET Socket, char *recvbuf, int recvbuflen, 0);
发送数据recv(SOCKET Socket, char *recvbuf, int recvbuflen, 0);
3.关闭连接:
shutdown(SOCKET Socket, SD_SEND);
4.删除套接字:
closesocket(SOCKET Socket);
5.WSAAsyncSelect模型,通知套接字端口有请求事件发生,用于更新数据
WSAAsyncSelect(SOCKET Socket,m_hWnd,WM_SOCKET, FD_READ|FD_WRITE|FD_ACCEPT);
WSAAsyncSelect(套接字,窗口句柄,自定义窗口消息ID,事件)
6.自定义窗口消息:
1.#define WM_SOCKET WM_USER+11 使用宏定义消息ID
2.afx_msg LRESULT OnSocket(WPARAM w, LPARAM l); 在CServerMFCDlg.h中声明消息响应函数
3.将消息映射到处理函数
BEGIN_MESSAGE_MAP(CServerMFCDlg, CDialog)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_LISTENBUTTON, &CServerMFCDlg::OnClickedListenbutton)
ON_MESSAGE(WM_SOCKET,OnSocket) 在CServerMFCDlg.cpp中加入这句,将消息映射到处理函数
END_MESSAGE_MAP()
3.MFC界面设计模块:
服务器端:
控件 | ID | 变量 | 说明 |
---|---|---|---|
ListBox(消息栏) | IDC_MESSAGELIST | m_MessageList | 类别为Control控件 |
ListBox(在线用户) | IDC_ONLINELIST | m_OnlineList | 类别为Value,类型为CString |
EditBox(IP) | IDC_IPEDIT | m_ip | 类别为Value,类型为CString |
EditBox(Port) | IDC_PORTEDIT | m_port | 类别为Value,类型为CString |
Button(监听按钮) | IDC_LISTENBUTTON | 类向导添加单击事件 |
客户端:
控件 | ID | 变量 | 说明 |
---|---|---|---|
ListBox(消息栏) | IDC_MESSAGELIST | m_MessageList | 类别为Control控件 |
EditBox(消息发送栏) | IDC_SENDEDIT | m_SendMessage | 类别为Value,类型为CString |
EditBox(IP) | IDC_IPEDIT | m_ip | 类别为Value,类型为CString |
EditBox(Port) | IDC_PORTEDIT | m_port | 类别为Value,类型为CString |
Button(发送按钮) | IDC_SENDBUTTON | 类向导添加单击事件 | |
Button(连接按钮) | IDC_CONNBUTTON | 类向导添加单击事件 | |
Button(断开按钮) | IDC_CLOSEBUTTON | 类向导添加单击事件 |
4.代码实现
// ServerMFCDlg.h : 头文件
//
#pragma once
#include "afxwin.h"
#include "afxcmn.h"
#define CLIEN_MAXNUM 12
// CServerMFCDlg 对话框
class CServerMFCDlg : public CDialog
{
// 构造
public:
CServerMFCDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_SERVERMFC_DIALOG };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
HICON m_hIcon;
// 生成的消息映射函数
virtual BOOL OnInitDialog();
afx_msg void OnPaint();
afx_msg HCURSOR OnQueryDragIcon();
afx_msg LRESULT OnSocket(WPARAM w, LPARAM l);
DECLARE_MESSAGE_MAP()
public:
CListBox m_MessageList;
CString m_ip;
CString m_port;
private:
char* serverName;
char* serverPort;
SOCKET ClientSocketList[CLIEN_MAXNUM];
char* ClientIPList[CLIEN_MAXNUM];
u_short ClientPortList[CLIEN_MAXNUM];
int cursor;
SOCKET ListenSocket;
WSADATA wsaData;
SOCKET ClientSocket;
void open();
public:
afx_msg void OnClickedListenbutton();
CListBox m_OnlineList;
};
// ServerMFCDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "ServerMFC.h"
#include "ServerMFCDlg.h"
#include "afxdialogex.h"
#include "WinSock2.h"
#include "windows.h"
#include "ws2tcpip.h"
#define CLIEN_MAXNUM 12
#define DEFAULT_BUFLEN 1024
#define WM_SOCKET WM_USER+11
#pragma comment(lib,"Ws2_32.lib")
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// CServerMFCDlg 对话框
CServerMFCDlg::CServerMFCDlg(CWnd* pParent /*=NULL*/)
: CDialog(CServerMFCDlg::IDD, pParent)
, m_ip(_T(""))
, m_port(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CServerMFCDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Control(pDX, IDC_MESSAGELIST, m_MessageList);
DDX_Text(pDX, IDC_IPEDIT, m_ip);
DDX_Text(pDX, IDC_PORTEDIT, m_port);
DDX_Control(pDX, IDC_LIST1, m_OnlineList);
}
BEGIN_MESSAGE_MAP(CServerMFCDlg, CDialog)
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_LISTENBUTTON, &CServerMFCDlg::OnClickedListenbutton)
ON_MESSAGE(WM_SOCKET,OnSocket)
END_MESSAGE_MAP()
// CServerMFCDlg 消息处理程序
BOOL CServerMFCDlg::OnInitDialog()
{
CDialog::OnInitDialog();
// 设置此对话框的图标。当应用程序主窗口不是对话框时,框架将自动
// 执行此操作
SetIcon(m_hIcon, TRUE); // 设置大图标
SetIcon(m_hIcon, FALSE); // 设置小图标
// TODO: 在此添加额外的初始化代码
cursor = 0;
for (int i=0; i<CLIEN_MAXNUM;i++)
ClientSocketList[i] = NULL;
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CServerMFCDlg::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
{
CDialog::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CServerMFCDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CServerMFCDlg::open()
{
struct addrinfo *result;
struct addrinfo hints;
ListenSocket = INVALID_SOCKET;
ClientSocket = INVALID_SOCKET;
int iResult = 0;
// Initialize Winsock
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (0 != iResult)
MessageBoxA(NULL,"WSAStartup failed with error","error",MB_OK);
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the server address and por
iResult = getaddrinfo(serverName, serverPort, &hints, &result);
if ( 0 != iResult) {
MessageBoxA(NULL,"getaddrinfo failed with error","error",MB_OK);
WSACleanup();
}
// Create a SOCKET for connecting to server
ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET) {
MessageBoxA(NULL,"socket failed with error","error",MB_OK);
freeaddrinfo(result);
WSACleanup();
}
// Setup the TCP listening socket
iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR) {
MessageBoxA(NULL,"bind failed with error","error",MB_OK);
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
}
freeaddrinfo(result);
iResult = listen(ListenSocket, CLIEN_MAXNUM);
if (iResult == SOCKET_ERROR) {
MessageBoxA(NULL,"listen failed with error","error",MB_OK);
closesocket(ListenSocket);
WSACleanup();
}
}
void CServerMFCDlg::OnClickedListenbutton()
{
//first we initialize the port and the ip address
//transmit the value of the Control to member
UpdateData(TRUE);
//convert to char* from CString
USES_CONVERSION;
serverName = T2A(m_ip.GetBuffer(0));
m_ip.ReleaseBuffer();
serverPort = T2A(m_port.GetBuffer(0));
m_port.ReleaseBuffer();
//start listen the port
open();
//output the successful information
CString openSuccessInfo;
openSuccessInfo.Format(_T("主机%s监听端口%s......"),m_ip.GetBuffer(0),m_port.GetBuffer(0));
m_MessageList.AddString(openSuccessInfo);
//transmit the value of member to the Control
UpdateData(FALSE);
WSAAsyncSelect(ListenSocket,m_hWnd,WM_SOCKET, FD_READ|FD_WRITE|FD_ACCEPT);
}
LRESULT CServerMFCDlg::OnSocket(WPARAM w, LPARAM l)
{
switch(l)
{
case FD_ACCEPT:
{
sockaddr_in m_client;
int sz;
sz = sizeof(sockaddr_in);
ClientSocket = accept(ListenSocket, (sockaddr*)&m_client, &sz);
if(ClientSocket == INVALID_SOCKET)
{
closesocket(ListenSocket);
WSACleanup();
}
ClientSocketList[cursor] = ClientSocket;
char* clientIP = inet_ntoa(m_client.sin_addr);
u_short clientPort = ntohs(m_client.sin_port);
ClientIPList[cursor] = clientIP;
ClientPortList[cursor] = clientPort;
cursor++;
CString receivedInfo;
receivedInfo.Format(_T("%s/%d 进入聊天室......"), CStringW(clientIP),clientPort);
m_MessageList.AddString(receivedInfo);
//add to online client list
CString clientInfo;
clientInfo.Format(_T("%s:%d"), CStringW(clientIP),clientPort);
m_OnlineList.AddString(clientInfo);
UpdateData(FALSE);
USES_CONVERSION;
char *buf = T2A(receivedInfo.GetBuffer(0));
int len = CStringA(receivedInfo).GetLength();
for(int i=0; i<cursor; i++)
send(ClientSocketList[i],buf,len,0);
}
break;
case FD_READ:
{
char* buffer = new char[DEFAULT_BUFLEN];
int res = -1;
int curClient;
for (int i=0; i<CLIEN_MAXNUM;i++)
{
res = recv(ClientSocketList[i], buffer, DEFAULT_BUFLEN,0);
if(res > 0)
{
curClient = i;
break;
}
}
buffer[res] = '\0';
CString receivedStr;
if(res > 0)
{
receivedStr.Format(_T("%s/%d:%s"), CStringW(ClientIPList[curClient]),ClientPortList[curClient],CStringW(buffer));
m_MessageList.AddString(receivedStr);
//transmit the value of member to the Control
UpdateData(FALSE);
}
USES_CONVERSION;
char *buf = T2A(receivedStr.GetBuffer(0));
int len = CStringA(receivedStr).GetLength();
for(int i=0; i<cursor; i++)
send(ClientSocketList[i],buf,len,0);
}
break;
}
return 1;
}
// ClientMFCDlg.h : 头文件
//
#pragma once
#include "afxwin.h"
// CClientMFCDlg 对话框
class CClientMFCDlg : public CDialog
{
// 构造
public:
CClientMFCDlg(CWnd* pParent = NULL); // 标准构造函数
// 对话框数据
enum { IDD = IDD_CLIENTMFC_DIALOG };
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 w, LPARAM l);
DECLARE_MESSAGE_MAP()
public:
CString m_ip;
CString m_port;
CString m_SendMessage;
CListBox m_MessageList;
private:
char* serverName;
char* serverPort;
WSADATA wsaData;
SOCKET ConnectSocket;
void conn();
void close();
public:
afx_msg void OnClickedConnbutton();
afx_msg void OnClickedSnedbutton();
afx_msg void OnBnClickedDisconnbutton();
};
// ClientMFCDlg.cpp : 实现文件
//
#include "stdafx.h"
#include "ClientMFC.h"
#include "ClientMFCDlg.h"
#include "afxdialogex.h"
#include "windows.h"
#include "winsock2.h"
#include "ws2tcpip.h"
#define DEFAULT_BUFLEN 1024
#define WM_SOCKET WM_USER+11
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 用于应用程序“关于”菜单项的 CAboutDlg 对话框
class CAboutDlg : public CDialog
{
public:
CAboutDlg();
// 对话框数据
enum { IDD = IDD_ABOUTBOX };
protected:
virtual void DoDataExchange(CDataExchange* pDX); // DDX/DDV 支持
// 实现
protected:
DECLARE_MESSAGE_MAP()
};
CAboutDlg::CAboutDlg() : CDialog(CAboutDlg::IDD)
{
}
void CAboutDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
}
BEGIN_MESSAGE_MAP(CAboutDlg, CDialog)
END_MESSAGE_MAP()
// CClientMFCDlg 对话框
CClientMFCDlg::CClientMFCDlg(CWnd* pParent /*=NULL*/)
: CDialog(CClientMFCDlg::IDD, pParent)
, m_ip(_T(""))
, m_port(_T(""))
, m_SendMessage(_T(""))
{
m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME);
}
void CClientMFCDlg::DoDataExchange(CDataExchange* pDX)
{
CDialog::DoDataExchange(pDX);
DDX_Text(pDX, IDC_EDIT1, m_ip);
DDX_Text(pDX, IDC_EDIT2, m_port);
DDX_Text(pDX, IDC_EDIT4, m_SendMessage);
DDX_Control(pDX, IDC_LIST1, m_MessageList);
}
BEGIN_MESSAGE_MAP(CClientMFCDlg, CDialog)
ON_WM_SYSCOMMAND()
ON_WM_PAINT()
ON_WM_QUERYDRAGICON()
ON_BN_CLICKED(IDC_CONNBUTTON, &CClientMFCDlg::OnClickedConnbutton)
ON_BN_CLICKED(IDC_SNEDBUTTON, &CClientMFCDlg::OnClickedSnedbutton)
ON_MESSAGE(WM_SOCKET,OnSocket)
ON_BN_CLICKED(IDC_DISCONNBUTTON, &CClientMFCDlg::OnBnClickedDisconnbutton)
END_MESSAGE_MAP()
// CClientMFCDlg 消息处理程序
BOOL CClientMFCDlg::OnInitDialog()
{
CDialog::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: 在此添加额外的初始化代码
return TRUE; // 除非将焦点设置到控件,否则返回 TRUE
}
void CClientMFCDlg::OnSysCommand(UINT nID, LPARAM lParam)
{
if ((nID & 0xFFF0) == IDM_ABOUTBOX)
{
CAboutDlg dlgAbout;
dlgAbout.DoModal();
}
else
{
CDialog::OnSysCommand(nID, lParam);
}
}
// 如果向对话框添加最小化按钮,则需要下面的代码
// 来绘制该图标。对于使用文档/视图模型的 MFC 应用程序,
// 这将由框架自动完成。
void CClientMFCDlg::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
{
CDialog::OnPaint();
}
}
//当用户拖动最小化窗口时系统调用此函数取得光标
//显示。
HCURSOR CClientMFCDlg::OnQueryDragIcon()
{
return static_cast<HCURSOR>(m_hIcon);
}
void CClientMFCDlg::conn()
{
ConnectSocket = INVALID_SOCKET;
struct addrinfo *ptr,*result = NULL;
struct addrinfo hints;
int iResult = 0;
iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
if (iResult != 0)
MessageBoxA(NULL,"WSAStartup failed with error","error",MB_OK);
/*hostName = new char[256];
gethostname(hostName,sizeof(hostName));*/
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the server address and port
iResult = getaddrinfo(serverName, serverPort, &hints, &result);
if ( 0 != iResult) {
MessageBoxA(NULL,"getaddrinfo failed with error","error",MB_OK);
WSACleanup();
}
// Attempt to connect to an address until one succeeds
for(ptr=result; ptr != NULL ;ptr=ptr->ai_next) {
// Create a SOCKET for connecting to server
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype,
ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET) {
WSACleanup();
}
// Connect to server.
iResult = connect( ConnectSocket, ptr->ai_addr, (int)ptr->ai_addrlen);
if (iResult == SOCKET_ERROR) {
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
continue;
}
break;
}
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET) {
MessageBoxA(NULL,"socket failed with error","error",MB_OK);
WSACleanup();
}
}
void CClientMFCDlg::close()
{
int iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
MessageBoxA(NULL,"shutdown failed with error","error",MB_OK);
closesocket(ConnectSocket);
WSACleanup();
}
closesocket(ConnectSocket);
WSACleanup();
}
void CClientMFCDlg::OnClickedConnbutton()
{
UpdateData(TRUE);
//convert to char* from CString
USES_CONVERSION;
serverName = T2A(m_ip.GetBuffer(0));
m_ip.ReleaseBuffer();
serverPort = T2A(m_port.GetBuffer(0));
m_port.ReleaseBuffer();
conn();
WSAAsyncSelect(ConnectSocket,m_hWnd,WM_SOCKET, FD_READ);
}
void CClientMFCDlg::OnClickedSnedbutton()
{
CString sendInfo;
UpdateData(TRUE);
sendInfo.Format(_T("%s"),CStringW(m_SendMessage.GetBuffer(0)));
int len = CStringA(sendInfo).GetLength();
USES_CONVERSION;
char *buf = T2A(sendInfo.GetBuffer(0));
int iSendResult = send(ConnectSocket, buf, len, 0);
if (iSendResult == SOCKET_ERROR) {
MessageBoxA(NULL,"send failed with error","error",MB_OK);
closesocket(ConnectSocket);
WSACleanup();
}
}
LRESULT CClientMFCDlg::OnSocket(WPARAM w, LPARAM l)
{
SOCKET s = (SOCKET)w;
switch(l)
{
case FD_READ:
char* buffer = new char[DEFAULT_BUFLEN];
int res = recv(s,buffer,DEFAULT_BUFLEN,0);
if(res > 0)
{
CString receivedStr;
buffer[res] = '\0';
receivedStr.Format(_T("%s"),CStringW(buffer));
m_MessageList.AddString(receivedStr);
//transmit the value of member to the Control
UpdateData(FALSE);
}
break;
}
return 1;
}
void CClientMFCDlg::OnBnClickedDisconnbutton()
{
close();
}
5.问题与解决方法
1.char*类型的变量转换成CString类型变量
char *buffer = new char[1024];
CString receivedStr;
receivedStr.Format(_T(“%s”),CStringW(buffer));
写成receivedStr.Format(_T(“%s”),buffer);会出现乱码,应用CStringW进行转换
2.CString类型的变量转换成char*类型变量
CString sendInfo;
USES_CONVERSION;
char *buf = T2A(sendInfo.GetBuffer(0));
2.接收数据时出现多余的数据
char* buffer = new char[DEFAULT_BUFLEN];
int res = recv(s,buffer,DEFAULT_BUFLEN,0);
buffer[res] = ‘\0’;//res为接收到的字节数,这里用\0表示字符串结束
3.在服务器端获得客户端的IP和端口
sockaddr_in m_client;
int sz;
sz = sizeof(sockaddr_in);
ClientSocket = accept(ListenSocket, (sockaddr*)&m_client, &sz);
char* clientIP = inet_ntoa(m_client.sin_addr);//获得主机IP
u_short clientPort = ntohs(m_client.sin_port);//获得主机端口号
4.send发送数据时总是不能发送正确字节数数据
CString::GetLength()获得正确的字节数
CString str(“你好ab”);
str.GetLength();//结果为4,当发送数据时,只能发送”你好”到另一端
CStringA(str).GetLength();//结果为6,正确
5.MFC空间添加变量更新数据
UpdateData(TRUE);//将控件值赋值给控件变量
UpdateData(FALSE);//将控件变量同步到控件中
6.运行截图
服务器监听端口:
cmd中netstat -ano 命令可以看出端口9080被监听
一个客户端(端口号为1135)请求连接服务器:
建立连接后修改了连接状态,由LISTENING变为ESTABLISHED
另一个客户端请求连接服务器(端口号为1135)
发送消息可以广播到其它的客户端
最后其中一个客户端(端口号为1135)断开连接
7.源代码
https://github.com/15045120/ClientMFC
https://github.com/15045120/ServerMFC
8.参考文献
[1]谢希仁.计算机网络[M] (第7版).电子工业出版社,2007.
[2]户根勤.网络是怎样连接的[M].人民邮电出版社,2017.1.
[3]徐斌.MFC小型局域网聊天室的实现[D].西南大学网络与继续教育学院专升本论文,2014:5-10.
[4]VC初学者.MFC自定义消息[OL].(2018-05-28).https://www.cnblogs.com/xydblog/archive/2014/02/25/3566266.html.
[5]u010306834.关于VS2010 CString.Format()之后乱码的问题[OL].(2018-05-28)https://blog.csdn.net/u010306834/article/details/39495305.
[6]beitcoder.CString 和 char * 的相互转换[OL].(2018-05-28)https://blog.csdn.net/beitcoder/article/details/1538154.
[7]weiyuefei.accept成功后获取客户端ip[OL].(2018-05-28)https://blog.csdn.net/weiyuefei/article/details/52956769.
[8]郑文亮.CString::GetLength()获得字节数的正确方法[OL].(2018-05-28)https://www.cnblogs.com/zhwl/archive/2012/11/09/2762962.html.
[9]Microsoft.Getting Started with Winsock[OL].(2018-05-28)https://msdn.microsoft.com/en-us/library/windows/desktop/ms738545(v=vs.85).aspx