代码改变世界

Windows Socket编程

2012-09-20 15:17  kalvin  阅读(448)  评论(0)    收藏  举报

 

Socket在英文中是插座的意思,设计者实际上也是暗指电话插座。

利用Socket进行通讯,主要有两种方式:

1.面向连接的流方式(TCP协议):通信可靠

2.无连接的数据报文方式:通信不可靠,但是通信速率快,适用于语音、图像和广播消息等。

在网络通讯中,由于网络拥挤或一次发送的数据量过大等原因,经常会发生交换的数据在短时间内不能传送完,收发数据的函数因此不能返回的现象,这种现象称为阻塞。

Winsock对可能阻塞的函数提供两种处理方式:

1.阻塞方式

在阻塞方式下,收发数据的函数在被调用后一直要到传送完毕或出错后才能返回,在阻塞期间,除了等网络操纵的完成不能进行任何其他操作。

2.非阻塞方式

在非阻塞方式,函数被调动后立即返回,当网络操作传送完成后,由Winsock给应用程序发送一个消息,通知操作完成,此时可以根据发送的消息传出的参数判断操作是否正常。

在编程时候应该尽量的使用非阻塞方式,因为在阻塞方式下,用户可能会因为等待的时间过长而失去等待的耐心,而关闭应用程序的主窗口。这样当网络操作的函数从Winsock的动态链接库蒸返回时,主程序已经从内存中删除,可能会造造成内存的异常。

Winsock基本Windows API

1.WSAStartup()

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

Windows Socket由DLL形式提供,为了完成一系列初始化操作,每一个使用Windows Socket的应用程序都必须进行WSAStartup()函数调用,并只有在成功地完成调用之后才能使用socket。

参数我wVersionRequested表示欲要使用的Windows Sockets API的版本:这死一个WORD型整数,它的高位字节定义的是次版本号,低位字节定义的是主版本号。

lpWSAData:指向WSADATA资料的指针。

传回值:成功返回0。

失败返回如下可能值:

WSASYSNOTREADY:表示网络设备没有准备好。

WSAVERNOTSUPPORTED:Winsock的版本信息号不支持。

WSAEINPROGRESS:一个阻塞式的Winsock1.1存在于进程中。

WSAEPROCLIM:已经达到Winsock使用量的上限。

WSAEFAULT:lpWSAData不是一个有效的指针。

2.socket():创建一个Socket

SOCKET socket(int af,int type,int proctocal);

所有的通信都是建立之前都要创建一个Socket,该函数的功能与文件操作中的fopen一样。afz指address family(地址簇),一般都填AF_INET,表示在Internet上的Socket,type是Socketd的类型,当采用流连接方式时用SOCK_STREAM,用数据报文方式时用SOCK_DGRAM.proctocal一般都为0,表示用对两种类型的Socket分别采用缺省的TCP和UDP传输协议。

函数返回值是由Winsock定义的一种数据类型SOCKET,它实际就是个整型数据在Socket创建成功时,代表Winsock分配给程序的Socket编号,后面调用传输函数时,就可以把它像文件指针一样引用。如果Socket建立失败,返回值为INVALID_SOCKET.

3.bind():创建的Socket指定通讯对象。

int bind(SOCKET S,struct sockadr_in *,int namelen);

S是已经创建好的一个SOCKET套接字,name是指向描述通信对象地址信息的结构体的指针,namelen是该结构体的长度。

sockaddr_in定义:

struct sockaddr_in(

       short                        sin_family,

       unsigned short          sin_port,

       struct in_addr           sin_addr,

       char                         sin_zero[8]);

sin_family是一套地址簇,通常被设成AF_INET,sin_port指端口号,sin_addr是指IP地址;sin_zero[8]主要是使用该结构体的大小和SOCKETADDR结构大小相同(SOCKADDR结构由一个无符号short型和一个长度为14的char型数组构成,这个结构一共16个字节),在sockaddr_in中添加这个长度8的数据,使得sockaddr_in的长度也是16,这样做的目的是使地址操作更方便。

4.设置等待连接状态Listen()

