主頁 > 軟體設計 > C語言 基于UDP的局域網多人聊天室的簡單代碼

C語言 基于UDP的局域網多人聊天室的簡單代碼

2021-01-24 22:47:08 軟體設計

[C]基于UDP的局域網多人聊天室的簡單代碼 ..編程小白,代碼可能比較冗長,請各位大佬指出不足 [*・ω・]

    • 運行現象
    • 思路
    • 程式原始碼(LINUX)
      • 1.head.h(頭檔案)
      • 2.datalink.c (存放著各種自定義函式介面)
      • 3.server.c(服務端)
      • 4.client.c(客戶端)
      • 5.Makefile()
    • 代碼中的不足之處
    • 原始碼檔案鏈接
    • 廢話

運行現象

1.當客戶端(client)打開,輸入昵稱后服務器(server)端以及客戶端的現象

服務器端會列印所登錄客戶端使用的ip資訊和埠號,并給其他用戶發送該用戶的登錄資訊
2.當客戶端發送訊息,以及服務端發送訊息,以及客戶端退出時現象
客戶端發送資訊
客戶端退出
服務端發送資訊

思路

1.因為服務端和客戶端都需要具備接收資料和發送資料的能力,因此需要使用多個執行緒分別完成接收和發送的操作(相關函式pthread_create())
2.因為需要通過服務端給每個用戶群發訊息,所以用戶的資訊需要儲存,用陣列和鏈表都可以,這里本人用的是鏈表
3.因為UDP協議發送資訊時用的是sedto()函式,每次發送資料都需要提供發送目標的ip和埠號,因此我們需要定義一個結構體來存放每個登錄用戶的埠號和ip號等資訊.

程式原始碼(LINUX)

1.head.h(頭檔案)

#ifndef _HEAD_H_
#define _HEAD_H_
#include <stdio.h>
#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <linux/in.h>
#include <string.h>
#include <pthread.h>
#include <semaphore.h>

//發送資訊的標志變數
#define LOGIN	0
#define QUIT	1
#define SESSION 2
#define SERVER	3
#define	RENAME	4

//存放客戶端發送資訊
typedef struct msg{
	int flag;
	char name[16];
	char info[64];
}msg;

//用來保存單個用戶的ip,埠號等資訊
typedef struct usermsg{
	char user_ip[32];
	unsigned short user_port;
	char user_name[16];
}usermsg;

//存放所有用戶資訊的鏈
typedef struct userlink{
	usermsg user;
	struct userlink *next;
}userlink;



int link_creat(userlink **);			//創建鏈表
int link_add(userlink *,usermsg *,int);	//添加用戶
int myperror(int,char*);				//報錯資訊(寫到后面忘了這個函式了,后面寫的函式幾乎都沒有做判斷...)
int link_delete(userlink*,usermsg*,int);//用戶退出,洗掉退出用戶的資訊
int mass(userlink*,usermsg*,msg*,int);	//服務器給各個客戶端群發資訊的函式
void* client_thread(void*);				//客戶端thread_create()創建執行緒函式的最后一個引數,該執行緒用于接收來自服務器的訊息
userlink* link_select(userlink*,usermsg*);//查找指定用戶的函式
void* clientmsg_thread(void *);			//服務端thread_create()創建執行緒函式的最后一個引數,該執行緒用于給客戶端發送服務端的訊息
void* servermsg_thread(void *);			//服務端thread_create()創建執行緒函式的最后一個引數,該執行緒用于給客戶端發送其他客戶端的訊息
#endif

2.datalink.c (存放著各種自定義函式介面)

