Windows socket,socket编程语言
转载地址:3358 hi . Baidu . com/huangfei 564/blog/item/c 8 c 9 c 8 e 6b 323 fc 503d 9202 . html
WinSock编程
用WinSock API编程要了解TCP/IP的基础知识。虽然可以直接使用WinSock API编写网络应用,但是要编写优秀的网络应用,必须具备一定的TCP/IP协议知识。
1.TCP/IP协议与WinSock网络编程接口的关系
WinSock不是一个网络协议,它只是一个网络编程接口,也就是说,它不是一个协议,但是它可以访问很多种网络协议,你可以把它看成是一些协议的封装。现在WinSock已经基本实现了,和协议无关。可以用WinSock调用各种协议的函数。那么,WinSock和TCP/IP协议是什么关系呢?实际上,WinSock是TCP/IP协议的一种封装。通过调用WinSock的接口函数,可以调用TCP/IP的各种函数。比如我要用TCP/IP协议发送数据,你可以用WinSock的接口函数Send()调用TCP/IP发送数据的函数。至于如何发送数据,WinSock已经为你打包了这个功能。
2.TCP/IP协议简介
TCP/IP协议涵盖的范围很广。它是一个四层协议,包括各种硬件和软件要求的定义。TCP/IP协议的确切术语应该是TCP/UDP/IP协议。UDP(用户数据报协议)是一种保护消息边界的协议,不保证可靠数据的传输。TCP(传输控制协议)是一种流协议。它提供可靠、有序、双向和面向连接的传输。
保护消息边界是指传输协议将数据作为独立的消息在互联网上传输,接收方只能接收独立的消息。也就是说有一个保护消息边界,接收方一次只能接收到发送方的一个数据包。
面向流是指无保护的消息保护边界。如果发送方连续发送数据,接收方可能在一个接收动作中接收到两个或多个数据包。
比如说我们连续发三个包,大小分别是2k,4k,8k。这三个数据包已经到达接收端的网络堆栈。如果使用UDP协议,无论我们使用多大的接收缓冲区来接收数据,我们都必须有三个接收动作来接收所有的数据包。在使用TCP协议时,我们只需将接收缓冲区的大小设置在14k以上,就可以一次接收所有数据包,并且只需要一个接收动作。
这是因为UDP协议保护消息边界,因此每条消息都是独立的。另一方面,流将数据视为一串数据流,它不认为数据是一次一条消息。所以很多人在使用TCP协议进行通信时,并不知道TCP是基于流的传输。连续发送数据时,他们往往知道TCP会丢包。实际上,当他们使用的缓冲区足够大时,他们可能一次接收两个或更多的数据包,而很多人往往会忽略这一点,只解析和检查第一个数据包,而忽略其他接收到的数据包。
3.3的简单过程。WinSock编程
WinSock编程分为两部分:服务器端和客户端。TCP服务器的一般流程如下:
对于任何基于WinSock的编程,必须首先初始化WinSock DLL库。
int WSAStarup(WORD wversion requested,LPWSADATA lpWsAData).
WVersionRequested是我们需要使用的WinSock版本。
调用这个接口函数可以初始化WinSock。
然后,您必须创建一个套接字。
SOCKET Socket(int af,int type,int protocol);
套接字是WinSock通信的核心。WinSock通信的所有数据传输都是通过套接字完成的。套接字包含两条信息,一条是IP地址,另一条是端口号。使用这两条信息,您可以确定网络中的任何通信节点。
调用Socket()接口函数创建套接字时,必须用需要通信的地址连接套接字。这种联系可以通过绑定函数来实现。
int bind(SOCKET s,const struct sockaddr FAR* name,int name len);
struct sockaddr_in{
短sin _ family
u _ short sin _ prot
结构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 hton l(u _ long host long);
u _ short htons(u _ short host short);
将网络字节转换成主机字节的功能;
u _ long ntohl(u _ long netlong);
u _ short ntohs(u _ short net short);
这样,在设置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 name len);
连接到服务器。
下面是创建服务器端和客户端的最简单的例子:
服务器创建:
WSADATA wsd
插座滑动;
套接字sclient
UINT端口=800;
int iAddrSize
struct sockaddr_in local,client
WSAStartup(0x11,wsd);
sListen=Socket ( AF_INET,SOCK_STREAM,IP poto _ IP);
local.sin _ family=AF _ INET
local . sin _ addr=htonl(in addr _ ANY);
local . sin _ port=htons(port);
bind( sListen,(struct sockaddr*) local,sizeof(local));
听(sListen,5);
sClient=accept( sListen,(struct sockaddr*) client,iAddrSize);
客户端创建:
WSADATA wsd
套接字sClient
UINT端口=800;
char szIp[]= 127 . 0 . 0 . 1 ;
int iAddrSize
struct sockaddr_in服务器;
WSAStartup(0x11,wsd);
sClient=Socket ( AF_INET,SOCK_STREAM,IP poto _ 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 close SOCKET(SOCKET s);
关闭套接字句柄,这样一个通信过程就完成了。
注意:上面的代码没有任何检查函数返回值。网络编程的话,一定要检查任何一个WinSock API函数的调用结果,因为很多时候函数调用不一定成功。上面描述的函数,如果返回值类型是int,如果函数调用失败,都会返回SOCKET_ERROR。
4.4的模型。WinSock编程
以上介绍的只是最简单的WinSock通信方式,但实际上在很多网络通信中有很多难以解决的突发情况。
例如,WinSock提供了两种套接字模式:锁定和解锁。使用锁定插座时,会用到许多功能,如accpet、send、recv等。如果没有要处理的数据,就不会返回,也就是说,你的应用程序会阻塞那些函数的调用。而如果使用非阻塞模式,调用这些函数,不管有没有数据,它都会返回。所以有可能我们在非阻塞模式下调用这些函数的时候,大多数情况下都会返回失败,所以我们需要处理很多意想不到的错误。
这显然不是我们希望看到的。我们可以使用WinSock通信模型来避免这些情况。
WinSock提供了五种套接字I/O模型来解决这些问题。它们是select、WSAAsyncSelect、WSAEventSelect、overlapped和completion端口。
下面是select和WSAASyncSelect的两个模型。
选择模型是最常见的I/O模型。使用
int select( int nfds,fd_set FAR* readfds,fd_set FAR* writefds,fd_set FAR* exceptfds,const struct time val FAR * time out);
函数检查要调用的套接字是否已经有要处理的数据。
Select包含三个套接字队列,分别代表:
Readfds,检查可读性,writefds,检查可写性,exceptfds,异常数据。
超时是选择函数的返回时间。
例如,要检查套接字是否有数据要接收,我们可以将套接字句柄添加到可读性检查队列中,然后调用select。如果没有要接收的数据,select函数会将socket从可读性检查队列中删除,所以我们只要检查socket句柄是否还存在于可读性检查队列中,就可以知道是否有要接收的数据。
WinSock提供了一些宏来操作套接字队列fd_set。
FD_CLR( s,*set)从队列集中删除句柄。
FD_ISSET( s,*set)检查队列集合中是否存在句柄s。
FD_SET( s,*set)将句柄s添加到队列集合。
FD_ZERO( *set)将集合队列初始化为空队列。
WSAASyncSelect(异步选择)模型:WSAAsyncSelect模型是在窗口和套接字句柄之间建立连接。当socket的一个网络事件发生时,会向窗口发送一个消息,然后在窗口的消息响应函数中可以接收和发送数据。
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的CSSocket类使用这个模型。