Windows的网络编程-之二-面向连接的协议

来源:互联网 发布:数据库方言 编辑:程序博客网 时间:2024/06/05 17:57

1      面向连接的协议

1.1     服务器端函数

1.1.1   bind( )

一旦为某种特定协议创建了套接字,就必须将套接字绑定到一个已知地址:

intbind(  SOCKET s,  const struct sockaddr* addr,  int addrlen );

1.1.2   listen( )

第二步是将套接字置入监听模式:

intlisten(  SOCKET s,  int backlog );

参数backlog指定了可以等待连接的最大队列长度。

1.1.3   accept( )和WSAAccept( )

通过accpet( )和WSAAccept(),可以为等待连接队列中的第一个连接请求提供服务。

SOCKETaccept(  SOCKET s,  struct sockaddr* addr,  int* addrlen );

SOCKETWSAAccept(  SOCKET s,  struct sockaddr* addr,   int* addrlen,

LPCONDITIONPROClpfnCondition,  DWORD dwCallbackData);

参数addr返回发出连接请求的客户机的IP地址信息,而参数addrlen则返回参数addr的长度。

accept( )的返回值是一个新的套接字描述符,对于该客户机后续的所有操作,都使用这个新的套接字。原来的监听套接字仍然处于监听模式,用于接受其他客户机连接。

参数lpfnCondition指定一个回调函数,用于服务器收到连接请求后的处理,定义如下:

intCALLBACK ConditionFunc(  LPWSABUF             lpCallerId,                 IN

LPWSABUF              lpCallerData,            IN

LPQOS                 lpSQOS,                    IN/OUT

LPQOS                 lpGQOS,                   IN/OUT

LPWSABUF              lpCalleeId,                 IN

LPWSABUF              lpCalleeData,            OUT

GROUPFAR *   g,                                OUT

DWORD_PTR    dwCallbackData      IN   );

typedefstruct __WSABUF

{

u_long len;

char FAR* buf;

}WSABUF, *LPWSABUF;

参数lpCallerId指定了发出连接请求的客户机地址。

参数lpCallerData指定了和连接请求一道,由客户机发出的连接数据。若客户机没有指定连接数据,那么该参数就为NULL(默认值)。注意:大多数网络协议并不提供对连接数据的支持。

参数lpSQOS和lpGQOS指定客户机请求的服务质量。如果客户机没有要求QOS,这两个参数都为NULL。这两个参数的不同之处在于:lpSQOS指定一个独立的连接,而lpGQOS用于套接字组,在Winsock中不支持套接字组。

参数lpClalleeId指定了与客户机连接的服务器地址。如果服务器和INADDR_ANY地址绑定在一起,任何一个网络接口都可为连接请求提供服务,该参数会返回实际建立连接的那个接口。

参数lpClalleeData指定了服务器需要在连接请求进程中返回给客户机的数据。如果服务器支持该功能,就可以在回调函数中设定len为服务器可向客户机返回的最大字节数,服务器就会试图向buf中写入len长度的数据,并重新设定len为实际写入buf并传输的连接数据字节数。如果服务器不支持连接数据,len就为0。注意:大多数协议都不提供对连接数据的支持。

服务器将传递给回调函数的参数处理完之后,必须指出到底是接受、拒绝还是延后客户机的连接请求。如果服务器接受连接,那么回调函数就应返回CF_ACCEPT。如果拒绝,回调函数就应返回CF_REJECT。如果现在还不能做出决定,就应返回CF_DEFER。

注意:回调函数在与WSAAccept( )函数相同的进程内运行,而且会立即返回,客户机的连接请求不一定非要从回调函数返回一个值之后才会得到满足。大多数情况下,最底层的网络堆栈在回调函数调用的那一刻,就已经接受了连接请求。如果回调函数返回CF_REJECT值,底层堆栈就会将连接关闭。

1.2     客户端函数

1.2.1   connect( )和WSAConnect( )

intconnect(  SOCKET s,  const struct sockaddr* addr,  int addrlen );

intWSAConnect(  SOCKET s,  const struct sockaddr* addr,  int addrlen,

LPWSABUFlpCallerData,  LPWSABUF lpCalleeData,

LPQOSlpSQOS,  LPQOS lpGQOS  );

当参数s是SOCK_DGRAM类型时,addr指定的是在s上进行通信的对端主机的地址,可以对该类型的s绑定多次,来改变s所对应的通信对端的地址;

当参数s是SOCK_STREAM类型时,参数addr指定的是服务器地址,只能对该类型的s绑定一次。

参数lpCallerData指向的缓冲区用于存放客户机向服务器发出的请求连接时的数据。

参数lpCalleeData指向的缓冲区用于存放服务器向客户机返回的建立连接时的数据。

1.3     数据传输函数

1.3.1   send( )和WSASend( )

intsend(  SOCKET s,  const char* buf,  int len, int flags  );

intWSASend(  SOCKET s,  LPWSABUF lpBuffers,  DWORD dwBufferCount,

LPDWORDlpNumberOfBytesSent,  DWORD dwFlags,

LPWSAOVERLAPPEDlpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINElpCompletionRoutine

);

WSASend( )成功的话返回0,否则返回SOCK_ERROR。

参数flags可以取:0、MSG_DONTROUTE或MSG_OOB。

参数lpFlags可以取:0、MSG_DONTROUTE、MSG_OOB或MSG_PARTIAL。MSG_PARTIAL标志只用于基于消息的协议,表示发送缓冲区内只存放了消息的一部分。

参数lpBuffers和dwBufferCount分别指定发送数据的缓冲区和缓冲区中WSABUF结构的数目。在一个套接字上利用多缓冲来发送数据时,顺序是从第一个到最后一个WSABUF结构。

参数lpNumberOfBytesSent指向的缓存区用于存放实际发送的字节数。

1.3.2   recv( )和WSARecv( )

intrecv(  SOCKET s,  char* buf, int len,  int flags  );

intWSARecv(  SOCKET s,  LPWSABUF lpBuffers,  DWORD dwBufferCount,

LPDWORDlpNumberOfBytesRecvd,  LPDWORD lpFlags,

LPWSAOVERLAPPEDlpOverlapped,

LPWSAOVERLAPPED_COMPLETION_ROUTINElpCompletionRoutine

);

参数lpNumberOfBytesReceived指向的缓存区用于存放接收到的字节数。

参数flags可以取:0、MSG_PEEK或MSG_OOB。MSG_PEEK会将数据复制到接收端缓冲区内,但不从系统缓冲区中删除这些数据。

参数lpFlags可以取:0、MSG_PEEK、MSG_OOB或MSG_PARTIAL。MSG_PARTIAL只用于基于消息的协议,作为输出被设置时表示接收到的数据只是消息的一部分,作为输出被清除时表示已经接收到了全部消息;作为输入时表示即使只接收到了消息的一部分也要完成接收并返回。

 

intPASCAL FAR WSARecvEx(  SOCKET s,  char* buf, int len,  int* flags  );

WSARecvEx( )和recv( )功能一样,除了参数flags的取值和意义是和WSARecv( )一样的。

1.4     关闭连接

1.4.1   shutdown( )

为了保证接收端能够收到发送端发出的所有数据,接收端应该通知接收端不再发送数据:

intshutdown(  SOCKET s,  int how );

参数how可以取:SD_RECEIVE、SD_SEND或SD_BOTH。

SD_RECEIVE表示不允许再对该套接字调用接收函数。

SE_SEND表示不允许再对该套接字调用发送函数。对TCP套接字来说,SE_SEND会在所有数据发出,并得到接收端确认之后,生成一个FIN包。

SD_BOTH表示取消收发操作。

shutdown( )并没有真正关闭该套接字,该套接字所占用的资源直到调用closesocket( )才会被释放。为了能够保证所有的数据被发送和接收,一般要在调用closesocket( )之前调用shutdown()。一个良好的关闭连接的顺序如下:

1.调用WSAAsyncSelect( )注册FD_CLOSE;

2.调用shutdown( ),设定参数how = SD_SEND;

3.当收到FD_CLOSE时,调用recv( )一直到返回0或SOCKET_ERROR;

4.调用closesocket( )。

1.4.2   closesocket( )

intclosesocket(  SOCKET s  );

1.5     面向连接的套接字的特点

大部分面向连接的协议也是流式传输协议,所以面向连接的套接字也可以称为流套接字。

流套接字上收发数据所用的函数不能保证对请求的数据量进行读取或写入。要保证将所有的字节发出去,可采用下面的代码:

char sendbuf[2048];

int nBytes = 2048;

int nLeft, idx;

 

nLeft = nBytes;

idx = 0;

while( nLeft > 0 )

{

ret = send( s,&sendbuf[idx], nLeft, 0 );

if( ret = = SOCKET_ERROR )

{

// Error

}

nLeft -= ret;

idx += ret;

}

因为流套接字是一个不间断的数据流,在流套接字上接收数据时和应该读取多少字节的数据之间通常没有关系。在基于流的套接字中,接收可能在指定的缓冲区填满之前就返回。所以如果传输的消息长度不同,就有必要利用自己定义的协议来通知接收端即将到来的消息长度。

综上所述,对于流套接字来说,一次发送可能对应多次接收,发送和接收之间不具有一一对应的关系。


原创粉丝点击