Windows网络编程,相信好多人都知道,但是我们一般都是用其他语言编写,例如C,C++,JAVA,python等等,这些语言都可以,但是汇编语言比较底层,利用它,我们可以更清晰的了解到网络编程的内在部分,这是其他语言不能相比的,好了,废话不多说,这其实就是这次的目的(毕竟水平欠缺,还是先来按照罗云斌老师的WIN32汇编书上的例子加以学习,举一反三吧)。

说道网络编程,现在我所接触到的程序开发,工具软件的使用,库等等都是基于Windows平台的,想要了解Windows的网络编程就必须要知道WinSock接口(也就是在OSI模型的传输层和网络层,说到这里,相信大家都对计算机网络原理的OSI模型比较熟悉吧,这是网络编程的基础,对于IP地址,子网掩码,网关,协议等等,这些都是搞计算机必须清楚的概念吧,在这里省略数千字),windows编程都有一个特点(大家都知道,他隐藏的比较深,只给我们这些程序员提供函数接口,至于函数内部长得什么样子,是丑还是漂亮,跟我们没有关系,他也不让我们知道,就像GDI 接口,所有图像的处理都靠的是函数,我们需要传递的只有参数而已),关于windows网络编程的接口就是Winsock  ,那剩下的就是关于这个接口提供的一大堆的函数,下面看一下一张简单的介绍图


上面就是WinSock接口的DLL库文件版本,这些库文件在编写程序的时候,都需要声明相应的头文件,以及自己手动使用函数装入相应的库文件。 

接下来就是有关套接字(socket)的内容,套接字就是实现通信的对象,用来作为操作对象(一般对于函数来说),它是建立连接双方对象,就像是打电话的两个人一样。

实现一个简单的TCP服务器端的大致步骤:


1. 创建套接字(socket)

2.绑定IP地址和端口(bind)

3.监听进入的连接(Listen)

4.接受连接(accept)

5.收发数据(send/recv)

6.关闭连接(closesocket)


下面我们就着手程序的实现,首先看一下程序步骤:

                    资源文件

对话框(模态) IDD_DIALOG1
图标 IDI_ICON2
静态文本框1 不用对其进行操作,没有设置ID
静态文本框2 IDC_COUNT   用来显示连接的客户端数量



源程序
程序入口,创建模态对话框 DialogBoxParam()
窗口处理过程 _ProcDlgMain
监听线程 _ListenThread
通信线程 _ServiceThread


下面首先来看一下资源文件,这次资源编写依然是使用所见即所得的资源编辑工具ResEdit,上面的介绍中资源文件一共包括四个部分,首先是一个对话框,然后在该对话框中添加图标,和两个静态文本框一个用来显示提示字符,一个用来显示连接服务端的的客户数量,在这里都是使用的该工具默认的ID值,可以在使用该工具创建的工程的文件夹下的resource.h  文件中查看具体的ID值,如果想自己中自定义的话,可以自己添加,关于资源文件不多说了,下面直接看一下资源文件的代码:


以前都是直接复制的代码,但是看着总感觉不够直观,这次直接将截图上传过来,感觉清楚多了。


下面来看一下程序的实现过程,在这里首先需要注意的一个问题就是,看如下代码:


注意:

push ecx           

invoke CreateThread,NULL,0,offset _ServiceThread,eax,NULL,esp

pop ecx


看到这里,首先需要温习一下函数参数的压栈和调用函数时参数的使用,invoke伪指令就是先检查函数的参数

然后将参数压栈(push ),在这里CreateThread函数的最后一个参数lpThread (就是保存的新线程的ID)并没有什么用处,不需要专门为它定义一个变量,这里使用push   ecx指令临时在堆栈中分配一个dword空间供其使用,调用时使用esp将该临时空间的地址传给函数即可,然后在函数返回的时候直接用一个pop  ecx操作释放堆栈空间,当时我还在想,为什么只把ecx释放呢?因为其它的都是函数中的参数,函数内会将使用的堆栈空间释放掉,这个我们不用操心,为了更好地理解,我把该程序使用OD载入,我们来看一下,这几句代码是怎么操作的,如下图:



首先push ecx  在堆栈中开辟一个空间,而此时栈顶指针esp(栈底指针是ebp)正好指向 ecx(也就是开辟的空间),经典之处就在这里,当函数调用的时候,esp作为CreateThread函数的最后一个参数处理,此时esp是指向开辟的空间的,所以该空间存储的就是函数的最后一个参数的返回值(线程ID),因为这个值没有什么用处,所以不直接指定变量浪费空间。