#include"head.h"
//自定義的報錯函式
int myperror(int flag,char *name){
	if(flag < 0){
		printf(">>server : %s is error",name);
		perror(":");
		exit(-1);
		return 0;
	}else{
		printf(">>server : %s is succeed\n",name);
		return 0;
	}
}
//創建鏈表
int link_creat(userlink **p){
	if((*p) == NULL){
		(*p) = (userlink*)malloc(sizeof(userlink));
		(*p) -> next = NULL;
		(*p) -> user.user_port = 0;
		return 0;
	}
	puts("==SERVER== :link is exit");
	return 0;
}
//添加用戶,并給所有人發送登錄資訊
int link_add(userlink *p,usermsg *umsg,int sockfd){
	if(p){
		userlink *new = (userlink*)malloc(sizeof(userlink));
		new -> next = p -> next;
		p -> next = new;
		p -> user.user_port ++;
		strcpy(new -> user.user_ip,umsg -> user_ip);
		new -> user.user_port = umsg -> user_port;
		strcpy(new -> user.user_name,umsg->user_name);
		//在服務端列印登入用戶的名字,ip,以及埠號
		printf(">>server : %s is login,--[ip : %s][port : %u]--\n",umsg -> user_name,
				umsg -> user_ip,umsg -> user_port);
		new = NULL;
		//定義一個新的資料包
		msg smsg;
		//給資料包定義一個flag標志位,然后往其中填充一條"xxx"用戶登錄的訊息
		smsg.flag = SERVER;
		memset(smsg.info,'\0',sizeof(smsg.info));
		sprintf(smsg.info,">> %s << is login",umsg -> user_name);
		//使用mass()群發函式,發給所有用戶(該函式在下方定義)
		mass(p,NULL,&smsg,sockfd);
		return 0;
	}else{
		puts("==SERVER== : link not exist");
		return 0;
	}
}
//尋找用戶的前一個用戶
userlink* link_select(userlink *p,usermsg *umsg){
	while(p -> next){
		if(!strcmp(p->next->user.user_ip,umsg->user_ip)&&p->next->user.user_port == umsg->user_port ){
			return p;
			break;
		}
		p = p -> next;
	}
	return NULL;
}
//用戶退出
int link_delete(userlink *head,usermsg *umsg,int sockfd){
	userlink *temp,*p = NULL;
	if(head){
		if((p = link_select(head,umsg))){
			temp = p -> next;
			p -> next = temp -> next;
			//在服務端列印一下退出用戶的名字
			printf(">>>server : user[ %s ] will be quit\n",temp ->user.user_name);
			//定義一個新的資料包,操作和上文中的登錄操作類似,將資料包填充后呼叫mass函式群發
			msg bmsg;
			bmsg.flag = QUIT;
			strcpy(bmsg.name,temp -> user.user_name);
			mass(head,umsg,&bmsg,sockfd);
			free(temp);
			temp = NULL;
			head -> user.user_port--;
		}else{
			puts("==SERVER== : user not exist");
			return 0;
		}
	}else{
		puts("==SERVER== : link not exist");
		return 0;
	}
}
//給所有用戶轉發訊息
int mass(userlink *p,usermsg *umsg,msg *bmsg,int sockfd){
	struct sockaddr_in clientaddr;
	socklen_t clientlen = sizeof(clientaddr);
	//當該資訊來自客戶端時
	if(bmsg->flag == SESSION){
		userlink *namep = NULL;
		if((namep = link_select(p,umsg))){
			strcpy(bmsg->name,namep->next->user.user_name);
		}else{
			puts("==SERVER== : error,the user is exit???");
		}
		//當該資訊來自服務端時
	}else if(bmsg -> flag == SERVER){
		strcpy(bmsg->name,"server");
		//當該資訊是客戶端的退出指令時
	}else if(bmsg -> flag == QUIT){
		memset(bmsg->info,'\0',strlen(bmsg->info));
		sprintf(bmsg->info,"[%s is quit]",bmsg->name);
		strcpy(bmsg->name,"server");

	}
	//在服務端列印一下這條資訊的內容
	printf(">>> [%s] : %s\n",bmsg->name,bmsg->info);
	p = p->next;
	//遍歷整個鏈表,將打包好的資訊發送給各個客戶端
	while(p){
		clientaddr.sin_family = AF_INET;
		clientaddr.sin_port = htons(p->user.user_port);
		clientaddr.sin_addr.s_addr = inet_addr(p->user.user_ip);
		sendto(sockfd,bmsg,sizeof(msg),0,(struct sockaddr*)&clientaddr,clientlen);
		p = p-> next;
	}
}

