Linux下網絡socket編程——實現服務器(select)與多個客戶端通信

?

一、關于socket通信

服務器端工作流程:

  • 調用 socket() 函數創建套接字 用 bind() 函數將創建的套接字與服務端IP地址綁定
  • 調用listen()函數監聽socket() 函數創建的套接字,等待客戶端連接 當客戶端請求到來之后
  • 調用 accept()函數接受連接請求,返回一個對應于此連接的新的套接字,做好通信準備
  • 調用 write()/read() 函數和 send()/recv()函數進行數據的讀寫,通過 accept() 返回的套接字和客戶端進行通信 關閉socket(close)

客戶端工作流程:

  • 調用 socket() 函數創建套接字
  • 調用 connect() 函數連接服務端
  • 調用write()/read() 函數或者 send()/recv() 函數進行數據的讀寫
  • 關閉socket(close)

?

二、用select實現服務器端編程:

select函數樓主在之前文章中(select函數用法)已經提及,不在多做綴述。下面貼上服務器端代碼servce.c

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

83

84

85

86

87

88

89

90

91

92

93

94

95

96

97

98

99

100

101

102

103

104

105

106

107

108

109

110

111

112

113

114

115

116

117

118

119

120

121

122

123

124

125

126

127

128

129

130

131

132

133

134

135

136

137

138

139

140

141

142

143

144

145

146

147

148

149

150

151

152

153

154

155

156

157

158

159

160

161

162

163

164

165

166

167

168

169

170

171

172

173

174

175

176

177

178

179

180

181

182

183

184

185

186

187

188

189

190

191

192

193

194

195

196

197

198

199

200

201

202

203

204

205

206

207

208

#include <stdio.h>

#include <netinet/in.h>?? //for souockaddr_in

#include <sys/types.h>?????

#include <sys/socket.h>

#include <errno.h>

#include <stdlib.h>

?

#include <arpa/inet.h>

?

//for select

#include <sys/time.h>

#include <sys/types.h>

#include <unistd.h>

#include <sys/select.h>

?

#include <strings.h>?? //for bzero

#include <string.h>

?

#define BUFF_SIZE 1024

#define backlog 7

#define ser_port 11277

#define CLI_NUM 3

?

?

int?client_fds[CLI_NUM];

?

int?main(int?agrc,char?**argv)

