一、基于TCP/IP协议的C/S模型
面向连接的,可靠的,基于字节流的传输层协议。
UDP/IP: 面向非连接的,不可靠的,基于数据报的传输层协议。
比如QQ,DNF,LOL等这些我们下载客户端的,都属于c/s模型的一个应用
c/s模型其实是概念层面的,实现层面可以是基于任何的网络协议。
常见的还有b/s模型: 浏览器/服务器模型 基于http/https协议的
二、服务端
#include<WinSock2.h> // windows socket 第2版, 名字不区分大小写 #pragma comment(lib, "Ws2_32.lib") // windows socket 第2版 32位的库文件, 名字不区分大小写
WORD wdVersion = MAKEWORD(2, 2); // 将2.2版本存入 wdVersion。 类型是WORD unsigned short int WSADATA wdSockkMsg; /* int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); wVersionRequested 为 WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号) lpWSAData 为指向 WSAData 结构体的指针。 */ // 打开网络库 int nRes = WSAStartup(wdVersion, &wdSockkMsg); if (0 != nRes) { // 返回值错误码 /* WSASYSNOTREADY 10091 底层网络子系统尚未准备好进行网络通信。 系统配置问题,重启下电脑,检查ws2_32库是否存在,或者是否在环境配置目录下 WSAVERNOTSUPPORTED 10092 此特定Windows套接字实现不提供所请求的Windows套接字支持的版本。 要使用的版本不支持 WSAEPROCLIM 10067 已达到对Windows套接字实现支持的任务数量的限制。 Windows Sockets实现可能限制同时使用它的应用程序的数量 WSAEINPROGRESS 10036 正在阻止Windows Sockets 1.1操作。 当前函数运行期间,由于某些原因造成阻塞,会返回在这个错误码,其他操作均禁止 WSAEFAULT 10014 lpWSAData参数不是有效指针。 参数写错了 */ switch (nRes) { case WSASYSNOTREADY: printf("检查网络库或者重启电脑试试\n"); break; case WSAVERNOTSUPPORTED: printf("请更新网络库\n"); break; case WSAEPROCLIM: printf("请尝试关掉不必要的软件,以为当前网络运行提供足够的资源\n"); break; case WSAEINPROGRESS: printf("请重新启动软件\n"); break; case WSAEFAULT: printf("函数参数错误\n"); break; } return -1; }
// 校验版本 if (2 != HIBYTE(wdSockkMsg.wVersion) || 2 != LOBYTE(wdSockkMsg.wVersion)) { // 版本不对,关闭网络库函数 WSACleanup(); return -1; }
SOCKET socketServer = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); /* 参数1: 地址的类型 参数2: 套接字类型 参数3: 协议类型 */ if (INVALID_SOCKET == socketServer) { int a = WSAGetLastError(); printf("错误码是 %d", a); // 清理网络库 WSACleanup(); return -1; }
2.4.1 地址的类型
ipv4: Internet协议版本4(IPv4)地址系列。
192.168.1.103,
0.0.0.0 ~ 255.255.255.255,
点分十进制表示法
4字节 32位的地址
ipv6: Internet协议版本6(IPv6)地址系列。
2001:0:3238:DFE1:63::FEFB
16字节 128位的地址
蓝牙地址系列。
如果计算机安装了蓝牙适配器和驱动程序,则Windows XP SP2或更高版本支持此地址系列。
6B:2D:BC:A9:8C:12
红外数据协会(IrDA)地址系列。
仅当计算机安装了红外端口和驱动程序时,才支持此地址系列。
2.4.2 套接字类型
一种套接字类型,提供可靠的消息数据报。 这种类型的一个示例是Windows中的实用通用多播(PGM)多播协议实现,通常称为可靠多播节目。
仅在安装了可靠多播协议时才支持此类型值。
2.4.3 协议的类型
用户数据报协议(UDP)。 当af参数为AF_INET或AF_INET6且类型参数为SOCK_DGRAM时,这是一个可能的值。
Internet控制消息协议(ICMP)。 当af参数为AF_UNSPEC,AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这是一个可能的值。
Internet组管理协议(IGMP)。 当af参数为AF_UNSPEC,AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这是一个可能的值。
用于可靠多播的PGM协议。 当af参数为AF_INET且类型参数为SOCK_RDM时,这是一个可能的值。 在针对Windows Vista及更高版本发布的Windows SDK上,此协议也称为IPPROTO_PGM。
仅在安装了可靠多播协议时才支持此协议值。
1. 通过参数3得到一个事儿,参数1 2 3三者是配套的,不是随便填的,即使用不同的协议,那要添加对应的那套参数。
2. 想要使用一个协议,咱们设备得支持才行,比如红外
3. 参数3中,有个可能这个词,所以说一般,参数3可以填写0,系统会自动帮我们选择协议类型
成功返回可用的socket, 不用了就一定要销毁套接字 closesocket(socketListen);
失败返回INVALID_SOCKET, 关闭网络库: WSACleanup(); 可用WSAGetLasterror()返回错误码
int bind(SOCKET socketServer, const sockaddr *addr,int namelen);SOCKADDR_IN si; // struct sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(12345); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (SOCKET_ERROR == bind(socketServer, (struct sockaddr *)&si, sizeof(si))) { // 出错了 int a = WSAGetLastError(); // 释放 closesocket(socketServer); WSACleanup(); return -1; } /* 参数1: socket创建的对象, 上一个函数创建了socket,绑定了协议信息(地址类型,套接字类型,协议类型),咱们bind函数就是绑定实质的地址,端口号 参数2: struct sockaddr { ushort sa_family; char sa_data[14]; }; struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; 参数2是标志绑定的地址与端口。因为sockaddr具体怎么存储什么不清楚,而sockaddr和sockaddr 的字节大小是一致的,所以在绑定的时候用 sockaddr_in来绑定,然后强制转换成sockaddr类型。 参数3: 参数2的类型大小, sizeof(参数2) 返回值: 成功返回0, 失败返回SOCKET_ERROR, 具体错误码通过int WSAGetLastError(void);获得 */
int WSAAPI listen(SOCKET s, int backlog);if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) { // 出错了 int a = WSAGetLastError(); // 释放 closesocket(socketServer); WSACleanup(); return -1; } /* 参数1: 服务器端的socket,也就是socket函数创建的 参数2: 挂起连接队列的最大长度。我们一般填写这个参数 , SOMAXCONN WSAAPI 调用约定, 这个我们可以忽略,这是给系统看的,跟我们没关 返回值: 成功返回0, 失败返回SOCKET_ERROR, 具体错误码通过int WSAGetLastError(void);获得 */
SOCKET WSAAPI accept(SOCKET s,sockaddr *addr, int *addrlen);
// 创建客户端链接, 可以理解为与客户端创建三次握手的过程 struct sockaddr_in clientMsg; int len = sizeof(clientMsg); SOCKET socketclient = accept(socketServer, (struct sockaddr *)&clientMsg, &len); /* 参数1: 我们上面创建的自己的socket 参数2: 客户端的地址端口信息结构体 参数3: 参数2的大小 参数2 3也能都设置成NULL,那就是不直接得到客户端的地址,端口号, 此时可以通过函数得到客户端信息: getpeername(newSocket, (struct sockaddr*)&sockClient, &nLen); 得到本地服务器信息: getsockname(sSocket, (sockaddr*)&addr, &nLen); 返回值 成功, 返回值就是给客户端包好的socket, 与客户端通信就靠这个 失败, 返回INVALID_SOCKET */ if (INVALID_SOCKET == socketclient) { printf("客户端连接失败\n"); // 出错了 int a = WSAGetLastError(); // 释放 closesocket(socketServer); WSACleanup(); return -1; }
int recv(SOCKET s, char *buf, int len, int flags); int WSAAPI send(SOCKET s, const char *buf, int len, int flags);
2.8.1 recv收消息
// 与客户端收发消息 char buf[1500] = { 0 }; int res = recv(socketclient, buf, 1499, 0); if (0 == res) { printf("客户端链接中断\n"); // 释放客户端链接 closesocket(socketServer); return -1; } else if (SOCKET_ERROR == res) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 根据实际情况处理 return -1; } else { printf("%d %s", res, buf); } /* 参数1: 客户端的socket,每个客户端对应唯一的socket 参数2: 客户端消息的存储空间,也就是个字符数组, 这个一般1500字节, 网络传输得最大单元,1500字节,也就是客户端发过来得数据,一次最大就是 1500字节,这是协议规定,这个数值也是根据很多情况,总结出来得最优值 参数3: 想要读取得字节个数, 一般是参数2得字节数-1,把\0字符串结尾留出来 参数4: 数据的读取方式 正常逻辑来说:我们从系统缓冲区把数据读到我们的buf,读到我们buf中后, 系统缓冲区的被读的就应该被删除掉了,不然也是浪费空间,毕竟, 通信时间长的话,那就爆炸了.我们将缓冲区的数据读到我们自己的buf, 根据需要处理相应的数据,这是我们可控的。系统缓冲区的数据,咱们无可奈何,操作不了 读出来的就删除的话,有很多的好处: 1、系统缓冲区读到的数据,比我们的buf多,那么我们读出来的,系统删掉,从而我们就可以依次的把所有数据读完了 2、可以计数收到了多少字节, 返回值就是本次读出来的数据 3、正常这种逻辑, 填0, 读出来的就删除 0, MSG_PEEK: 窥视传入的数据。 数据将复制到缓冲区中,但不会从输入队列中删除。读出来的不删除 这个东西是不建议被使用的: 1)、读数据不行 2)、无法计数 MSG_OOB: 带外数据, 就是传输一段数据,在外带一个额外的特殊数据 不建议被使用了: 1)、TCP协议规范(RFC 793)中OOB的原始描述被“主机要求”规范取代( RFC 1122),但仍有许多机器具有RFC 793 OOB实现。 2)、既然两种数据,那咱们就send两次,另一方recv两次就行了,何必搞得那么神神秘秘,浪费计算机精力 MSG_WAITALL: 直到系统缓冲区字节数满足参数3所请求得字节数,才开始读取 返回值: 读出来字节数大小,读没了咋办?在recv函数卡着,等着客户端发来数据,即阻塞,同步 客户端下线,这端返回0,释放客户端socket 执行失败,返回SOCKET_ERROR, WSAGetLastError()得到错误码 */
int WSAAPI send(SOCKET s, const char *buf, int len, int flags);if (SOCKET_ERROR == send(socketclient, "abcd", sizeof("abcd"), 0)) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); return -1; } /* 参数1: 目标的socket,每个客户端对应唯一的socket 参数2: 给对方发送的字节串, 这个不要超过1500字节, 发送时候,协议要进行包装,加上协议信息,也叫协议头,或者叫包头 这个大小不同的协议不一样,链路层14字节,ip头20字节,tcp头20字节,数据结尾还要有状态确认,加起来也几十个字节, 所以实际咱们的数据位,不能写1500个,要留出来,那就1024吧,或者最多1400,就差不多了。 超过1500系统咋办?系统会分片处理: 比如2000个字节, 系统分成两个包,分两次发送出去: 1400+包头==1500(假设包头100字节),600+包头==700 结果: 1、系统要给咱们分包再打包,再发送, 客户端接收到了还得拆包,组合数据。从而增加了系统的工作,降低效率 2、有的协议,就把分片后的二包直接丢了 参数3: 要发送数据的字节个数 参数4: 1、写0就行了 2、 MSG_OOB: 意义同recv 3、 MSG_DONTROUTE: 指定数据不应受路由限制。 Windows套接字服务提供程序可以选择忽略此标志。 返回值: 成功返回写入的字节数, 执行失败,返回 SOCKET_ERROR */
三、客户端
#include<WinSock2.h> // windows socket 第2版, 名字不区分大小写 #pragma comment(lib, "Ws2_32.lib") // windows socket 第2版 32位的库文件, 名字不区分大小写
WORD wdVersion = MAKEWORD(2, 2); // 将2.2版本存入 wdVersion。 类型是WORD unsigned short int WSADATA wdSockkMsg; /* int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); wVersionRequested 为 WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号) lpWSAData 为指向 WSAData 结构体的指针。 */ // 打开网络库 int nRes = WSAStartup(wdVersion, &wdSockkMsg); if (0 != nRes) { // 返回值错误码 /* WSASYSNOTREADY 10091 底层网络子系统尚未准备好进行网络通信。 系统配置问题,重启下电脑,检查ws2_32库是否存在,或者是否在环境配置目录下 WSAVERNOTSUPPORTED 10092 此特定Windows套接字实现不提供所请求的Windows套接字支持的版本。 要使用的版本不支持 WSAEPROCLIM 10067 已达到对Windows套接字实现支持的任务数量的限制。 Windows Sockets实现可能限制同时使用它的应用程序的数量 WSAEINPROGRESS 10036 正在阻止Windows Sockets 1.1操作。 当前函数运行期间,由于某些原因造成阻塞,会返回在这个错误码,其他操作均禁止 WSAEFAULT 10014 lpWSAData参数不是有效指针。 参数写错了 */ switch (nRes) { case WSASYSNOTREADY: printf("检查网络库或者重启电脑试试\n"); break; case WSAVERNOTSUPPORTED: printf("请更新网络库\n"); break; case WSAEPROCLIM: printf("请尝试关掉不必要的软件,以为当前网络运行提供足够的资源\n"); break; case WSAEINPROGRESS: printf("请重新启动软件\n"); break; case WSAEFAULT: printf("函数参数错误\n"); break; } return -1; }
// 校验版本 if (2 != HIBYTE(wdSockkMsg.wVersion) || 2 != LOBYTE(wdSockkMsg.wVersion)) { // 版本不对,关闭网络库函数 WSACleanup(); return -1; }
// 创建 socket SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == socketServer) { int a = WSAGetLastError(); a = WSAGetLastError(); printf("错误码是 %d", a); // 清理网络库 WSACleanup(); return -1; }
int WSAAPI connect(SOCKET s, const sockaddr *name, int namelen);SOCKADDR_IN serverMsg; // struct sockaddr_in si; serverMsg.sin_family = AF_INET; serverMsg.sin_port = htons(12345); serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (SOCKET_ERROR == connect(socketServer, (struct sockaddr *)&serverMsg, sizeof(serverMsg))) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 释放 closesocket(socketServer); WSACleanup(); return -1; }
// 收发消息 char buf[1500] = { 0 }; int res = recv(socketServer, buf, 1499, 0); if (0 == res) { printf("客户端链接中断\n"); // 释放客户端链接 closesocket(socketServer); return -1; } else if (SOCKET_ERROR == res) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 根据实际情况处理 return -1; } else { printf("%d %s\n", res, buf); } if (SOCKET_ERROR == send(socketServer, "客户端收到了服务器的消息", sizeof("客户端收到了服务器的消息"), 0)) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); return -1; }
四、代码整合
4.1 服务端
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<string.h> #include<WinSock2.h> // windows socket 第2版, 名字不区分大小写 #pragma comment(lib, "Ws2_32.lib") // windows socket 第2版 32位的库文件, 名字不区分大小写 int main() { WORD wdVersion = MAKEWORD(2, 2); // 将2.2版本存入 wdVersion。 类型是 WORD: unsigned short int WSADATA wdSockkMsg; /* int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData); wVersionRequested 为 WinSock 规范的版本号,低字节为主版本号,高字节为副版本号(修正版本号) lpWSAData 为指向 WSAData 结构体的指针。 */ // 打开网络库 int nRes = WSAStartup(wdVersion, &wdSockkMsg); if (0 != nRes) { // 返回值错误码 /* WSASYSNOTREADY 10091 底层网络子系统尚未准备好进行网络通信。 系统配置问题,重启下电脑,检查ws2_32库是否存在,或者是否在环境配置目录下 WSAVERNOTSUPPORTED 10092 此特定Windows套接字实现不提供所请求的Windows套接字支持的版本。 要使用的版本不支持 WSAEPROCLIM 10067 已达到对Windows套接字实现支持的任务数量的限制。 Windows Sockets实现可能限制同时使用它的应用程序的数量 WSAEINPROGRESS 10036 正在阻止Windows Sockets 1.1操作。 当前函数运行期间,由于某些原因造成阻塞,会返回在这个错误码,其他操作均禁止 WSAEFAULT 10014 lpWSAData参数不是有效指针。 参数写错了 */ switch (nRes) { case WSASYSNOTREADY: printf("检查网络库或者重启电脑试试\n"); break; case WSAVERNOTSUPPORTED: printf("请更新网络库\n"); break; case WSAEPROCLIM: printf("请尝试关掉不必要的软件,以为当前网络运行提供足够的资源\n"); break; case WSAEINPROGRESS: printf("请重新启动软件\n"); break; case WSAEFAULT: printf("函数参数错误\n"); break; } } // 校验版本 if (2 != HIBYTE(wdSockkMsg.wVersion) || 2 != LOBYTE(wdSockkMsg.wVersion)) { // 版本不对,关闭网络库函数 WSACleanup(); return -1; } // 创建socket SOCKET socketServer = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); /* 参数1: 地址的类型 参数2: 套接字类型 参数3: 协议类型 */ if (INVALID_SOCKET == socketServer) { int a = WSAGetLastError(); a = WSAGetLastError(); printf("错误码是 %d", a); // 清理网络库 WSACleanup(); return -1; } // bind绑定地址和端口 SOCKADDR_IN si; // struct sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(12345); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (SOCKET_ERROR == bind(socketServer, (struct sockaddr *)&si, sizeof(si))) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 释放 closesocket(socketServer); WSACleanup(); return -1; } /* 参数1: socket创建的对象, 上一个函数创建了socket,绑定了协议信息(地址类型,套接字类型,协议类型),咱们bind函数就是绑定实质的地址,端口号 参数2: struct sockaddr { ushort sa_family; char sa_data[14]; }; struct sockaddr_in { short sin_family; u_short sin_port; struct in_addr sin_addr; char sin_zero[8]; }; 参数2是标志绑定的地址与端口。因为sockaddr具体怎么存储什么不清楚,而sockaddr和sockaddr 的字节大小是一致的,所以在绑定的时候用 sockaddr_in来绑定,然后强制转换成sockaddr类型。 参数3: 参数2的类型大小, sizeof(参数2) */ // 监听 if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 释放 closesocket(socketServer); WSACleanup(); return -1; } // 创建客户端链接, 可以理解为创建三次握手的过程 struct sockaddr_in clientMsg; int len = sizeof(clientMsg); SOCKET socketclient = accept(socketServer, (struct sockaddr *)&clientMsg, &len); /* 参数1: 我们上面创建的自己的socket 参数2: 客户端的地址端口信息结构体 参数3: 参数2的大小 参数2 3也能都设置成NULL,那就是不直接得到客户端的地址,端口号, 此时可以通过函数得到客户端信息: getpeername(newSocket, (struct sockaddr*)&sockClient, &nLen); 得到本地服务器信息: getsockname(sSocket, (sockaddr*)&addr, &nLen); 返回值 成功, 返回值就是给客户端包好的socket, 与客户端通信就靠这个 失败, 返回INVALID_SOCKET */ if (INVALID_SOCKET == socketclient) { printf("客户端连接失败\n"); // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 释放 closesocket(socketServer); WSACleanup(); return -1; } printf("客户端链接成功\n"); // 与客户端收发消息 while (1) { char sendMsg[1500] = { 0 }; scanf("%s", sendMsg); if (SOCKET_ERROR == send(socketclient, sendMsg, strlen(sendMsg), 0)) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); return -1; } /* 参数1: 目标的socket,每个客户端对应唯一的socket 参数2: 给对方发送的字节串, 这个不要超过1500字节, 发送时候,协议要进行包装,加上协议信息,也叫协议头,或者叫包头 这个大小不同的协议不一样,链路层14字节,ip头20字节,tcp头20字节,数据结尾还要有状态确认,加起来也几十个字节, 所以实际咱们的数据位,不能写1500个,要留出来,那就1024吧,或者最多1400,就差不多了。 超过1500系统咋办?系统会分片处理: 比如2000个字节, 系统分成两个包,分两次发送出去: 1400+包头==1500(假设包头100字节),600+包头==700 结果: 1、系统要给咱们分包再打包,再发送, 客户端接收到了还得拆包,组合数据。从而增加了系统的工作,降低效率 2、有的协议,就把分片后的二包直接丢了 参数3: 要发送数据的字节个数 参数4: 1、写0就行了 2、 MSG_OOB: 意义同recv 3、 MSG_DONTROUTE: 指定数据不应受路由限制。 Windows套接字服务提供程序可以选择忽略此标志。 返回值: 成功返回写入的字节数, 执行失败,返回 SOCKET_ERROR */ char buf[1500] = { 0 }; int res = recv(socketclient, buf, 50, 0); if (0 == res) { printf("客户端链接中断\n"); // 释放客户端链接 closesocket(socketServer); return -1; } else if (SOCKET_ERROR == res) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 根据实际情况处理 return -1; } else { printf("%d %s", res, buf); } /* 参数1: 客户端的socket,每个客户端对应唯一的socket 参数2: 客户端消息的存储空间,也就是个字符数组, 这个一般1500字节, 网络传输得最大单元,1500字节,也就是客户端发过来得数据,一次最大就是 1500字节,这是协议规定,这个数值也是根据很多情况,总结出来得最优值 参数3: 想要读取得字节个数, 一般是参数2得字节数-1,把\0字符串结尾留出来 参数4: 数据的读取方式 正常逻辑来说:我们从系统缓冲区把数据读到我们的buf,读到我们buf中后, 系统缓冲区的被读的就应该被删除掉了,不然也是浪费空间,毕竟, 通信时间长的话,那就爆炸了.我们将缓冲区的数据读到我们自己的buf, 根据需要处理相应的数据,这是我们可控的。系统缓冲区的数据,咱们无可奈何,操作不了 读出来的就删除的话,有很多的好处: 1、系统缓冲区读到的数据,比我们的buf多,那么我们读出来的,系统删掉,从而我们就可以依次的把所有数据读完了 2、可以计数收到了多少字节, 返回值就是本次读出来的数据 3、正常这种逻辑, 填0, 读出来的就删除 0, MSG_PEEK: 窥视传入的数据。 数据将复制到缓冲区中,但不会从输入队列中删除。读出来的不删除 这个东西是不建议被使用的: 1)、读数据不行 2)、无法计数 MSG_OOB: 带外数据, 就是传输一段数据,在外带一个额外的特殊数据 不建议被使用了: 1)、TCP协议规范(RFC 793)中OOB的原始描述被“主机要求”规范取代( RFC 1122),但仍有许多机器具有RFC 793 OOB实现。 2)、既然两种数据,那咱们就send两次,另一方recv两次就行了,何必搞得那么神神秘秘,浪费计算机精力 MSG_WAITALL: 直到系统缓冲区字节数满足参数3所请求得字节数,才开始读取 返回值: 读出来字节数大小,读没了咋办?在recv函数卡着,等着客户端发来数据,即阻塞,同步 客户端下线,这端返回0,释放客户端socket 执行失败,返回SOCKET_ERROR, WSAGetLastError()得到错误码 */ } // 关闭socket closesocket(socketclient); closesocket(socketServer); // 清理网络库 WSACleanup(); return EXIT_SUCCESS; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<string.h> #include<WinSock2.h> // windows socket 第2版, 名字不区分大小写 #pragma comment(lib, "Ws2_32.lib") // windows socket 第2版 32位的库文件, 名字不区分大小写 int main() { WORD wdVersion = MAKEWORD(2, 2); WSADATA wdSockkMsg; // 打开网络库 int nRes = WSAStartup(wdVersion, &wdSockkMsg); if (0 != nRes) { switch (nRes) { case WSASYSNOTREADY: printf("检查网络库或者重启电脑试试\n"); break; case WSAVERNOTSUPPORTED: printf("请更新网络库\n"); break; case WSAEPROCLIM: printf("请尝试关掉不必要的软件,以为当前网络运行提供足够的资源\n"); break; case WSAEINPROGRESS: printf("请重新启动软件\n"); break; case WSAEFAULT: printf("函数参数错误\n"); break; } } // 校验版本 if (2 != HIBYTE(wdSockkMsg.wVersion) || 2 != LOBYTE(wdSockkMsg.wVersion)) { WSACleanup(); return -1; } // 创建socket SOCKET socketServer = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP); if (INVALID_SOCKET == socketServer) { int a = WSAGetLastError(); a = WSAGetLastError(); printf("错误码是 %d", a); WSACleanup(); return -1; } // bind绑定地址和端口 SOCKADDR_IN si; // struct sockaddr_in si; si.sin_family = AF_INET; si.sin_port = htons(12345); si.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (SOCKET_ERROR == bind(socketServer, (struct sockaddr *)&si, sizeof(si))) { // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 释放 closesocket(socketServer); WSACleanup(); return -1; } // 监听 if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) { int a = WSAGetLastError(); printf("出错了 %d", a); closesocket(socketServer); WSACleanup(); return -1; } // 创建客户端链接, 可以理解为创建三次握手的过程 struct sockaddr_in clientMsg; int len = sizeof(clientMsg); SOCKET socketclient = accept(socketServer, (struct sockaddr *)&clientMsg, &len); if (INVALID_SOCKET == socketclient) { printf("客户端连接失败\n"); // 出错了 int a = WSAGetLastError(); printf("出错了 %d", a); // 释放 closesocket(socketServer); WSACleanup(); return -1; } printf("客户端链接成功\n"); // 与客户端收发消息 while (1) { char sendMsg[1500] = { 0 }; scanf("%s", sendMsg); if (SOCKET_ERROR == send(socketclient, sendMsg, strlen(sendMsg), 0)) { int a = WSAGetLastError(); printf("出错了 %d", a); return -1; } char buf[1500] = { 0 }; int res = recv(socketclient, buf, 50, 0); if (0 == res) { printf("客户端链接中断\n"); closesocket(socketServer); return -1; } else if (SOCKET_ERROR == res) { int a = WSAGetLastError(); printf("出错了 %d", a); return -1; } else { printf("%d %s", res, buf); } } // 关闭socket closesocket(socketclient); closesocket(socketServer); // 清理网络库 WSACleanup(); return EXIT_SUCCESS; }
#define _CRT_SECURE_NO_WARNINGS 1 #include<stdio.h> #include<WinSock2.h> #include<string.h> #pragma comment(lib, "Ws2_32.lib") int main() { WORD wdVersion = MAKEWORD(2, 2); // 将2.2版本存入 wdVersion。 类型是 WORD: unsigned short int WSADATA wdSockkMsg; // 打开网络库 int nRes = WSAStartup(wdVersion, &wdSockkMsg); if (0 != nRes) { // 返回值错误码 switch (nRes) { case WSASYSNOTREADY: printf("检查网络库或者重启电脑试试\n"); break; case WSAVERNOTSUPPORTED: printf("请更新网络库\n"); break; case WSAEPROCLIM: printf("请尝试关掉不必要的软件,以为当前网络运行提供足够的资源\n"); break; case WSAEINPROGRESS: printf("请重新启动软件\n"); break; case WSAEFAULT: printf("函数参数错误\n"); break; } return -1; } // 校验版本 if (2 != HIBYTE(wdSockkMsg.wVersion) || 2 != LOBYTE(wdSockkMsg.wVersion)) { // 版本不对,关闭网络库函数 WSACleanup(); return -1; } // 创建 socket SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == socketServer) { int a = WSAGetLastError(); a = WSAGetLastError(); printf("出错了 %d", a); WSACleanup(); return -1; } // 连接服务器 SOCKADDR_IN serverMsg; // struct sockaddr_in si; serverMsg.sin_family = AF_INET; serverMsg.sin_port = htons(12345); serverMsg.sin_addr.S_un.S_addr = inet_addr("127.0.0.1"); if (SOCKET_ERROR == connect(socketServer, (struct sockaddr *)&serverMsg, sizeof(serverMsg))) { int a = WSAGetLastError(); printf("出错了 %d", a); closesocket(socketServer); WSACleanup(); return -1; } // 循环收发消息 while (1) { char buf[1500] = { 0 }; int res = recv(socketServer, buf, 50, 0); if (0 == res) { printf("客户端链接中断\n"); closesocket(socketServer); return -1; } else if (SOCKET_ERROR == res) { int a = WSAGetLastError(); printf("出错了 %d", a); return -1; } else { printf("%d %s\n", res, buf); } scanf("%s", buf); if (SOCKET_ERROR == send(socketServer, buf, strlen(buf), 0)) { int a = WSAGetLastError(); printf("出错了 %d", a); return -1; } } // 关闭socket closesocket(socketServer); // 清理网络库 WSACleanup(); return 0; }