其它的就不多说了,下面来看一下程序源代码:







下面来看一下一些陌生的结构类型和API函数:


Select()

功能:

确定一个或多个套接口的状态,本函数用于确定一个或多个套接口的状态,对每一个套接口,调用者可查询它的可读性、可写性及错误状态信息,用fd_set结构来表示一组等待检查的套接口,在调用返回时,这个结构存有满足一定条件的套接口组的子集,并且select()返回满足条件的套接口的数目。

原型:

int PASCAL FAR select( int nfds, fd_set FAR* readfds, fd_set FAR* writefds, fd_set FAR* exceptfds, const struct timeval FAR* timeout); 

参数:

nfds:是一个整数值,是指集合中所有文件描述符的范围,即所有文件描述符的最大值加1,不能错!在Windows中这个参数的值无所谓,可以设置不正确。

readfds:(可选)指针,指向一个fd_set 结构

writefds:(可选)指针,指向一个fd_set结构

exceptfds:(可选)指针,指向一个fd_set结构

timeout:等待时间,指向一个timeval结构

返回值:

elect()调用返回处于就绪状态并且已经包含在fd_set结构中的描述字总数;如果超时则返回0;否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError获取相应错误代码。

当返回位-1时,所有描述符集清0。

当返回为0时,超时不修改任何描述符集。

当返回为非0时,在3个描述符集里,依旧是1的位就是准备好的描述符。这也就是为什么,每次用select后都要用FD_ISSET的原因。

Recv()

功能:

用于已连接的数据报或流式套接口进行数据的接收。

原型:

int recv( _In_ SOCKET s, _Out_ char *buf lpbuf, _In_ int len, _In_ int flags);

参数:

s:   指定读取的套接字句柄

lpbuf: 指向一个用来返回数据的缓冲区

len:指定缓冲区的大小

flags:指定读取时的选项,它可以是MSG-PEEK和MSG_OOB的组合,MSG_PEEK表示返回数据后并不从缓冲区中清除数据

返回值:

若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

错误代码:

WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。

WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。

WSAENOTCONN:套接口未连接。

WSAEINTR:阻塞进程被WSACancelBlockingCall()取消。

WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。

WSAENOTSOCK:描述字不是一个套接口。

WSAEOPNOTSUPP:指定了MSG_OOB,但套接口不是SOCK_STREAM类型的。

WSAESHUTDOWN:套接口已被关闭。当一个套接口以0或2的how参数调用shutdown()关闭后,无法再用recv()接收数据。

WSAEWOULDBLOCK:套接口标识为非阻塞模式,但接收操作会产生阻塞。

WSAEMSGSIZE:数据报太大无法全部装入缓冲区,故被剪切。

WSAEINVAL:套接口未用bind()进行捆绑。

WSAECONNABORTED:由于超时或其他原因,虚电路失效。

WSAECONNRESET:远端强制中止了虚电路。

linux版本:

第四个参数:

MSG_DONTROUTE 绕过路由表查找。

MSG_DONTWAIT 仅本操作非阻塞。

MSG_OOB 发送或接收带外数据。

MSG_PEEK 窥看外来消息。

MSG_WAITALL 等待所有数据。

返回值:

若无错误发生,recv()返回读入的字节数。如果连接已中止,返回0。如果发生错误,返回-1,应用程序可通过perror()获取相应错误信息。

Send()

功能:

经套接字传送消息

原型:

send (int s,*buf lpbuf,size_t len,int flags);

参数:

s:指定套接字句柄

lpbuf:指向要发送数据的缓冲区

Len:指定要发送数据的长度

flags:参数指定选项,一般为0


返回值:成功则返回实际传送出去的字符数,失败返回-1


