socket有关函数介绍
WSADATA data;
/// <summary>
/// 激活socket库
/// 参数一:2个字节的WORD(ushort),低字节表示主版本号,高字节表示所需WinSock实现的最低版本
/// 参数二:函数填入被激活的socket库信息,如实现的版本
/// </summary>
/// <returns>0:正常 其他:错误原因</returns>
int err = WSAStartup(MAKEWORD(2, 2), &data);
if (err != 0)
{
std::cout << "初始化失败";
return 0;
}
/// <summary>
/// 创建一个socket
/// 参数一:协议族,指socket使用的网络协议 AF_INET(IPv4) AF_INET6(IPv6)
/// 参数二:数据传输形式 SOCK_STREAM(有序可靠的分段流) SOCK_DGRAM(离散报文)
/// 参数三:传输协议 IPPROTO_TCP(tcp) IPPROTO_UDP(udp) 0(根据数据形式(参数二)指定合适协议)
/// </summary>
SOCKET sock = socket(AF_INET, SOCK_DGRAM, IPPROTO_UDP);
/// <summary>
/// 返回此线程中最近一次的错误代码
/// socket函数如果出现错误就会返回-1,windows中使用宏SOCKET_ERROR代替
/// 但是一个-1不能获得准确的错误信息,所以使用此函数
/// 此函数只获得最近的错误,如果发生一个错误后可能会连环造成其他错误发生
/// 就无法获得源头错误,因此应当在socket函数返回-1时就立即调用此函数
/// </summary>
WSAGetLastError();
/// <summary>
/// 停止传输消息,用于tcp连接的断开前使用
/// 参数二:SD_SEND(停止发送消息,并会发送出一个FIN) SD_RECEIVE(停止接受消息) SD_BOTH(都停止)
/// </summary>
shutdown(sock, SD_BOTH);
//关闭socket
closesocket(sock);
//关闭socket库,释放资源(会结束所有socket的操作,确保socket都关闭或未使用)
//WSAStartup()激活时采用的引用计数,即可多次激活,所以关闭的次数必须与激活次数一致
WSACleanup();
WSAGetLastError获得的都是异常的编号,可以在这篇博客 查看编号对应的错误详细信息
socket地址有关函数
//socket地址
sockaddr add;
add.sa_family;//地址类型,应该与socket创建时的网络协议一致(ipv4,ipv6等)
add.sa_data;//存储真正地址
//地址初始化专业数据类型
//socketAPI建立时没有类和多态继承,所以在socket函数需要
//地址数据时必须手动把这个数据转为sockaddr
sockaddr_in addr_in;
addr_in.sin_family;//与sockaddr一样
addr_in.sin_port;//16位端口号
addr_in.sin_addr;//地址
addr_in.sin_zero;//不使用,用于填补大小与sockaddr大小一致,所以全设为0
//主机字节序与网络字节序之间转化,因为tcp/ip协议族可能和主机在多字节数的字节序上标准不同(即网络中的大端小端问题)
//h(ost)ton(net)s(hort)/l(ong)
htons(88);//主机->网络(uint16)
htonl(66);//主机->网络(uint32)
ntohs(88);//网络->主机(uint16)
ntohl(66);//网络->主机(uint32)
//绑定socket
//int bind(SOCKET socket,const sockaddr*add,int add_len);
//成功返回0,错误返回-1
//example:初始化一个sockaddr_in
sockaddr_in myAdd;
memset(myAdd.sin_zero, 0, sizeof(myAdd.sin_zero));
myAdd.sin_family = AF_INET;
myAdd.sin_port = htons(88);
myAdd.sin_addr.S_un.S_un_b.s_b1 = 192;
myAdd.sin_addr.S_un.S_un_b.s_b2 = 168;
myAdd.sin_addr.S_un.S_un_b.s_b3 = 1;
myAdd.sin_addr.S_un.S_un_b.s_b4 = 9;
//直接把字符串转化为地址
inet_pton(AF_INET, "192.168.1.9", &myAdd.sin_addr);
Example:创建一个简单的服务端TCPsocket
#include<WinSock2.h>//socketx相关头文件(注意是2)
#include<WS2tcpip.h>//地址转换头文件
#pragma comment(lib, "ws2_32.lib")//使得WinSock2可以使用
#include<memory>
#include<cstring>
int main()
{
/***********************1.初始化环境***********************/
WSADATA data;
int err = WSAStartup(MAKEWORD(2, 2), &data);
if (err != 0){
std::cout << "初始化失败"<<WSAGetLastError()<<std::endl;
return 1;
}
/******************2.创建socket**************************/
SOCKET sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if(sock==INVALID_SOCKET){
std::cout<<"socket创建失败:"<<WSAGetLastError()<<std::endl;
return 1;
}
/*3.*********绑定监听socket的IP与端口号*******************/
sockaddr_in addr;
memset(addr,0,sizeof(addr));
addr.sin_port=htons(8866);
addr.sin_family=AF_INET;
//如果这个程序是要拿去服务器上运行的,那这里的ip就不能用127,应该使用对应服务器上真实的ip
//否则客户端会连接不上的
inet_pton(AF_INET,"127.0.0.1",&addr.sin_addr);
//把sockaddr_in类型强制转为sockaddr 注意sizeof的是sockaddr_in的大小而不是目标sockaddr
err=bind(sock,reinterpret_cast<sockaddr*>(&addr),sizeof(addr));
if(err==-1){
std::cout<<"bindsocket异常:"<<WSAGetLastError()<<std::endl;
}
/****************3.设置监听数目限制**********************************/
//socket开始监听消息,这样才能收到客户端发来的信息 参数二:最多可可以有多少个客户同时连接
if(listen(sock,100)==-1){
std::cout<<"socket启动监听出现异常:"<<WSAGetLastError()<<std::endl;
return 1;
}
/****************************4.接收连接的客户端**************************/
sockaddr_in clientAddr;
memset(clientAddr,0,sizeof(clientAddr));
int addrLen=sizeof(clientAddr);//存放客户端地址缓存区的大小,必须要赋予一初始值,否则传入函数运行时会报错
SOCKET client=accept(sock,reinterpret_cast<sockaddr*>(&clientAddr),&addrLen);
if (client == INVALID_SOCKET){
std::cout << "accept异常:" << GetLastError() << std::endl;
return nullptr;
}
/****************************5.发送消息给客户端*************************/
char str[]="hello client!Nice to meet有!";
//此函数的返回值是发送出去的数据长度,如果数据比操作系统里面的发送缓冲区现有空间大就不能全部发送出去
int res=send(client,str,strlen(str));//是阻塞I/O
if(res==-1){
std::cout<<"发送数据异常:"<<WSAGetLastError()<<std::endl;
}
/*********************************6.接收数据*****************************/
char buffer[1024];
//参数:接收消息的socket 消息存放缓存区 缓存区大小(如果过小,数据不能放进去全,就会舍弃) 对控制数据接收时的 //标志按位或运算结果,大多设为0
int len=recv(client,buffer,1024,0);//阻塞I/O
if(len==-1){
std::cout<<"接收数据异常:"<<GetLastError()<<std::endl;
return -1;
}
return 0;
}
select介绍
上文的accept和recv方法都是阻塞I/O的,意思就是如果没有客户端连接过来或者发送来消息,程序就会一直停在那里,直到完成它的操作。因此我们可以使用select函数,去检查一边socket集合里面是否有socket发送来了消息、请求连接甚至于出现了错误,然后再对那些需要处理的socket调用阻塞I/O函数(因为它们都是有数据需要处理的,所以调用函数后就会立即执行,不会造成程序阻塞现象)
fd_set read_set;//存放待检查的socket
FD_ZERO(&read_set);//赋值为0(且必须要为0,因此初始的fd_set里面会有很多默认的socket)
FD_SET(socket, read_set);//向fd_set添加一个socket
FD_ISSET(socket, &read_set);//检查fd_set里面有没有指定的socket
//参数一:在POSIX(可移植)平台下是代表检查的socket列表里的最大编号
//POSIX下所有socket都是整数,所以直接选择socket的最大值即可
//Windows下socket是指针,所以没啥用
//参数四:超时前可等待的最长时间指针,nullptr表示不等待
//如果任意socket检查时超时,停止其他socket的检查,并且所有检查列表的是返回的空
//返回值:所有检查列表的剩余socket总数
int select(int nfds,fd_set*readfds,fd_set*writefds,fd_set*exceptfds,const timeval*timeout);
Example:对select函数的一个简单封装
#pragma once
#include<vector>
#include"TcpSocket.h"
#include<WinSock2.h>//socketx相关头文件(注意是2)
class TCPSelectUtil {
//TcpSocket是对socket进行了一下封装
typedef std::shared_ptr<TcpSocket> socketPtr;
typedef const std::vector<socketPtr> cosVecPtr;
typedef std::vector<socketPtr> VecPtr;
private:
//把fd_set里面的元素放入vector保存(外部调用时返回的是vector,便于后续的操作)
static void Fd_Set2Vector(const fd_set& inSet, cosVecPtr* inVector, VecPtr* outVector)
{
if (inVector && outVector)
{
outVector->clear();
//遍历待检查的vector,判断这个socket是不是在fd_set里面
for (auto& p : *inVector)
{
if (FD_ISSET(p->mSocket, &inSet))
{
outVector->push_back(p);
}
}
}
}
//把vector里的socket放入fd_set里面(因为调用select时必须传入fd_set)
static void Vector2Fd_set(cosVecPtr* inVector, fd_set& outSet)
{
if (inVector != nullptr)
{
for (auto& p : *inVector)
{
FD_SET(p->mSocket, &outSet);
}
}
}
public:
static int Select(cosVecPtr* checkReadSockets, VecPtr* readableSockets,
cosVecPtr* checkWriteSocket, VecPtr* writableSockets,
cosVecPtr* checkExceptSockets, VecPtr* exceptbleSockets, const timeval* timeout = nullptr
)
{
fd_set read;
FD_ZERO(&read);//必须要归0,初始给的fd_ser内部有很多的元素
fd_set write;
FD_ZERO(&write);//必须要归0
fd_set except;
FD_ZERO(&except);//必须要归0
Vector2Fd_set(checkReadSockets, read);
Vector2Fd_set(checkWriteSocket, write);
Vector2Fd_set(checkExceptSockets, except);
int res = select(0, &read, &write, &except, timeout);
int err = WSAGetLastError();
if (err != 0)
std::cout << "SelectError: " << err << std::endl;
if (res > 0)
{
Fd_Set2Vector(read, checkReadSockets, readableSockets);
Fd_Set2Vector(write, checkWriteSocket, writableSockets);
Fd_Set2Vector(except, checkExceptSockets, exceptbleSockets);
}
return res;
}
};