windows socket网络编程一:最简单的服务器和客户端搭建

您所在的位置:网站首页 ip转移最简单的方法 windows socket网络编程一:最简单的服务器和客户端搭建

windows socket网络编程一:最简单的服务器和客户端搭建

2024-07-15 03:02:34| 来源: 网络整理| 查看: 265

文章目录 简介服务器网络版本1、打开网络库2、校验版本3、创建socket4、绑定地址和端口5、监听6、接受链接7、与客户端收发消息 客户端1、打开网络库2、校验版本3、创建socket4、连接服务器5、与客户端收发消息类比 运行结果源码链接遇到的问题头文件冲突

简介

socket又是什么? 将网络底层复杂的协议体系,执行流程,进行了封装后就是SOCKET了,也就是说,SOCKET是我们调用协议进行通信的操作接口,将复杂的协议过程与我们编程人员分开,我们直接操作一个简单SOCKET就行了,对于底层的协议 过程细节,我们可以完全不用知道。 在编译器转定义后就是一个unsigned int,目测当作id使用。

最简单的客户端、服务器通信流程大致如下: 在这里插入图片描述 收发信息可以进行很多次。

服务器 网络版本

我们使用新版本第二版

网络版本一:

#include #pragma comment(lib, "wsock32.lib")

网络版本二:

#include #pragma comment(lib, "ws2_32.lib")

注:不管是64编译环境还是32编译环境,都是用这个ws2_32.lib,并没有ws2_64.lib。

1、打开网络库 int WSAAPI WSAStartup( WORD wVersionRequested, LPWSADATA lpWSAData );

功能:打开网络库/启动网络库(函数名字分析W—windows、S—socket、A—Asynchronous 异步) 参数:

