Windows 网络通信套接字技术

文章目录

一、TCP/IP介绍

1、TCP/IP体系结构

TCP/IP协议实际上就是在物理网上的一组完整的网络协议。其中TCP是提供传输 层服务,而IP则是提供网络层服务。TCP/IP协议包括如下协议,其结构如图所示。

Windows 网络通信套接字技术
IP: 网间协议(Internet Protocol) 负责主机间数据的路由和网络上数据的存储。 同时为ICMP,TCP,UDP提供分组发送服务。用户进程通常不需要涉及这一层。

ARP: 地址解析协议(Address Resolution Protocol),此协议将网络地址映射 到硬件地址。

RARP:反向地址解析协议(Reverse Address Resolution Protocol),此协议 将硬件地址映射到网络地址。

ICMP:网间报文控制协议(Internet Control Message Protocol),此协议处理 信关和主机的差错和传送控制。

TCP:传送控制协议(Transmission Control Protocol),这是一种提供给用户 进程的可靠的全双工字节流面向连接的协议。它要为用户进程提供虚电路服务, 并为数据可靠传输建立检查。
UDP:用户数据报协议(User Datagram Protocol),这是提供给用户进程的无 连接协议,用于传送数据而不执行正确性检查。
FTP:文件传输协议(File Transfer Protocol),允许用户以文件操作的方式
(文件的增、删、改、查、传送等)与另一主机相互通信。
SMTP:简单邮件传送协议(Simple Mail Transfer Protocol),SMTP协议为系 统之间传送电子邮件。

TELNET:终端协议(Telnet Terminal Procotol),允许用户以虚终端方式访问 远程主机。

HTTP:超文本传输协议(Hypertext Transfer Procotol)。

TFTP:简单文件传输协议(Trivial File Transfer Protocol)。

2、TCP/IP特点

TCP/IP协议的核心部分是传输层协议(TCP、UDP)、网络层协议(IP)和物理 接口层,这三层通常是在操作系统内核中实现,因此用户一般不涉及。编程时, 编程界面有两种形式:

第一种是由内核心直接提供的系统调用;

第二种是使用以库函数方式提供的各种函数。前者为核内实现,后者为核外实现。 用户服务要通过核外的应用程序才能实现,所以要使用套接字(socket)来实现。 TCP/IP协议核心与应用程序关系如图所示。
Windows 网络通信套接字技术

3、TCP/IP协议与WinSock网络编程接口的关系

WinSock并不是一种网络协议,它只是一个网络编程接口,也就是说,它不是协议,但是它可以访问很多种网络协议,可以把它当作一些协议的封装。现在的
WinSock已经基本上实现了与协议无关。可以使用WinSock来调用多种协议的功能。 那么,WinSock和TCP/IP协议到底是什么关系呢?实际上,WinSock就是TCP/IP
协议的一种封装,可通过调用WinSock的接口函数来调用TCP/IP的各种功能。

二、套接字原理

1、客户机/服务器模式

在TCP/IP网络中两个进程间的相互作用的主机模式是客户机/服务器模式
(Client/Server model)。该模式的建立基于以下两点:
第一,非对等作用;
第二,通信完全是异步的。

客户机/服务器模式在操作过程中采取的是主动请示方式:

服务器端:

首先服务器方要先启动,并根据请示提供相应服务,过程如下:

  • 打开一通信通道并告知本地主机,它愿意在某一个公认地址上接收-客户请求;
  • 等待客户请求到达该端口;
  • 接收到重复服务请求,处理该请求并发送应答信号;
  • 返回第二步,等待另一客户请求;
  • 关闭服务器。

客户端:

  • 打开一通信通道,并连接到服务器所在主机的特定端口;
  • 向服务器发送服务请求报文,等待并接收应答;继续提出请求……;
  • 请求结束后关闭通信通道并终止。

2、基本套接字

为了更好说明套接字编程原理,这里介绍几个基本的套接字。

  1. 创建套接字——socket()
    功能:使用前创建一个新的套接字。
    格式:SOCKET PASCAL FAR socket(int af,int type,int procotol);
    参数:af:通信发生的区域
    type:要建立的套接字类型
    procotol:使用的特定协议
  2. 指定本地地址——bind()
    功能:将套接字地址与所创建的套接字号联系起来。
    格式:int PASCAL FAR bind(SOCKET s,const struct sockaddr FAR * name,int namelen);
    参数:s是由socket()调用返回的并且未作连接的套接字描述符(套接字号)。
    其它:没有错误,bind()返回0,否则SOCKET_ERROR。
    Windows 网络通信套接字技术
  3. 建立套接字连接——connect()和accept()
    功能:共同完成连接工作。
    格式:int PASCAL FAR connect(SOCKET s,const struct sockaddr FAR * name,int namelen);
    SOCKET PASCAL FAR accept(SOCKET s,struct sockaddr FAR *name,int FAR * addrlen);
    参数:同上
  4. 监听连接——listen()
    功能:用于面向连接服务器,表明它愿意接收连接。
    格式:int PASCAL FAR listen(SOCKET s, int backlog);
  5. 数据传输——send()与recv()
    功能:数据的发送与接收。
    格式:int PASCAL FAR send(SOCKET s,const char FAR * buf,int len,int flags);
    int PASCAL FAR recv(SOCKET s,const char FAR * buf,int len,int flags);
    参数:buf指向存有传输数据的缓冲区的指针。
  6. 多路复用——select()
    功能:用来检测一个或多个套接字状态。
    格式:int PASCAL FAR select(int nfds,fd_set FAR * readfds,fd_set FAR * writefds, fd_set FAR * exceptfds,const struct timeval FAR * timeout);
    参数:readfds指向要做读检测的指针
    writefds指向要做写检测的指针
    exceptfds指向要检测是否出错的指针
    timeout为最大等待时间
  7. 关闭套接字——closesocket()
    功能:关闭套接字s。
    格式:BOOL PASCAL FAR closesocket(SOCKET s);

