热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Windows网络编程(一)

Windows网络编程(一):创建链接文章目录Windows网络编程(一):创建链接一、使用前的

Windows网络编程(一):创建链接


文章目录

    • Windows网络编程(一):创建链接
      • 一、使用前的准备
      • 二、使用中的初始化
      • 三、错误处理
      • 四、很重要的一个概念——IP寻址
      • 五、很重要的socket连接概念
      • 六、创建一个服务器的监听模式
        • 1. 服务器开启TCP的过程
        • 2. 客户端连接服务器的API函数
        • 3. UDP的数据传输连接过程
      • 七、关于socket编程中INADDR_ANY的理解
      • 八、开始传输数据
        • 两个传输层协议的数据传输
        • (一)TCP的数据传输
          • 1. 发送函数
          • 2. 接收函数
        • (二)UDP的数据传输
          • 1. 发送数据
          • 2. 接收数据
      • 九、错误处理


一、使用前的准备

  必须使用到的头文件是winsock2.hwinsock.h两个头文件中选择一个。

  需要注意的是,单纯的引入这个头文件是不能完成代码的编译的。因为winsock需要使用静态链接库WSOCK32.LIB进行静态链接。因此需要采用以下其中之一的方式将静态库进入,否则在编译的时候会出现以下的错误:

NetDemo-12ac36.o : error LNK2019: 无法解析的外部符号 __imp_WSAStartup,该符号在函数 main 中被引用
NetDemo-12ac36.o : error LNK2019: 无法解析的外部符号 __imp_WSACleanup,该符号在函数 main 中被引用
NetDemo-12ac36.o : error LNK2019: 无法解析的外部符号 __imp_WSAGetLastError,该符号在函数 main 中被引用

  • 第一种方法是在代码中进行连接:

#pragma comment(lib, "WSOCK32")

  • 第二种方法是在编译是进行连接:

clang++ .\NetDemo.cpp -o NetDemo.exe -L F:\uCard\VC6.0green\VC98\Lib\ -lWSOCK32

二、使用中的初始化

  包含头文件之后,需要的做的第二件事就是将winsock进行初始化,初始化的过程主要是使用这个函数加载合适的winsock DLL。然后才能进行使用,使用的初始化函数原型如下:

int WSAStartup(WORD wVersionRequested,LPWSADATA lpWSAData
);

  然后使用winsock进行socket编程,使用完winsock之后,最终需要将socket进行释放资源,使用的是以下的函数原型如下:

int WSACleanup();

本函数原型的使用实例是:

WSAData wsadata;
WSAStartup(MAKEWORD(2, 2), &wsadata);
WSACleanup();

三、错误处理

  在进行编程的时候需要对在网络中出现的问题进行错误检查和错误处理,在这里使用的函数原型如下所示:

int WSAGetLastError();

  这个函数接收的错误必须是在winsock被加载完成之后,才能进行错误的获取,否则不能获取错误。正是因为这个原因,这个函数调用需要在WSAStartup之后获取错误代码。

四、很重要的一个概念——IP寻址

  理解结构体——SOCKADDR_IN,这个结构体主要的是以下结构:

struct sockaddr_in
{short sin_family; // 协议栈u_short sin_port; // 端口号struct in_addr sin_addr; // 自己的IP地址char sin_zero[8];// 用于填充的字节
};

  接下来分别介绍以上结构体中的各字段:

  • sin_familysin\_familysin_family:在IP协议栈中的值为AF_INET
  • sin_portsin\_portsin_port:设置IP地址的端口号
  • sin_addrsin\_addrsin_addr:IP地址的一个结构体:很重要,以下就是针对这个IP地址进行处理。

  主要的问题就是将点分十进制转换成为一个长整型函数,主要的函数原型就是以下的函数:

unsigned long inet_addr(const char FAR * cp);

  这样可能存在一个问题,对于不同的主机厂商,他们设定的IP地址在本机中的序列可能有所不同,即我们所说的大端字节序和小端字节序的区别。然而在同一个网络上就需要将以上的IP地址进行统一,在这里我们统一使用大端字节序作为网络上IP的标准。

于是针对这一需求,就存在大量的函数用于解决这个问题,将主机字节序转换成为网络字节序。下列的四个API都能实现以上的需求:

u_long htonl(u_long hostlong);// 返回值就是最终的数据
int WSAHtonl(SOCKET s, u_long hostlong, u_long FAR * lpnetlong);// 返回值放置在lpnetlong中
u_short htons(u_short hostshort);
int WSAHtons(SOCKET s, u_short hostshort, u_short FAR * lpnetshort);

实现反方向转化的函数为:

u_long htonl(u_long netlong);// 返回值就是最终的数据
int WSAHtonl(SOCKET s, u_long netlong, u_long FAR * lphostlong);// 返回值放置在lpnetlong中
u_short htons(u_short netshort);
int WSAHtons(SOCKET s, u_short netshort, u_short FAR * lphostshort);

敬请期待

五、很重要的socket连接概念

  socket在网络编程中是一个很重要的概念,针对socket这个结构体有以下的结构:

SOCKET socket(int af; // 协议的地址族int type; // 套接字的类型int protocol; // 使用的协议
);

  其实我们不难知道,socket是传输层的一个概念,因此以上的结构体如果我们采用TCP/IP协议族的话,那么以上结构体中各字段的含义如下:

  • afafaf:这个值就设置为和以上相同AF_INET
  • typetypetype:这个值根据TCP和UDP的不同而设置为不同的关键字——对于TCP这个字段的值设置为SOCK_STREAMSOCK\_STREAMSOCK_STREAM,对于UDP这个字段的值设置为SOCK_DGRAMSOCK\_DGRAMSOCK_DGRAM ,其实也不难理解,因为对于TCP的传输就是使用的流式数据传输;而针对UDP采用的是数据包的形式进行数据传输
  • protocolprotocolprotocol:这个就是协议了,根据使用的传输层协议设置这个字段,若采用的是TCP则本字段设置为IPPROTO_TCPIPPROTO\_TCPIPPROTO_TCP,若使用的协议为UDP,那么本字段的值设置为IPPROTO_UDPIPPROTO\_UDPIPPROTO_UDP

六、创建一个服务器的监听模式

  这个过程让我们来梳理一下,我们所说的服务器本质上就是一个进程,如果想要被客户端连接,那么服务器必须在一个已知名称(其实就是一个socket上,我们知道socket就是IP地址+端口号,时刻注意这个需要绑定的地址服务就是服务器自己的IP)上进行监听。那么这样一来整个服务器需要做的工作就比较明了了——

1. 服务器开启TCP的过程


  • 将WinSock所提供的套接字和已知的名称绑定起来。在这里使用的APIbind()bind()bind()

int bind(SOCKET s, // 等待客户连接的套接字,是客户的套接字const struct sockaddr FAR * name, // 就是一个普通的缓冲区。根据使用的协议必须实际地填充一个地址缓冲区,并在调用时,将其转成一个sockaddrint namelen// 由协议决定的要传递的地址结构的长度
);

SOCKET s;
SOCKETADDR_IN tcpaddr;
int port = 5150;s = socket(AF_INET, sOCK_STREAM, IPPROTO_TCP);
tcpaddr.sin_family = AF_INET;
tcpaddr.sin_port = htons(port);
tcpaddr.sin_addr.s_addr = htonl(INADDR_ANY);bind(s, (SOCKADDR *)&tcpaddr, sizeof(tcpaddr));

  • 然后将绑定的已知的名称进行监听——这里使用的APIlisten()listen()listen(),以等待客户端的连接,监听的函数原型是以下:

int listen(SOCKET s, // 这个是被绑定的套接字int backlog // 被阻塞的连接的最大队列长度
);

  • 如果有客户端连接到这个服务器,那么服务器需要做的就是接受这个请求,并和他建立连接,这里使用的APIaccept()accept()accept()

SOCKET accept(SOCKET s,struct sockaddr FAR * addr, // 用来存储请求客户端的IP地址int FAR * addrlen //
);

2. 客户端连接服务器的API函数

客户端创建socketsocketsocket连接服务器的主要过程是以下几步:

  1. 创建一个socketsocketsocket

  2. 建立一个SOCKADDRSOCKADDRSOCKADDR的地址结构,这个地址是准备连接到服务器的IP地址,在TCP/IPTCP/IPTCP/IP协议中,这个地址结构就是监听服务器的IP地址和端口号

  3. 使用connectconnectconnect初始化客户机与服务器的连接

int connect(SOCKET s,const struct sockaddr FAR * name,int namelen
);

3. UDP的数据传输连接过程

对于UDPUDPUDP连接的serverserverserver的主要工作的流程是以下的几步:

1)初始化socketsocketsocket

2)将socketsocketsocket绑定自己的ipipip地址和端口

