首页 > 代码库 > Windows程序设计笔记4:第10章:TCP/IP和网络通信
Windows程序设计笔记4:第10章:TCP/IP和网络通信
WinSock接口:Windows处理网络的API
套接字socket
流套接字:SOCKET_STREAM 可靠连接 TCP HTTP POP3
数据报套接字:SOCKET_DGRAM 不可靠连接 UDP
寻址方式:
1:sockaddr的第1个版本
struct sockaddr{
u_short sa_family; //地址家族
char sa_data[14]; //数据
}
2.sockaddr的TCP/IP版本的 sockaddr_in
struct sockaddr_in{
short sin_family; //地址家族
u_short sin_port; //端口号
struct in_addr sin_addr; //IP地址
char sa_data[14]; //空字节,设为0,8个字节
}
WinSock编程流程
1:WinSock的装入、初始化、释放
int WSAstartup{
WORD wVersionRequested; //WinSock的版本
LPWSADATA lpWSAData; //指针,返回DLL的详细信息
}
int WSAcleanup(void)
2:套接字的创建和关闭
SOCKET socket{
int af; //套接字地址格式
int type; //套接字类型
int protocol; //套接字使用协议
}
SOCKET_STREAM:TCP可靠的流套接字
SOCKET_DGRAM :UDP不可靠的数据报套接字
SOCKET_RAW :原始套接字
3.绑定套接字
int bind(
SOCKET s; //套接字句柄
const struct socketaddr* name; //关联的本地地址
int namelen; //地址的长度
)
4.设置套接字进入监听状态
int listen(
SOCKET s; //套接字句柄
int backlog; //套接字尚未连接的最大数量
)
5.接受连接请求
SOCKET accept(
SOCKET s;
struct socketaddr* addr; //指向地址
int* addlen; //指向地址长度的指针
)
6.收发数据
int send(
SOCKET s; //套接字句柄
const char FAR* buf; //缓冲区地址
int len,; //缓冲区长度
int flags; //指定的调用方式,通常设为0;
)
int recv(SOCKET s,const char FAR*,int len,int)
ServerDemo.cpp文件
#include <winsock2.h> // 为了使用Winsock API函数 #include <stdio.h> #include <windows.h> // 告诉连接器与WS2_32库连接 #pragma comment(lib,"WS2_32.lib") int main(int argc, char* argv[]) { // 初始化WS2_32.dll WSADATA wsaData; WORD sockVersion = MAKEWORD(2, 0); ::WSAStartup(sockVersion, &wsaData); // 创建套节字 SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(s == INVALID_SOCKET) { printf("Failed socket() \n"); ::WSACleanup(); return 0; } // 填充sockaddr_in结构 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(8888); sin.sin_addr.S_un.S_addr = INADDR_ANY; // 绑定这个套节字到一个本地地址 if(::bind(s, (LPSOCKADDR)&sin, sizeof(sin)) == SOCKET_ERROR) { printf("Failed bind() \n"); ::WSACleanup(); return 0; } // 进入监听模式 if(::listen(s, 2) == SOCKET_ERROR) { printf("Failed listen()"); ::WSACleanup(); return 0; } // 循环接受客户的连接请求 sockaddr_in remoteAddr; int nAddrLen = sizeof(remoteAddr); SOCKET client; char szText[] = " 10ServerDemo! \r\n"; while(TRUE) { // 接受一个新连接 client = ::accept(s, (SOCKADDR*)&remoteAddr, &nAddrLen); if(client == INVALID_SOCKET) { printf("Failed accept()"); continue; } printf(" 接受到一个连接:%s \r\n", inet_ntoa(remoteAddr.sin_addr)); // 向客户端发送数据 ::send(client, szText, strlen(szText), 0); // 关闭同客户端的连接 ::closesocket(client); } // 关闭监听套节字 ::closesocket(s); // 释放WS2_32库 ::WSACleanup(); return 0; }
ClientDemo.cpp文件
#include <winsock2.h> // 为了使用Winsock API函数 #include <stdio.h> #include <windows.h> // 告诉连接器与WS2_32库连接 #pragma comment(lib,"WS2_32.lib") int main(int argc, char* argv[]) { // 初始化WS2_32.dll WSADATA wsaData; WORD sockVersion = MAKEWORD(2, 0); ::WSAStartup(sockVersion, &wsaData); // 创建套节字 SOCKET s = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(s == INVALID_SOCKET) { printf("Failed socket() \n"); ::WSACleanup(); return 0; } // 也可以在这里调用bind函数绑定一个本地地址 // 否则系统将会自动安排 // 填写远程地址信息 sockaddr_in servAddr; servAddr.sin_family = AF_INET; servAddr.sin_port = htons(8888); // 注意,这里要填写服务器程序(10ServerDemo程序)所在机器的IP地址 // 如果你的计算机没有联网,直接使用127.0.0.1即可 servAddr.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if(::connect(s, (sockaddr*)&servAddr, sizeof(servAddr)) == -1) { printf("Failed connect() \n"); ::WSACleanup(); return 0; } // 接收数据 char buff[256]; int nRecv = ::recv(s, buff, 256, 0); if(nRecv > 0) { buff[nRecv] = ‘\0‘; printf(" 接收到数据:%s", buff); } // 关闭套节字 ::closesocket(s); // 释放WS2_32库 ::WSACleanup(); return 0; }
TCPServer.h文件
#include <afxwin.h> #include <afxcmn.h> #include <winsock2.h> // 告诉连接器与WS2_32库连接 #pragma comment(lib,"WS2_32.lib") #define MAX_SOCKET 56 // 定义此服务器所能接受的最大客户量 class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMainDialog : public CDialog { public: CMainDialog(CWnd* pParentWnd = NULL); protected: // 创建套节字,并设置为监听状态,准备接受客户的连接 BOOL CreateAndListen(int nPort); // 关闭所有套节字,包括监听套节字和所有accept函数返回的套节字 void CloseAllSocket(); // 向客户连接列表中添加一个客户 BOOL AddClient(SOCKET s); // 从客户连接列表中移处一个客户 void RemoveClient(SOCKET s); protected: // 两个子窗口控件,一个是状态栏,一个是列表框 CStatusBarCtrl m_bar; CListBox m_listInfo; // 监听套节字句柄 SOCKET m_socket; // 客户连接列表 SOCKET m_arClient[MAX_SOCKET]; // 套节字数组 int m_nClient; // 上述数组的大小 protected: virtual BOOL OnInitDialog(); virtual void OnCancel(); // 开启或停止服务 afx_msg void OnStart(); // 清空信息 afx_msg void OnClear(); // 套节字通知事件 afx_msg long OnSocket(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() };
TCPServer.cpp文件
#include "TCPClient.h" #include "resource.h" // 定义网络事件通知消息 #define WM_SOCKET WM_USER + 1 CMyApp theApp; BOOL CMyApp::InitInstance() { // 初始化Winsock库 WSADATA wsaData; WORD sockVersion = MAKEWORD(2, 0); ::WSAStartup(sockVersion, &wsaData); // 弹出主窗口对话框 CMainDialog dlg; m_pMainWnd = &dlg; dlg.DoModal(); // 释放Winsock库 ::WSACleanup(); return FALSE; } CMainDialog::CMainDialog(CWnd* pParentWnd):CDialog(IDD_MAINDIALOG, pParentWnd) { } BEGIN_MESSAGE_MAP(CMainDialog, CDialog) ON_BN_CLICKED(IDC_START, OnStart) ON_BN_CLICKED(IDC_CLEAR, OnClear) ON_MESSAGE(WM_SOCKET, OnSocket) END_MESSAGE_MAP() BOOL CMainDialog::OnInitDialog() { CDialog::OnInitDialog(); // 设置图标 SetIcon(theApp.LoadIcon(IDI_MAIN), FALSE); // 创建状态栏,设置它的属性 m_bar.Create(WS_CHILD|WS_VISIBLE|SBS_SIZEGRIP, CRect(0, 0, 0, 0), this, 101); m_bar.SetBkColor(RGB(0xa6, 0xca, 0xf0)); // 背景色 int arWidth[] = { 200, -1 }; m_bar.SetParts(2, arWidth); // 分栏 m_bar.SetText(" Windows程序设计进阶之路!", 1, 0); // 第一个栏的文本 m_bar.SetText(" 空闲", 0, 0); // 第二个栏的文本 // 设置列表框控件到m_listInfo对象的关联 m_listInfo.SubclassDlgItem(IDC_INFO, this); // 初始化监听套节字和连接列表 m_socket = INVALID_SOCKET; m_nClient = 0; // 下面是取得本地IP地址的过程,将它显示在状态栏的第一个分栏中 // 取得本机名称 char szHost[256]; ::gethostname(szHost, 256); // 通过本机名称取得地址信息 HOSTENT* pHost = gethostbyname(szHost); if(pHost != NULL) { CString sIP; // 得到第一个IP地址 in_addr *addr =(in_addr*) *(pHost->h_addr_list); // 显示给用户 sIP.Format(" 本机IP:%s", inet_ntoa(addr[0])); m_bar.SetText(sIP, 0, 0); } return TRUE; } void CMainDialog::OnStart() { if(m_socket == INVALID_SOCKET) // 开启服务 { // 取得端口号 CString sPort; GetDlgItem(IDC_PORT)->GetWindowText(sPort); int nPort = atoi(sPort); if(nPort < 1 || nPort > 65535) { MessageBox("端口号错误!"); return; } // 创建监听套节字,使它进入监听状态 if(!CreateAndListen(nPort)) { MessageBox("启动服务出错!"); return; } // 设置相关子窗口控件状态 GetDlgItem(IDC_START)->SetWindowText("停止服务"); m_bar.SetText(" 正在监听……", 0, 0); GetDlgItem(IDC_PORT)->EnableWindow(FALSE); } else // 停止服务 { // 关闭所有连接 CloseAllSocket(); // 设置相关子窗口控件状态 GetDlgItem(IDC_START)->SetWindowText("开启服务"); m_bar.SetText(" 空闲", 0, 0); GetDlgItem(IDC_PORT)->EnableWindow(TRUE); } } void CMainDialog::OnCancel() { CloseAllSocket(); CDialog::OnCancel(); } void CMainDialog::OnClear() { m_listInfo.ResetContent(); } long CMainDialog::OnSocket(WPARAM wParam, LPARAM lParam) { // 取得有事件发生的套节字句柄 SOCKET s = wParam; // 查看是否出错 if(WSAGETSELECTERROR(lParam)) { RemoveClient(s); ::closesocket(s); return 0; } // 处理发生的事件 switch(WSAGETSELECTEVENT(lParam)) { case FD_ACCEPT: // 监听中的套接字检测到有连接进入 { if(m_nClient < MAX_SOCKET) { // 接受连接请求,新的套节字client是新连接的套节字 SOCKET client = ::accept(s, NULL, NULL); // 设置新的套节字为窗口通知消息类型 int i = ::WSAAsyncSelect(client, m_hWnd, WM_SOCKET, FD_READ|FD_WRITE|FD_CLOSE); AddClient(client); } else { MessageBox("连接客户太多!"); } } break; case FD_CLOSE: // 检测到套接字对应的连接被关闭。 { RemoveClient(s); ::closesocket(s); } break; case FD_READ: // 套接字接受到对方发送过来的数据包 { // 取得对方的IP地址和端口号(使用getpeername函数) // Peer对方的地址信息 sockaddr_in sockAddr; memset(&sockAddr, 0, sizeof(sockAddr)); int nSockAddrLen = sizeof(sockAddr); ::getpeername(s, (SOCKADDR*)&sockAddr, &nSockAddrLen); // 转化为主机字节顺序 int nPeerPort = ::ntohs(sockAddr.sin_port); // 转化为字符串IP CString sPeerIP = ::inet_ntoa(sockAddr.sin_addr); // 取得对方的主机名称 // 取得网络字节顺序的IP值 DWORD dwIP = ::inet_addr(sPeerIP); // 获取主机名称,注意其中第一个参数的转化 hostent* pHost = ::gethostbyaddr((LPSTR)&dwIP, 4, AF_INET); char szHostName[256]; strncpy(szHostName, pHost->h_name, 256); // 接受真正的网络数据 char szText[1024] = { 0 }; ::recv(s, szText, 1024, 0); // 显示给用户 CString strItem = CString(szHostName) + "["+ sPeerIP+ "]: " + CString(szText); m_listInfo.InsertString(0, strItem); } break; } return 0; } BOOL CMainDialog::CreateAndListen(int nPort) { if(m_socket == INVALID_SOCKET) ::closesocket(m_socket); // 创建套节字 m_socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(m_socket == INVALID_SOCKET) return FALSE; // 填写要关联的本地地址 sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(nPort); sin.sin_addr.s_addr = INADDR_ANY; // 绑定端口 if(::bind(m_socket, (sockaddr*)&sin, sizeof(sin)) == SOCKET_ERROR) { return FALSE; } // 设置socket为窗口通知消息类型 ::WSAAsyncSelect(m_socket, m_hWnd, WM_SOCKET, FD_ACCEPT|FD_CLOSE); // 进入监听模式 ::listen(m_socket, 5); return TRUE; } BOOL CMainDialog::AddClient(SOCKET s) { if(m_nClient < MAX_SOCKET) { // 添加新的成员 m_arClient[m_nClient++] = s; return TRUE; } return FALSE; } void CMainDialog::RemoveClient(SOCKET s) { BOOL bFind = FALSE; for(int i=0; i<m_nClient; i++) { if(m_arClient[i] == s) { bFind = TRUE; break; } } // 如果找到就将此成员从列表中移除 if(bFind) { m_nClient--; // 将此成员后面的成员都向前移动一个单位 for(int j=i; j<m_nClient; j++) { m_arClient[j] = m_arClient[j+1]; } } } void CMainDialog::CloseAllSocket() { // 关闭监听套节字 if(m_socket != INVALID_SOCKET) { ::closesocket(m_socket); m_socket = INVALID_SOCKET; } // 关闭所有客户的连接 for(int i=0; i<m_nClient; i++) { ::closesocket(m_arClient[i]); } m_nClient = 0; }
TCPClient.h文件
#include <afxwin.h> #include <afxcmn.h> #include <winsock2.h> // 告诉连接器与WS2_32库连接 #pragma comment(lib,"WS2_32.lib") class CMyApp : public CWinApp { public: BOOL InitInstance(); }; class CMainDialog : public CDialog { public: CMainDialog(CWnd* pParentWnd = NULL); protected: // 连接服务器 BOOL Connect(LPCTSTR pszRemoteAddr, u_short nPort); // 向文本框中添加文本 void AddStringToList(LPCTSTR pszText, BOOL bRecv = TRUE); protected: // 状态栏子窗口控件 CStatusBarCtrl m_bar; // 用于与服务器取得连接的套节字句柄 SOCKET m_socket; protected: virtual BOOL OnInitDialog(); virtual void OnCancel(); // 取得或断开连接 afx_msg void OnButtonConnect(); // 发送数据 afx_msg void OnButtonSend(); // 清空编辑框 afx_msg void OnButtonClear(); // 套节字通知事件 afx_msg long OnSocket(WPARAM wParam, LPARAM lParam); DECLARE_MESSAGE_MAP() };
TCPClient.cpp文件
#include "TCPClient.h" #include "resource.h" // 定义网络事件通知消息 #define WM_SOCKET WM_USER + 1 CMyApp theApp; BOOL CMyApp::InitInstance() { // 初始化Winsock库 WSADATA wsaData; WORD sockVersion = MAKEWORD(2, 0); ::WSAStartup(sockVersion, &wsaData); // 弹出主窗口对话框 CMainDialog dlg; m_pMainWnd = &dlg; dlg.DoModal(); // 释放Winsock库 ::WSACleanup(); return FALSE; } CMainDialog::CMainDialog(CWnd* pParentWnd):CDialog(IDD_MAINDIALOG, pParentWnd) { } BEGIN_MESSAGE_MAP(CMainDialog, CDialog) ON_BN_CLICKED(IDC_CONNECT, OnButtonConnect) ON_BN_CLICKED(IDC_SEND, OnButtonSend) ON_BN_CLICKED(IDC_CLEAR, OnButtonClear) ON_MESSAGE(WM_SOCKET, OnSocket) END_MESSAGE_MAP() BOOL CMainDialog::OnInitDialog() { CDialog::OnInitDialog(); // 设置图标 SetIcon(theApp.LoadIcon(IDI_MAIN), FALSE); // 创建状态栏,设置它的属性 m_bar.Create(WS_CHILD|WS_VISIBLE|SBS_SIZEGRIP, CRect(0, 0, 0, 0), this, 101); m_bar.SetBkColor(RGB(0xa6, 0xca, 0xf0)); // 背景色 int arWidth[] = { 200, -1 }; m_bar.SetParts(2, arWidth); // 分栏 m_bar.SetText(" Windows程序设计进阶之路!", 1, 0); // 第一个栏的文本 m_bar.SetText(" 空闲", 0, 0); // 第二个栏的文本 // 初始化发送按钮和发送编辑框的状态 GetDlgItem(IDC_SEND)->EnableWindow(FALSE); GetDlgItem(IDC_TEXT)->EnableWindow(FALSE); // 初始化连接套节字 m_socket = INVALID_SOCKET; return TRUE; } void CMainDialog::OnCancel() { if(m_socket != INVALID_SOCKET) ::closesocket(m_socket); CDialog::OnCancel(); } void CMainDialog::OnButtonClear() { GetDlgItem(IDC_INFO)->SetWindowText(""); } void CMainDialog::OnButtonConnect() { if(m_socket == INVALID_SOCKET) // 连接服务器 { // 取得服务器地址 CString sAddr; GetDlgItem(IDC_ADDR)->GetWindowText(sAddr); if(sAddr.IsEmpty()) { MessageBox("请输入服务器地址!"); return; } // 取得端口号 CString sPort; GetDlgItem(IDC_PORT)->GetWindowText(sPort); int nPort = atoi(sPort); if(nPort < 1 || nPort > 65535) { MessageBox("端口号错误!"); return; } // 试图连接服务器 if(!Connect(sAddr, nPort)) { MessageBox("连接服务器出错!"); return; } // 设置用户界面 GetDlgItem(IDC_CONNECT)->SetWindowText("取消"); m_bar.SetText(" 正在连接……", 0, 0); } else // 断开服务器 { // 关闭套节字 ::closesocket(m_socket); m_socket = INVALID_SOCKET; // 设置用户界面 GetDlgItem(IDC_CONNECT)->SetWindowText("连接服务器"); m_bar.SetText(" 空闲", 0, 0); GetDlgItem(IDC_ADDR)->EnableWindow(TRUE); GetDlgItem(IDC_PORT)->EnableWindow(TRUE); GetDlgItem(IDC_TEXT)->EnableWindow(FALSE); GetDlgItem(IDC_SEND)->EnableWindow(FALSE); } } long CMainDialog::OnSocket(WPARAM wParam, LPARAM lParam) { // 取得有事件发生的套节字句柄 SOCKET s = wParam; // 查看是否出错 if(WSAGETSELECTERROR(lParam)) { if(m_socket != SOCKET_ERROR) OnButtonConnect(); m_bar.SetText(" 连接出错!", 0, 0); return 0; } // 处理发生的事件 switch(WSAGETSELECTEVENT(lParam)) { case FD_CONNECT: // 套节字正确的连接到服务器 { // 设置用户界面 GetDlgItem(IDC_CONNECT)->SetWindowText("断开连接"); GetDlgItem(IDC_ADDR)->EnableWindow(FALSE); GetDlgItem(IDC_PORT)->EnableWindow(FALSE); GetDlgItem(IDC_TEXT)->EnableWindow(TRUE); GetDlgItem(IDC_SEND)->EnableWindow(TRUE); m_bar.SetText(" 已经连接到服务器", 0, 0); } break; case FD_READ: // 套接字接受到对方发送过来的数据包 { // 从服务器接受数据 char szText[1024] = { 0 }; ::recv(s, szText, 1024, 0); // 显示给用户 AddStringToList(CString(szText) + "\r\n"); } break; case FD_CLOSE: OnButtonConnect(); break; } return 0; } void CMainDialog::OnButtonSend() { if(m_socket == INVALID_SOCKET) { return; } // 取得要发送的字符串 CString sText; GetDlgItem(IDC_TEXT)->GetWindowText(sText); // 添加一个“回车换行” // 注意,添加它并不是必须的,但是如果使用本软件作为客户端调试网络协议, // 比如SMTP、FTP等,就要添加它了。因为这些协议都要求使用“回车换行”作为一个命令的结束标记 sText += "\r\n"; // 发送数据到服务器 if(::send(m_socket, sText, sText.GetLength(), 0) != -1) { AddStringToList(sText, FALSE); GetDlgItem(IDC_TEXT)->SetWindowText(""); } } BOOL CMainDialog::Connect(LPCTSTR pszRemoteAddr, u_short nPort) { // 创建套节字 m_socket = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if(m_socket == INVALID_SOCKET) { return FALSE; } // 设置socket为窗口通知消息类型 ::WSAAsyncSelect(m_socket, m_hWnd, WM_SOCKET, FD_CONNECT | FD_CLOSE | FD_WRITE | FD_READ); // 假定szAddr是IP地址 ULONG uAddr = ::inet_addr(pszRemoteAddr); if(uAddr == INADDR_NONE) { // 不是IP地址,就认为这是主机名称 // 从主机名取得IP地址 hostent* pHost = ::gethostbyname(pszRemoteAddr); if(pHost == NULL) { ::closesocket(m_socket); m_socket = INVALID_SOCKET; return FALSE; } // 得到以网络字节顺序排列的IP地址 uAddr = ((struct in_addr*)*(pHost->h_addr_list))->s_addr; } // 填写服务器地址信息 sockaddr_in remote; remote.sin_addr.S_un.S_addr = uAddr; remote.sin_family = AF_INET; remote.sin_port = htons(nPort); // 连接到远程机 ::connect(m_socket, (sockaddr*)&remote, sizeof(sockaddr)); return TRUE; } void CMainDialog::AddStringToList(LPCTSTR pszText, BOOL bRecv) { CString strEdit; GetDlgItem(IDC_INFO)->GetWindowText(strEdit); if(bRecv) { strEdit += "【Recv】:"; strEdit += pszText; } else { strEdit += "【Send】:"; strEdit += pszText; } GetDlgItem(IDC_INFO)->SetWindowText(strEdit); }
Windows程序设计笔记4:第10章:TCP/IP和网络通信