特点:面向连接的、可靠的、基于字节流的传输层协议。
C/S 即 客户端/服务器 模型。
socket:套节字
网络头文件,网络库:
#include // 网络头文件,Windows socket第二版
#pragma comment(lib, "ws2_32.lib") // 加载网络库,Windows socket第二版,32位; // 不管是32位还是64位环境,是第一版还是第二版,都用ws2_32.lib
//函数原型
WSAStartup(
WORD wVersionRequested,
LPWSADATA lpWSAData
);
功能:打开/启动网络库启动了这个库,库里的函数才可以使用。
参数一:
使用的库的版本,类型是WORD(由 unsigned short 转定义而来)
定义一个WORD: WORD verSion = MAKEWORD(2, 2); 其中MAKEWORD(主版本,副版本)
参数二:
类型为LPWSADATA lpWSAData,系统获取网络配置信息,然后返回给此参数;当参数前有LP前缀时,应传对应类型变量地址
定义一个WSADATA结构体,然后传入它的地址 WSADATA dat;
返回值:
返回值为int类型,如果打开网络库成功,返回0,失败返回各种错误码
WORD verSion = MAKEWORD(2, 2);
WSADATA dat;
int nRes = WSAStartup(verSion, &dat);
//最后程序结束前一定要关闭网络库:WSACleanup();
if (2 != HIBYTE(dat.wVersion) || 2 != LOBYTE(dat.wVersion))
{
// 版本不对
WSACleanup(); //关闭库
return 0;
}
socket:将复杂的协议体系,执行流程封装成的一个接口,将复杂的协议与编程分开,直接操作socket,方便了网络程序开发;
其本质是一种数据类型,就是一个整数,表示着当前应用程序,协议等信息
//函数原型
socket(
int af,
int type,
int protocol
);
参数一:地址类型
是IPv4地址:AF_INET, IPv6地址:AF_INET6(两种常用的)
参数二:套节字类型
常用的有:
SOCK_STREAM,一种支持数据报的套节字类型,可靠、双向、基于字节流,使用的协议为TCP协议
SOCK_DGRAM, 支持数据报的套节字类型,使用的协议为UDP协议
参数三:协议类型
常用的有:
IPPROTO_TCP:TCP协议;IPPROTO_UDP:UDP协议
返回值:
成功返回可用的socket,socket不用时,一定要销毁:closesocket()
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
//函数原型
int
bind(
SOCKET s,
const struct sockaddr *name,
int namelen
);
作用:
给创建的socket绑定地址与端口号
参数一:自己创建的socket
参数二:一个sockaddr结构体的指针,存储着地址类型、IP地址、端口号信息
使用方法:为了使用方便,使用sockaddr_in类型
sockaddr_in sin = {};
sin.sin_family = AF_INET; 地址类型
sin.sin_port = htons(4567); 端口号
sin.sin_addr.S_un.S_addr = inet_addr(“192.168.1.4”); IP地址使用的端口号必须是本机没有占用的端口号,如不确定,可使用如下方法查看:
打开cmd 输入 netstat -ano 查看被使用的所有端口
输入netstat -ano|findstr “12345” 查看是否被占用
最后将sockaddr_in类型强转成sockaddr* 即 (sockaddr*)&sin
参数三:参数二的类型大小,常用sizeof()
返回值:成功返回0,失败返回SOCKET_ERROR,具体错误码通过 int WSAGetLastError(void) 获得
sockaddr_in sin = {};
sin.sin_family = AF_INET;
sin.sin_port = htons(4567);
sin.sin_addr.S_un.S_addr = inet_addr("192.168.1.4");
//2 bind 绑定用于接受客户端链接的网络端口
if (SOCKET_ERROR == bind(sock, (sockaddr*)&sin, sizeof(sin)))
{
cout << "绑定网络端口失败" << endl;
}
else {
cout << "绑定网络端口成功" << endl;
}
int
listen(
SOCKET s,
int backlog
);
作用:
将套节字置入正在传入侦听连接的状态
参数一:服务器端socket
参数二:挂起连接队列的最大长度
比如有100个用户连接请求,系统一次只能处理20个,剩下的80个就会进入此队列,一般填写SOMAXCONN,让系统自动选择
返回值:成功返回0,失败返回SOCKET_ERROR
if (SOCKET_ERROR == listen(sock, 5))
{
cout << "监听网络端口失败" << endl;
}
else {
cout << "监听网络端口成功" << endl;
}
SOCKET
accept(
SOCKET s,
struct sockaddr * addr,
int * addrlen
);
作用:
listen监听客户端的连接请求,accept将客户端的信息绑定到一个socket上,然后通过返回值返回
参数一:自己创建的socket
参数二:客户端的地址端口信息结构体,和bind函数的第二个参数一样
参数三:参数二的大小
PS:参数二,三都可以设置为NULL,不直接得到客户端的地址,端口等信息
返回值:成功返回客户端socket,失败返回INVALID_SOCKET
sockaddr_in clientAddr = {};
int nAddrLen = sizeof(sockaddr_in);
SOCKET cSock = INVALID_SOCKET;
cSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
if (INVALID_SOCKET == cSock)
{
cout << "错误,接受到无效客户端SOCKET..." << endl;
}
接收消息:得到指定客户端(参数一)发来的消息,原理为调用recv,通过socket找到协议的缓冲区,并把数据拷贝进参数二(用户自己设置的缓冲区)
int
recv(
SOCKET s,
char * buf,
int len,
int flags
); // recv接收消息过程是阻塞的
参数一:客户端的socket
参数二:接收消息的存储空间(协议规定网络最大传输单元为1500字节)
参数三:想要读取的字节数,一般为参数二字节数减一
参数四:一般为0:表示将协议缓冲区中的数据拷贝到我们的Buf中,然后协议的缓冲区就删除这一部分已拷贝的数据(读出来就删除)
MSG_PEEK:数据被复制到Buf中,但不会从协议的缓冲区中删除
MSG_OOB:传输一段数据,在外带一个额外的特殊数据
MSG_WAITALL:知道协议的缓冲区中数据的字节数满足参数三请求的字节数,才开始读取
返回值:读出来的字节数大小,若客户端下线,返回0,若执行失败,返回SOCKET_ERROR
int nlen = recv(cSock, recvBuf, 128, 0);
if (nlen == 0)
{
cout << "客户端退出..." << endl;
}
发送消息:向目标发送数据,send函数将我们的数据复制进系统的协议发送缓冲区中,然后系统发送出去,最大传输单元1500字节
int
send(
SOCKET s,
const char * buf,
int len,
int flags
);
参数一:目标的socket
参数二:给对方发送的数据
参数三:字节个数
惨数四:一般为0,表示正常的发送
MSG_OOB:意义同recv
MSG_DONTROUTE:指定数据应不受路由限制
返回值:成功则返回写入的字节数,失败返回SOCKET_ERROR
send(cSock, msg.c_str(), msg.length(), 0);
服务端代码:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")
using namespace std;
int main()
{
// 启动Windows socket2 环境
//初始化套节字
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
// 校验版本
if (2 != HIBYTE(dat.wVersion) || 2 != LOBYTE(dat.wVersion))
{
// 版本不对
WSACleanup();
return 0;
}
//1 建立一个socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sin = {};
sin.sin_family = AF_INET;
sin.sin_port = htons(4567);
sin.sin_addr.S_un.S_addr = inet_addr("192.168.1.4");
//2 bind 绑定用于接受客户端链接的网络端口
if (SOCKET_ERROR == bind(sock, (sockaddr*)&sin, sizeof(sin)))
{
cout << "绑定网络端口失败" << endl;
}
else {
cout << "绑定网络端口成功" << endl;
}
//3. listen监听网络端口
if (SOCKET_ERROR == listen(sock, 5))
{
cout << "监听网络端口失败" << endl;
}
else {
cout << "监听网络端口成功" << endl;
}
//4 accept 等待客户端链接
sockaddr_in clientAddr = {};
int nAddrLen = sizeof(sockaddr_in);
SOCKET cSock = INVALID_SOCKET;
string msg = "Hello, I'm Server";
cSock = accept(sock, (sockaddr*)&clientAddr, &nAddrLen);
if (INVALID_SOCKET == cSock)
{
cout << "错误,接受到无效客户端SOCKET..." << endl;
}
cout << "新客户端加入:" << inet_ntoa(clientAddr.sin_addr) << endl;
//5 send 向客户端发送数据
char recvBuf[128] = {};
while (true)
{
int nlen = recv(cSock, recvBuf, 128, 0);
if (nlen <= 0)
{
cout << "客户端退出..." << endl;
break;
}
send(cSock, msg.c_str(), msg.length(), 0);
}
//6 关闭套接字closesocket
closesocket(sock);
//清楚Windows socket环境
WSACleanup();
}
打开网络库
校验版本
创建socket
连接到服务器
int connect( SOCKET s, const struct sockaddr * name, int namelen );
作用:连接服务器并将服务器信息与服务器socket绑定到一起
参数一:服务器socket
参数二:服务器IP地址和端口号的结构体
参数三:参数二结构体大小
返回值:成功返回0,失败返回SOCKET_ERROR
与服务器收发消息
客户端代码:
#define WIN32_LEAN_AND_MEAN
#include
#include
#include
#include
#pragma comment(lib, "ws2_32.lib")
using namespace std;
int main()
{
// 启动Windows socket2 环境
//初始化套节字
WORD ver = MAKEWORD(2, 2);
WSADATA dat;
WSAStartup(ver, &dat);
//1 创建一个socket
SOCKET sock = socket(AF_INET, SOCK_STREAM, 0);
if (INVALID_SOCKET == sock)
{
cout << "错误,建立socket失败" << endl;
}
else
{
cout << "建立socket成功.." << endl;
}
//2 连接服务器
sockaddr_in sin = {};
sin.sin_family = AF_INET;
sin.sin_port = htons(4567);
sin.sin_addr.S_un.S_addr = inet_addr("192.168.1.4");
if (SOCKET_ERROR == connect(sock, (sockaddr*)&sin, sizeof(sockaddr_in)))
{
cout << "建立连接失败..." << endl;
}
else {
cout << "建立连接成功..." << endl;
}
//3 接收服务器信息
char recvBuf[256] = {};
int nlen = recv(sock, recvBuf, 256, 0);
if (nlen > 0)
{
cout << "接受到数据:" << recvBuf << endl;
}
//4 关闭套节字closesocket
closesocket(sock);
getchar();
//清楚Windows socket环境
WSACleanup();
}