心跳包和tcp
心跳包
这节用的还是上节课的代码,算是对上一节的补充扩展
keep-alive-保活
在ChatClient中添加类向导
1 2 3 4 5 6
| void CChatClientDlg::OnTimer(UINT_PTR nIDEvent) { CPackage pkg(PT_HEARTBEAT, m_client); sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer)); CDialogEx::OnTimer(nIDEvent); }
|
添加类
1 2 3 4 5 6 7 8 9 10 11 12 13
| #pragma once #include<Windows.h> class CLock { public: CLock(); ~CLock(); void Lock(); void Unlock(); private: CRITICAL_SECTION m_cs; };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| #include "CLock.h"
CLock::CLock() { InitializeCriticalSection(&m_cs); }
CLock::~CLock() { DeleteCriticalSection(&m_cs); }
void CLock::Lock() { EnterCriticalSection(&m_cs); }
void CLock::Unlock() { LeaveCriticalSection(&m_cs); }
|
因为修改的地方太多直接放完整代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71
| #pragma once
#define WIN32_LEAN_AND_MEAN #include <windows.h> #include <WinSock2.h> #pragma comment(lib,"ws2_32.lib")
enum PackageType { PT_ONLINE, PT_OFFLINE, PT_PUBLIC, PT_PRIVATE, PT_HEARTBEAT };
#define NAMELEN 64 struct CLient { bool operator==(const CLient& obj) { if (m_si.sin_addr.S_un.S_addr != obj.m_si.sin_addr.S_un.S_addr) { return false; } if (m_si.sin_port != obj.m_si.sin_port) { return false; } if (strcmp(m_szName, obj.m_szName) != 0) { return false; } return true; } sockaddr_in m_si; char m_szName[NAMELEN]; };
#define MASLEN 1200 struct CPackage { CPackage(){} CPackage(PackageType pt, CLient& client, char* szMsg = NULL): m_pt(pt), m_client(client) { memset(m_szMsg, 0, sizeof(m_szMsg)); if (szMsg != NULL) { strcpy(m_szMsg, szMsg); } } CPackage(PackageType pt, CLient& ci, CLient& ciDst, char* szMsg = NULL) : m_pt(pt), m_client(ci), m_ciDst(ciDst) { memset(m_szMsg, 0, sizeof(m_szMsg)); if (szMsg != NULL) { strcpy(m_szMsg, szMsg); } } PackageType m_pt; CLient m_client; CLient m_ciDst; char m_szMsg[MASLEN]; };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211
|
#include <iostream> #include <list> #include <vector> using namespace std;
#include "..\common\Proto.h" #include "CLock.h"
struct CClientInfo { time_t m_nLastTime = 0; CLient m_client; };
vector<CClientInfo> g_lstCIs; CLock g_lckForLstCis;
void OnLine(SOCKET sock, CPackage& pkg); void OffLine(SOCKET sock, CPackage& pkg); void OnPublic(SOCKET sock, CPackage& pkg); void OnPrivate(SOCKET sock, CPackage& pkg); void OnHeartBeat(SOCKET sock, CPackage& pkg); DWORD WINAPI CheckHeartBeatThreadFunc(LPVOID lpParam);
int main() { SOCKET sockServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockServer == INVALID_SOCKET) { cout << "socket 失败" << endl; return 0; } sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(0x7788); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "bind 失败" << endl; return 0; }
HANDLE hThread = CreateThread( NULL, 0, CheckHeartBeatThreadFunc, (LPVOID)sockServer, 0, NULL ); if (hThread == NULL) { cout << "启动心跳线程 失败" << endl; closesocket(sockServer); return 0; } CloseHandle(hThread);
while (true) { CPackage pkg; sockaddr_in siFrom = {}; int nSizeofSi = sizeof(sockaddr_in); nRet = recvfrom(sockServer, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&siFrom, &nSizeofSi); if (nRet == 0 || nRet == SOCKET_ERROR) { cout << "recvfrom失败" << endl; } else { switch (pkg.m_pt) { case PT_ONLINE: OnLine(sockServer, pkg); break; case PT_OFFLINE: OffLine(sockServer, pkg); break; case PT_PUBLIC: OnPublic(sockServer, pkg); break; case PT_PRIVATE: OnPrivate(sockServer, pkg); break; case PT_HEARTBEAT: OnHeartBeat(sockServer, pkg); default: break; } } }
std::cout << "Hello World!\n"; }
void OnLine(SOCKET sock, CPackage& pkg) { printf("[Log]:%s %d %s online\r\n", inet_ntoa(pkg.m_client.m_si.sin_addr), pkg.m_client.m_si.sin_port, pkg.m_client.m_szName);
g_lckForLstCis.Lock(); for (auto& ci : g_lstCIs) { CPackage pkgSend(PT_ONLINE, ci.m_client); sendto(sock, (char*)&pkgSend, sizeof(pkgSend), 0, (sockaddr*)&pkg.m_client.m_si, sizeof(pkg.m_client.m_si)); } g_lstCIs.push_back(CClientInfo{ time(NULL),pkg.m_client }); CPackage pkgSend(PT_ONLINE, pkg.m_client); for (auto& ci : g_lstCIs) { sendto(sock, (char*)&pkgSend, sizeof(pkgSend), 0, (sockaddr*)&ci.m_client.m_si, sizeof(ci.m_client.m_si)); } g_lckForLstCis.Unlock(); }
void OnPublic(SOCKET sock, CPackage& pkg) { g_lckForLstCis.Lock(); for (auto& ci : g_lstCIs) { sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&ci.m_client.m_si, sizeof(ci.m_client.m_si)); } g_lckForLstCis.Unlock(); }
void OnPrivate(SOCKET sock, CPackage& pkg) { sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&pkg.m_ciDst.m_si, sizeof(pkg.m_ciDst.m_si)); }
void OnHeartBeat(SOCKET sock, CPackage& pkg) { g_lckForLstCis.Lock(); for (auto itr = g_lstCIs.begin(); itr != g_lstCIs.end(); ++itr) { if (itr->m_client == pkg.m_client) { printf("[Log]:%s %d %s heartbeat\r\n", inet_ntoa(pkg.m_client.m_si.sin_addr), pkg.m_client.m_si.sin_port, pkg.m_client.m_szName); itr->m_nLastTime = time(NULL); break; } } g_lckForLstCis.Unlock(); }
void OffLine(SOCKET sock, CPackage& pkg) { printf("[Log]:%s %d %s offline\r\n", inet_ntoa(pkg.m_client.m_si.sin_addr), pkg.m_client.m_si.sin_port, pkg.m_client.m_szName); g_lckForLstCis.Lock(); for (auto itr=g_lstCIs.begin();itr!=g_lstCIs.end();++itr) { if (itr->m_client == pkg.m_client) { g_lstCIs.erase(itr); break; } }
for (auto& ci : g_lstCIs) { sendto(sock, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&ci.m_client.m_si, sizeof(ci.m_client.m_si)); } g_lckForLstCis.Unlock(); }
DWORD WINAPI CheckHeartBeatThreadFunc(LPVOID lpParam) { SOCKET sockServer = (SOCKET)lpParam; const time_t nTmElappse = 5; while (true) { time_t tmCurrent = time(NULL); g_lckForLstCis.Lock(); for (auto itr = g_lstCIs.begin(); itr != g_lstCIs.end(); ++itr) { if (tmCurrent - itr->m_nLastTime >= nTmElappse) { CPackage pkg(PT_OFFLINE, itr->m_client); OffLine(sockServer, pkg); break; } } g_lckForLstCis.Unlock(); } return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50
|
#pragma once
#include "..\common\Proto.h"
class CChatClientDlg : public CDialogEx {
public: CChatClientDlg(CWnd* pParent = nullptr);
#ifdef AFX_DESIGN_TIME enum { IDD = IDD_CHATCLIENT_DIALOG }; #endif
protected: virtual void DoDataExchange(CDataExchange* pDX);
protected: HICON m_hIcon; SOCKET m_sockClient; sockaddr_in m_siServer; CLient m_client; BOOL m_bWorking = FALSE; static UINT WorkThreadFunc(LPVOID lpParam); void EnableOnOffUI(BOOL bOnLine);
virtual BOOL OnInitDialog(); afx_msg void OnSysCommand(UINT nID, LPARAM lParam); afx_msg void OnPaint(); afx_msg HCURSOR OnQueryDragIcon(); DECLARE_MESSAGE_MAP() public: CString m_strMsg; CString m_strNick; CString m_strShowMsg; CListBox m_lstClients; afx_msg void OnBnClickedOnline(); afx_msg void OnBnClickedOffline(); afx_msg void OnBnClickedPublic(); afx_msg void OnBnClickedPrivate(); afx_msg void OnTimer(UINT_PTR nIDEvent); };
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345 346 347
|
#include "pch.h" #include "framework.h" #include "ChatClient.h" #include "ChatClientDlg.h" #include "afxdialogex.h"
#ifdef _DEBUG #define new DEBUG_NEW #endif
class CAboutDlg : public CDialogEx { public: CAboutDlg();
#ifdef AFX_DESIGN_TIME enum { IDD = IDD_ABOUTBOX }; #endif
protected: virtual void DoDataExchange(CDataExchange* pDX);
protected: DECLARE_MESSAGE_MAP() };
CAboutDlg::CAboutDlg() : CDialogEx(IDD_ABOUTBOX) { }
void CAboutDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); }
BEGIN_MESSAGE_MAP(CAboutDlg, CDialogEx) END_MESSAGE_MAP()
CChatClientDlg::CChatClientDlg(CWnd* pParent ) : CDialogEx(IDD_CHATCLIENT_DIALOG, pParent) , m_strMsg(_T("")) , m_strNick(_T("")) , m_strShowMsg(_T("")) { m_hIcon = AfxGetApp()->LoadIcon(IDR_MAINFRAME); }
void CChatClientDlg::DoDataExchange(CDataExchange* pDX) { CDialogEx::DoDataExchange(pDX); DDX_Text(pDX, EDT_MSG, m_strMsg); DDX_Text(pDX, EDT_NICK, m_strNick); DDX_Text(pDX, EDT_SHOWMSG, m_strShowMsg); DDX_Control(pDX, LSTB_CLIENTS, m_lstClients); }
BEGIN_MESSAGE_MAP(CChatClientDlg, CDialogEx) ON_WM_SYSCOMMAND() ON_WM_PAINT() ON_WM_QUERYDRAGICON() ON_BN_CLICKED(BTN_ONLINE, &CChatClientDlg::OnBnClickedOnline) ON_BN_CLICKED(BTN_OFFLINE, &CChatClientDlg::OnBnClickedOffline) ON_BN_CLICKED(BTN_PUBLIC, &CChatClientDlg::OnBnClickedPublic) ON_BN_CLICKED(BTN_PRIVATE, &CChatClientDlg::OnBnClickedPrivate) ON_WM_TIMER() END_MESSAGE_MAP()
BOOL CChatClientDlg::OnInitDialog() { CDialogEx::OnInitDialog();
ASSERT((IDM_ABOUTBOX & 0xFFF0) == IDM_ABOUTBOX); ASSERT(IDM_ABOUTBOX < 0xF000);
CMenu* pSysMenu = GetSystemMenu(FALSE); if (pSysMenu != nullptr) { 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);
m_siServer.sin_family = AF_INET; m_siServer.sin_port = htons(0x7788); m_siServer.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); return TRUE; }
void CChatClientDlg::OnSysCommand(UINT nID, LPARAM lParam) { if ((nID & 0xFFF0) == IDM_ABOUTBOX) { CAboutDlg dlgAbout; dlgAbout.DoModal(); } else { CDialogEx::OnSysCommand(nID, lParam); } }
void CChatClientDlg::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 CChatClientDlg::OnQueryDragIcon() { return static_cast<HCURSOR>(m_hIcon); }
UINT CChatClientDlg::WorkThreadFunc(LPVOID lpParam) { CChatClientDlg* pDlg = (CChatClientDlg*)lpParam; while (pDlg->m_bWorking) { CPackage pkg; sockaddr_in siFrom = {}; int nSizeofSi = sizeof(sockaddr_in); int nIdx = recvfrom(pDlg->m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&siFrom, &nSizeofSi); if (nIdx == 0 || nIdx == SOCKET_ERROR) { continue; } else { switch (pkg.m_pt) { case PT_ONLINE: { int nIdx = pDlg->m_lstClients.AddString(pkg.m_client.m_szName); CLient* pCi = new CLient(pkg.m_client); pDlg->m_lstClients.SetItemData(nIdx, (DWORD_PTR)pCi); break; } case PT_OFFLINE: { int nCount = pDlg->m_lstClients.GetCount(); for (size_t i = 0; i < nCount; i++) { CLient* pCi = (CLient*)pDlg->m_lstClients.GetItemData(i); if (*pCi == pkg.m_client) { pDlg->m_lstClients.DeleteString(i); delete pCi; break; } } break; } case PT_PUBLIC: { CString strFmt; strFmt.Format("[群聊]%s 说: \t%s\r\n",pkg.m_client.m_szName, pkg.m_szMsg); pDlg->m_strShowMsg += strFmt; pDlg->GetDlgItem(EDT_SHOWMSG)->SetWindowText(pDlg->m_strShowMsg); break; } case PT_PRIVATE: { CString strFmt; strFmt.Format("[私聊]%s 说: \t%s\r\n", pkg.m_client.m_szName, pkg.m_szMsg); pDlg->m_strShowMsg += strFmt; pDlg->GetDlgItem(EDT_SHOWMSG)->SetWindowText(pDlg->m_strShowMsg); break; } } } } return 0; }
void CChatClientDlg::EnableOnOffUI(BOOL bOnline) { if (bOnline) { GetDlgItem(BTN_ONLINE)->EnableWindow(FALSE); GetDlgItem(BTN_OFFLINE)->EnableWindow(TRUE); GetDlgItem(BTN_PUBLIC)->EnableWindow(TRUE); GetDlgItem(BTN_PRIVATE)->EnableWindow(TRUE); } else { GetDlgItem(BTN_ONLINE)->EnableWindow(TRUE); GetDlgItem(BTN_OFFLINE)->EnableWindow(FALSE); GetDlgItem(BTN_PUBLIC)->EnableWindow(FALSE); GetDlgItem(BTN_PRIVATE)->EnableWindow(FALSE); } }
void CChatClientDlg::OnBnClickedOnline() { UpdateData(TRUE); if (m_strNick.IsEmpty()) { AfxMessageBox("昵称不能为空"); return; }
m_sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (m_sockClient == INVALID_SOCKET) { AfxMessageBox("socket 失败"); return; }
sockaddr_in si; si.sin_family = AF_INET; si.sin_port = 0; si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = bind(m_sockClient, (sockaddr*)&si, sizeof(si));
int nLen = sizeof(si); getsockname(m_sockClient, (sockaddr*)&si, &nLen);
m_client.m_si.sin_family = AF_INET; m_client.m_si.sin_port = si.sin_port; m_client.m_si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); strcpy(m_client.m_szName, m_strNick.GetBuffer());
CPackage pkg(PT_ONLINE, m_client); sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer));
m_bWorking = TRUE; AfxBeginThread(WorkThreadFunc, (LPVOID)this); SetTimer(1, 1000, NULL);
EnableOnOffUI(TRUE); }
void CChatClientDlg::OnBnClickedOffline() { CPackage pkg(PT_OFFLINE, m_client); sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer)); m_bWorking = FALSE; m_lstClients.ResetContent(); EnableOnOffUI(FALSE); }
void CChatClientDlg::OnBnClickedPublic() { UpdateData(TRUE); if (m_strMsg.IsEmpty()) { AfxMessageBox("消息不能为空"); return; }
CPackage pkg(PT_PUBLIC, m_client, m_strMsg.GetBuffer()); sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer)); }
void CChatClientDlg::OnBnClickedPrivate() { UpdateData(TRUE); if (m_strMsg.IsEmpty()) { AfxMessageBox("消息不能为空"); return; }
int nIdx = m_lstClients.GetCurSel(); if (nIdx == -1) { AfxMessageBox("未选择私聊对象"); return; } CLient* pCi = (CLient*)m_lstClients.GetItemData(nIdx);
CPackage pkg(PT_PRIVATE, m_client, *pCi, m_strMsg.GetBuffer()); sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer)); }
void CChatClientDlg::OnTimer(UINT_PTR nIDEvent) { CPackage pkg(PT_HEARTBEAT, m_client); sendto(m_sockClient, (char*)&pkg, sizeof(pkg), 0, (sockaddr*)&m_siServer, sizeof(m_siServer)); CDialogEx::OnTimer(nIDEvent); }
|
运行成功后,用户在线时会发送心跳包表示还该窗口还存活,下线后则停止发送心跳包
tcp
这节的代码是在第一节课的代码上修改得来的,框架与第一节课udp的代码有类似的地方
服务器:
- 创建socket
- 绑定端
- 监听
- 等待连接
- 收发数据
- 关闭socket
客户端:
- 创建socket
- 连接服务器
- 收发数据
- 关闭socket
listen
在网络编程中,服务器通常会创建一个套接字(Socket)并将其绑定到一个特定的IP地址和端口上。这个套接字用于监听客户端的连接请求。
当服务器套接字调用 listen() 函数后,它进入监听状态。这意味着服务器已准备好接受客户端的连接请求。
服务器可以监听一个特定的端口,等待客户端尝试建立连接。
参数
标识绑定的未连接的套接字的描述符。
挂起的连接队列的最大长度。 如果设置为 SOMAXCONN,则负责套接字 的基础 服务提供商会将积压工作设置为最大合理值。 如果设置为 SOMAXCONN_HINT (N) (其中 N 为数字) ,则积压工作值为 N,调整为在 200、65535) (范围内。 请注意, SOMAXCONN_HINT 可用于将积压工作设置为比 SOMAXCONN 更大的值。
SOMAXCONN_HINT 仅受 Microsoft TCP/IP 服务提供商支持。 没有用于获取实际积压工作值的标准预配。
返回值
如果未发生错误, 则侦听 返回零。 否则,将返回 值 SOCKET_ERROR ,并且可以通过调用WSAGetLastError来检索特定的错误代码。
accept
accept是一个常用于网络编程的函数,它的作用是使服务器端接受客户端的连接请求。当建立连接后,服务器端就可以和客户端进行收发数据。
语法
1 2 3 4 5
| SOCKET WSAAPI accept( [in] SOCKET s, [out] sockaddr *addr, [in, out] int *addrlen );
|
参数
一个描述符,用于标识已使用 侦 听函数置于侦听状态的套接字。 连接实际上是使用 accept 返回的套接字建立的。
指向接收连接实体地址的缓冲区的可选指针,该地址称为通信层。 addr 参数的确切格式由创建sockaddr 结构中的套接字时建立的地址系列确定。
指向包含 addr 参数指向的结构长度的整数的可选指针。
返回值
如果未发生错误, 则 accept 将返回 类型为 SOCKET 的值,该值是新套接字的描述符。 此返回值是建立实际连接的套接字的句柄。
否则,将返回 值 INVALID_SOCKET ,并且可以通过调用WSAGetLastError来检索特定的错误代码。
addrlen 引用的整数最初包含 addr 指向的空间量。返回时,它将包含返回的地址的实际长度(以字节为单位)。
recv
recv 函数从连接的套接字或绑定的无连接套接字接收数据。
语法
1 2 3 4 5 6
| int recv( [in] SOCKET s, [out] char *buf, [in] int len, [in] int flags );
|
参数
标识连接的套接字的描述符。
指向用于接收传入数据的缓冲区的指针。
buf 参数指向的缓冲区的长度(以字节为单位)。
影响此函数行为的一组标志。 请参阅下面的备注。 有关此参数的可能值的详细信息,请参阅“备注”部分。
返回值
如果未发生错误, recv 将返回收到的字节数, buf 参数指向的缓冲区将包含接收的此数据。 如果连接已正常关闭,则返回值为零。
否则,将返回值 SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。
send
send 函数在连接的套接字上发送数据。
不论是客户还是服务器应用程序都用send函数来向TCP连接的另一端发送数据。客户程序一般用send函数向服务器发送请求,而服务器则通常用send函数来向客户程序发送应答。
语法
1 2 3 4 5 6
| int WSAAPI send( [in] SOCKET s, [in] const char *buf, [in] int len, [in] int flags );
|
参数
标识已连接套接字的描述符。
指向包含要传输的数据的缓冲区的指针。
buf 参数指向的缓冲区中数据的长度(以字节为单位)。
一组指定调用方式的标志。 此参数是使用以下任一值的按位 OR 运算符构造的。
展开表
值 |
含义 |
MSG_DONTROUTE |
:指定数据不应受到路由的约束。 Windows 套接字服务提供程序可以选择忽略此标志。 |
MSG_OOB |
仅) (流式套接字(例如SOCK_STREAM)发送 OOB 数据。 |
返回值
如果未发生错误, send 将返回发送的总字节数,该字节数可能小于 在 len 参数中请求发送的字节数。 否则,将返回值 SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。
connect
connect 函数与指定的套接字建立连接。
语法
1 2 3 4 5
| int WSAAPI connect( [in] SOCKET s, [in] const sockaddr *name, [in] int namelen );
|
参数
标识未连接的套接字的描述符。
指向应建立连接的sockaddr结构的指针。
name 参数指向的sockaddr 结构的长度(以字节为单位)。
返回值
如果未发生错误, 则连接 返回零。 否则,它将返回SOCKET_ERROR,并且可以通过调用WSAGetLastError来检索特定的错误代码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| SOCKET sockServer = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP);
if (sockServer == INVALID_SOCKET) { cout << "socket 失败" << endl; return 0; } sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(0x7788); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "bind 失败" << endl; return 0; }
nRet = listen(sockServer, SOMAXCONN); if (nRet == SOCKET_ERROR) { cout << "listen 失败" << endl; return 0; }
sockaddr_in siClient; int nLen = sizeof(siClient); SOCKET sock = accept(sockServer, (sockaddr*)&siClient, &nLen); if (sock == INVALID_SOCKET) { cout << "accept 失败" << endl; } while (true) { char szBuff[MAXBYTE] = {}; nRet = recv(sock, szBuff, sizeof(szBuff), 0); if (nRet == 0 || nRet == SOCKET_ERROR) { cout << "recv 失败" << endl; } else { printf("from %s %d recv: %s\r\n", inet_ntoa(siClient.sin_addr), ntohs(siClient.sin_port), szBuff); }
char szSend[] = { "recv ok!" }; nRet = send(sock, szSend, sizeof(szSend), 0); if (nRet == SOCKET_ERROR) { cout << "send失败" << endl; } }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41
| SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (sockClient == INVALID_SOCKET) { cout << "socket 失败" << endl; return 0; } sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(0x7788); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = connect(sockClient, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "connect 失败" << endl; return 0; } while (true) { char szSend[MAXBYTE] = { }; cin >> szSend; int nRet = send(sockClient, szSend, sizeof(szSend), 0); if (nRet == SOCKET_ERROR) { cout << "sendto 失败" << endl; }
char szBuff[MAXBYTE]; sockaddr_in siFrom = {}; int nSizeofSi = sizeof(sockaddr_in); nRet = recv(sockClient, szBuff, sizeof(szBuff), 0); if (nRet == 0 || nRet == SOCKET_ERROR) { cout << "recvfrom失败" << endl; } else { printf("from %s %d recv: %s \r\n", inet_ntoa(siFrom.sin_addr), ntohs(siFrom.sin_port), szBuff); } }
|
成功运行后从客户端向服务器发消息可以被接收到,同时服务器向客户端回复也可以被接受到
getpeername
参数
标识连接的套接字的描述符。
接收对等方地址的 SOCKADDR 结构。
指向 name 参数的大小(以字节为单位)的指针。
返回值
如果未发生错误, getpeername 将返回零。 否则,将返回值 SOCKET_ERROR,并且可以通过调用 WSAGetLastError 检索特定的错误代码。
使用场景
1、tcp服务端通过accept返回的套接字,通过 getpeername 可以获取到客户端的IP地址、端口;
2、其他需要获取对端IP地址或端口的情况。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <iostream> using namespace std; #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <Winsock2.h> #pragma comment(lib,"Ws2_32.lib") DWORD WINAPI WorkThreadFunc(LPVOID lpParam); int main() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return 0; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return 0; }
SOCKET sockServer = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP); if (sockServer == INVALID_SOCKET) { cout << "socket 失败" << endl; return 0; } sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(0x7788); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "bind 失败" << endl; return 0; }
nRet = listen(sockServer, 1); if (nRet == SOCKET_ERROR) { cout << "listen 失败" << endl; return 0; }
while (true) { sockaddr_in siClient; int nLen = sizeof(siClient); SOCKET sock = accept(sockServer, (sockaddr*)&siClient, &nLen); if (sock == INVALID_SOCKET) { cout << "accept 失败" << endl; }
HANDLE hThread = CreateThread( NULL, 0, WorkThreadFunc, (LPVOID)sock, 0, NULL ); CloseHandle(hThread); }
cout << "Hello World!\n"; return 0; }
DWORD WINAPI WorkThreadFunc(LPVOID lpParam) { SOCKET sock = (SOCKET)lpParam; while (true) { char szBuff[MAXBYTE] = {}; int nRet = recv(sock, szBuff, sizeof(szBuff), 0); if (nRet == 0 || nRet == SOCKET_ERROR) { cout << "recv 失败" << endl; } else { sockaddr_in siClient; int nLen = sizeof(siClient); getpeername(sock, (sockaddr*)&siClient, &nLen); printf("from %s %d recv: %s\r\n", inet_ntoa(siClient.sin_addr), ntohs(siClient.sin_port), szBuff); }
char szSend[] = { "recv ok!" }; nRet = send(sock, szSend, sizeof(szSend), 0); if (nRet == SOCKET_ERROR) { cout << "send失败" << endl; } } return 0; }
|
成功运行后服务器可以接受来自不同客户端的信息
粘包
TCP协议是⾯向流(字节流)的协议,UDP是⾯向消息(报文)的协议。UDP段都是⼀条消息,应⽤程序必须以消息为单位提取数据,不能⼀次提取任意字节的数据
UDP具有保护消息边界,在每个UDP包中就有了消息头(消息来源地址,端⼝等信息),这样对于接收端来说就容易进⾏区分处理了。传输协议把数据当作⼀条独⽴的消息在⽹上传输,接收端只能接收独⽴的消息。接收端⼀次只能接收发送端发出的⼀个数据包,如果⼀次接受数据的⼤⼩⼩于发送端⼀次发送的数据⼤⼩,就会丢失⼀部分数据,即使丢失,接受端也不会分两次去接收。
udp-数据报
tcp-数据流-粘包
粘包:多个数据包被连续存储于连续的缓存中,在对数据包进行读取时由于无法确定发生方的发送边界,而采用某一估测值大小来进行数据读出,若双方的size不一致时就会使指发送方发送的若干包数据到接收方接收时粘成一包,从接收缓冲区看,后一包数据的头紧接着前一包数据的尾。
udp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <iostream> using namespace std; #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <Winsock2.h> #pragma comment(lib,"Ws2_32.lib")
int main() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return 0; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return 0; }
SOCKET sockServer = socket(AF_INET, SOCK_DGRAM , IPPROTO_UDP); if (sockServer == INVALID_SOCKET) { cout << "socket 失败" << endl; return 0; } sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(0x7788); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "bind 失败" << endl; return 0; }
char szBuff[8]; sockaddr_in siFrom = {}; int nSizeofSi = sizeof(sockaddr_in); nRet = recvfrom(sockServer, szBuff, sizeof(szBuff), 0, (sockaddr*)&siFrom, &nSizeofSi); nRet = recvfrom(sockServer, szBuff, sizeof(szBuff), 0, (sockaddr*)&siFrom, &nSizeofSi); cout << "Hello World!\n"; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <iostream> using namespace std; #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <Winsock2.h> #pragma comment(lib,"Ws2_32.lib")
int main() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return 0; }
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return 0; }
SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP); if (sockClient == INVALID_SOCKET) { cout << "socket 失败" << endl; return 0; } sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(0x7788); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
char szSend0[4] = { '\x11','\x22','\x33', '\x44' }; int nRet = sendto(sockClient, szSend0, sizeof(szSend0), 0, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "sendto 失败" << endl; }
char szSend[4] = { '\x55','\x66','\x77', '\x88' }; nRet = sendto(sockClient, szSend, sizeof(szSend), 0, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "sendto 失败" << endl; }
cout << "Hello World!\n"; }
|
下断点后运行,第一次接收到了客户端传来的4个字节
第二次在同一个地址接收到了客户端传来的剩下的4个字节
tcp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <iostream> using namespace std; #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <Winsock2.h> #pragma comment(lib,"Ws2_32.lib") DWORD WINAPI WorkThreadFunc(LPVOID lpParam); int main() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return 0; } if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return 0; }
SOCKET sockServer = socket(AF_INET, SOCK_STREAM , IPPROTO_TCP); if (sockServer == INVALID_SOCKET) { cout << "socket 失败" << endl; return 0; } sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(0x7788); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = bind(sockServer, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "bind 失败" << endl; return 0; }
nRet = listen(sockServer, 1); if (nRet == SOCKET_ERROR) { cout << "listen 失败" << endl; return 0; }
sockaddr_in siClient; int nLen = sizeof(siClient); SOCKET sock = accept(sockServer, (sockaddr*)&siClient, &nLen); if (sock == INVALID_SOCKET) { cout << "accept 失败" << endl; }
char szBuff[8] = {}; nRet = recv(sock, szBuff, sizeof(szBuff), 0);
cout << "Hello World!\n"; return 0; }
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52
|
#define _WINSOCK_DEPRECATED_NO_WARNINGS #include <iostream> using namespace std; #define WIN32_LEAN_AND_MEAN
#include <windows.h> #include <Winsock2.h> #pragma comment(lib,"Ws2_32.lib")
int main() { WORD wVersionRequested; WSADATA wsaData; int err; wVersionRequested = MAKEWORD(2, 2); err = WSAStartup(wVersionRequested, &wsaData); if (err != 0) { return 0; }
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) { WSACleanup(); return 0; }
SOCKET sockClient = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (sockClient == INVALID_SOCKET) { cout << "socket 失败" << endl; return 0; } sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(0x7788); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); int nRet = connect(sockClient, (sockaddr*)&si, sizeof(si)); if (nRet == SOCKET_ERROR) { cout << "connect 失败" << endl; return 0; } char szSend0[4] = { 1,2,3,4 }; nRet = send(sockClient, szSend0, sizeof(szSend0), 0); char szSend[4] = { 5,6,7,8 }; nRet = send(sockClient, szSend, sizeof(szSend), 0); cout << "Hello World!\n"; }
|
下断点后运行,可以一次性收到全部的8个字节
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| sockaddr_in siClient; int nLen = sizeof(siClient); SOCKET sock = accept(sockServer, (sockaddr*)&siClient, &nLen); if (sock == INVALID_SOCKET) { cout << "accept 失败" << endl; }
while (true) { int nSize = 0; nRet = recv(sock, (char*)&nSize, sizeof(nSize), 0); char* szBuff = new char[nSize]; nRet = recv(sock, szBuff, nSize, 0); }
cout << "Hello World!\n"; return 0;
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| if (nRet == SOCKET_ERROR) { cout << "connect 失败" << endl; return 0; } int nSize = 0x1000; char* szSend = new char[nSize]; memset(szSend, 0x66, nSize); nRet = send(sockClient, (char*)&nSize, sizeof(nSize), 0); nRet = send(sockClient, szSend, (nSize), 0); nSize = 100; szSend = new char[nSize]; memset(szSend, 0x55, nSize); nRet = send(sockClient, (char*)&nSize, sizeof(nSize), 0); nRet = send(sockClient, szSend, (nSize), 0); cout << "Hello World!\n";
|
第一次接受到来自客户端发送的信息
第二次接受到来自客户端发送的信息