3.server.c(服務端)

#include"head.h"

//執行緒信號量
sem_t sem_server;

//執行緒從該結構體中拿資料
struct thread_pass{
	int sockfd;
	userlink *head;
	msg *bmsg;
	usermsg *umsg;
}pass;

//同上,也是用來給執行緒拿資料的
struct buf{
	int sockfd;
	userlink *head;
}bufpack;

int main(int argc, const char *argv[])
{
	//創建鏈表
	userlink *head = NULL;
	link_creat(&head);
	//生成套接字檔案描述符
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	myperror(sockfd,"socket");
	struct sockaddr_in serveraddr;
	//服務端的ip,協議族,埠號等資訊
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(atoi(argv[2]));
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	myperror(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr)),"bind");
	//用來接收客戶端的ip,埠號資訊的結構體
	struct sockaddr_in clientaddr;
	socklen_t clientlen = sizeof(clientaddr);
	//自定義的存放客戶端資訊的結構體,便于往鏈表中填充
		usermsg umsg;
		//存放正文資訊以及資訊標志符的結構體資料包
		msg bmsg,smsg;
		pthread_t tid_send_clientmsg,tid_send_servermsg;
		bufpack.head = head;
		bufpack.sockfd = sockfd;
		//創建兩個執行緒
		pthread_create(&tid_send_servermsg,NULL,servermsg_thread,NULL);
		pthread_create(&tid_send_clientmsg,NULL,clientmsg_thread,NULL);
		//執行緒分離
		pthread_detach(tid_send_clientmsg);
		pthread_detach(tid_send_clientmsg);
		//初始化執行緒信號量
		sem_init(&sem_server,0,0);
		//回圈接收資料
		while(1){
			recvfrom(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&clientaddr,&clientlen);
			//將存放在clientaddr中的客戶端ip和埠號存放在我們自定義的umsg結構體里
			strcpy(umsg.user_ip,(char*)inet_ntoa(clientaddr.sin_addr.s_addr));
			umsg.user_port = ntohs(clientaddr.sin_port);
			switch(bmsg.flag){
				//當信號量為LOGIN時,為用戶登錄,添加鏈表
			case LOGIN:
				strcpy(umsg.user_name,bmsg.info);
				link_add(head,&umsg,sockfd);
				break;
				//為QUIT為用戶退出,洗掉鏈表中該用戶的資訊
			case QUIT:	
				link_delete(head,&umsg,sockfd);
				break;
				//SESSION為用戶發送過來的正文訊息,釋放一個信號量,交給執行緒處理
			case SESSION:
				pass.head = head;
				pass.bmsg = &bmsg;
				pass.umsg = &umsg;
				pass.sockfd = sockfd;
				sem_post(&sem_server);
			}
		}
		return 0;
}
//用來群發服務端訊息的執行緒
void* servermsg_thread(void *p){
	msg smsg;
	smsg.flag = SERVER;
	while(1){
		//fgets阻塞,從終端的輸入快取區獲取資訊,然后發送,沒有資訊則阻塞
		fgets(smsg.info,sizeof(smsg.info),stdin);
		(smsg.info)[strlen(smsg.info) - 1] = '\0';
		mass(bufpack.head,NULL,&smsg,bufpack.sockfd);
	}

	return 0;
}
//用啦群發客戶端訊息的執行緒
void* clientmsg_thread(void *p){
	while(1){
		//當只有主執行緒中信號量釋放時才會向下運行,否則阻塞
		sem_wait(&sem_server);
		mass(pass.head,pass.umsg,pass.bmsg,pass.sockfd);	
	}
}

4.client.c(客戶端)

