文章目录

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

简介

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

最简单的客户端、服务器通信流程大致如下:

收发信息可以进行很多次。

服务器

网络版本

我们使用新版本第二版

网络版本一:

#include <winsock.h>
#pragma comment(lib, "wsock32.lib")

网络版本二:

#include <WinSock2.h>
#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];
    }
    

    程序调试截图:

返回值:

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

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

// 开启网络库
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_INET 2 IPv4
    AF_INET6 23 IPv6
    AF_IRDA 26 红外
    AF_BTH 32 蓝牙
  • type — 套接字类型
    套接字类型 宏展开 含义
    SOCK_STREAM 1 一种套接字类型,提供带有OOB数据传输机制的顺序,可靠,双向,基于连接的字节流。 此套接字类型使用传输控制协议(TCP)作为Internet地址系列(AF_INET或AF_INET6)。
    SOCK_DGRAM 2 一种支持数据报的套接字类型,它是固定(通常很小)最大长度的无连接,不可靠的缓冲区。 此套接字类型使用用户数据报协议(UDP)作为Internet地址系列(AF_INET或AF_INET6)。
    SOCK_RAW 3 一种套接字类型,提供允许应用程序操作下一个上层协议头的原始套接字。 要操作IPv4标头,必须在套接字上设置IP_HDRINCL套接字选项。 要操作IPv6标头,必须在套接字上设置IPV6_HDRINCL套接字选项。
    SOCK_RDM 4 一种套接字类型,提供可靠的消息数据报。 这种类型的一个示例是Windows中的实用通用多播(PGM)多播协议实现,通常称为可靠多播节目。仅在安装了可靠多播协议时才支持此类型值。
    SOCK_SEQPACKET 5 一种套接字类型,提供基于数据报的伪流数据包。
  • protocol — 协议类型
    协议类型 含义
    IPPROTO_TCP 传输控制协议(TCP)。 当af参数为AF_INET或AF_INET6且类型参数为SOCK_STREAM时,这是一个可能的值。
    IPPROTO_UDP 用户数据报协议(UDP)。 当af参数为AF_INET或AF_INET6且类型参数为SOCK_DGRAM时,这是一个可能的值。
    IPPROTO_ICMP Internet控制消息协议(ICMP)。 当af参数为AF_UNSPEC,AF_INET或AF_INET6且类型参数为SOCK_RAW或未指定时,这是一个可能的值。
    IPPROTO_IGMP Internet组管理协议(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 — socket
  • name — 地址和端口
    typedef struct sockaddr {#if (_WIN32_WINNT < 0x0600)u_short sa_family;
    #elseADDRESS_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 — socket
  • backlog — 挂起的连接队列的最大长度。如果设置为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 — socket
  • addr — 客户端地址和端口
  • 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 — socket
  • buf — 消息数据的存储空间(网络传输得最大单元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 — socket
  • buf — 要发送的数据
  • 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 — socket
  • name — 服务器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 <Windows.h>
#include <WinSock2.h>int main()
{return 0;
}

报错:

解决方法:

  1. 交换#include <WinSock2.h>写在#include <Windows.h>上方
  2. 在头文件上方添加宏#define WIN32_LEAN_AND_MEAN

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

  1. Windows Socket 网络编程(一)

    **来源:http://www.vckbase.com/document/viewdoc/?id=472 Windows Socket 网络编程(一)** -- TCP/IP体系结构.特点及相关术语 ...

  2. linux网络编程一:主机字节序与网络字节序的的判断

     linux网络编程一:主机字节序与网络字节序的的判断(1)现代CPU的累加器一次能装载至少4字节(32位),即一个整数.那么这4字节在内存中排列的顺序将影响它被累加器装载成的整数值,这就是字节序问题 ...

  3. 使用Dev C++进行Windows socket网络编程,需链接lws2_32库

    背景 在我们使用Dev C++进行C语言编程时,如果我们引入的库是C语言标准库,那我们是不要在编译器选项中进行额外的设置的,但是如果我们使用的是一些不是C语言标准库,那我们可能就需要在编译器选择中进行 ...

  4. Windows Socket 网络编程(二)

    来源: http://www.vckbase.com/document/viewdoc/?id=484 Windows Socket 网络编程(二) -- 套接字编程原理 作者: 冰点工作室 小鹰 [ ...

  5. 深入分析websocket协议,从3个方面设计网络应用层协议丨网络编程|网络IO|epoll|socket|网络协议丨c/c++linux服务器开发

    深入分析websocket协议,从3个方面设计网络应用层协议 视频讲解如下: 深入分析websocket协议,从3个方面设计网络应用层协议丨网络编程|网络IO|epoll|socket|网络协议丨c/ ...

  6. daytime协议的服务器和客户端程序,用socket套接字实现daytime协议服务器和客户端程序.doc...

    文档介绍: 用socket套接字实现daytime协议服务器和客户端程序.doc一.设计目的为了提高同学的自主动手能力,把理论知识运用于实践中,从实践中更好的领悟所学的知识.二.题目要求及需求分析1. ...

  7. 网络编程之Telnet简单远程登录协议

    这里不讲解telnet协议的实现原理,主要用于学习telnet实际的编写方式. 使用的网络协议:tcp/ip telnet协议 编程语言:C/C++ 库:socket套接字 基于Windows的tel ...

  8. Windows Socket编程笔记之最简单的小Demo

    Windows Socket编程的大致过程: 服务器端: ----过程-------------对应的API-------  0.初始化         |  WSAStartup()  1.创建So ...

  9. linux网络编程一

    网络基础 协议的概念 什么是协议 从应用的角度出发,协议可理解为"规则",是数据传输和数据的解释的规则. 假设,A.B双方欲传输文件.规定: 第一次,传输文件名,接收方接收到文件名 ...

最新文章

  1. 绩效面谈流程,阿里是这样做的
  2. 模拟计算机怎么做,如何为具有独立模拟输出的计算机或音频系统制作8通道放大器...
  3. Gradle编译spring3.x报错找不到itextpdf4.2.2解决方案
  4. 调用import win32com.client出错的解决办法
  5. 2021-09-08FTRL 跟随正确的领导者
  6. 高级前端工程师知识图谱
  7. android---动画入门(一)
  8. 平面几何常用定理、结论总结 第一篇 三角形及其引线、引圆
  9. 学习go语言里Duck typing 概念
  10. 6.1.3. Mentor: Design Not Just for Usability, but Learnability
  11. Struts2检测工具
  12. 钉钉添加自定义机器人,实现每周定时@某人
  13. Doctrine 查询语法
  14. Github上的开源项目5
  15. 人类一败涂地怎么正在连接服务器,人类一败涂地联机显示正在连接服务器解决办法...
  16. ios - 7之状态栏黑底白字
  17. 父亲节送礼!购机推荐iQOO Neo6 SE与红米Note 11T Pro
  18. [2014/7]onscripter For windows/MAC/Linux V3中文版
  19. el-input 输入框禁止输入特殊字符
  20. Raptor实践参考:求圆周长

热门文章

  1. LOL 手游超简单安装!安卓+iOS全都有!
  2. stm8lLCD显示
  3. mpu6050原理图_STM32控制 MPU6050 六轴陀螺仪资料汇总
  4. python分析股票数据_Python股票分析系列——系列介绍和获取股票数据.p1
  5. C# 创建、部署和调用WebService的简单示例 webservice 可以用于分布式应用程序之间的交互,和不同程序之间的交互。 概念性的东西就不说太多,下面开始创建一个简单的webservi
  6. MyRPCDemo netty+jdk动态代理+反射+序列化,反序列化手写rpc框架
  7. 将 CARLA egg 文件安装到 Python 坏境
  8. 思维挑战11:摘苹果问题-一维数组
  9. Elasticsearch 系列(一) 简介
  10. python基于selenium实现自动删除qq空间留言板