3)recvfromrecvfromrecvfrom以获取客户端的ipipip地址,然后进行通信

4)sendtosendtosendto发送数据

对于UDPUDPUDP连接的clientclientclient的主要步骤是以下几步:

1)初始化socketsocketsocket

2)使用sendtosendtosendto向服务器的socketsocketsocket发送数据

存在的疑问就是:QQ用户即使在对方离线的情况下,是怎样能够收取对方在自己离线时发送给自己的信息

七、关于socket编程中INADDR_ANY的理解

  实际上这个INADDR_ANY转换成为点分十进制是0.0.0.0,这个地址让人感觉就比较迷茫。在网络地址中设置0.0.0.0这个地址的用意实际上是这样的——

  我们知道一台主机可能有多个IP地址,就比如我们之前学到的在进行环回测试时候的保留地址127.0.0.1,这个地址是不可能出现在任何公网情况下的,当我们的主机连接网络时,就会获取路由器为我们分配的一个IP地址,这样一看,我们的就存在两个IP。还有其他更多的情况是可能服务器有多个网卡什么的可能会有更多的地址,因此一台主机的IP地址可能存在多个

  即然存在以上的问题,(需求)那么我们当然希望访问主机的任意一个IP地址都能够得到服务器的响应,这其实就是存在的问题。如果我们按照原来的思路,将服务器的socket只绑定在一个IP上,那么结果就是——客户端只能通过这个唯一的IP访问服务器,剩下的IP就不能进行访问了。按照原来的绑定方式,那么只能将每个IP地址都绑定一个socket进行管理,这样就会相当繁琐,因此有以的方法——

  为了解决这个问题,于是设置将服务器的socket与0.0.0.0进行绑定,这个特殊的IP地址表示的是本机的所有IP地址,这样理解的话,也就是说我们使用一个socket就监听了好多地址,而且最后只需要管理一个socket。这就大大简化了最后的管理过程。

八、开始传输数据


两个传输层协议的数据传输


(一)TCP的数据传输


1. 发送函数

在已经建立连接的套接字上发送数据的主要API是sendsendsend,这个函数的原型是以下的方式:

int send(SOCKET s,const char FAR * buf,//指向缓冲区的指针int len,//缓冲区的大小int flags
);

2. 接收函数

主要的套接字函数接收是主要是API是recvrecvrecv,这个函数的原型是以下的方式:

int recv(SOCKET s,char FAR * buf,int len,int flags
);

(二)UDP的数据传输


1. 发送数据

在发送的过程中,使用的接口是sendtosendtosendto,这个函数的原型是以下的方式:

int sendto(SOCKET s,const char FAR * buf,int len, int flags,const struct sockaddr * FAR * to,int len
);

2. 接收数据

在接收的过程当中,使用的接口是recvfromrecvfromrecvfrom,这个函数主要是以下的方式,通过这个函数可以获取发送方的IP地址和端口号.

int recvfrom(SOCKET s,char FAR * buf,int len, int flags,struct sockaddr FAR * from,int FAR * fromlen
);

九、错误处理

windowssocketwindows\quad socketwindowssocket编程当中,也不是所有的都是一帆风顺的,因此对于错误的处理也应当足够引起我们的重视,一般来说,我们可以通过函数来获取winsockwinsockwinsock的错误类型,从而定位错误。

int WSAGetLastError(void);

在连接过程当中发生错误之后,可以调用这个函数,就可以获取返回的错误的整数代码。这些值可能存在于WINSOCK1.HWINSOCK1.HWINSOCK1.HWINSOCK2.HWINSOCK2.HWINSOCK2.H中。


