《TCP IP网络编程》第六章 基于UDP的服务器端/客户端

第六章 基于UDP的服务器端/客户端

6.1 理解UDP

TCP像电话一样可靠,UDP像寄信一样不可靠。

UDP可靠性略弱,但性能更强。

TCP为了提供可靠的数据传输服务,在不可靠的IP层进行流控制,而UDP缺少这种流控制机制。

每次交换的数据量越大,TCP的传输速率就越接近UDP的传输速率。

UDP内部工作原理

IP的作用:让离开主机B的UDP数据包准确传递给主机A
UDP的作用:根据端口号将传到主机的数据包交付给最终的UDP套接字

UDP的高效使用

UDP也是具有一定的可靠性的,不要认为它完全不可靠。

针对压缩数据包,丢一个包都可能导致最终无法解压缩成功,这种一般用TCP。
针对网络实时传输的视频、音频等多媒体数据,丢失一小部分没啥问题;但因为要提供实时的服务,速度就是非常重要的因素,此时需要用UDP。

TCP比UDP慢的原因:

  1. 收发数据前后进行的连接设置及清除过程
  2. 收发数据过程中为保证可靠性而添加的流控制

如果收发的数据量小,但需要频繁连接时,UDP比TCP更高效。

6.2 实现基于UDP的服务器端/客户端

UDP中的服务器端和客户端没有连接

无需listen和accept,只有创建套接字过程数据交换过程

UDP服务器端和客户端均只需1个套接字

TCP中套接字是一对一关系,除了守门的服务器套接字,还需要10个服务器端套接字。

UDP中,不管是服务器端还是客户端,都只需要一个套接字。

一个UDP套接字能和多态主机通信。只需要一个套接字就可以向任意主机传送数据。

基于UDP的数据I/O函数

UDP每次传输数据都要添加目标地址信息。

发送UDP数据的函数:

#include<sys/socket.h>
ssize_t sendto(int sock, void * buff, size_t nbytes, int flags, struct sockaddr* to, socklen_t addrlen);
				sock	用于传输数据的UDP套接字文件描述符
				buff	保存待传输数据的缓冲地址值
				nbytes	待传输的数据长度,以字节为单位
				flags	可选项参数,若没有则传递0
				to		存有目标地址信息的sockaddr结构体变量的地址值
				addrlen	传递给参数to的地址值结构体变量长度

成功时返回传输的字节数,失败时返回-1

接收UDP数据的函数:

#include<sys/socket.h>
ssize_t recvfrom(int sock, void * buff, size_t nbytes, int flags, struct sockaddr* from, socklen_t * addrlen);
				sock	用于接收数据的UDP套接字文件描述符
				buff	保存接收数据的缓冲地址值
				nbytes	可接收的最大字节数,故无法超过参数buff所指的缓冲大小
				flags	可选项参数,若没有则传入0
				from	存有发送端地址信息的sockaddr结构体变量的地址值
				addrlen	保存参数from的结构体变量长度的变量地址值
				
成功时返回接收的字节数,失败时返回-1
基于UDP的回声服务器端/客户端

服务器端:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc,char * argv[]){
        int serv_sock;
        char message[BUF_SIZE];
        int str_len;
        socklen_t clnt_adr_sz;
        int str_len;
        socklen_t clnt_adr_sz;
        struct sockaddr_in serv_adr,clnt_adr;
        if(argc != 2){
                printf("Usage:%s <port>\n",argv[0]);
                exit(1);
        }

        serv_sock = socket(PF_INET,SOCK_DGRAM,0);
        if(serv_sock == -1){
                error_handling("UDP socket creation error");
        }

        memset(&serv_adr,0,sizeof(serv_adr));
        serv_adr.sin_family = AF_INET;
        serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_adr.sin_port = htons(atoi(argv[1]));

        if(bind(serv_sock,(struct sockaddr*)&serv_adr,sizeof(serv_adr)) == -1)
                error_handling("bind() error");

        while(1){
                clnt_adr_sz = sizeof(clnt_adr);
                str_len = recvfrom(serv_sock,message,BUF_SIZE,0,(struct sockaddr*)&clnt_adr,&clnt_adr_sz);
                sendto(serv_sock,message,str_len,0,(struct sockaddr*)&clnt_adr,clnt_adr_sz);
        }
        close(serv_sock);
        return 0;
}