{

????int?ser_souck_fd;

????int?i;??

????char?input_message[BUFF_SIZE];

????char?resv_message[BUFF_SIZE];

?

?

????struct?sockaddr_in ser_addr;

????ser_addr.sin_family= AF_INET;????//IPV4

????ser_addr.sin_port = htons(ser_port);

????ser_addr.sin_addr.s_addr = INADDR_ANY;??//指定的是所有地址

?

????//creat socket

????if( (ser_souck_fd = socket(AF_INET,SOCK_STREAM,0)) < 0 )

????{

????????perror("creat failure");

????????return?-1;

????}

?

????//bind soucket

????if(bind(ser_souck_fd, (const?struct?sockaddr *)&ser_addr,sizeof(ser_addr)) < 0)

????{

????????perror("bind failure");

????????return?-1;

????}

?

????//listen

????if(listen(ser_souck_fd, backlog) < 0)

????{

????????perror("listen failure");

????????return?-1;

????}

?

?

????//fd_set

????fd_set ser_fdset;

????int?max_fd=1;

????struct?timeval mytime;

????printf("wait for client connnect!\n");

?

????while(1)

????{

????????mytime.tv_sec=27;

????????mytime.tv_usec=0;

?

????????FD_ZERO(&ser_fdset);

?

????????//add standard input

????????FD_SET(0,&ser_fdset);

????????if(max_fd < 0)

????????{

????????????max_fd=0;

????????}

?

????????//add serverce

????????FD_SET(ser_souck_fd,&ser_fdset);

????????if(max_fd < ser_souck_fd)

????????{

????????????max_fd = ser_souck_fd;

????????}

?

????????//add client

????????for(i=0;i<CLI_NUM;i++)??//用數組定義多個客戶端fd

????????{

????????????if(client_fds[i]!=0)

????????????{

????????????????FD_SET(client_fds[i],&ser_fdset);

????????????????if(max_fd < client_fds[i])

????????????????{

????????????????????max_fd = client_fds[i];

????????????????}

????????????}

????????}

?

????????//select多路復用

????????int?ret = select(max_fd + 1, &ser_fdset, NULL, NULL, &mytime);

?

????????if(ret < 0)???

????????{???

????????????perror("select failure\n");???

????????????continue;???

????????}???

?

????????else?if(ret == 0)

????????{

????????????printf("time out!");

????????????continue;

????????}

?

????????else

????????{

????????????if(FD_ISSET(0,&ser_fdset))?//標準輸入是否存在于ser_fdset集合中(也就是說,檢測到輸入時,做如下事情)

????????????{

????????????????printf("send message to");

????????????????bzero(input_message,BUFF_SIZE);

????????????????fgets(input_message,BUFF_SIZE,stdin);

?

????????????????for(i=0;i<CLI_NUM;i++)

????????????????{

????????????????????if(client_fds[i] != 0)

????????????????????{

????????????????????????printf("client_fds[%d]=%d\n", i, client_fds[i]);

????????????????????????send(client_fds[i], input_message, BUFF_SIZE, 0);

????????????????????}

????????????????}

?

????????????}

?

????????????if(FD_ISSET(ser_souck_fd, &ser_fdset))

????????????{

????????????????struct?sockaddr_in client_address;

????????????????socklen_t address_len;

????????????????int?client_sock_fd = accept(ser_souck_fd,(struct?sockaddr *)&client_address, &address_len);

????????????????if(client_sock_fd > 0)

????????????????{

????????????????????int?flags=-1;

????????????????????//一個客戶端到來分配一個fd,CLI_NUM=3,則最多只能有三個客戶端,超過4以后跳出for循環,flags重新被賦值為-1

????????????????????for(i=0;i<CLI_NUM;i++)

????????????????????{

????????????????????????if(client_fds[i] == 0)

????????????????????????{

????????????????????????????flags=i;

????????????????????????????client_fds[i] = client_sock_fd;

????????????????????????????break;

????????????????????????}

????????????????????}

?

?

????????????????????if?(flags >= 0)

????????????????????{

????????????????????????printf("new user client[%d] add sucessfully!\n",flags);

?

????????????????????}

?

????????????????????else?//flags=-1

????????????????????{??

????????????????????????char?full_message[]="the client is full!can't join!\n";

????????????????????????bzero(input_message,BUFF_SIZE);

????????????????????????strncpy(input_message, full_message,100);

????????????????????????send(client_sock_fd, input_message, BUFF_SIZE, 0);

?

????????????????????}

????????????????}???

????????????}

?

????????}

?

????????//deal with the message

?

????????for(i=0; i<CLI_NUM; i++)

????????{

????????????if(client_fds[i] != 0)

????????????{

????????????????if(FD_ISSET(client_fds[i],&ser_fdset))

????????????????{

????????????????????bzero(resv_message,BUFF_SIZE);

????????????????????int?byte_num=read(client_fds[i],resv_message,BUFF_SIZE);

????????????????????if(byte_num > 0)

????????????????????{

????????????????????????printf("message form client[%d]:%s\n", i, resv_message);

????????????????????}

????????????????????else?if(byte_num < 0)

????????????????????{

????????????????????????printf("rescessed error!");

????????????????????}

?

????????????????????//某個客戶端退出

????????????????????else??//cancel fdset and set fd=0

????????????????????{

????????????????????????printf("clien[%d] exit!\n",i);

????????????????????????FD_CLR(client_fds[i], &ser_fdset);

????????????????????????client_fds[i] = 0;

???????????????????????// printf("clien[%d] exit!\n",i);

????????????????????????continue;??//這里如果用break的話一個客戶端退出會造成服務器也退出。?

????????????????????}

????????????????}

????????????}

????????}???

????}

????return?0;

}

select實現多路復用,多路復用,顧名思義,就是說各做各的事,標準輸入事件到來,有相關函數處理。服務器處理服務器的事件,客戶端到來時有相關函數對其進行處理,通過select遍歷各fd的讀寫情況,就不用擔心阻塞了。

三、用epoll實現客戶端編程:

1、客戶端程序(epoll_client.c):

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

