【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