socket(

功能:

创建一个能够进行网络通信的套接字。

原型:

int socket(int af, int type, int protocol);

参数:

af:指定套接字使用的地址格式,对于TCP/IP协议族,该参数置AF_INET;

type:

指定要创建的套接字类型,流套接字类型为SOCK_STREAM、数据报套接字类型为SOCK_DGRAM、原始套接字SOCK_RAW(WinSock接口并不适用某种特定的协议去封装它,而是由程序自行处理数据包以及协议首部);

protocol:应用程序所使用的通信协议。此参数可以指定单个协议系列中的不同传输协议。在Internet通讯域中,此参数一般取值为0,系统会根据套接字的类型决定应使用的传输层协议。
返回值:
如果调用成功就返回新创建的套接字的描述符(句柄),如果失败就返回INVALID_SOCKET

CloseSocket()

功能:

关闭一个套接口。更确切地说,它释放套接口描述字s,以后对s的访问均以WSAENOTSOCK错误返回。若本次为对套接口的最后一次访问,则相应的名字信息及数据队列都将被释放

原型:

int PASCAL FAR closesocket( SOCKET s);

参数:

s:一个套接口的描述字(句柄)

返回值:

如无错误发生,则closesocket()返回0。否则的话,返回SOCKET_ERROR错误,应用程序可通过WSAGetLastError()获取相应错误代码。

错误代码:

WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。

WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。

WSAENOTSOCK:描述字不是一个套接口。

WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。

WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用。

WSAEWOULDBLOCK:该套接口设置为非阻塞方式且SO_LINGER设置为非零超时间隔。

htons()

功能:

将整型变量从主机字节顺序转变成网络字节顺序, 就是整数在地址空间存储方式变为:高位字节存放在内存的低地址处。

网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用big-endian排序方式。

原型:htons{u_short hostshort}

参数:

hostshort:16位无符号整数

返回值:

TCP/IP网络字节顺序.(32位的IP地址形式)


Bind()

功能:

将一本地地址与一套接口捆绑。

     原型参数:

int PASCAL FAR bind( SOCKET s, const struct sockaddr FAR* name,

int namelen);

s:标识一未捆绑套接口的描述字。

name:赋予套接口的地址。sockaddr结构定义如下:

struct sockaddr{

u_short sa_family;

char sa_data[14];

};

namelen:name名字的长度。

返回值:

成功返回0,失败返回-1.

Listen()

功能:
监听一个套接字
原型:

int PASCAL FAR listen (SOCKET s, int backlog);

参数:

s:  服务端套接字
backlog:等待连接队列的最大长度,

比方说,你将backlog定为10, 当有15个连接请求的时候,前面10个连接请求就被放置在请求队列中,后面5个请求被拒绝。千千万万要注意:这个10并不是表示客户端最大的连接数为10, 实际上可以有很多很多的客户端(实践证明也是如此)。

返回值:

成功返回0, 失败返回-1.

accept()

功能:

在一个套接口接受一个连接

原型参数:

SOCKET PASCAL FAR accept( SOCKET s, struct sockaddr FAR* addr,

int FAR* addrlen);

s:套接口描述字,该套接口在listen()后监听连接。

addr:(可选)指针,指向一缓冲区,其中接收为通讯层所知的连接实体的地址。Addr参数的实际格式由套接口创建时所产生的地址族确定。

addrlen:(可选)指针,指向存有addr地址长度的整形数。

返回值:

如果没有错误产生,则accept()返回一个描述所接受包的SOCKET类型的值。否则的话,返回INVALID_SOCKET错误,应用程序可通过调用WSAGetLastError()来获得特定的错误代码。

addrlen所指的整形数初始时包含addr所指地址空间的大小,在返回时它包含实际返回地址的字节长度。

错误代码:

WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。

WSAENETDOWN:WINDOWS套接口实现检测到网络子系统失效。

WSAEFAULT:addrlen参数太小(小于socket结构的大小)。

WSAEINTR:通过一个WSACancelBlockingCall()来取消一个(阻塞的)调用。

WSAEINPROGRESS:一个阻塞的WINDOWS套接口调用正在运行中。

WSAEINVAL:在accept()前未激活listen()。

WSAEMFILE:调用accept()时队列为空,无可用的描述字。

WSAENOBUFS:无可用缓冲区空间。

WSAENOTSOCK:描述字不是一个套接口。

WSAEOPNOTSUPP:该套接口类型不支持面向连接服务。

WSAEWOULDBLOCK:该套接口为非阻塞方式且无连接可供接受。

下面来看一下几个陌生的结构:

INADDR_ANY:

INADDR_ANY就是指定地址为0.0.0.0的地址,这个地址事实上表示不确定地址,或"所有地址"、"任意地址"。 一般来说,在各个系统中均定义成为0值。



WSAData

struct WSAData {

WORD wVersion;

WORD wHighVersion;

char szDescription[WSADESCRIPTION_LEN+1];

char szSystemStatus[WSASYSSTATUS_LEN+1];

unsigned short iMaxSockets;

unsigned short iMaxUdpDg;

char *lpVendorInfo;

};

元素介绍:

wVersion

Windows Sockets DLL期望调用者使用的Windows Sockets规范的版本。 高位字节存储副版本号, 低位字节存储主版本号,可以用WORD MAKEWORD(BYTE,BYTE ) 返回这个值,例如:MAKEWORD(1,1)

wHighVersion

这个DLL能够支持的Windows Sockets规范的最高版本。通常它与wVersion相同。

szDescription

以null结尾的ASCII字符串,Windows Sockets DLL将对Windows Sockets实现的描述拷贝到这个字符串中,包括制造商标识。文本(最多可以有256个字符)可以包含任何字符,但是要注意不能包含控制字符和格式字符,应用程序对其最可能的使用方式是把它(可能被截断)显示在在状态信息中。

szSystemStatus

以null结尾的ASCII字符串,Windows Sockets DLL把有关的状态或配置信息拷贝到该字符串中。Windows Sockets DLL应当仅在这些信息对用户或支持人员有用时才使用它们,它不应被作为szDescription域的扩展。

iMaxSockets

单个进程能够打开的socket的最大数目。Windows Sockets的实现能提供一个全局的socket池,可以为任何进程分配;或者它也可以为socket分配属于进程的资源。这个数字能够很好地反映Windows Sockets DLL或网络软件的配置方式。应用程序的编写者可以通过这个数字来粗略地指明Windows Sockets的实现方式对应用程序是否有用。例如,X Windows服务器在第一次启动的时候可能会检查iMaxSockets的值:如果这个值小于8,应用程序将显示一条错误信息,指示用户重新配置网络软件(这是一种可能要使用szSystemStatus文本的场合)。显然无法保证某个应用程序能够真正分配iMaxSockets个socket,因为可能有其它WindowsSockets应用程序正在使用。

iMaxUdpDg

Windows Sockets应用程序能够发送或接收的最大的用户数据包协议(UDP)的数据包大小,以字节为单位。如果实现方式没有限制,那么iMaxUdpDg为零。在Berkeley sockets的许多实现中,对于UDP数据包有个固有的限制(在必要时被分解),大小为8192字节。Windows Sockets的实现可以对碎片重组缓冲区的分配作出限制。对于适合的WindowsSockets 实现,iMaxUdpDg的最小值为512。注意不管iMaxUdpDg的值是什么,都不推荐你发回一个比网络的最大传送单元(MTU)还大的广播数据包。(Windows Sockets API 没有提供发现MTU的机制,但是它不会小于512个字节)。WinSock2.0版中已被废弃。

lpVendorInfo

指向销售商的数据结构的指针。这个结构的定义(如果有)超出了WindowsSockets规范的范围。

fd_set   struct

   fd_count     dword      ?             ;fd_array中存放的套接字句柄数量

   fd_array      dword        FD_SETSIZE DUP(?);套接字句柄列表

fd_set    ends


Sockaddr_in

折叠sockaddr_in(在netinet/in.h中定义):

struct sockaddr_in {

short sin_family; /* Address family */

unsigned short sin_port; /* Port number */

struct in_addr sin_addr; /* Internet address */

unsigned char sin_zero[8]; /* Same size as struct sockaddr */

};

sin_family指代协议族,在socket编程中只能是AF_INET

sin_port存储端口号(使用网络字节顺序),在linux下,端口号的范围0~65535,同时0~1024范围的端口号已经被系统使用或保留。

sin_addr存储IP地址,使用in_addr这个数据结构

sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。

s_addr按照网络字节顺序存储IP地址

sockaddr_in和sockaddr是并列的结构,指向sockaddr_in的结构体的指针也可以指向

sockaddr的结构体,并代替它。也就是说,你可以使用sockaddr_in建立你所需要的信息,

timeval    STRUCT

tv_sec          dword      ?      ;秒
tv_usec        dword       ?     ;微秒
timeval     ends


win32汇编实现一个简单的TCP服务端程序(WinSock的简单认知应用)相关推荐

  1. TCP服务端程序开发

    TCP服务端程序开发 1. 开发 TCP 服务端程序开发步骤回顾 创建服务端端套接字对象 绑定端口号 设置监听 等待接受客户端的连接请求 接收数据 发送数据 关闭套接字 2. socket 类的介绍 ...

  2. 网络编程之TCP服务端程序开发

    TCP服务端程序开发 学习目标 能够写出TCP服务端应用程序接收和发送消息 1. 开发 TCP 服务端程序开发步骤回顾 创建服务端端套接字对象 绑定端口号 设置监听 等待接受客户端的连接请求 接收数据 ...

  3. Go实现简单的TCP服务端客户端通信(有黏包)

    目录 客户端代码 服务端代码 封包协议 客户端代码 // socket_stick/client2/main.gofunc main() {conn, err := net.Dial("tc ...

  4. OSI七层模型以及TCP/UDP客户端/服务端程序实例

    OSI七层模型以及TCP/UDP客户端/服务端程序实例 一.前言 二.OSI简介 2.1 OSI概念 2.2 划分原则 2.3 OSI七层模型 2.4 模型举例 三.Linux下TCP/UDP程序开发 ...

  5. Qt 的 tcp服务端讲解

    TCP TCP : (Tramsmission Control Protocl)传输控制协议,一种面向连接的,可靠的,基于字节流的传输层通信协议. 本文目标 实现一个简单的服务器,可以接受客户端的信息 ...

  6. SVN服务端的搭建和简单使用

    SVN简单说明 SVN是Subversion的简称,是一个开放源代码的版本控制系统,相较于RCS.CVS,它采用了分支管理系统,它的设计目标就是取代CVS.互联网上很多版本控制服务已从CVS迁移到Su ...

  7. Go语言实现TCP服务端和客户端

    Go语言实现TCP服务端和客户端 Go语言实现TCP通信 TCP协议 TCP服务端 TCP客户端 本文转载自Go语言实现TCP通信 Go语言实现TCP通信 TCP协议 TCP/IP(Transmiss ...

  8. JAVA用最简单的方法来构建一个高可用的服务端,提升系统可用性

    一.什么是提升系统的高可用性 JAVA服务端,顾名思义就是23体验网为用户提供服务的.停工时间,就是不能向用户提供服务的时间.高可用,就是系统具有高度可用性,尽量减少停工时间.如何用最简单的方法来搭建 ...

  9. netty tcp服务端主动断开客户端_【Netty】服务端和客户端

    欢迎关注公众号:[爱编程] 如果有需要后台回复2019赠送1T的学习资料哦!! 本文是基于Netty4.1.36进行分析 服务端 Netty服务端的启动代码基本都是如下: private void s ...

  10. 【技术分享】linux各种一句话反弹shell总结——攻击者指定服务端,受害者主机(无公网IP)主动连接攻击者的服务端程序(CC server),开启一个shell交互,就叫反弹shell。...

    反弹shell背景: 想要搞清楚这个问题,首先要搞清楚什么是反弹,为什么要反弹. 假设我们攻击了一台机器,打开了该机器的一个端口,攻击者在自己的机器去连接目标机器(目标ip:目标机器端口),这是比较常 ...

最新文章

  1. PyTorch 数据并行处理
  2. 2017-2018-1 我爱学Java 第一周 作业
  3. mysql 5.6.34.rpm_离线安装mysql5.6及依赖_centos7离线安装mysql5.6.34
  4. em表示什么长度单位_em单位_html长度单位EM
  5. 博弈论进阶之Anti-SG游戏与SJ定理
  6. SpringBoot集成Spring Security(3)——异常处理
  7. 高并发情况下修改系统参数
  8. 手写Maven的archetype项目脚手架
  9. 利用计算机进行频数分布表制作,实验三 利用Excel软件作频数分布表和统计图表...
  10. 压铸件孔隙率的检测与等级测定
  11. 俄亥俄州立大学宣布开放 Swift 编程和 App 开发课程
  12. 关于pfx证书和cer证书
  13. 地图 显示 动态轨迹_动态轨迹怎么制作?华为Watch GT2轻松搞定
  14. Contest3410 - 2022大中小学生联合训练第五场
  15. Screenshots of Super Head Go-puzzle of colors
  16. linux应用程序注册表,如何打开 Linux 中 Windows 程序的注册表编辑器
  17. 国内外深度学习开放数据集下载集合(值得收藏,不断更新)
  18. C语言入门Part 3—大致梳理(中篇)
  19. python中syntaxerror什么意思_在Python中,“SyntaxError:调用'print'时缺少括号”是什么意思?...
  20. uni-app配置搜索框

热门文章

  1. 应用商店打开服务器错误,教你应用商店出现错误的修复方法
  2. c语言程序设计的例题,C语言程序设计例题与习题--详细介绍
  3. form 表单添加请求头_Golang GinWeb框架5-绑定多种请求类型的数据
  4. 32f4怎样同时采集两个adc_硬件-TI-ADC
  5. 百度糯米android面试题,【百度百度糯米队列百度编程基础数据安全面试题】面试问题:编程:使用C实… - 看准网...
  6. Vue-动态绑定属性
  7. 动态规划实战10 leetcode-64. Minimum Path Sum
  8. php图片位置偏移代码,关于PHP的fseek()方法,究竟是什么偏移(位置)?
  9. Angr安装与使用之使用篇(八)
  10. (2)二进制文件方式部署Kubernetes高可用集群----------部署环境准备