int Listen(SOCKET s, int backlog)

对于服务器的程序,当申请到Socket,并制定通讯对象INADDR_ANY之后,就应该等待一个客户机的程序来要求连接,listen()就是把一个Socket设置这种状态的函数。

参数backlog是等待连接的队列长度,可取1~5.如果当某个客户程序要求连接之时,服务器已与其他客户程序连接,则后来的连接请求会被放在队列中,等待服务器空闲的时候再与之连接,当队列达到指定长度时,再来连接请求都将被拒绝。

5.accept()

Socket accept(SOCKET s,struct sockaddr_in * addr,int * addrlen);

当没有连接请求时,对于阻塞方式,就进入等待状态,直至有一个请求到达为止。accept()在接收到连接请求之后,会为这个连接建立一个新的Socket来与对方通信,并把它作为返回值。新建的SOCKET与原来的Socket有相同的特性,包括端口号。原来的Socket被释放,用于继续等待其他的连接请求。而新生成的Socket才是与客户端进行通讯的实际Socket。所以一般将参数中的Socket称作“监听”Socket,它只负责接受连接,而不负责通话,而对于accept函数返回的SOCKET,把它称为“会话”Socket,它只负责与客户端通话。参数中的指针addr和addrlen用来返回客户机的sockaddr-in结构体,通过addr可得到客户机的IP地址和连接端口,具体内容见bind()函数。

注意:bind()\listen()\accept()函数一般都属于服务程序,属于被动等待的函数。

对于客户程序,要主动提出连接请求,应使用connect()函数。

int connect(SOCKET s,struct sockaddr_in * name,int namelen)

其中参数与bind函数相同,用来指定通讯对象,如果连接失败,该函数会返回SOCKET_ERROR

6 send()/recv()

int send(SOCKET s,char * buf,int len,int flags)

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

s是连接用的,socket、buf和len是发送或者接收的数据包及其长度,参数flags一般取0.recv()函数实际上读取send()函数发送过来的一个数据包,当读到的数据字节少于规定接收的数目时,就把数据全部接收,并返回实际结构的字节数,当读到的数据多于规定值时,在流方式下剩余的数据由下个recv()读出,在数据报文方式下多余的数据将被丢弃。这两个函数在出错时都返回SOCKET_ERROR。

而对数据报文通讯方式(UDP方式)由于通讯前,不需要进行连接,所以可以直接进行发送和接收数据。

int recvfrom(SOCKET s,char * buf,int len,int flags,struct sockaddr_in from,int * fromlen)

int sendto(SOCKET s,char * buf,int len ,int flags,struct socketaddr_in to,int * tolen) 

7.closesocket():关闭socket

流方式通讯server与client两端通讯。

Winsocket异步模式下通讯

Winsock通过异步选择寒素WSAAsyncSelect()来实现非阻塞通信。方法是由该函数指定某种网络时间(如数据到达、可以发送数据、有数据请求连接等),当被指定的网络时间发生时,由Winsock发送又程序事先约定的消息,程序中就可以根据这些消息做相应的处理。格式如下:

int WSAAsyncSelect(SOCKET s,HWND hWnd,unsigned int wMsg,long IEvent);

如果想将异步模式改为阻塞模式,先得将异步模式取消,方法是将IEvent参数设置为0;

ioctsocket():控制Socket的模式

int PASCAL FAR ioctsocket(SOCKET s,long cmd,u_long FAR *argP);

TCP套接字的阻塞调用过程(面向连接的流方式过程)

Server端

(1)调用WSAStartup()初始化Winsock

(2) 调用socket()创建一个监听Socket

(3)调用bind()为监听Socket指定通信对象

(4)调用listen()设置等待连接状态

(5)调用accept()接收连接并生成会话socket.

(7)调用send()和recv()进行对话

(8)closesocket()关闭sockt

Client端

(1)调用WSAStartup()初始化Winsock

(2) 调用socket()创建一个监听Socket

(3)调用accept()接收连接并生成会话socket.

(4)调用send()和recv()进行对话

(5)closesocket()关闭sockt