TCP与UDP的异同(服务端接收数据,客户端发送数据)
面向TCP连接的socket通信程序:
服务端:创建套接字,指定协议族(sockaddr_in),绑定,监听(listen),接受链接(accept),发送或接收数据;客户端:创建套接字,指定协议族,连接,发送或接收数据
这几个步骤都是必须的。
补充:在发送和接受数据时:write/send/sendto,read/recv/recvfrom都可以用,通常会用:send,recv;但需要注意的是:在面向UDP的socket程序中,发送数据时,如果用sendto的话,就不用connect了;但是,在面向TCP的程序中,在发送数据时,即使sendto,也必须connect,也就是说connect这一步是必不可少的。
面向UDP连接的socket通信程序:
服务端:创建套接字,指定协议族(sockaddr_in),绑定(不需要listen和accept),发送或接收数据;客户端:创建套接字,指定协议族,连接(和TCP的客户端步骤一样),发送或接收数据。
补充:在发送和接收数据时,和TCP大同小异,write/send/sendto,read/recv/recvfrom都可以用,但UDP通常会用sendto,recvfrom;需要注意的是:当用sendto发送数据的时候,就不用connect了(用了也没事),其他的(write,send)必须connect。
补充:无论是TCP还是UDP,默认情况下创建的都是阻塞模式(blocking)的套接字,执行到accept,connect,write/send/sendto,read/recv/recvfrom等语句时,会一直等待(connect有点例外,它连接一段时间,如果连接不成功,会以错误形式返回,不会一直等待)。
可以把socket设置成非阻塞模式,linux下用fcntl函数,windows下用的是ioctlsocket函数。(TCP和UDP设置成非阻塞模式以后,效果是一样的,都不再等待,而是立即返回。只是sendto和send一次发送的最大数据量可能不同,两种模式下返回的错误代码应该也是相同的)
设置成非阻塞模式以后,这些函数不再等待会立即返回(这和windows下是相同的),至于错误时返回的值应该也是和windows下相同的(具体没试,send和recv在windows错误时返回的值,请看2011-4-27的博客:“套接字的同步阻塞(blocking)与异步非阻塞(no blocking)”)。
TCP面向连接,UDP面向无连接(在默认的阻塞模式下):
read/recv/recvfrom:当客户端退出程序或断开连接时,TCP的这个函数会立即返回不再阻塞(因为服务端自己知道客户端已经退出或断开连接,证明它是面向连接的),而UDP的这个函数将会始终保持阻塞(因为服务端自己不知道客户端已经退出或断开连接,证明它是面向无连接的)。
TCP无边界,UDP有边界(在默认的阻塞模式下):
read/recv/recvfrom:TCP,客户端连续发送数据,只要服务端的这个函数的缓冲区足够大,会一次性接收过来(客户端是分好几次发过来,是有边界的,而服务端却一次性接收过来,所以证明是无边界的);UDP:客户端连续发送数据,即使服务端的这个函数的缓冲区足够大,也只会一次一次的接收,发送多少次接收多少次(客户端分几次发送过来,服务端就必须按几次接收,从而证明,这种UDP的通讯模式是有边界的)。
补充(来自网络):
1.socket()的参数不同
2.UDP Server不需要调用listen和accept
3.UDP收发数据用sendto/recvfrom函数
4.UDP:shutdown函数无效
5.TCP:地址信息在connect/accept时确定
UDP:在sendto/recvfrom函数中每次均需指定地址信息
Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接,所以在发送数据时应指明目的地址。
sendto()函数原型为:
int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen);
该函数比send()函数多了两个参数,to表示目地机的IP地址和端口号信息,而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。
Recvfrom()函数原型为:
int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen);
from是一个struct sockaddr类型的变量,该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时,fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1,并置相应的errno。
如果你对UDP模式的socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息。(这一点正说明了我在“面向UDP连接的socket通信程序”中所补充的内容)
总结:从sendto和recvfrom的后两个参数想到的:当客户端向服务端发送数据时,客户端必须知道服务端的IP地址和端口号,而结构体sockaddr_in正是完成了这项工作。所以在客户端程序中,一定要指定需要连接的服务端的IP地址和端口号(TCP中必须指定,因为它是面向连接的,交换数据前必须connect,而connect时就必须用到那个结构体;UDP可以不指定,那就需要让服务端先发送数据到客户端,客户端通过recvfrom函数接收,从而通过参数from得到了存放服务端IP地址和端口号的那个结构体,然后就可以通过它来交换数据了,后来一想这样不行,因为服务端向客户端发送数据时,在sendto中要指定客户端的IP地址和端口号,这个就又需要客户端也创建一个套接字打开一个端口并把它们绑定在一起接受连接。从而,得出的结论是:UDP中:无论服务端还是客户端,开始时,发送数据方一定要在程序中指定接收数据方的IP和端口,接收方通过recvfrom得到数据以后也就得到了发送方的IP和端口就可以通过这个结构体发送数据了实现了数据的双向传递(好像不对,UDP面向无连接,这个连接有可能过一会就断掉了,更重要的是,发送方根本就没有和socket绑定,也就是说那个端口是临时分配的,所以我认为即使成功也是偶然,有待证明);TCP不同:它是必须在客户端指定服务端的IP和端口,建立连接,然后两者可以任意接收和发送数据,实现双向数据传递)。
另外:
TCP在运行客户端connect之前必须先运行服务端,不然的话connect的连接会出错;UDP不一样(客户端发送信息,服务端接收信息,服务端需要绑定,客户端就需要指定服务端的IP和端口),先运行客户端和后运行客户端一样通信,只是当先运行客户端后运行服务端时,从客户端起来到服务端起来的这段时间内发送的数据服务端就收不到了(这也从另个方面体现了TCP的面向连接和UDP的面向无连接)。还有一点需要注意:UDP中,客户端先运行,用connect+write/send/sendto:在服务端起来之前不发送数据过去,起来之后,和后运行客户端收发数据一样,在服务端起来之前发送数据了,那么服务端起来之后,客户端第一次发送的数据,服务端会收不到,再发就一样了;用sendto(不用connect)的话,则不管服务端起来之前发没发数据,服务端起来之后都照常收发数据,从客户端起来到服务端起来的这段时间内发送的数据服务端就收不到了(这一点,或许是和上面所说的“如果你对UDP模式的socket调用了connect()函数时,你也可以利用send()和recv()进行数据传输,但该socket仍然是数据报socket,并且利用传输层的UDP服务。但在发送或接收数据报时,内核会自动为之加上目地和源地址信息”有关,有待进一步证明)。
最后再补充一个小知识点:sizeof()这个函数,参数是一个变量时根本不用括号(用的话也行),只要用空格隔开即可;但是如果是一个数据类型的话,则必须用括号。