【Linux】UDP与TCP的对比并写出TCP和UDP的服务端
-
UDP
(1.)无连接
UDP在传输数据的时候不需要建立连接,可以直接传输。(这一点在UDP服务端程序中可以看到),因此传输速度比较快,适用于传视频,音频。
(2.)传输层协议
(3.)不可靠传输
a:因为UDP在传输时不需要建立连接,很容易出现错误
b:UDP只有一个socket接收缓冲区,没有socket发送缓冲区,只要是有数据发送,不管对是否可以正确的接受。而在对方的就收缓冲区满了之后,新来的数据无法进入到socket的接收缓冲区中,会造成数据丢失,UDP是没有流量控制的,因此UDP的数据是不可靠的
c:不能对发送的数据进行排序,不能保证包序。
(4.)面向数据报
a:应用层给UDP多少数据,UDP都会原样发送,既不会拆分,也不会合并(这里会有应用层给的数据很大或者很小的问题)
b:因为UDP头部(8个字节)定义了UDP数据报的长度(最大64k),所以数据是一条一条的发送和接收。因此数据不能够灵活的控制发送数量和大小,但是因为UDP数据长度固定,所以不会出现沾包问题
-
TCP
(1.)有连接
在发送数据之前需要服务端和客户端建立连接才可以发送数据(在TCP的服务端程序中可以很好的体现)
(2.)传输层协议
(3.)可靠传输(参考TCP的三次握手,四次挥手)
a:确认应答机制-->发送的每条数据都需要确认回复一下
b:超时重传机制-->发送方等待一段时间后要是没有收到接收方发来的回复,就认为发送失败,那么发送方将重新发送
其中这个超时是递增的,次数有限,超过了重传次数就会断开网络,避免浪费资源
c:序号/确认序号-->保证数据包的有序传输
(4.)面向字节流
收发字节比较灵活,但是数据没有明显的边界,发送和接收数据的时候很容易造成沾包
-
总结:
TCP要保证可靠传输就需要付出额外的代价,这样就会使它的传输性能大大降低。它适用于文件传输等,数据安全性较高的场景
UDP数据传输实时性高,常用于传输音乐,音频等。对数据的完整性要求不高,适用于实时性高的场景,传输速度快
-
DUP服务端编写过程
创建socket----->绑定地址----->接收数据------>发送数据----->关闭socket
1.) 创建socket
int socket(int domain, int type, int protocol);
//domain(地址域) : AF_INET
//type套接字类型 : SOCK_STREAM---->字节流(TCP)
// SOCK_DGRAM---->数据报(DUP)
//protocol协议类型: 0 默认
// IPPROTO_TCP
// IPPROTO_UDP
//返回值:-1 (创建失败)
2.)绑定地址
注意:客户端程序中不推介手动绑定地址,因为绑定有可能失败,但是客户端发送数据的时候,具体哪个端口和地址都行,只要成功发送就可以。所以不需要我们手动绑定地址
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
//参数列表
//sockfd:套接字描述符
//addr : 要绑定的地址信息
//addrlen : 地址信息长度
//返回值:失败返回-1
a:转换网络字节序的接口函数:
#include <arpa/inet.h>
uint16_t htons(uint16_t hostshort);
b:将字符串地址转化为网络地址信息的接口函数
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
//只能转化IPV4
3.)接收数据
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
//参数列表:
//sockfd:socket描述符
//buf:用于存储接收的数据
//len: 想要接受的数据长度
//flags : 0 --->阻塞,如果缓存区没有数据就一直挂起等待
//src_addr : 用于确定数据是哪一个客户端发送的(地址信息)
//addrlen : 地址信息的长度
//返回值:失败返回-1
4.)发送数据
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);
//参数列表:
//sockfd:socket描述符,发送数据就是通过这个socket所绑定的地址发送
//buf:发送的数据
//len:要发送的数据长度
//flags:0-->默认阻塞式发送
//dest_addr:数据要发送的对端地址
//addrlen:地址信息长度
5.)关闭socket
close( )
实现代码:
1 #include<stdio.h>
2 #include<sfdafsys/socket.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5 #include<string.h>
6 #include<errno.h>
7 #include<arpa/inet.h>
8
9 int main()
10 {
11
12 //创建socket
13 int sockfd = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
14
15 if(sockfd < 0)
16 {
17 printf("perror sockfd");
18 return -1;
19 }
20
21 //绑定地址
22 struct sockaddr_in addr;
23 addr.sin_family = AF_INET;
24 addr.sin_port = htons(9000);
25 addr.sin_addr.s_addr = inet_addr("192.168.179.128");
26
27 socklen_t len = sizeof(addr);
28
29 int ret = bind(sockfd,(struct sockaddr*)&addr,len);
30
31 if(ret < 0)
32 {
33 printf("perror bind");
34 return -1;
35 }
36 while(1)
37 {
38 //接收数据
39 //客户端地址
40 struct sockaddr_in cli_addr;
41 char buf[1024] = {0};
42 len = sizeof(cli_addr);
43 ssize_t r_ret = recvfrom(sockfd,buf,1023,0,(struct sockaddr*)&cli_addr,&len);
44 if(r_ret < 0)
45 {
46 printf("perror recvfrom");
47 return -1;
48 }
49
50 printf("client[%s:%d] say=> %s\n",inet_ntoa(cli_addr.sin_addr),ntohs(cli_addr.sin_port),buf);
51 //发送数据
52
53 memset(buf,0x00,1024);
54 printf("请输入=> ");
55 scanf("%s",buf);
56 ssize_t rret= sendto (sockfd,buf,strlen(buf),0,(struct sockaddr*)&cli_addr,len);
57 if(rret < 0)
58 {
59 printf("perror sendto");
60 return -1;
61 }
62
63
64 }
65 //关闭socket
66 close(sockfd);
67 return 0;
68 }
-
TCP的服务端代码
创建socket---->绑定地址---->监听---->建立连接---->接收数据---->发送数据
1.),2)创建socket和UDP差不多,绑定地址和UDP也差不多
3.)监听
int listen(int sockfd, int backlog);
//sockfd:socket描述符
//backlog :最大的同时并发连接数(连接成功队列中的节点数)并不是tcp的最大连接数
//返回值:失败返回 - 1
4.)建立连接
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
//sockfd: 监听socket描述符
//addr:新建立的客户端地址信息
//addrlen:地址信息长度
//返回值:
//成功:返回非负整数,新的socket连接描述符
//失败: - 1
accept函数是一个阻塞型函数,连接成功队列中如果没有新的socket连接就会挂起等待
5.)接收数据
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
//sockfd:建立连接成功的socket描述符
//buf:用于接收数据
//len: 用于指定接收数据长度
//flags : 默认 0 --->阻塞式接收
//返回值:失败返回小于0返回值
//等于0---->表示对端关闭连接
//成功:返回实际接收的长度
6.)发送数据
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
//sockfd:建立连接成功的socket描述符
//buf:用于接收数据
//len:用于指定发送数据长度
//flags:默认 0
7.)关闭socket
clsoe( );
代码展示:
1 #include<stdio.h>
2 #include<unistd.h>
3 #include<stdlib.h>
4 #include<string.h>
5 #include<errno.h>
6 #include<netinet/in.h>
7 #include<arpa/inet.h>
8 #include<sys/socket.h>
9 int main(int argc,char* argv[])
10 {
11
12 //判断输入参数是否有误
13
14 if(argc != 3)
15 {
16 printf("input is wrong!!!");
17 return -1;
18 }
19
20 //创建socket
21 int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
22 if(sockfd < 0)
23 {
24 perror("socket error");
25 return -1;
26 }
27
28 //绑定地址
29 struct sockaddr_in addr;
30 addr.sin_family = AF_INET;
31 addr.sin_port = htons(atoi(argv[2]));
32 addr.sin_addr.s_addr = inet_addr(argv[1]);
33 socklen_t len = sizeof(addr);
34 int ret = bind(sockfd,(struct sockaddr*)&addr,len);
35 if(ret < 0)
36 {
37 perror("bind error");
38 return -1;
39 }
40
41 //开始监视
42 if(listen(sockfd,5) < 0)
43 {
44 perror("listen error");
45 return -1;
46 }
47
48
49 while(1)
50 {
51 //开始接收新建立的socket数据(发起连接请求)
52 struct sockaddr_in cli_addr;
53 len = sizeof(cli_addr);
54 int new_sockfd = accept(sockfd,(struct sockaddr*)&cli_addr,&len);
55 if(new_sockfd < 0)
56 {
57 perror("accept error");
58 continue;
59 }
60
61 while(1)
62 { //接收数据
63 char buf[1024] = {0};
64 ssize_t rret = recv(new_sockfd,buf,1023,0);
65 if(rret < 0)
66 {
67 perror("recv error");
68 close(new_sockfd);
69 continue;
70 }
71
72 if(rret == 0)
73 {
74 perror("close port ");
75 close(new_sockfd);
76 continue;
77 }
78
79 printf("client say=》[ %s ]",buf);
80 //发送数据
81 //清空buf
82 memset(buf,0x00,1024);
83 printf("请输入=》 ");
84 scanf("%s",buf);
85 send(new_sockfd,buf,strlen(buf),0);
86
87 }
88 }
89
90 //关闭
91 close(sockfd);
92
93 return 0;
94 }
95