#include"head.h"

int main(int argc, const char *argv[])
{
	//創建套接字檔案描述符
	int sockfd = socket(AF_INET,SOCK_DGRAM,0);
	myperror(sockfd,"socket");
	struct sockaddr_in serveraddr;
	pthread_t tid;
	msg bmsg;
	//服務端的各種資訊,用于后面sendto()函式
	serveraddr.sin_port = htons(atoi(argv[2]));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_addr.s_addr = inet_addr(argv[1]);
	//輸入名字的提示陳述句
	printf(">>>name (Less than 15 character) : ");
	fgets(bmsg.info,16,stdin);
	(bmsg.info)[strlen(bmsg.info) - 1] = '\0';
	bmsg.flag = LOGIN;
	sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
	//創建執行緒
	pthread_create(&tid,NULL,client_thread,&sockfd);
	pthread_detach(tid);
	while(1){
		//從終端輸入快取區獲取資訊,沒有則阻塞
		fgets(bmsg.info,sizeof(bmsg.info),stdin);
		(bmsg.info)[strlen(bmsg.info) - 1] = '\0';
		//當輸入#quit  ---這一退出指令時執行的陳述句,將標志位賦值為QUIT
		if(!strcmp(bmsg.info,"#quit")){
			bmsg.flag =	QUIT; 
			sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
			sleep(1);
			pthread_cancel(tid);
			break;
		}else{
			bmsg.flag = SESSION;
		}
		sendto(sockfd,&bmsg,sizeof(bmsg),0,(struct sockaddr*)&serveraddr,sizeof(serveraddr));
	}
	return 0;
}
//用于接收服務端發來的訊息,并列印在終端上
void* client_thread(void *p){
	msg recvmsg; 
	while(1){
		recvfrom(*(int *)p,&recvmsg,sizeof(recvmsg),0,NULL,NULL);
		printf(">>> [%s] : %s\n",recvmsg.name,recvmsg.info);
	}	
}

5.Makefile()

.PHONY:all
all:client server
OBJS1=client.o datalink.o
OBJS2=server.o datalink.o
CC=gcc
CFLAGS=-g

client:$(OBJS1)
	$(CC) $(CFLAGS) $^ -o $@ -lpthread
server:$(OBJS2)
	$(CC) $(CFLAGS) $^ -o $@ -lpthread
.PHONY:clean
clean:
	rm $(OBJS)

代碼中的不足之處

1.沒有輸入字符溢位時的優化陳述句
2.用戶重名無法判斷
3.服務端沒有正常退出的陳述句
4.代碼冗長,定義的變數以及結構體太多了,不方便閱讀

原始碼檔案鏈接

源檔案下載地址

廢話

本人為剛學嵌入式不久的編程小白,目前只學習了一些C語言,檔案IO和TCP、UDP協議的一些基礎知識,代碼寫的很爛,非常渴望各位大佬批評以及指點,
最后,非常感謝各位大佬點開這篇帖子,你們的點擊也一定是我學習的動力,感謝???????????·??·????????????

轉載請註明出處,本文鏈接:https://www.uj5u.com/ruanti/251819.html

標籤:其他

上一篇:Python爬蟲自學系列(四)

下一篇:看完這篇文章小白也能一次性搞得懂遞回問題!!!

標籤雲
其他(157675) Python(38076) JavaScript(25376) Java(17977) C(15215) 區塊鏈(8255) C#(7972) AI(7469) 爪哇(7425) MySQL(7132) html(6777) 基礎類(6313) sql(6102) 熊猫(6058) PHP(5869) 数组(5741) R(5409) Linux(5327) 反应(5209) 腳本語言(PerlPython)(5129) 非技術區(4971) Android(4554) 数据框(4311) css(4259) 节点.js(4032) C語言(3288) json(3245) 列表(3129) 扑(3119) C++語言(3117) 安卓(2998) 打字稿(2995) VBA(2789) Java相關(2746) 疑難問題(2699) 细绳(2522) 單片機工控(2479) iOS(2429) ASP.NET(2402) MongoDB(2323) 麻木的(2285) 正则表达式(2254) 字典(2211) 循环(2198) 迅速(2185) 擅长(2169) 镖(2155) 功能(1967) .NET技术(1958) Web開發(1951) python-3.x(1918) HtmlCss(1915) 弹簧靴(1913) C++(1909) xml(1889) PostgreSQL(1872) .NETCore(1853) 谷歌表格(1846) Unity3D(1843) for循环(1842)