wVersionRequested — 调用者可以使用的Windows套接字规范的最高版本。高位字节指定次版本号;低位字节指定主版本号。(支持版本:1.0、1.1、2.0、2.1、2.2)lpWSAData — 接收Windows套接字实现的详细信息。struct WSAData { WORD wVersion; // 我们要使用的版本 WORD wHighVersion; // 系统能提供给我们最高的版本 unsigned short iMaxSockets; // 返回可用的socket的数量,二版本之后就没用了 unsigned short iMaxUdpDg; // UDP数据报信息的大小,2版本之后就没用了 char *lpVendorInfo; // 供应商特定的信息,2版本之后就没用了 char szDescription[WSADESCRIPTION_LEN + 1]; // 当前库的描述信息 char szSystemStatus[WSASYS_STATUS_LEN + 1]; } 程序调试截图: 在这里插入图片描述

返回值:

如果成功,返回零。如果失败,返回错误码。 错误码宏展开直翻原因通俗解释原因WSASYSNOTREADY10091底层网络子系统尚未准备好进行网络通信系统配置问题,重启下电脑,检查ws2_32库是否存在,或者是否在环境配置目录下WSAVERNOTSUPPORTED10092此特定Windows套接字实现不提供所请求的Windows套接字支持的版本。要使用的版本不支持WSAEPROCLIM10067已达到对Windows套接字实现支持的任务数量的限制Windows Sockets实现可能限制同时使用它的应用程序的数量WSAEINPROGRESS10036正在阻止Windows Sockets 1.1操作。当前函数运行期间,由于某些原因造成阻塞,会返回在这个错误码,其他操作均禁止WSAEFAULT10014lpWSAData参数不是有效指针参数写错了

代码(错误处理就简单点了):

// 开启网络库 WORD wVersionRequird = MAKEWORD(2, 2); // MAKEWORD(主版本,副版本) WSADATA wdScokMsg; switch (WSAStartup(wVersionRequird, &wdScokMsg)) { case WSASYSNOTREADY: printf("重启电脑试试,或者检查网络库\n"); return -1; case WSAVERNOTSUPPORTED: printf("请更新网络库\n"); return -1; case WSAEPROCLIM: printf("请尝试关掉不必要的软件,以为当前网络运行提供充足的资源\n"); return -1; case WSAEINPROGRESS: printf("请重新启动\n"); return -1; }

虽然我们有正确的代码,但是我们也可以测试一下不正确的版本号会发生什么:

错误版本号问题主版本号为0返回错误码WSAVERNOTSUPPORTED,不支持该版本有对应的主版本,没有对应的副版本没问题,得到该主版本的最大副版本 1.1 2.2并使用超过最大主版本没问题,使用系统能提供的最大的版本 2.2 2、校验版本

这一步也许不是必须的,不过网络版本还是比较重要的,需要确定。

// 校验版本 if (2 != HIBYTE(wdScokMsg.wVersion) || 2 != LOBYTE(wdScokMsg.wVersion)) { printf("版本不存在\n"); WSACleanup(); return -1; } 3、创建socket SOCKET WSAAPI socket( int af, int type, int protocol );

功能:创建一个SOCKET 参数:

af — 地址族 常用地址族宏展开含义AF_INET2IPv4AF_INET623IPv6AF_IRDA26红外AF_BTH32蓝牙 type — 套接字类型 套接字类型宏展开含义SOCK_STREAM1一种套接字类型,提供带有OOB数据传输机制的顺序,可靠,双向,基于连接的字节流。 此套接字类型使用传输控制协议(TCP)作为Internet地址系列(AF_INET或AF_INET6)。SOCK_DGRAM2一种支持数据报的套接字类型,它是固定(通常很小)最大长度的无连接,不可靠的缓冲区。 此套接字类型使用用户数据报协议(UDP)作为Internet地址系列(AF_INET或AF_INET6)。SOCK_RAW3一种套接字类型,提供允许应用程序操作下一个上层协议头的原始套接字。 要操作IPv4标头,必须在套接字上设置IP_HDRINCL套接字选项。 要操作IPv6标头,必须在套接字上设置IPV6_HDRINCL套接字选项。SOCK_RDM4一种套接字类型,提供可靠的消息数据报。 这种类型的一个示例是Windows中的实用通用多播(PGM)多播协议实现,通常称为可靠多播节目。仅在安装了可靠多播协议时才支持此类型值。SOCK_SEQPACKET5一种套接字类型,提供基于数据报的伪流数据包。 protocol — 协议类型 协议类型含义IPPROTO_TCP传输控制协议(TCP)。 当af参数为AF_INET或AF_INET6且类型参数为SOCK_STREAM时,这是一个可能的值。IPPROTO_UDP用户数据报协议(UDP)。 当af参数为AF_INET或AF_INET6且类型参数为SOCK_DGRAM时,这是一个可能的值。IPPROTO_ICMPInternet控制消息协议(ICMP)。 当af参数为AF_UNSPEC,AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这是一个可能的值。IPPROTO_IGMPInternet组管理协议(IGMP)。 当af参数为AF_UNSPEC,AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这是一个可能的值。IPPROTO_RM用于可靠多播的PGM协议。 当af参数为AF_INET且类型参数为SOCK_RDM时,这是一个可能的值。 在针对Windows Vista及更高版本发布的Windows SDK上,此协议也称为IPPROTO_PGM。仅在安装了可靠多播协议时才支持此协议值。0调用者不希望指定协议,服务提供商将选择要使用的协议。

通过上面对参数的描述来看我们三个参数需要配合使用,比如说我们使用tcp通信需要基于字节流,而不是数据包,不能乱用。

返回值:

如果成功,返回socket。如果失败,返回INVALID_SOCKET。

代码:

// 创建服务器socket(监听套接字) SOCKET socketServer = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP); if (INVALID_SOCKET == socketServer) { printf("创建socket失败 error:%d\n", WSAGetLastError()); WSACleanup(); return -1; }

我们测试一下基于数据包的tcp(SOCKET socketServer = socket(AF_INET, SOCK_DGRAM, IPPROTO_TCP);)会有什么问题: 在这里插入图片描述 在这里插入图片描述

4、绑定地址和端口 int WSAAPI bind( SOCKET s, const sockaddr *name, int namelen );

功能:给socket绑定具体地址(定位到电脑)与端口号(定位到具体应用) 参数:

s — socketname — 地址和端口typedef struct sockaddr { #if (_WIN32_WINNT < 0x0600) u_short sa_family; #else ADDRESS_FAMILY sa_family; // Address family. #endif //(_WIN32_WINNT < 0x0600) CHAR sa_data[14]; // Up to 14 bytes of direct address. } SOCKADDR, *PSOCKADDR, FAR *LPSOCKADDR; // 和sockaddr相同 便于我们赋值 typedef struct sockaddr_in { #if(_WIN32_WINNT < 0x0600) short sin_family; #else //(_WIN32_WINNT < 0x0600) ADDRESS_FAMILY sin_family; // 地址族 #endif //(_WIN32_WINNT < 0x0600) USHORT sin_port; // 端口 IN_ADDR sin_addr; // 地址 CHAR sin_zero[8]; } SOCKADDR_IN, *PSOCKADDR_IN; namelen — 参数二的大小

其中端口号理论上取值范围0~65535。实际上介于0~1023,为系统保留占用端口号,(如:21端口分配给FTP(文件传输协议)服务、25端口分配给SMTP(简单邮件传输协议)服务、80端口分配给HTTP服务)所以我们使用其他的端口号。 因为同一个端口号不能同时被两个程序使用,万一我们想要的端口号被占用了,想知道是什么程序占用的,可以使用cmd查看:

打开运行cmd输入netstat -ano,查看被使用的所有端口netstat -aon | findstr “12345”,检查我们要使用的端口号是否被使用了

返回值:

如果成功,返回0。如果失败,返回SOCKET_ERROR。

代码:

// 绑定地址 SOCKADDR_IN sockAddress; sockAddress.sin_family = AF_INET; sockAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); // INADDR_ANY --- 任何地址都可以 sockAddress.sin_port = htons(6666); if (SOCKET_ERROR == bind(socketServer, (struct sockaddr*)&sockAddress, sizeof(sockAddress))) { printf("bind 失败 error:%d\n", WSAGetLastError()); closesocket(socketServer); WSACleanup(); return -1; }

我们测试一下已经被占用的端口: 在这里插入图片描述 在这里插入图片描述

5、监听 int WSAAPI listen( SOCKET s, int backlog );

功能:将套接字置于正在侦听传入连接的状态 参数:

s — socketbacklog — 挂起的连接队列的最大长度。如果设置为SOMAXCONN,则负责套接字s的基础服务提供商将积压设置为最大合理值。

返回值:

如果成功,返回0。如果失败,返回SOCKET_ERROR。

代码:

// 开始监听 if (SOCKET_ERROR == listen(socketServer, SOMAXCONN)) { printf("listen 失败 error:%d\n", WSAGetLastError()); closesocket(socketServer); WSACleanup(); return -1; }

我们测试一下未bind的情况: 在这里插入图片描述 在这里插入图片描述

6、接受链接 SOCKET WSAAPI accept( SOCKET s, sockaddr *addr, int *addrlen );

作用:允许在套接字上进行传入连接尝试。listen监听客户端来的链接,accept将客户端的信息绑定到一个socket上,也就是给客户端创建一个socket,通过返回值返回给我们客户端的socket。(一次只能创建一个,有几个客户端链接,就要调用几次。) 参数:

s — socketaddr — 客户端地址和端口addrlen — 参数二的大小

注:如果参数二和参数三都填NULL,那就不是直接得不到客户端信息。可以通过函数得到客户端信息getpeername(newSocket, (struct sockaddr*)&sockClient, &nLen);。对应的得到本地服务器信息getsockname(sSocket, (sockaddr*)&addr, &nLen);。

返回值:

如果成功,返回客户端socket。如果失败,返回INVALID_SOCKET。

代码:

// 接受链接 SOCKADDR_IN sockClient; int nLen = sizeof(sockClient); SOCKET socketClient = accept(socketServer, (struct sockaddr*)&sockClient, &nLen); if (INVALID_SOCKET == socketClient) { printf("accept 失败 error:%d\n", WSAGetLastError()); closesocket(socketServer); WSACleanup(); return -1; } printf("客户端连接成功\n"); 7、与客户端收发消息 int WSAAPI recv( SOCKET s, char *buf, int len, int flags );

功能:得到指定目标(参数1)发来的消息 参数:

s — socketbuf — 消息数据的存储空间(网络传输得最大单元MTU为1500字节,也就是客户端发过来得数据,一次最大就是1500字节,当然可以减去报文的40字节,这是协议规定,这个数值也是根据很多情况,总结出来得最优值。)len — buf参数指向的缓冲区的长度(以字节为单位)flags — 数据的读取方式 值含义MSG_PEEK窥视传入的数据。 数据将复制到缓冲区中,但不会从输入队列中删除。MSG_OOB处理带外(OOB)数据。就是传输一段数据,在外带一个额外的特殊数据。不建议被使用,实在不行send两次。MSG_WAITALL当满足事件之一(调用方提供的缓冲区已满、连接已关闭、该请求已被取消或发生错误)就读取数据,并从输入队列中删除。0有数据就读取数据,并从输入队列中删除。

返回值:

如果成功,返回接收到的字节数。如果已正常关闭连接,返回值为零。如果失败,返回SOCKET_ERROR。 int WSAAPI send( SOCKET s, const char *buf, int len, int flags );

功能:向目标发送数据 参数:

s — socketbuf — 要发送的数据len — buf参数指向的缓冲区的长度(以字节为单位)flags — 数据的发送方式 值含义MSG_OOB与上面对应处理带外(OOB)数据。MSG_DONTROUTE指定数据不应受路由限制。 Windows套接字服务提供程序可以选择忽略此标志。0正常发送

返回值:

如果成功,返回已发送的字节数。如果失败,返回SOCKET_ERROR。

代码:

// 与客户端收发消息 while (1) { // 缓冲区 char szRecvBuffer[1500]; char szSendBuffer[1500]; int result = recv(socketClient, szRecvBuffer, sizeof(szRecvBuffer), 0); if (0 == result) // 客户端正常关闭 { printf("客户端正常下线\n"); break; // 因为这个例子只接受一次客户端请求所以服务器关闭 } else if (SOCKET_ERROR == result) // recv出错 { printf("recv 失败 error:%d\n", WSAGetLastError()); break; } else // 给客户端发消息 { // 接收到客户端消息 printf("Client Data : %s \n", szRecvBuffer); // 给客户回信 scanf_s("%s", szSendBuffer, 1500); getchar(); if (SOCKET_ERROR == send(socketClient, szSendBuffer, strlen(szSendBuffer) + 1, 0)) { printf("send 失败 error:%d\n", WSAGetLastError()); break; } } } 客户端 1、打开网络库

与服务器类似

2、校验版本

与服务器类似

3、创建socket

与服务器类似

4、连接服务器 int WSAAPI connect( SOCKET s, const sockaddr *name, int namelen );

功能:连接服务器 参数:

s — socketname — 服务器ip地址端口号结构体namelen — 参数二的大小

返回值:

如果成功,返回0。如果失败,返回SOCKET_ERROR。

代码:

// 连接服务器 SOCKADDR_IN sockAddress; sockAddress.sin_family = AF_INET; sockAddress.sin_addr.s_addr = inet_addr("127.0.0.1"); sockAddress.sin_port = htons(6666); if (SOCKET_ERROR == connect(socketClient, (struct sockaddr*)&sockAddress, sizeof(sockAddress))) { printf("connect 失败 error:%d\n", WSAGetLastError()); closesocket(socketClient); WSACleanup(); return -1; } 5、与客户端收发消息

与服务器类似

类比

我们可以把操作socket类比于操作file:

socket文件建立socket socket声明File*连接服务器 connect打开文件fopen向服务器发送数据 send写文件fwrite接收服务器数据 recv读文件fread关闭socket closesocket关闭文件fclose 运行结果

在这里插入图片描述

源码链接

百度云链接:https://pan.baidu.com/s/1xBOiSADlAG2gO1TC6BBO_A 提取码:sxbd

遇到的问题 头文件冲突

代码:

#include #include int main() { return 0; }

报错: 在这里插入图片描述 解决方法:

交换#include 写在#include 上方在头文件上方添加宏#define WIN32_LEAN_AND_MEAN


【本文地址】

公司简介

联系我们

今日新闻


点击排行

实验室常用的仪器、试剂和
说到实验室常用到的东西,主要就分为仪器、试剂和耗
不用再找了,全球10大实验
01、赛默飞世尔科技(热电)Thermo Fisher Scientif
三代水柜的量产巅峰T-72坦
作者:寞寒最近,西边闹腾挺大,本来小寞以为忙完这
通风柜跟实验室通风系统有
说到通风柜跟实验室通风,不少人都纠结二者到底是不
集消毒杀菌、烘干收纳为一
厨房是家里细菌较多的地方,潮湿的环境、没有完全密
实验室设备之全钢实验台如
全钢实验台是实验室家具中较为重要的家具之一,很多

推荐新闻


图片新闻

实验室药品柜的特性有哪些
实验室药品柜是实验室家具的重要组成部分之一,主要
小学科学实验中有哪些教学
计算机 计算器 一般 打孔器 打气筒 仪器车 显微镜
实验室各种仪器原理动图讲
1.紫外分光光谱UV分析原理:吸收紫外光能量,引起分
高中化学常见仪器及实验装
1、可加热仪器:2、计量仪器:(1)仪器A的名称:量
微生物操作主要设备和器具
今天盘点一下微生物操作主要设备和器具,别嫌我啰嗦
浅谈通风柜使用基本常识
 众所周知,通风柜功能中最主要的就是排气功能。在

专题文章

    CopyRight 2018-2019 实验室设备网 版权所有 win10的实时保护怎么永久关闭