81

82

#include<stdio.h>???

#include<stdlib.h>???

#include<netinet/in.h>???

#include<sys/socket.h>???

#include<arpa/inet.h>???

#include<string.h>???

#include<unistd.h>???

?

#include <sys/epoll.h>

#include <errno.h>

#include <fcntl.h>

?

#define BUFFER_SIZE 1024???

?

int?main(int?argc,?const?char?* argv[])???

{??

????int?i,n;

????int?connfd,sockfd;

????struct?epoll_event ev,events[20];?//ev用于注冊事件,數組用于回傳要處理的事件

????int?epfd=epoll_create(256);//創建一個epoll的句柄,其中256為你epoll所支持的最大句柄數

?

????struct?sockaddr_in client_addr;

????struct?sockaddr_in server_addr;???

?

????server_addr.sin_family = AF_INET;???

????server_addr.sin_port = htons(11277);???

????server_addr.sin_addr.s_addr =INADDR_ANY;???

????bzero(&(server_addr.sin_zero), 8);???

?

????int?server_sock_fd = socket(AF_INET, SOCK_STREAM, 0);?

?

????ev.data.fd=server_sock_fd;//設置與要處理的事件相關的文件描述符

????ev.events=EPOLLIN|EPOLLET;//設置要處理的事件類型

????epoll_ctl(epfd,EPOLL_CTL_ADD,server_sock_fd,&ev);//注冊epoll事件

?

????if(server_sock_fd == -1)???

????{???

????????perror("socket error");???

????????return?1;???

????}???

?

????char?recv_msg[BUFFER_SIZE];???

????char?input_msg[BUFFER_SIZE];???

?

????if(connect(server_sock_fd, (struct?sockaddr *)&server_addr,?sizeof(struct?sockaddr_in)) == 0)???

????{???

????????for(;;)

????????{

????????????int?nfds=epoll_wait(epfd,events,20,500);//等待epoll事件的發生

????????????for(i=0;i<nfds;++i)

????????????{???

????????????????if(events[i].events&EPOLLOUT)?//有數據發送,寫socket

????????????????{

????????????????????bzero(input_msg, BUFFER_SIZE);???

????????????????????fgets(input_msg, BUFFER_SIZE, stdin);???

?

????????????????????sockfd = events[i].data.fd;

????????????????????write(sockfd, recv_msg, n);

?

????????????????????ev.data.fd=sockfd;

????????????????????ev.events=EPOLLIN|EPOLLET;

????????????????????epoll_ctl(epfd,EPOLL_CTL_ADD,sockfd,&ev);

????????????????}??

?

????????????????else?if(events[i].events&EPOLLIN)//有數據到來,讀socket

????????????????{

????????????????????bzero(recv_msg, BUFFER_SIZE);

????????????????????if((n = read(server_sock_fd, recv_msg, BUFFER_SIZE)) <0 )

????????????????????{

????????????????????????printf("read error!");

????????????????????}

?

????????????????????ev.data.fd=server_sock_fd;

????????????????????ev.events=EPOLLOUT|EPOLLET;

????????????????????printf("%s\n",recv_msg);

????????????????}

?

????????????}???????

????????}

????}???

????return?0;???

}??

2、關于epoll函數:

相比于select,epoll最大的好處在于它不會隨著監聽fd數目的增長而降低效率。因為在內核中的select實現中,它是采用輪詢來處理的,輪詢的fd數目越多,自然耗時越多。并且,在linux/posix_types.h頭文件有這樣的聲明:?
#define __FD_SETSIZE 1024?
表示select最多同時監聽1024個fd

一共三個函數:

1

2

1、?int?epoll_create (int?size);

創建一個epoll的句柄

size用來告訴內核這個監聽的數目一共有多大。這個參數不同于select()中的第一個參數,給出最大監聽的fd+1的值。需要注意的是,當創建好epoll句柄后,它就是會占用一個fd值,在linux下如果查看/proc/進程id/fd/,是能夠看到這個fd的,所以在使用完epoll后,必須調用close()關閉,否則可能導致fd被耗盡。

1

2、?int?epoll_ctl (int?epfd ,?int?op,?int?fd,?struct?epoll_event *event);