熱門瀏覽
  • 面試突擊第一季,第二季,第三季

    第一季必考 https://www.bilibili.com/video/BV1FE411y79Y?from=search&seid=15921726601957489746 第二季分布式 https://www.bilibili.com/video/BV13f4y127ee/?spm_id_fro ......

    uj5u.com 2020-09-10 05:35:24 more
  • 第三單元作業總結

    1.前言 這應該是本學期最后一次寫作業總結了吧。總體來說,對作業的節奏也差不多掌握了,作業做起來的效率也更高了。雖然和之前的作業一樣,作業中都要用到新的知識,但是相比之前,更加懂得了如何利用工具以及資料。雖然之間卡過殼,但總體而言,這幾次作業還算完成的比較好。 2.作業程序總結 相比前兩個單元,此單 ......

    uj5u.com 2020-09-10 05:35:41 more
  • 北航OO(2020)第四單元博客作業暨課程總結博客

    北航OO(2020)第四單元博客作業暨課程總結博客 本單元作業的架構設計 在本單元中,由于UML圖具有比較清晰的樹形結構,因此我對其中需要進行查詢操作的元素進行了包裝,在樹的父節點中存盤所有孩子的參考。考慮到性能問題,我采用了快取機制,一次查詢后盡可能快取已經遍歷過的資訊,以減少遍歷次數。 本單元我 ......

    uj5u.com 2020-09-10 05:35:48 more
  • BUAA_OO_第四單元

    一、UML決議器設計 ? 先看下題目:第四單元實作一個基于JDK 8帶有效性檢查的UML(Unified Modeling Language)類圖,順序圖,狀態圖分析器 MyUmlInteraction,實際上我們要建立一個有向圖模型,UML中的物件(元素)可能與同級元素連接,也可與低級元素相連形成 ......

    uj5u.com 2020-09-10 05:35:54 more
  • 6.1邏輯運算子

    邏輯運算子 1. && 短路與 運算式1 && 運算式2 01.運算式1為true并且運算式2也為true 整體回傳為true 02.運算式1為false,將不會執行運算式2 整體回傳為false 03.只要有一個運算式為false 整體回傳為false 2. || 短路或 運算式1 || 運算式2 ......

    uj5u.com 2020-09-10 05:35:56 more
  • BUAAOO 第四單元 & 課程總結

    1. 第四單元:StarUml檔案決議 本單元采用了圖模型決議UML。 UML檔案可以抽象為圖、子圖、邊的邏輯結構。 在實作中,圖的節點包括類、介面、屬性,子圖包括狀態圖、順序圖等。 采用了三次遍歷UML元素的方法建圖,第一遍遍歷建點,第二、三次遍歷設定屬性、連邊,實作圖物件的初始化。這里借鑒了一些 ......

    uj5u.com 2020-09-10 05:36:06 more
  • 談談我對C# 多型的理解

    面向物件三要素:封裝、繼承、多型。 封裝和繼承,這兩個比較好理解,但要理解多型的話,可就稍微有點難度了。今天,我們就來講講多型的理解。 我們應該經常會看到面試題目:請談談對多型的理解。 其實呢,多型非常簡單,就一句話:呼叫同一種方法產生了不同的結果。 具體實作方式有三種。 一、多載 多載很簡單。 p ......

    uj5u.com 2020-09-10 05:36:09 more
  • Python 資料驅動工具:DDT

    背景 python 的unittest 沒有自帶資料驅動功能。 所以如果使用unittest,同時又想使用資料驅動,那么就可以使用DDT來完成。 DDT是 “Data-Driven Tests”的縮寫。 資料:http://ddt.readthedocs.io/en/latest/ 使用方法 dd. ......

    uj5u.com 2020-09-10 05:36:13 more
  • Python里面的xlrd模塊詳解

    那我就一下面積個問題對xlrd模塊進行學習一下: 1.什么是xlrd模塊? 2.為什么使用xlrd模塊? 3.怎樣使用xlrd模塊? 1.什么是xlrd模塊? ?python操作excel主要用到xlrd和xlwt這兩個庫,即xlrd是讀excel,xlwt是寫excel的庫。 今天就先來說一下xl ......

    uj5u.com 2020-09-10 05:36:28 more
  • 當我們創建HashMap時,底層到底做了什么?

    jdk1.7中的底層實作程序(底層基于陣列+鏈表) 在我們new HashMap()時,底層創建了默認長度為16的一維陣列Entry[ ] table。當我們呼叫map.put(key1,value1)方法向HashMap里添加資料的時候: 首先,呼叫key1所在類的hashCode()計算key1 ......

    uj5u.com 2020-09-10 05:36:38 more