void error_handling(char * message){
        fputs(message,stderr);
        fputc('\n',stderr);
        exit(1);
}

客户端:

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<sys/socket.h>

#define BUF_SIZE 30
void error_handling(char *message);

int main(int argc,char* argv[]){
        int sock;
        char message[BUF_SIZE];
        int str_len;
        socklen_t adr_sz;

        struct sockaddr_in serv_adr,from_adr;
        if(argc != 3){
                printf("Usage: %s <IP> <port>\n",argv[0]);
                exit(1);
        }

        sock = socket(PF_INET, SOCK_DGRAM,0);
        if(sock == -1){
                error_handling("socket() error");
        }

        memset(&serv_adr,0,sizeof(serv_adr));
        serv_adr.sin_family = AF_INET;
        serv_adr.sin_addr.s_addr = inet_addr(argv[1]);
        serv_adr.sin_port = htons(atoi(argv[2]));

        while(1){
                fputs("Insert message(q to quit):",stdout);
                fgets(message,sizeof(message),stdin);
                if(!strcmp(message,"q\n")|| !strcmp(message,"Q\n"))
                        break;

                sendto(sock,message,strlen(message),0,(struct sockaddr*)&serv_adr,sizeof(serv_adr));
                adr_sz = sizeof(from_adr);
                str_len = recvfrom(sock,message,BUF_SIZE,0,(struct sockaddr*)&from_adr,&adr_sz);
                message[str_len] = 0;
                printf("Message from server:%s",message);
        }
        close(sock);
        return 0;
}

void error_handling(char * message){
        fputs(message,stderr);
        fputc('\n',stderr);
        exit(1);
}

运行结果:

[root@VM_0_10_centos udp]# ./uclient 127.0.0.1 9190
Insert message(q to quit):Hi UDP Server?
Message from server:Hi UDP Server?
Insert message(q to quit):Nice to meet you!
Message from server:Nice to meet you!
Insert message(q to quit):Good Bye~
Message from server:Good Bye~
Insert message(q to quit):q
UDP客户端套接字的地址分配

调用sendto函数的时候,自动分配IP和端口号
UDP客户端中通常无需额外的地址分配过程

6.3 UDP的数据传输特性和调用connect函数

UDP数据传输中存在数据边界。

调用I/O的次数非常重要,输入函数的调用次数应该等于输出函数的调用次数。

已连接UDP套接字 与 未连接UDP套接字

通过sendto函数传输数据的过程大致可以分为以下3个阶段:

  1. 第一阶段:向UDP套接字注册目标IP和端口号
  2. 第二阶段:传输数据
  3. 第三阶段:删除UDP套接字中注册的目标地址信息

因此可以重复利用同一套UDP套接字向不同的目标传输数据。

未注册目标地址信息的套接字——>未连接套接字
注册了目标地址信息的套接字——>已连接套接字

但是,如果要与同一台主机进行长时间通信时,将UDP套接字变成已连接套接字会提高效率。

创建已连接UDP套接字

很简单,只需针对UDP套接字调用connect函数:

sock = socket(PF_INET,SOCK_DGRAM,0);				这里确实是在建立UDP的套接字
memset(&adr,0,sizeof(adr));
adr.sin_family = AF_INET;
adr.sin_addr.s_addr = htonl(INADDR_ANY);
adr.sin_port = htons(atoi(argv[1]));
connect(sock,(struct sockaddr*)&adr,sizeof(adr));   关键的一句话

对UDP套接字调用connect函数并非意味着要与对方UDP套接字连接,只是向UDP套接字注册目标IP和端口信息罢了。

调用connect之后,每次调用sendto只需传输数据,因为已经指定了收发对象,所以不仅可以使用sendtorecvfrom函数,还可以使用writeread函数进行通信。

6.4 习题

(1)UDP为什么比TCP速度快?为什么TCP数据传输可靠而UDP数据传输不可靠?