3、典型过程图

  1. 面向连接(基于TCP)socket 编程
    为了实现服务器与客户机的通信,服务器和客户机都必须建立套接字。服务器 与客户机的工作原理可以用下面的过程来描述:
    Windows 网络通信套接字技术
    Windows 网络通信套接字技术
    客户机与服务器建立面向连接的(基于TCP)套接字进行通信,请求与响应过程 及应用程序流程可用如图表示。
    Windows 网络通信套接字技术

  2. 面向无连接(基于UDP)的socket 编程
    无连接服务器一般都是面向事务处理的,一个请求一个应答就完成了客户程序与 服务程序之间的相互作用。
    Windows 网络通信套接字技术
    Windows 网络通信套接字技术

Windows 网络通信套接字技术

三、Windows Socket 程序设计

1、Windows Socket网络程序设计核心

1.启动与终止
在所有Windows Sockets 函数中,只有启动函数WSAStartup()和终止函数 WSACleanup()是必须使用的。启动函数必须是第一个使用的函数,而且它允许 指定 Windows Sockets API 的版本,并获得 SOCKETS的特定的一些技术细节。 函数原型如下:

int PASCAL FAR WSAStartup(WORD wVersionRequested, LPWSADATA  lpWSAData);

其中:wVersionRequested保证SOCKETS可正常运行的DLL版本,如果不支持, 则返回错误信息。

下面这段代码,说明如何进行WSAStartup()的调用。

WORD wVersionRequested;	// 定义版本信息变量 
WSADATA wsaData;		//定义数据信息变量
int err;				//定义错误号变量
wVersionRequested = MAKEWORD(1,1);	//给版本信息赋值
err = WSAStartup(wVersionRequested, &wsaData); //给错误信息赋值 if(err!=0)
{
return;			//通知用户找不到合适的版本
}
//确认 Windows Sockets DLL 支持 1.1 版本
//DLL 版本可以高于 1.1

//系统返回的版本号始终是最低要求的 1.1,即应用程序与DLL 中可支持的最低版 本号
if(LOBYTE(wsaData.wVersion)!= 1|| HIBYTE(wsaData.wVersion)!=1)
{
	WSACleanup(); //通知用户找不到合适的版本
 	return;
}

//Windows Sockets DLL 被进程接受,可以进入下一步操作

关闭函数使用时,任何打开并已连接的 SOCK_STREAM 套接字被复位,但那些 已由 closesocket()函数关闭的但仍有未发送数据的套接字不受影响,未发送的数 据仍将被发送。程序运行时可能会多次调用WSAStartuo()函数,但必须保证每次 调用时的 wVersionRequested 的值是相同的。

2.异步请求服务

Windows Sockets 除支持Berkeley Sockets中同步请求,还增加了一类异步请求 服务函数WSAAsyncGerXByY()。该函数是阻塞请求函数的异步版本。应用程序 调用它时,由 Windows Sockets DLL初始化这一操作并返回调用者,此函数返 回一个异步句柄,用来标识这个操作。当结果存储在调用者提供的缓冲区,并且 发送一个消息到应用程序相应窗口。

常用结构如下:

HANDLE taskHnd;
char hostname="rs6000";
taskHnd = WSAAsyncBetHostByName(hWnd,wMsg,hostname,buf,buflen);

需要注意,由于Windows 的内存对像可以设置为可移动和可丢弃,因此在操作 内存对象时,必须保证WIindows Sockets DLL对象是可用的。

3.异步数据传输
使用send()或sendto()函数来发送数据,使用recv()或recvfrom()来接收数据。 Windows Sockets不鼓励用户使用阻塞方式传输数据,因为那样可能会阻塞整个 Windows 环境。下面看一个异步数据传输实例:
假设套接字s 在连接建立后,已经使用了函数 WSAAsyncSelect()在其上注册了 网络事件FD_READ和FD_WRITE,并且wMsg值为UM_SOCK,那么就可以在 Windows 消息循环中增加如下的分支语句:

