Windows Sockets 规范是 Windows 平台下定义的可以兼容二进制数据传输的网络编程接口,是基于伯克利加利福尼亚大学的 BSD UNIX Sockets 的实现。
Socket 套接字分为两种类型:数据报 socket 和数据流 socket。
数据报套接字,即无连接套接字,不保证传输的可靠性,比如视频通信、时钟同步、与服务器的“心跳”同步等应用场景。
数据流套接字是基于显示连接的套接字,提供没有记录边界的双向字节数据流,具有可靠的发送顺序,适用大量数据且可靠性要求高的传输。
CSocket实现数据流网络通信
步骤:
1、创建服务器通信程序:
在高级功能中勾选Widows套接字,否则需要手动包含套接字的头文件
#include <afxsock.h>
并且在应用程序类的InitInsatnce函数中初始化套接字:
BOOL CSockServerApp::InitInstance()
{
.......
CWinApp::InitInstance();
if (!AfxSocketInit()) //手动添加初始化套接字
{
return FALSE;
}
......
}
2、与服务器一样创建客户端程序(略),其中可以把两个项目都方在同一个解决方案里,如:
3、分别给服务器和客户端添加CSocket的派生类
利用类向导,添加虚函数 OnReceive 用于测试服务器发送数据给客户端
自动生成的代码 头文件:
#pragma once
// CClientSocket 命令目标
class CClientSocket : public CSocket
{
public:
CClientSocket();
virtual ~CClientSocket();
virtual void OnReceive(int nErrorCode);
};
源文件:
// ClientSocket.cpp : 实现文件
//
#include "stdafx.h"
#include "TestSockClient.h"
#include "ClientSocket.h"
// CClientSocket
CClientSocket::CClientSocket()
{
}
CClientSocket::~CClientSocket()
{
}
// CClientSocket 成员函数
void CClientSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CSocket::OnReceive(nErrorCode);
}
同样的方法创建服务器的套接字,不同之处,比客户端多添加一个OnAccept的虚函数:
头文件:
#pragma once
// CServerSocket 命令目标
class CServerSocket : public CSocket
{
public:
CServerSocket();
virtual ~CServerSocket();
virtual void OnReceive(int nErrorCode);
virtual void OnAccept(int nErrorCode);
};
源文件:
// ServerSocket.cpp : 实现文件
//
#include "stdafx.h"
#include "TestSockServer.h"
#include "ServerSocket.h"
// CServerSocket
CServerSocket::CServerSocket()
{
}
CServerSocket::~CServerSocket()
{
}
// CServerSocket 成员函数
void CServerSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CSocket::OnReceive(nErrorCode);
}
void CServerSocket::OnAccept(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CSocket::OnAccept(nErrorCode);
}
4、网络通信的流程其实很简单:
服务端:创建套接字-》侦听 侦听套接字
客户端:创建套接字-》连接服务器-》服务端侦听套接字获取连接事件(OnAccept)
服务端处理连接事件:创建一个 通信套接字 与客户端进行网络通信 ,也就是说服务端有一个侦听套接字和可能多个通信套接字。
网络通信:客户端发送消息(Send),服务端中负责与客户端通信的套接字(OnReceive),通过Receive函数获取消息内容,完成通信。
创建套接字:
CServerSocket s; //应该定义在主窗口类中
//创建套接字和侦听可以放在对话框的 OnInitDialog 函数中
s.Create(8000); // 8000是服务器的端口 第二个参数默认为流式套接字,第三个参数为IP,默认本机
s.Listen(5); // 侦听队列,超过等待连接数据会阻塞
客户端套接字完成创建即可,注意要绑定在服务器端口(8000)不同的端口上。
CClientSocket c; //应该定义在主窗口类中
//创建套接字可以放在对话框的 OnInitDialog 函数中
c.Create(8001);
//连接和发送消息的功能可以放于按钮点击事件处理中
c.Connect("127.0.0.1",8000); // 连接到服务器 127.0.0.1 本机IP
char buff[256]="hello"; //测试数据
c.Send(buff,strlen(buff)); // 发送数据到服务器
服务端套接字 OnAccept 函数,侦听和与客户端通信的套接字可以使用同一个类
void CServerSocket::OnAccept(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
CServerSocket *p = new CServerSocket();
this->Accept(*p);
CSocket::OnAccept(nErrorCode);
}
new 一个通信套接字(不能为对象,局部对象会被销毁),与客户端进行连接。这是一种最简单的测试方法,有点类似“内存泄漏”----------new 在堆里,而定义的指针 p 是一个局部变量!
“内存泄漏” : 服务端收到客户端信息时, OnReceive 会被调用,堆中的通信套接字又可以获取到了。
void CServerSocket::OnReceive(int nErrorCode)
{
// TODO: 在此添加专用代码和/或调用基类
char buff[256];
int size = this->Receive(buff,256);
// this 为服务端的通信套接字,又“冒”出来了
//size为实际接受的数据
buff[size]='\0'; //字符串结束符
::AfxMessageBox(buff);
CSocket::OnReceive(nErrorCode);
}
5、改进与提升方向
- 使用多线程(窗口线程)与通信套接字绑定来实现网络通信,尤其是服务端。
- CSocketFile 的Write 、Read 来代替 套接字 Send 和 Receive ,实现大数据的通信
- “安全”数据报方式实现客户端的互连(不通过服务器转发)