windows最底层的网络函数, 其他的都是对他们的二次封装, 是通用的。
#include <WinSock2.h> // winsock.h 第一版头文件
#pragma comment(lib, "ws2_32.lib") // wsock32.lib 第一版动态库
int WSAAPI WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); // windows socket Asynchronous startup 异步启动网络库
输入主版本存在, 副版本不存在:打开主版本最高的版本号
系统配置问题, 重启电脑, 检查ws2_32的工作目录
达到了windows能够打开的网络库数量(硬件能够打开的最大数量, 计算机的端口数量65536个)
WORD wdVersion = MAKEWORD(2, 2); // 版本号, 高字节:1, 低字节:2
WSADATA wdSockMsg;
int nRes = WSAStartup(wdVersion, &wdSockMsg); // LP/P 开头的是需要地址
if (!nRes)
{
switch (nRes)
{
case WSASYSNOTREADY:
printf("重启电脑或检查网络库!\n");
break;
case WSAVERNOTSUPPORTED:
printf("使用的版本不支持, 请更新网络库\n");
break;
case WSAEINPROGRESS:
printf("请重新启动\n");
break;
case WSAEPROCLIM:
printf("请关闭不必要的软件\n");
break;
case WSAEFAULT:
break;
default:
break;
}
}
通过检查结构体的内容判断当前打开的版本是不是需要的版本
if (2 != HIBYTE(wdSockMsg.wVersion) || 2 != LOBYTE(wdSockMsg.wVersion))
{
// 打开的版本不对
// 清理/关闭网络库, 也可以做其他的提示/操作
WSACleanup();
return 0;
}
将复杂的协议过程和编程人员分开, 只需要使用socket就可以
SOCKET WSAAPI socket(int af, int type, int protocol);
地址类型:ipv4,ipv6,蓝牙地址,红外数据协会地址系列等
ipv4:4字节地址, 0.0.0.0~255.255.255.255, 无符号int类型的范围
SOCK_STREAM:顺序、可靠、双向、基于连接,TCP
SOCK_DGRAM:固定最大长度的无链接、不可靠的缓冲区, UDP
IPPROTO_TCP:AF_INET/AF_INET6、SOCK_STREAM, 可以填这个协议(也可以写其他的符合的协议)
SOCKET sockServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
closesocket(socketServer);
INVALID_SOCKET:执行失败, 清理网络库, 结束程序
if (INVALID_SOCKET == sockServer)
{
WSACleanup();
return 0;
}
int errorcode = WSAGetLastError();
int bind( SOCKET s, const sockaddr *addr, int namelen);
将sockaddr_in强制转换称sockaddr
sockaddr_in si;
si.sin_family = AF_INET;
si.sin_port = htons(27015); // 将int转换位指定的类型, 更规范
si.sin_addr是一个结构体, 包含一个联合, 联合中有三个成员, 也就是可以用三种方式赋值
si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
si.sin_addr.S_un.S_un_b.s_b1 = 192;
si.sin_addr.S_un.S_un_b.s_b2 = 168;
... ...
netstat -ano
netstat -ano|findstr "123456"
失败:SOCKET_ERROR, 具体错误吗通过WSAGetLastError()查找
int bres = bind(sockServer, (const sockaddr *)&si, sizeof(sockaddr_in));
if (SOCKET_ERROR == bres)
{
int a = WSAGetLastError();
closesocket(sockServer);
WSACleanup();
return 0;
}
挂起连接队列的最大长度:系统一次只能处理固定的长度,剩下的暂时放在队列中, 2~20左右, SOMAXCONN:系统自己设定
作用:决定:函数名字的编译方式, 参数的入栈顺序, 函数的调用时间
if (SOCKET_ERROR == listen(sockServer, SOMAXCONN))
{
int errorcode = WSAGetLastError();
closesocket(sockServer);
WSACleanup();
return 0;
}
SOCKET WSAAPI accept(SOCKET s, sockaddr *addr, int *addrlen);
之后可以调用getpeername()获得具体信息
SOCKET sockClient = accept(sockServer, NULL, NULL);
getpeername(sockClient, (sockaddr *)&clientMsg, &nLen);
getsockname(socket, (sockaddr *)&clientMsg, &nLen); // 得到本地的链接信息, 第一个参数没什么用
sockaddr_in clientMsg;
int len = sizeof(clientMsg);
SOCKET sockClient = accept(sockServer, (sockaddr *)&clientMsg, &len);
if (INVALID_SOCKET == sockClient)
{
int errorcode = WSAGetLastError();
closesocket(sockServer);
WSACleanup();
return 0;
}
int recv(
SOCKET s,
char *buf,
int len,
int flags
);
recv是通过socket找到缓冲区, 将数据从协议缓冲区复制到代码中
客户端消息的存储空间, 字符数组, 一般1500字节(单次最大传输单元)
要读取的字节个数, 一般是参数二字节数-1, 把'\0'留出来
0 :读出来之后会删除协议缓冲区中的内容
MSG_PEEK :读出来不删除
MSG_OOB :带外数据, 传输一段数据, 另外附带一个额外的特殊数据(一个字节), 不建议使用
MSG_WAITALL :直到系统缓冲区字节数满足参数三所请求的字节数(大于等于), 才开始读取
错误码:WSAGetLastError()进行相应的处理
char buf[1500] = { 0 };
int res = recv(sockClient, buf, sizeof(buf) - 1, 0);
if (0 == res)
{
printf("连接中断,客户端下线\n");
}
else if (SOCKET_ERROR == res)
{
// 出错了
int errorcode = WSAGetLastError();
// 根据实际情况处理
}
else
{
printf("%d:", res);
printf("%s\n", buf);
}
int WSAAPI send(
SOCKET s,
const char *buf,
int len,
int flags
);
将我们的数据复制到协议缓冲区, 计算机伺机发送出去
超过1500字节:分多次发出去(效率低一点), 有些协议会把多的数据丢弃
发送字节的个数 1400/1024(去掉各个层的包头、尾)
0
MSG_OOB :带外数据, 额外带一个特殊数据
MSG_DONTROUTE :指定数据不受路由限制, windows套接字可以选择忽略此标志, (可能会走固定的线路)
WSAGetLastError()得到错误码, 根据错误码信息做相应处理:重启、等待、不用理会... ...
res = send(sockClient, "abcd", sizeof("abcd"), 0);
if (SOCKET_ERROR == res)
{
int errorcode = WSAGetLastError();
printf("socketServer send failed\n");
}