最新发布
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:20:47 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:20:25 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:20:17 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:20:10 more
  • 【中介者設計模式詳解】C/Java/JS/Go/Python/TS不同語言實作

    * 中介者模式是一種行為型設計模式,它可以用來減少類之間的直接依賴關系,
    * 將物件之間的通信封裝到一個中介者物件中,從而使得各個物件之間的關系更加松散。
    * 在中介者模式中,物件之間不再直接相互互動,而是通過中介者來中轉訊息。 ......

    uj5u.com 2023-04-20 08:19:44 more
  • 露天煤礦現場調研和交流案例分享

    他們集團的資訊化公司及研究院在一個礦區正在做智能礦山的統一平臺的 試點,專案投資大概1億,包括了礦山的各方面的內容,顯示得我們這次交流有點多余。他們2年前開始做智能礦山的規劃,有很多煤礦行業專家的加持,他們的描述是非常完美,但是去年底應該上線的平臺,現在還沒有看到影子。他們確實有很多場景需求,但是被... ......

    uj5u.com 2023-04-20 08:19:07 more
  • 《社區人員管理》實戰案例設計&個人案例分享

    設計是一個讓人夢想成真程序,開始編碼、測驗、除錯之前進行需求分析和架構設計,才能保證關鍵方面都做正確 ......

    uj5u.com 2023-04-20 08:18:57 more
  • 軟體架構生態化-多角色交付的探索實踐

    作為一個技術架構師,不僅僅要緊跟行業技術趨勢,還要結合研發團隊現狀及痛點,探索新的交付方案。在日常中,你是否遇到如下問題 “ 業務需求排期長研發是瓶頸;非研發角色感受不到研發技改提效的變化;引入ISV 團隊又擔心質量和安全,培訓周期長“等等,基于此我們探索了一種新的技術體系及交付方案來解決如上問題。 ......

    uj5u.com 2023-04-20 08:18:49 more
  • 05單件模式

    #經典的單件模式 public class Singleton { private static Singleton uniqueInstance; //一個靜態變數持有Singleton類的唯一實體。 // 其他有用的實體變數寫在這里 //構造器宣告為私有,只有Singleton可以實體化這個類! ......

    uj5u.com 2023-04-19 08:42:51 more
  • 【架構與設計】常見微服務分層架構的區別和落地實踐

    軟體工程的方方面面都遵循一個最基本的道理:沒有銀彈,架構分層模型更是如此,每一種都有各自優缺點,所以請根據不同的業務場景,并遵循簡單、可演進這兩個重要的架構原則選擇合適的架構分層模型即可。 ......

    uj5u.com 2023-04-19 08:42:41 more