您的位置:首页 > 编程语言

Windows Socket编程

2020-07-01 18:40 113 查看

本文转载自https://www.geek-share.com/detail/2473161321.html,并加以整理和修改。

1. TCP/IP协议与WinSock网络编程接口的关系
       WinSock并不是一种网络协议,它只是一个网络编程接口,也就是说,它不是协议,但是它可以访问很多种网络协议,你可以把他当作一些协议的封装。现在的WinSock已经基本上实现了与协议无关。你可以使用WinSock来调用多种协议的功能。那么,WinSock和TCP/IP协议到底是什么关系呢?实际上,WinSock就是TCP/IP协议的一种封装,你可以通过调用WinSock的接口函数来调用TCP/IP的各种功能.例如我想用TCP/IP协议发送数据,你就可以使用WinSock的接口函数Send()来调用TCP/IP的发送数据功能,至于具体怎么发送数据,WinSock已经帮你封装好了这种功能。

2.WinSock编程简单流程
       WinSock编程分为服务器端和客户端两部分,TCP服务器端的大体流程如下:
    (1)对于任何基于WinSock的编程首先必须要初始化WinSock DLL库。

[code]int WSAStarup( WORD wVersionRequested,LPWSADATA lpWsAData )。
//wVersionRequested是我们要求使用的WinSock的版本。
//调用这个接口函数可以初始化WinSock。

(2)创建一个套接字(Socket)。

[code]SOCKET Socket(int af,int type,int protocol)

套接字可以说是WinSock通讯的核心。WinSock通讯的所有数据传输,都是通过套接字来完成的,套接字包含了两个信息,一个是IP地址,一个是Port端口号,使用这两个信息,就可以确定网络中的任何一个通讯节点。
     (3)当调用了Socket()接口函数创建了一个套接字后,必须把套接字与你需要进行通讯的地址建立联系,可以通过绑定函数来实现这种联系。

[code]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地址信息。
       在这里,必须还得提一下有关'大端(big-endian)'小端(little-endian)'。因为各种不同的计算机处理数据时的方法是不一样的,Intel X86处理器上是用'小端'形式来表示多字节的编号,就是把低字节放在前面,把高字节放在后面,而互联网标准却正好相反,所以,必须把主机字节转换成网络字节的顺序。WinSock API提供了几个函数。
       把主机字节转化成网络字节的函数:

[code]u_long htonl(u_long hostlong);
u_short htons(u_short hostshort);

把网络字节转化成主机字节的函数:

[code]u_long ntohl(u_long netlong);
u_short ntohs(u_short netshort) ;

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

[code]int listen(SOCKET s,int backlog);

如果客户端有了连接请求,我们还必须使用accept来接受客户端的请求。

[code]int accept(SOCKET s,struct sockaddr FAR* addr,int FAR* addrlen);

现在基本上已经完成了一个服务器的建立,而客户端的建立的流程则是初始化WinSock,然后创建Socket套接字,再使用
int connect(SOCKET s,const struct sockaddr FAR* name,int namelen) ;来连接服务端。
下面是一个最简单的创建服务器端和客户端的例子
       服务器端的创建:

[code]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);

客户端的创建:

[code]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));

当服务器端和客户端建立连接以后,无论是客户端,还是服务器端都可以使用以下函数来接收和发送数据,因为,TCP连接是双向的。

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

当要关闭通讯连结的时候,任何一方都可以调用int shutdown(SOCKET s,int how);来关闭套接字的指定功能,再调用int closeSocket(SOCKET s) ;来关闭套接字句柄,这样一个通讯过程就算完成了。
        注意:上面的代码没有任何检查函数返回值,如果你作网络编程就一定要检查任何一个WinSock API函数的调用结果,因为很多时候函数调用并不一定成功。上面介绍的函数,返回值类型是int的话,如果函数调用失败的话,返回的都是SOCKET_ERROR。
        3.WinSock编程的模型
        上面介绍的仅仅是最简单的WinSock通讯的方法,而实际中很多网络通讯的却很多难以解决的意外情况。例如,WinSock提供了两种套接字模式:锁定和非锁定。当使用锁定套接字的时候,使用的很多函数,例如accpet、send、recv等等,如果没有数据需要处理,这些函数都不会返回,也就是说,你的应用程序会阻塞在那些函数的调用处。而如果使用非阻塞模式,调用这些函数,不管你有没有数据到达,他都会返回。所以有可能我们在非阻塞模式里,调用这些函数大部分的情况下会返回失败,所以就需要我们来处理很多的意外出错。这显然不是我们想要看到的情况。我们可以采用WinSock的通讯模型来避免这些情况的发生。

[code]u_long mode = 0;
ioctlsocket(s,FIONBIO,&mode);   //控制为阻塞方式。

u_long mode = 1;
ioctlsocket(s,FIONBIO,&mode);   //控制为非阻塞方式。

WinSock提供了五种套接字I/O模型来解决这些问题。他们分别是select(选择),WSAAsyncSelect(异步选择),WSAEventSelect (事件选择,overlapped(重叠) , completion port(完成端口) 。这里详细介绍一下select,WSAASyncSelect两种模型。Select模型是最常见的I/O模型。使用int select( int nfds , fd_set FAR* readfds , fd_set FAR* writefds,fd_set FAR* exceptfds,const struct timeval FAR * timeout ) ;函数来检查你要调用的Socket套接字是否已经有了需要处理的数据。select包含三个Socket队列,分别代表:readfds ,检查可读性,writefds,检查可写性,exceptfds,例外数据。timeout是select函数的返回时间。例如,想要检查一个套接字是否有数据需要接收,我们可以把套接字句柄加入可读性检查队列中,然后调用select,如果,该套接字没有数据需要接收,select函数会把该套接字从可读性检查队列中删除掉,所以我们只要检查该套接字句柄是否还存在于可读性队列中,就可以知道到底有没有数据需要接收了。
       WinSock提供了一些宏用来操作套接字队列fd_set。

[code]FD_CLR( s,*set) 从队列set删除句柄s。
FD_ISSET( s, *set) 检查句柄s是否存在与队列set中。
FD_SET( s,*set )把句柄s添加到队列set中。
FD_ZERO( *set ) 把set队列初始化成空队列。

WSAAsyncSelect(异步选择)模型:WSAASyncSelect模型就是把一个窗口和套接字句柄建立起连接,套接字的网络事件发生时时候,就会把某个消息发送到窗口,然后可以在窗口的消息响应函数中处理数据的接收和发送。int WSAAsyncSelect( SOCKET s, HWND hWnd , unsigned int wMsg , long lEvent ) ;这个函数可以把套接字句柄和窗口建立起连接,wMsg 是我们必须自定义的一个消息。lEvent就是制定的网络事件。包括FD_READ , FD_WRITE ,FD_ACCEPT,FD_CONNECT,FD_CLOSE 。几个事件。例如,需要接收FD_READ , FD_WRITE , FD_CLOSE 的网络事件。可以调用WSAAsyncSelect( s , hWnd , WM_SOCKET , FD_READ | FD_WRITE | FD_CLOSE ) ;这样,当有FD_READ , FD_WRITE 或者 FD_CLOSE网络事件时,窗口hWnd将会收到WM_SOCKET消息,消息参数的lParam标志了是什么事件发生,MFC的CSocket类,就是使用这个模型。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: