面向连接的通信和无连接的通信
1.面向连接的通信
在IP中,面向连接的通信时通过TCP/IP协议来实现的。应用程序在使用TCP通信时,需要建立一个虚拟连接。其模型如下:
服务器端
一旦为协议创建了套接字,就要将套接字绑定到一个已知地址上,用bind函数来实现。其定义如下:
int bind( SOCKET s, const struct sockaddr FAR *name, int namelen);s:为要连接的套接字。name:其为类型为struct sockaddr。对于TCP协议要用结构体SOCKADDR_IN,要将该结构体转换为该类型。namelen:表示要传递的、由协议决定的地址结构的长度,即第二个参数的长度。
当将套接字绑定后,就是将套接字置于监听状态,函数为listen。其定义如下:
int listen( SOCKET s, int backlog);s:其为绑定的套接字。backlog:表示等待连接队列的最大长度。当服务器接受了一个连接,就将该连接请求从该队列中删除;当连接请求超过队列长度,就会发回WSAECONNREFUSED错误。
为了接受连接请求,要使用函数accept、WSAAccept或AcceptEx来实现。其中accept函数定义如下:
SOCKET accept( SOCKET s, struct sockaddr FAR* addr, int FAR* addrlen);s:为绑定的套接字。addr:为TCP应该是一个有效的SOCKADDR_IN的地址,如果是其他协议就应该是相应的SOCKADDR结构。addr:为SOCKADDR_IN结构体的长度。返回值:其返回一个新的套接字描述符,其后与客服端的所有操作都应该使用该新的套接字。
通过accept函数,可以讲等待连接队列的第一个请求提供服务。accept返回后,addr会被相应的对方的IP4信息填充。
客户端
客户端创建需要下面3个步骤:
1)创建一个套接字。2)建立一个SOCKADDR地址结构。其为服务器的IP地址和端口号。3)用connect或WSAConnect函数来与服务器建立连接。连接套接字通过函数conenct、WSAConnect或ConnectEx来完成。其中connect函数的定义如下:
int connect( SOCKET s, const struct sockaddr FAR* name, int namelen);s:为创建的套接字。name:是TCP的套接字地址结构SOCKADDR_IN,其为要连接的服务器。namelen:为name的长度。
数据传输
要在已建立的套接字上发送数据,可以使用两个函数:send和WSASend。其中send函数的定义如下:
int send( SOCKET s, const char FAR* buf, int len, int flags);s:为已建立的、用于发送数据的套接字。buf:其为指向字符的缓冲区,其为要发送的数据。len:指向发送的缓冲区的字符数,即要发送的数据长度。flags:可为0、MSG_DONTROUTE、MSG_OOB(紧急数据)。支持“或”运算。返回值:执行成功返回发送的字节数,执行错误返回SOCKET_ERROR。
Send API的Winsock 2版本的函数为WSASend,其定义如下:
int WSASend( SOCKET s. LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfByteSent, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);s:为准备发送数据的套接字。lpBuffers:指向一个或多个WSABUF结构的指针。既可以是一个独立的结构,也可以是一个结构数组。ldwBufferCount:传递的WSABUF结构的数量。lpNumberOfByteSent:其为已经发送了的字节数。dwFlags:其可为为MSG_PEEK、MSG_OOB和MSG_PARTIAL,支持“或”运算。lpOverlapped和lpCompletionRoutine:用于重叠I/O。返回值:执行成功返回0;失败返回SOCK_ERROR。
一个特殊的传输函数,该函数起初将套接字置于关闭状态,并发送断开的数据。只适用于从容关机和断开数据的传输协议。该函数的行为与利用SD_SEND参数调用shutdown函数差不多,但它还要发送参数boundDisconnectData中的数据。之后发送禁止在该套接字上进行。发送失败发挥SOCKET_ERROR。该函数为WSASendDisconnect。其定义如下:
int WSASendDisconnect( SOCKET s, LPWSABUF lpOutboundDisconnectData);
在已经连接的套接字上接收数据,有三个函数recv、WSARecv和WSARecvDisconnect。其中recv函数的定义如下:
int recv( SOCKET s, char FAR *buf, int len, int flags);s:为准备接收数据的套接字。buf:用于接收数据的缓冲区。len:准备接收的字节数或buf缓冲区的长度。flags:与send函数相同:0、MSG_PEEK、MSG_OOB。其中0表示什么行为;MSG_PEEK表示将可用数据复制到用户缓冲区中,但不从系统的缓冲区中删除。返回值:返回获取的数据字节数。
WSARecv函数在recv函数的基础上增加了一些新特性,如重叠I/O和部分数据报通知。其定义如下:
int WSARecv( SOCKET s. LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfByteRecvd, DWORD dwFlags, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);s:为准备接收数据的套接字。lpBuffers:指向一个或多个WSABUF结构的指针。既可以是一个独立的结构,也可以是一个结构数组。ldwBufferCount:传递的WSABUF结构的数量。lpNumberOfByteRecvd:其为已经接收到的字节数。dwFlags:其可为为MSG_PEEK、MSG_OOB和MSG_PARTIAL,支持“或”运算。lpOverlapped和lpCompletionRoutine:用于重叠I/O。返回值:执行成功返回0;失败返回SOCK_ERROR。
WSARecvDisconnect函数的定义如下:
int WSARecvDisconnect( SOCKET s, LPWSABUF lpInboundDisconnectData);其中s为已建立连接的套接字,lpInboundDisconnectData为WSABUF结构的接收数据的缓冲区。其可以接收断开数据,只能接收由WSASendDisconnect函数发送的数据,不能接收普通数据。其接收完断开数据,就会取消接收远程通信方的数据,其作用和带参数的SD_RECEIVE的shutdown函数相同。
注意:在流协议上使用发送和和接收数据,无法保证接收和发送的数据量。
中断连接
一旦用完了套接字连接,需要释放套接字连接,释放所有资源。可以有两个函数来实现:closesocket和shutdown。但是closesocket函数可能导致数据丢失,而shutdown就可以从容终止。
为了保证通信方能够接收到应用程序发出的所有数据,好的应用程序,应该通知对方不要在发送数据。这个可以通过shutdown函数来实现,该函数定义如下:
int shutdown( SOCKET s, int how);s:为要关闭的套接字。how:为SD_SEND、SD_RECEIVE和SD_BOTH中一个。SD_RECEIVE表示不允许在调用接收函数,表示要重置连接。SD_SEND表示不允许在调用发送函数,对于TCP会将所有数据发送完且得到确认后,发送FIN包。SD_BOTH表示取消连接两端的收发操作。但并非所有的协议都支持从容关闭,因此需要注意。
closesocket函数用于关闭套接字。该函数会释放套接字描述符,并且会将所有队列中的数据。其定义如下:
int closesocket(SOCKET s);
2.无连接通信
对于无连接的协议,操作过程相对比较简单。首先使用socket和WSASocket函数初始化套接字,然后使用bind函数将套接字绑定到准备接收数据的接口上,然后使用recvfrom和WSARecvFrom函数来接收数据,使用sendto和WSASendTo函数来发送数据。由于是无连接的,就没从容关闭和正常关闭的说法,如果套接字使用完了,可以调用closesocket函数来释放套接字分配的相关资源。
recvfrom函数的定义如下:
int recvfrom( SOCKET s, char FAR *buf, int len, int flags, struct sockaddr FAR *from, int fromlen);s、buf和len:和recv参数相同。flags:其可为MSG_OOB、MS_PEEK。from:其为一个SOCKADDR结构。fromlen:为from参数的长度。
recvfrom在Winsock2中的版本为WSARecvFrom,其定义如下:
int WSARecvFrom( SOCKET s. LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfByteRecvd, DWORD dwFlags, struct sockaddr FAR* lpfrom, LPINT lpFromlen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);发送函数sendto,其定义如下:
int sendto( SOCKET s, char FAR *buf, int len, int flags, struct sockaddr FAR *to, int tolen);
sendto在Winsock2中的版本为WSASendTo,其定义如下:
int WSASendTo( SOCKET s. LPWSABUF lpBuffers, DWORD dwBufferCount, LPDWORD lpNumberOfByteSent, DWORD dwFlags, struct sockaddr FAR* lpto, LPINT lptolen, LPWSAOVERLAPPED lpOverlapped, LPWSAOVERLAPPED_COMPLETION_ROUTINE lpCompletionRoutine);在无连接套接字上建立连接:无连接套接字一旦建立,就可以使用SOCKADDR结构体调用conenct和WSAConnect函数来建立“单向连接”,这样就可以使用recv和WSARecv函数或send和WSASend函数来进行收发数据了。
3.一些重要的API函数
1.getpeername
该函数用于获取通信方套接字的地址信息,该信息是关于已连接的那个套接字。其定义如下:
int getpeername( SOCKET s, sruct sockaddr FAR* name, int FAR* namelen);
2.getsockname
该函数获取给定套接字的本地接口的地址信息。其定义如下:
int getpsockname( SOCKET s, sruct sockaddr FAR* name, int FAR* namelen);
3.WSADuplicateSocket
WSADuplicateSocket函数用于建立WSAPROTOCOL_INFO结构,该结构体可传递到另一个进程。这样另一个进程就可以打开指向同一个套接字的句柄,这样这一个进程可以对该资源进行操作。其定义如下:
int WSADuplicateSocket( SOCKET s, DWORD dwProcessId, LPWSAPROTOCOL_INFO lpProtocolInfo);