epoll的事件注冊函數,它不同與select()是在監聽事件時告訴內核要監聽什么類型的事件,而是在這里先注冊要監聽的事件類型。第一個參數是epoll_create()的返回值,第二個參數表示動作,用三個宏來表示:

EPOLL_CTL_ADD:注冊新的fd到epfd中;

EPOLL_CTL_MOD:修改已經注冊的fd的監聽事件;

EPOLL_CTL_DEL:從epfd中刪除一個fd;

第三個參數是需要監聽的fd

第四個參數是告訴內核需要監聽什么事,struct epoll_event結構如下:

1

2

3

4

5

struct?epoll_event

?{

????__uint32_t events;????/* Epoll events */

????epoll_data_t data;?????/* User data variable */

};

  

1

2

3

4

5

6

7

typedef?union?epoll_data

?{

????void?*ptr;

????int?fd;

????__uint32_t u32;

????__uint64_t u64;

} epoll_data_t;

events可以是以下幾個宏的集合:

  • EPOLLIN :表示對應的文件描述符可以讀(包括對端SOCKET正常關閉);
  • EPOLLOUT:表示對應的文件描述符可以寫;
  • EPOLLPRI:表示對應的文件描述符有緊急的數據可讀(這里應該表示有帶外數據到來);
  • EPOLLERR:表示對應的文件描述符發生錯誤;
  • EPOLLHUP:表示對應的文件描述符被掛斷;
  • EPOLLET: 將EPOLL設為邊緣觸發(Edge Triggered)模式,這是相對于水平觸發(Level Triggered)來說的。
  • EPOLLONESHOT:只監聽一次事件,當監聽完這次事件之后,如果還需要繼續監聽這個socket的話,需要再次把這個socket加入到EPOLL隊列里

1

3、?int?epoll_wait(int?epfd,?struct?epoll_event * events,?int?maxevents,?int?timeout);

等待事件的產生,類似于select()調用。

參數events用來從內核得到事件的集合,

maxevents告之內核這個events有多大,這個 maxevents的值不能大于創建epoll_create()時的size,

參數timeout是超時時間(毫秒,0會立即返回,-1將不確定,也有說法說是永久阻塞)。該函數返回需要處理的事件數目,如返回0表示已超時。

使用步驟:

<1>首先通過create_epoll(int maxfds)來創建一個epoll的句柄,其中maxfds為你epoll所支持的最大句柄數。這個函數會返回一個新的epoll句柄,之后的所有操作將通過這個句柄來進行操作。在用完之后,記得用close()來關閉這個創建出來的epoll句柄。

<2>然后每一幀的調用epoll_wait (int epfd, epoll_event events, int max events, int timeout) 來查詢所有的網絡接口。

<3>kdpfd為用epoll_create創建之后的句柄,events是一個epoll_event*的指針,當epoll_wait這個函數操作成功之后,epoll_events里面將儲存所有的讀寫事件。max_events是當前需要監聽的所有socket句柄數。最后一個timeout是 epoll_wait的超時,為0的時候表示馬上返回,為-1的時候表示一直等下去,直到有事件范圍,為任意正整數的時候表示等這么長的時間,如果一直沒有事件,則返回。一般如果網絡主循環是單獨的線程的話,可以用-1來等,這樣可以保證一些效率,如果是和主邏輯在同一個線程的話,則可以用0來保證主循環的效率。 epoll_wait返回之后應該是一個循環,遍歷所有的事件。

基本上都是如下的框架:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

for( ; ; )

????{

????????nfds = epoll_wait(epfd,events,20,500);

????????for(i=0;i<nfds;++i)

????????{

????????????if(events[i].data.fd==listenfd)?//有新的連接

????????????{

?????????????????????connfd = accept(listenfd,(sockaddr *)&clientaddr, &clilen);????//accept這個連接

?????????????????????ev.data.fd=connfd;

?????????????????????ev.events=EPOLLIN|EPOLLET;

?????????????????????epoll_ctl(epfd,EPOLL_CTL_ADD,connfd,&ev);?//將新的fd添加到epoll的監聽隊列中

????????????}

?

???????????else?if( events[i].events&EPOLLIN )?//接收到數據,讀socket

????????????{

?????????????????????n = read(sockfd, line, MAXLINE)) < 0????//讀

?????????????????????ev.data.ptr = md;?????//md為自定義類型,添加數據

?????????????????????ev.events=EPOLLOUT|EPOLLET;

?????????????????????epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);//修改標識符,等待下一個循環時發送數據,異步處理的精髓

????????????}