case UM_SOCK:
switch(lParam)
{
case FD_READ:
len = recv(wParam,lpBuffer,length,0);  break;
case FD_WRITE:  while(send(wParam,lpBuffer,len,0)!=SOCKET_ERROR)  break;
}
break;

4.出错处理
Windows提供了一个函数来获取最近的错误码WSAGetLastError(),推荐的编写 方式如下:

len = send (s,lpBuffer,len,0);
of((len==SOCKET_ERROR)&&
	(WSAGetLastError()==WSAWOULDBLOCK))
{ 
	...
}

2、WinSock编程基本流程

WinSock编程分为服务器端和客户端两部分,TCP服务器端的大体流程如下:

  1. 对于任何基于WinSock的编程首先必须要初始化WinSock DLL库,使用接口函数:
int WSAStarup( WORD wVersionRequested,LPWSADATA lpWsAData )

初始化WinSock 。参数wVersionRequested是要求使用的WinSock的版本。

  1. 然后必须创建一个套接字(Socket),使用函数:
SOCKET Socket(int af,int type,int protocol)。

来实现。套接字可以说是WinSock通讯的核心。WinSock通讯的所有数据传输, 都是通过套接字来完成的,套接字包含了两个信息,一个是IP地址,一个是Port 端口号,使用这两个信息,就可以确定网络中的任何一个通讯节点。

  1. 当调用了Socket()接口函数创建了一个套接字后,必须把套接字与你需要进行通讯的地址建立联 系,可以通过绑定函数来实现这种联系:
int bind(SOCKET s,const struct sockaddr FAR* name,int namelen);  struct sockaddr_in
{
short sin_family ;  u_short sin_prot ;
struct in_addr sin_addr ;  char sin_sero[8] ;
}

这就包含了需要建立连接的本地的地址,包括地址族、IP和端口信息。 sin_family字段必须把它设为AF_INET,这是告诉WinSock使用的是IP地址族。 sin_prot就是要用来通讯的端口号。sin_addr就是要用来通讯的IP地址信息。
由于各种不同的计算机处理数据时的方法是不一样的,Intel X86处理器表示多字 节的编号时,把低字节放在前面,把高字节放在后面,而互联网标准却正好相反, 所以,必须把主机字节转换成网络字节的顺序。

WinSock API提供了几个函数:

把主机字节转化成网络字节的函数:
u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);
把网络字节转化成主机字节的函数:
u_long ntohl(u_long netlong);
u_short ntohs(u_short netshort) ;



这样,设置IP地址和port端口时,就必须把主机字节转化成网络字节后,才能用 Bind()函数来绑定套接字和地址。当绑定完成之后,服务器端必须建立一个监听 的队列来接收客户端的连接请求。
int listen(SOCKET s,int backlog);
这个函数可以把套接字转成监听模式。

如果客户端有了连接请求,还必须使用:
int accept(SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen);
来接受客户端的请求。
现在基本上已经完成了一个服务器的建立,而客户端的建立的流程则是初始化WinSock,然后创建Socket套接字,再使用:
int connect(SOCKET s,const struct sockaddr FAR* name,int namelen);
来连接服务端。



下面是一个最简单的创建服务器端和客户端的例子。

服务器端的创建:
WSADATA wsd;
SOCKET sListen;  
SOCKET sclient;  
UINT port = 800;  
int iAddrSize;
struct sockaddr_in local , client;  
WSAStartup(0x11 , &wsd);
sListen = Socket ( AF_INET , SOCK_STREAM , IPPOTO_IP);  local.sin_family = AF_INET;
local.sin_addr = htonl(INADDR_ANY );  
local.sin_port = htons( port );
bind( sListen , (struct sockaddr*)&local , sizeof( local ));  listen(sListen , 5);
sClient = accept(sListen , (struct sockaddr*)&client ,&iAddrSize);

客户端的创建:
WSADATA wsd;  
SOCKET sClient;  
UINT port = 800;// 此端口根据服务器提供
char szIp[] = "127.0.0.1";  
int iAddrSize;
struct sockaddr_in server;
WSAStartup( 0x11 , &wsd);

sClient = Socket( AF_INET , SOCK_STREAM , IPPOTO_IP);  server.sin_family = AF_INET;
server.sin_addr = inet_addr( szIp );  
server.sin_port = htons( port );
connect(sClient , (struct sockaddr*)&server , sizeof( server));

当服务器端和客户端建立连接以后,无论是客户端,还是服务器端都可以使用:

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

函数来接收和发送数据,因为,TCP连接是双向的。

当要关闭通讯连接的时候,任何一方都可以调用:

int shutdown(SOCKET s,int how);

来关闭套接字的指定功能,再调用:

int closeSocket(SOCKET s);

来关闭套接字句柄,这样一个通讯过程就算完成了。

上一篇:GDC翻译:Far Cry 5 的程序化世界生成(第一部分:1~5)


下一篇:POI2014 FAR-FarmCraft 树形DP+贪心