推荐阅读
  • 全面介绍Windows内存管理机制及C++内存分配实例(四):内存映射文件
    本文旨在全面介绍Windows内存管理机制及C++内存分配实例中的内存映射文件。通过对内存映射文件的使用场合和与虚拟内存的区别进行解析,帮助读者更好地理解操作系统的内存管理机制。同时,本文还提供了相关章节的链接,方便读者深入学习Windows内存管理及C++内存分配实例的其他内容。 ... [详细]
  • 如何用JNI技术调用Java接口以及提高Java性能的详解
    本文介绍了如何使用JNI技术调用Java接口,并详细解析了如何通过JNI技术提高Java的性能。同时还讨论了JNI调用Java的private方法、Java开发中使用JNI技术的情况以及使用Java的JNI技术调用C++时的运行效率问题。文章还介绍了JNIEnv类型的使用方法,包括创建Java对象、调用Java对象的方法、获取Java对象的属性等操作。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 本文介绍了C#中生成随机数的三种方法,并分析了其中存在的问题。首先介绍了使用Random类生成随机数的默认方法,但在高并发情况下可能会出现重复的情况。接着通过循环生成了一系列随机数,进一步突显了这个问题。文章指出,随机数生成在任何编程语言中都是必备的功能,但Random类生成的随机数并不可靠。最后,提出了需要寻找其他可靠的随机数生成方法的建议。 ... [详细]
  • 如何用UE4制作2D游戏文档——计算篇
    篇首语:本文由编程笔记#小编为大家整理,主要介绍了如何用UE4制作2D游戏文档——计算篇相关的知识,希望对你有一定的参考价值。 ... [详细]
  • 本文介绍了在Windows环境下如何配置php+apache环境,包括下载php7和apache2.4、安装vc2015运行时环境、启动php7和apache2.4等步骤。希望对需要搭建php7环境的读者有一定的参考价值。摘要长度为169字。 ... [详细]
  • Android源码深入理解JNI技术的概述和应用
    本文介绍了Android源码中的JNI技术,包括概述和应用。JNI是Java Native Interface的缩写,是一种技术,可以实现Java程序调用Native语言写的函数,以及Native程序调用Java层的函数。在Android平台上,JNI充当了连接Java世界和Native世界的桥梁。本文通过分析Android源码中的相关文件和位置,深入探讨了JNI技术在Android开发中的重要性和应用场景。 ... [详细]
  • 本文介绍了Python爬虫技术基础篇面向对象高级编程(中)中的多重继承概念。通过继承,子类可以扩展父类的功能。文章以动物类层次的设计为例,讨论了按照不同分类方式设计类层次的复杂性和多重继承的优势。最后给出了哺乳动物和鸟类的设计示例,以及能跑、能飞、宠物类和非宠物类的增加对类数量的影响。 ... [详细]
  • 本文介绍了win7系统休眠功能无法启动和关闭的解决方法,包括在控制面板中启用休眠功能、设置系统休眠的时间、通过命令行定时休眠、手动进入休眠状态等方法。 ... [详细]
  • 本文介绍了一种轻巧方便的工具——集算器,通过使用集算器可以将文本日志变成结构化数据,然后可以使用SQL式查询。集算器利用集算语言的优点,将日志内容结构化为数据表结构,SPL支持直接对结构化的文件进行SQL查询,不再需要安装配置第三方数据库软件。本文还详细介绍了具体的实施过程。 ... [详细]
  • 本文介绍了一种图的存储和遍历方法——链式前向星法,该方法在存储带边权的图时时间效率比vector略高且节省空间。然而,链式前向星法存图的最大问题是对一个点的出边进行排序去重不容易,但在平行边无所谓的情况下选择这个方法是非常明智的。文章还提及了图中搜索树的父子关系一般不是很重要,同时给出了相应的代码示例。 ... [详细]
  • 本文介绍了NetCore WebAPI开发的探索过程,包括新建项目、运行接口获取数据、跨平台部署等。同时还提供了客户端访问代码示例,包括Post函数、服务器post地址、api参数等。详细讲解了部署模式选择、框架依赖和独立部署的区别,以及在Windows和Linux平台上的部署方法。 ... [详细]
  • 本文介绍了Hyperledger Fabric外部链码构建与运行的相关知识,包括在Hyperledger Fabric 2.0版本之前链码构建和运行的困难性,外部构建模式的实现原理以及外部构建和运行API的使用方法。通过本文的介绍,读者可以了解到如何利用外部构建和运行的方式来实现链码的构建和运行,并且不再受限于特定的语言和部署环境。 ... [详细]
  • 本文介绍了在多平台下进行条件编译的必要性,以及具体的实现方法。通过示例代码展示了如何使用条件编译来实现不同平台的功能。最后总结了只要接口相同,不同平台下的编译运行结果也会相同。 ... [详细]
  • C++字符字符串处理及字符集编码方案
    本文介绍了C++中字符字符串处理的问题,并详细解释了字符集编码方案,包括UNICODE、Windows apps采用的UTF-16编码、ASCII、SBCS和DBCS编码方案。同时说明了ANSI C标准和Windows中的字符/字符串数据类型实现。文章还提到了在编译时需要定义UNICODE宏以支持unicode编码,否则将使用windows code page编译。最后,给出了相关的头文件和数据类型定义。 ... [详细]
author-avatar
拍友2502882547
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有