????????????else?if(events[i].events&EPOLLOUT)?//有數據待發送,寫socket

????????????{

?????????????????????struct?myepoll_data* md = (myepoll_data*)events[i].data.ptr;????//取數據

?????????????????????sockfd = md->fd;

?????????????????????send( sockfd, md->ptr,?strlen((char*)md->ptr), 0 );????????//發送數據

?????????????????????ev.data.fd=sockfd;

?????????????????????ev.events=EPOLLIN|EPOLLET;

?????????????????????epoll_ctl(epfd,EPOLL_CTL_MOD,sockfd,&ev);?//修改標識符,等待下一個循環時接收數據

????????????}

????????????else

????????????{

?????????????????????//其他的處理

????????????}

????????}

????}

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/383634.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/383634.shtml
英文地址,請注明出處:http://en.pswp.cn/news/383634.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

SQL Server【四】

identity 主鍵自動增長&#xff0c;用戶不需要為identity修飾的主鍵賦值 create table student (std_id int primary key identity(10,5),--(10,5)可以省略&#xff0c;默認為(1,1)std_name nvarchar(200) not null ) select * from student insert into student values (張三…

TCP服務器/客戶端實例(C/C )

1.1、Linux下的TCP服務器&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <unistd.h> #include <arpa/inet.h> #include <sys/types.h> #include <sys/socket.h>void error_handling(char *mess…

pip代理解決pip下載失敗問題

在用pip下載各種庫的時候發現速度實在是太慢了&#xff0c;還會有各種奇奇怪怪的問題&#xff0c;動不動就玄學失敗。 在網上找來找去找到知乎上一位大佬的回答&#xff1a;傳送門&#xff0c;用了豆瓣的代理。哇咔咔&#xff0c;媽媽再也不用擔心我下載失敗了。 代理&#x…

實現Linux select IO復用C/S服務器代碼

服務器端#include<stdio.h> #include<unistd.h> #include<stdlib.h> #include<string.h> #include<sys/socket.h> #include<sys/stat.h> #include<arpa/inet.h> #include <sys/select.h>#define MAXBUF 256 #define MAXLISTEN…

Bellman-Ford算法和SPFA算法

Belloman-Ford算法 算法介紹 Dijkstra可以解決單源無負邊最短路徑問題。但是當遇到含有負邊的單源最短路徑問題就需要使用Bellman-Ford算法來解決。Bellman-Ford算法還可以檢測出負環。 算法步驟 源點s,數組d[u]d[u]d[u]表示s到u的最短距離初始化&#xff1a;d[s]0d[s]0d[s…

C語言實現單鏈表操作

SLIST_H #ifndef __SLIST_H__ #define __SLIST_H__ #include<cstdio> #include<malloc.h> #include<assert.h> typedef int ElemType; typedef struct Node { //定義單鏈表中的結點信息 ElemType data; //結點的數據域 struct Node *next; //結點的指針…

計算機網絡【4】傳輸層

概述 傳輸層是只有主機才有的層次 傳輸層的功能&#xff1a; 傳輸層提供進程和進程之間的邏輯通信&#xff08;網絡層提供主機與主機之間的邏輯通信&#xff09;復用和分用傳輸層對收到的報文進行差錯檢測 傳輸層有兩個協議&#xff1a; 面向連接的傳輸層控制協議TCP&…

Plotly繪圖

在做Python數據分析實驗的時候發現使用Plotly庫繪圖比較漂亮&#xff0c;在網上找到了一個比較好的教程&#xff0c;這里記錄一下&#xff0c;方便以后查找。 傳送門

計算機網絡【0】概述

計算機網絡概念和功能 概念 是一個將分散的、具有獨立功能的計算機系統&#xff0c;通過通信設備與線路連接起來&#xff0c;由功能完善的軟件實現資源共享和信息傳遞的系統。 計算機網絡是互連的、自治&#xff08;無主從關系&#xff09;的計算機集合。 功能 數據通信&am…