UDP和TCP不同,不进行流控制。由于该控制涉及到套接字的连接和结束,以及整个数据收发过程,因此,TCP传输的数据是可以信赖的。相反,UDP不进行这种控制,因此无法信任数据的传输,但正因UDP不进行流量控制,所以比TCP更快。

(2)UDP的特点(不是很全)
不存在连接概念;
有两个目标,也可以只用一个UDP套接字实现连接;
UDP套接字中可以使用已经分配给TCP的同一端口号;
可以在同一台主机进行TCP和UDP的数据传输;

(3)UDP数据包向对方主机的UDP套接字传递过程中,IP和UDP分别负责哪些部分?

IP负责链路选择。UDP负责端到端的传输。

(4)UDP一般比TCP快,但根据交换数据的特点,其差异可大可小。请说明何种情况下UDP的性能优于TCP。

UDP与TCP不同,不经过连接以及断开SOCKET的过程,因此,在频繁的连接及断开的情况下,UDP的数据收发能力会凸显出更好的性能。

(5)客户端TCP套接字调用connect函数时自动分配IP和端口号。UDP中不调用bind函数,那何时分配IP和端口号?

首次调用sendto函数时,发现尚未分配信息,则给相应的套接字自动分配IP和端口号

(6)TCP客户端必须调用connect函数,而UDP中可以选择性调用。请问,在UDP中调用connect函数有哪些好处?

每当以UDP套接字为对象调用sendto函数时,都要经过以下过程:

第一阶段:为目标UDP注册端口和IP
第二阶段:数据传输
第三阶段:删除UDP注册的IP和端口信息
其中,只要调用connect函数,就可以忽略每次传输数据时反复进行的第一阶段和第三阶段。然而,调用connect函数并不意味着经过连接过程只是将IP地址和端口号指定在UDP的发送对象上。这样connect函数使用后,还可以用write、read函数进行数据处理,而不必使用sendto、recvfrom。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
好的,我可以为您提供一些关于创建基于TCP服务器端/客户端的指导。 首先,您需要了解一些基本概念和步骤: 1. TCP/IP协议:TCP/IP协议是一种常用的网络通信协议,用于控制网络上的数据传输。它是由两个协议组成的:TCP(传输控制协议)和IP(网际协议)。 2. 服务器端服务器端是指在网络上提供服务的计算机程序。服务器端通常需要创建一个socket,监听客户端的连接请求,并接受来自客户端的请求,处理请求并返回响应。 3. 客户端客户端是指连接到服务器端的计算机程序。客户端通常需要创建一个socket,连接到服务器端,并发送请求,等待服务器端响应。 接下来,我们可以按照以下步骤创建一个基于TCP服务器端/客户端,用于收发文件: 1. 创建服务器端socket:使用socket()函数创建一个新的socket,指定协议族(AF_INET表示IPv4)和socket类型(SOCK_STREAM表示流式套接字)。 2. 绑定服务器端socket:使用bind()函数将socket服务器端地址(IP地址和端口号)绑定起来。 3. 监听客户端连接请求:使用listen()函数启动服务器端socket监听客户端连接请求。 4. 接受客户端连接请求:使用accept()函数接受来自客户端的连接请求,并创建一个新的socket用于与客户端通信。 5. 接收客户端发送的文件:使用recv()函数从客户端接收文件数据,并写入到服务器端的文件中。 6. 发送响应给客户端:使用send()函数向客户端发送一个响应,表示文件传输完成。 7. 关闭服务器端socket:使用close()函数关闭服务器端socket客户端的步骤如下: 1. 创建客户端socket:使用socket()函数创建一个新的socket,指定协议族(AF_INET表示IPv4)和socket类型(SOCK_STREAM表示流式套接字)。 2. 连接服务器端:使用connect()函数连接到服务器端,指定服务器端地址(IP地址和端口号)。 3. 打开要发送的文件:使用fopen()函数打开要发送的文件。 4. 发送文件数据给服务器端:使用send()函数将文件数据发送给服务器端。 5. 接收服务器端的响应:使用recv()函数接收服务器端的响应,判断文件是否传输完成。 6. 关闭客户端socket:使用close()函数关闭客户端socket。 以上是基于TCP服务器端/客户端的基本步骤,您可以根据自己的需求进行修改和扩展。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值