計算機網絡【1】物理層

物理層解決如何在連接各種計算機的傳輸媒體上傳輸數據比特流&#xff0c;而不是指具體的傳輸媒體。 確定與傳輸媒體接口有關的特性 機械特性&#xff1a;定義物理連接的特性&#xff0c;如規格、接口形狀、引線數目、引腳數目、排列電氣特性&#xff1a;規定傳輸二進制位時的電…

計算機網路【2】數據鏈路層

結點&#xff1a;主機、路由器 鏈路&#xff1a;兩個節點的物理通道 數據鏈路&#xff1a;邏輯通道&#xff0c;把實現 控制數據傳輸協議的硬件和軟件加到鏈路上就構成數據鏈路 幀&#xff1a;鏈路層的協議數據單元&#xff0c;封裝網絡層數據報 數據鏈路層在物理層提供服務的…

計算機網絡【5】應用層

應用層對應用程序的通信提供服務 應用層協議定義&#xff1a; 應用層的功能&#xff1a; 文件傳輸、訪問和管理電子郵件虛擬終端查詢服務和遠程作業登錄 重要協議&#xff1a;FTP、SMTP、POP3、HTTP、DNS 網絡應用模型 客戶/服務器模型&#xff08;Client/Server&#x…

操作系統【八】文件管理

文件&#xff1a;一組有意義的信息/數據集合 文件的屬性&#xff1a; 文件名&#xff1a;由創建文件的用戶決定文件名&#xff0c;主要是為了方便用戶找到文件。同一個目錄下不允許有重名文件標識符&#xff1a;一個系統內的個文件標識符唯一&#xff0c;對用戶來說毫無可讀性…

數據庫原理及應用【六】數據庫設計

數據依賴 函數依賴FD&#xff1a;一個屬性或者一組屬性的值可以決定另一個屬性的值 多值依賴MVD&#xff1a;一個屬性或者一組屬性的值可以決定另一個屬性的值的集合。FD是MVD的特例 符號表示&#xff1a;Name->->Course&#xff0c;課程多值依賴于姓名 連接依賴&#x…

數據可視化【一】JavaScript學習

本博客是我學習Curran Kelleher老師數據可視化課程的筆記&#xff0c;感興趣的小伙伴可以點擊這里學習。 three cores of data visualization: analysisdesignconstruction 推薦書籍《visualization analysis & design》 使用https://vizhub.com/進行編程學習&#xff…

數據庫原理及應用【二】數據模型

層次模型 tree Record and fieldParent-Child relationship(PCR) 每個記錄類型只有一個父節點 無法表達多對多信息 采用虛記錄解決多對多 網狀數據模型 系&#xff1a;主記錄->屬記錄 主記錄和屬記錄都可以有好多個 關系模型 表&#xff1a;table/relation 擁有更高的…

數據可視化【二】HTML+CSS+SVG+D3

HTML、CSS和SVG學習實現代碼&#xff1a;https://vizhub.com/Edward-Elric233/89185eb96bc64a9d81777873a0ccd0b9 index.html <!DOCTYPE html> <html><head><title>Shapes with SVG and CSS</title><link rel"stylesheet" href&qu…

數據可視化【三】基本概念

Visualization is suitable when there is a need to augment human capabilities rather than replace people with computational decision-making methods. 當可以信賴的智能化的解決方案存在的時候&#xff0c;可視化是不必要的。 當不知道需要分析的問題是什么的時候&…

數據可視化【四】Bar Chart

Make a Bar Chart Representing a data table in JavaScriptCreating rectangles for each rowUsing linear and band scalesThe margin conventionAdding axes 以下學習內容參考博客&#xff1a;傳送門 select()選擇所有指定元素的第一個 selectAll()選擇指定元素的全部 上…

數據庫原理及應用【三】DBMS+SQL

DBMS Query LanguagesInterface and maintaining tools(GUI)APIsClass Library QL 不是圖靈完備的&#xff0c;不是一種編程語言。 QL SQL是一種非過程化的查詢語言。 DDL數據定義語言&#xff1a;表&#xff0c;視圖QL 查詢語言DML 數據操縱語言DCL 數據控制語言 Base t…