socket网络编程
端口 :主机上一个应用程序的代号(端口不变)
为什么不用PID来表示一个应用
因为PID会变化,而端口是不变的
套接字进程间通信——跨越主机
1、主机字节序列和网络字节序列
主机字节序列分为大端字节序和小端字节序,不同的主机采用的字节序列可能不同。大
端字节序是指一个整数的高位字节存储在内存的低地址处,低位字节存储在内存的高地址
处。小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的
低地址处。 在两台使用不同字节序的主机之间传递数据时,可能会出现冲突。所以,在将数
据发送到网络时规定整形数据使用大端字节序,所以也把大端字节序成为网络字节序列。对
方接收到数据后,可以根据自己的字节序进行转换
Linux 系统提供如下 4 个函数来完成主机字节序和网络字节序之间的转换:
# include <netinet/in.h> uint32_t htonl(uint32_t hostlong);//长整型的主机字节转网络字节序 uint32_t ntohl(uint32_t netlong);//长整型的网络字节序转主机字节序 uint32_t htons(uint16_t hostshort);//短整型的主机字节转网络字节序列 uint32_t ntonhs(uint16_t hostshort);//短整型的网络字节序列转主机字节序列
2.套接字地址结构
2.1通用socket地址结构
socket网络编程接口中表示socket地址是结构体sockaddr,其定义如下:
# include <bits/socket.h> struct sockaddr//通用套接字地址结构 {sa_family_t sa_family;char sa_data[14]; }
sa_family成员是地址族类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。常见的协议族和对应的地址族如下图所示:
2.2 专用socket地址结构
TCP/IP协议族有sockaddr_in和sockaddr_in6两个专用socket地址结构体,它们分别用于IPV4和IPV6:
/*sin_family:地址族 AF_INETsin_port:端口号,需要用网络字节序表示sin_addr:IPV4地址结构:s_addr以网络字节序表示IPV4地址 */ struct in_addr {u_int32_t s_addr; }; struct sockaddr_in {sa_family sin_family;u_int16_t sin_port;struct in_addr sin_addr; }; struct in6_addr {unsigned char sa_addr[16]; }; struct sockaddr_in6 {sa_family_t sin6_family;//地址族:AF_INETu_inet16_t sin6_port;//端口号:用网络字节序表示u_int32_t sin6_flowinfo;//流信息,应设置为0struct in6_addr sin6_addr;//IPV6地址结构体u_int32_t sin6_scope_id;//scope ID,尚处于实验阶段 };
2.3 IP地址转换函数
通常,人们习惯用点分十进制字符串表示IPV4地址,但编程中我们需要把它们转换成整数方能使用,下面函数可用于点分十进制字符串表示的IPV4地址和网络字节序整数表示的IPV4地址之间的转换:
# include <arpa/inet.h> in_addr_t inet_addr(const char*cp);//字符串表示的IPV地址转化为网络字节序 char*inet_ntoa(struct in_addr in);//IPV4地址的网络字节序转化为字符串表示
3.网络编程接口
# include <sys.types.h> # include <sys/socket.h> int socket(int domain,int type,int protocol); /* socket()创建套接字,成功返回套接字的文件描述符,失败返回-1 第一个参数:domain(地址族):AF_UNIX、AF_INET、AF_INET6 第二个参数:type(服务类型):设置套接字的服务类型SOCK_STREAM(TCP协议) SOCK_DGRAM(UDP协议) 第三个参数:protocol(协议版本):一般设置为0,表示使用默认协议 */ int bind(int sockfd,const struct sockaddr*addr,socklen_t addrlen); /* bind():将sockfd与一个socket地址绑定,成功返回0,失败返回-1 sockfd是网络套接字描述符 addr是地址结构 addrlen是socket地址长度 */ int listen(int sockfd,int backlog); /* listen()创建一个监听队列以存储待处理的客户连接,成功返回0,失败返回-1 sockfd是被监听的socket套接字 backlog表示处于完全连接状态的socket的上限 */ int accept(int sockfd,struct sockaddr*addr,socklen_t*addrlen); /* accept()从listen监听队列中接收一个连接,成功返回一个新的连接socket,该socket唯一地标识了被接收的这个连接,失败返回-1 sockfd是执行过listen系统调用的监听socket addr参数用来获取被接受连接的远端socket地址 addrlen指定该socket地址的长度 */ ssize_t recv(int sockfd,void*buff,size_t len,int flags); ssize_t send(int sockfd,const void*buff,size_t len,int flags); /* TCP数据读写: recv()读取sockfd上的数据,buff和len参数分别指定读缓冲区的位置和大小 send()往socket上写入数据,buff和len参数分别指定写缓冲区的位置和数据长度 flgs参数为数据收发提供了额外的控制 */ int connect(int sockfd,const struct sockaddr*serv_addr,socklen_t addrlen); /* connect()客户端需要通过此系统调用来主动与服务器建立连接,成功返回0,失败返回-1 第一个参数:sockfd时由socket()返回的一个套接字标识符 第二个参数:serv_addr是服务器监听的socket地址 第三个参数:addrlen则指定这个地址的长度 */ ssize_t recvfrom(int sockfd,void*buff,size_t len,int flags,struct sockaddr*src_addr,socklen_t*addrlen); ssize_t sendto(int sockfd,void*buff,size_t len,int flags,struct sockaddr*dest_addr,socklen_t addrlen); /* UDP数据读取: recvfrom()读取sockfd上的数据,buff和len参数分别指读缓冲区的位置和大小,src_addr记录发生端的socket地址,addrlen指定该地址的长度 sendto()往socket上写入数据,buff和冷参数分别指读取缓冲区的位置和数据长度,dest_addr指定接收数据端的地址,addrlen指定该地址的长度 */
4.TCP编程流程
TCP提供的是面向连接的、可靠的、字节流服务。TCP的服务器端和客户端编程流程如下:
socket()方法是用来创建一个套接字,有了套接字就可以通过网络进行数据的收发。这也是为什么进行网络通信的程序首先要创建一个套接字。创建套接字时要指定使用的服务类型,使用TCP协议选择流式服务(SOCK_STREAM)。
bind()方法是用来指定套接字使用的IP地址和端口。IP地址就是自己主机的地址,如果主机没有接入网络,测试程序时可以使用回环地址"127.0.0.1"。端口是一个16位的整形值,一般0-1024为知名端口,入HTTP使用的是80号端口。这类端口一般用户不能随便使用。其次,1024-4096为保留端口,,用户一般也不用。4096以上为临时端口,用户可以使用。在Linux上,1024以内的端口只有root用户可以使用。
listen()方法是用来创建监听队列。监听队列有两种,一个是存放未完成三次握手的连接,一种是存放已完成三次握手的连接。listen()第二个参数就是指定已完成三次握手队列的长度。
accept()处理存放在listen创建的已完成三次握手的队列中的连接。每处理一个连接,则accept()返回该连接对应的套接字描述符。如果该队列为空,则accept()阻塞。
connect()方法一般由客户端程序执行,需要指定连接的服务器端的IP地址和端口。该方法执行后,会进行三次握手,建立连接。
send()方法用来向TCP连接的对端发送数据。send()执行成功,只能说明将数据成功写入到发送端的发送缓冲区中,并不能说明数据已经发送到了对端。send()的返回值为实际写入到发送端缓冲区中的数据长度。
recv()方法用来接收TCP连接的对端发送来的数据。recv()从本端的缓冲区中读取数据,如果接收缓冲区中没有数据,则recv()方法会阻塞。返回值是实际读到的字节数,如果recv()返回值为0,说明对方已经关闭了TCP连接。
close()方法用来关闭TCP连接。此时,会进行四次挥手。
TCP服务端代码ser.c如下:
# include <stdio.h> # include <stdlib.h> # include <unistd.h> # include <string.h> # include <assert.h> # include <sys/socket.h> # include <arpa/inet.h> # include <netinet/in.h> int main () {int sockfd=socket(AF_INET,SOCK_STREAM,0);//第一步创建套接字assert(sockfd!=-1);struct sockaddr_in saddr;//IPV4地址结构,saddr以网络字节序列表示地址结构memset(&saddr,0,sizeof (saddr));//清空saddr,防止占用空间saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);//htons将主机字节序转换为网络字节序saddr.sin_addr.s_addr=inet_addr("127.0.0.1");//回环地址int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));//讲套接字标识符与套接字socket地址绑定,成功返回0,失败返回-1.assert(res!=-1);res=listen(sockfd,5);//创建一个监听队列,处理待处理的连接assert(res!=-1);while(1){struct sockaddr_in caddr;//远端连接服务器的地址结构socklen_t len=sizeof (caddr);int c=accept(sockfd,(struct sockaddr*)&caddr,&len);//从listen监听队列中接收一个连接,成功返回一个新的连接socket,该socket唯一地标识了被接收的这个连接,失败返回-1if (c==-1){continue;}printf("accept c=%d\n",c);char data[128]={0};int n=recv(c,data,127,0);printf ("n=%d,buff=%s\n",n,data);send(c,"ok",2,0);close(c);//关闭连接,开始四次挥手 } close(sockfd); exit(0); }
TCP客服端代码cli.c如下:
# include <stdio.h> # include <stdlib.h> # include <unistd.h> # include <string.h> # include <assert.h> # include <sys/socket.h> # include <arpa/inet.h> # include <netinet/in.h> int main () {int sockfd=socket(AF_INET,SOCK_STREAM,0);assert(sockfd!=-1);struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=connect(sockfd,(struct sockaddr*)&saddr,sizeof (saddr));//客服端需要通过此系统调用来主动与服务器端建立连接,成功返回0,失败返回-1。assert(res!=-1);printf ("please input:");char buff[128]={0};fgets(buff,128,stdin);send(sockfd,buff,strlen(buff)-1,0);//往socked上写入数据int n=recv(sockfd,data,127,0);//读取sockfd上的数据printf ("%s\n",data);close(sockfd);exit(0); }
结果:
5.多进程、多线程处理并发
如图所示,当一个客户端与服务器建立连接以后·,服务器端accept()返回,进而准备循环接收客服端发过来数据 。如果客服端暂时没发数据,服务端会在recv()阻塞。此时其他客户端向服务器发起连接后,由于服务器阻塞了,无法执行accept()接受连接,也就是其他客户端发生的数据,服务器无法读取,服务器也就无法并发同时处理多个客户端。
这个问题可以通过引入多线程或多进程来解决。服务器端接受一个客服端的连接后,创建一个线程或者进程,然后在新创建的线程中循环处理数据。主线程(父进程)只负责监听客服端的连接,并使用accept()接受连接,不进行数据的处理。如下图所示:
多线程处理并发的服务器端代码ser.c如下:
# include <stdio.h> # include <stdlib.h> # include <unistd.h> # include <string.h> # include <assert.h> # include <sys/socket.h> # include <netinet/in.h> # include <arpa/inet.h> # include <pthread.h> void*fun(void *arg) {int c=(int)arg;while (1){char buff[128]={0};if (recv(c,buff,127,0)<=0){break;}printf ("recv(%d)=%s\n",c,buff);send(c,"ok",2,0);}printf ("one client over(%d)\n",c);close(c); } int main () {int sockfd=socket(AF_INET,SOCK_STREAT,0);assert(sockfd!=-1);struct sockaddr_in saddr,caddr;memset(&saddr,0,sizeof (saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=bind(sockfd,(struct sockaddr*)&saddr,sizeof(saddr));assert(res!=-1);res=listen(sockfd,5);assert(res!=-1);while (1){int len=sizeof(caddr);int c=accept(sockfd,(struct sockaddr*)&caddr,sizeof (caddr));if (c<0){continue;}printf ("accept c=%d\n",c);pthread_t id;pthread_create(&id,NULL,fun,(void*)c);}close(sockfd);exit(0); }
6.UDP编程流程
UDP提供的是无连接、不可靠、数据服务。编程流程如下。
socket()用来创建套接字,使用udp协议时,选择数据报服务SOCK_DGRAM。sendto()用来发送数据,由于udp时无连接的,每次发送数据都需要指定对端的地址(IP和端口)。recvfrom()接收数据,每次都需要传给该方法一个地址结构来存放发送端的地址·。recvfrom()可以用来接收所有客服端发送给当前应用程序的地址,并不是一个只能接收某一个客服端的数据。
UDP服务端编程代码示例:
# include <stdio.h> # include <stdlib.h> # include <string.h> # include <unistd.h> # include <assert.h> # include <sys/socket.h> # include <netinet/in.h> # include <arpa/inet.h> int main () {int sockfd=socket(AF_INET,SOCK_DGRAM,0);assert(sockfd!=-1);struct socksaddr_in saddr,caddr;memset(&saddr,0,sizeof (saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");int res=bind(sockfd,(struct saddr*)&saddr,sizeof(saddr));assert(res!=-1);while (1){int len=sizeof(caddr);char buff[128]={0};recvfrom(sockfd,buff,127,0,(struct sockaddr*)&caddr,&len);printf ("ip:%s,port:%d,buff=%s\n",inet_ntoa(caddr.sin_addr),ntohs(caddr.sin_port),buff);sendto(sockfd,"ok",2,0,(struct sockaddr*)&caddr,sizeof (caddr));}close(sockfd); }
UDP客户端代码:
# include <stdio.h> # include <stdlib.h> # include <string.h> # include <unistd.h> # include <sys/socket.h> # include <assert.h> # include <netinet/in.h> # include <arpa/inet.h> int main () {int sockfd=socket(AF_INET,SOCK_DGRAM,0);assert(sockfd!=-1);struct sockaddr_in saddr;memset(&saddr,0,sizeof(saddr));saddr.sin_family=AF_INET;saddr.sin_port=htons(6000);saddr.sin_addr.s_addr=inet_addr("127.0.0.1");while(1){char buff[128]={0};printf ("input:\n");fgets(buff,128,stdin);if(strcnmp(buff,"end",3)==0){break;}sendto(sockfd,buff,strlen(buff),0,(struct sockaddr*)&saddr,sizeof(saddr));memset(buff,0,128);int len=sizeof(saddr);recvfrom(sockfd,buff,127,0,(struct saddr*)&saddr,&len);printf ("buff=%s\n",buff);} close(sockfd); }
相关文章:

socket网络编程
端口 :主机上一个应用程序的代号(端口不变) 为什么不用PID来表示一个应用 因为PID会变化,而端口是不变的 套接字进程间通信——跨越主机 1、主机字节序列和网络字节序列 主机字节序列分为大端字节序和小端字节序,不同…...

IO多路复用机制详解
高性能IO模型浅析 服务器端编程经常需要构造高性能的IO模型,常见的IO模型有四种: (1)同步阻塞IO(Blocking IO):即传统的IO模型。 (2)同步非阻塞IO(Non-blo…...

选择一款好用的营销项目管理可以更好帮您解决任何问题
营销项目管理软件哪个好用?使用Zoho Projects营销项目管理软件,您可以从营销活动中获得最佳结果,并获得可执行的见解。Zoho Projects的营销项目管理软件可让您和您的团队全面了解您的所有活动。监控您的社交渠道、跟踪结果并在一处进行交流。…...
计算机网络(第八版)第三章知识总结(期末复习可用)
本笔记来源于博主上课所记笔记整理,可能不全,欢迎大家批评指正,如果觉得有用记得点个赞,给博主点个关注...该笔记将会持续更新...整理不易,希望大家多多点赞。 第一章 第三章 数据链路层 数据链路层属于计算机网络的低…...

VScode配置8086汇编环境
目录 0、感慨 1、VScode的安装 2、下载MASM/TASM插件 3、测试汇编环境 新建文件 汇编文件配置 汇编代码的运行 0、感慨 搭配一个简单些的环境,对于我们汇编的学习很有帮助,在这里又不得不感叹vscode的强大,使用VScodeMASM/TASM插件就…...

银行数字化转型导师坚鹏:银行同业核心产品与营销策略解读
数字化背景下银行同业核心产品与营销策略解读课程背景: 数字化背景下,很多银行存在以下问题:不清楚银行同业核心产品发展现状?不清楚如何银行同业产品营销策略?不知道如何更好地挖掘他行优质客户? 课…...

在线答题考试小程序源码系统 支持在线刷题+考试二合一+安装部署教程
分享一个在线答题考试小程序源码系统,支持在线刷题考试二合一,程序包含前后端和详细的安装部署教程,可以用来给学生刷题,给员工刷题,给政企员工刷题,万能通用版适合任何行业在线刷题及考试。 系统功能一览&…...

同城跑腿小程序怎么做
随着市场需求越来越大,分工越来明细,很多人看到了跑腿类的商机,特别是学校、小区、商务园等场所。无论是校园跑腿还是社会类跑腿,例中代取快递、代拿包裹、代搬东西上下楼、代排队、帮忙办事、代买东西、代送等等功能都少不了&…...

爬虫逆向学习(五):使用RPC框架serkio解决逆向难题
serkio应用实战前言实战开发多次调用加密方法破解失败如何刷新加密方法同一个浏览器的加密代码如何给不同用户使用注意事项总结前言 最近在工作中遇到了一个反爬虫产品,处于技术能力和新产品迭代更新快的考虑,最后选择使用RPC技术解决问题,因…...

NumPy 秘籍中文第二版:三、掌握常用函数
原文:NumPy Cookbook - Second Edition 协议:CC BY-NC-SA 4.0 译者:飞龙 在本章中,我们将介绍许多常用函数: sqrt(),log(),arange(),astype()和sum()ceil(),modf()&…...

蓝桥杯基础17:BASIC-02试题 序列求和
资源限制 内存限制:256.0MB C/C时间限制:1.0s Java时间限制:3.0s Python时间限制:5.0s 问题描述 求123...n的值。 输入格式 输入包括一个整数n。 输出格式 输出一行,包括一个整数,表示123...n…...
vue移动端实现vue-pdf在线预览与展示,并且解决页面汉字空白的问题
vue移动端实现pdf的页面在线预览展示,CMapReaderFactory可以解决文字不展示、空白问题 //1、安装依赖vue-pdf npm install --save vue-pdf//2、使用组件 <pdf v-for"i in numPages" ref"pdfs" :src"pdfUrl" :key"i" …...
代码随想录算法训练营第四十九天 | 121. 买卖股票的最佳时机、122.买卖股票的最佳时机II
打卡第49天,买卖股票系列了 今日任务 ● 121. 买卖股票的最佳时机 ● 122.买卖股票的最佳时机II 121. 买卖股票的最佳时机 给定一个数组 prices ,它的第 i 个元素 prices[i] 表示一支给定股票第 i 天的价格。 你只能选择 某一天 买入这只股票&#x…...
【职场篇】程序员是否吃青春饭?程序员在35岁之后是否需要转行?
你们好 那么众所周知呢像空姐 还有模特这种职业呢 都是吃青春饭的 那么到了一定年龄呢 他们可能就不做这一行了 那么其实程序员这个职业呢 有的人认为他也是吃青春饭的 普遍人都认为呢 如果程序员做到35岁呢 没有转管理岗位 可能以后就没有什么前途了 可能就要考虑换别的行业了…...

( “树” 之 DFS) 226. 翻转二叉树 ——【Leetcode每日一题】
226. 翻转二叉树 给你一棵二叉树的根节点 root ,翻转这棵二叉树,并返回其根节点。 示例 1: 输入:root [4,2,7,1,3,6,9] 输出:[4,7,2,9,6,3,1] 示例 2: 输入:root [2,1,3] 输出:[…...

实验7---myBatis和Spring整合
实验七 myBatis和Spring整合 一、实验目的及任务 通过该实验,掌握mybatis和spring整合方法,掌握生成mapper实现类的两种生成方式。 二、实验环境及条件 主机操作系统为Win10,Tomcat,j2sdk1.6或以上版本。 三、实验实施步骤 略 四、实验报告内…...

DJ3-4 传输层(第四节课)
目录 一、TCP 概述 二、TCP 报文段的首部字段格式 三、TCP 往返时延的估计和超时 1. 估计往返时间 2. RTT 估计例子 3. 估计往返时间的偏差 4. 设置重传超时间隔 一、TCP 概述 全双工服务:允许在同一时间同一连接上,数据能够双向传输。注意&#…...

2023爱分析·商业智能应用解决方案市场厂商评估报告:数聚股份
目录 1. 研究范围定义 2. 商业智能应用解决方案市场分析 3. 厂商评估:数聚股份 4. 入选证书 1. 研究范围定义 商业智能(BI)是在实现数据集成和统一管理的基础上,利用数据存储和处理、分析与展示等技术,满足企…...
Kotlin方法执行顺序
方法的执行顺序 主构造函数init代码块次构造函数...

Ubuntu系统配置SonarQube + cppcheck + Jenkins
SonarQube1. postgresql安装及配置1.1 安装postgresql1.2 创建sonarqube用户1.3 设置数据库2. 安装sonarqube2.1 设置sonarqube2.2 修改sonarqube目录权限2.3 sonar.properties2.4 设置systemd管理sonarqube2.5 web3. 配置sonarscanner3.1 下载3.2 配置4. 配置cppcheck4.1 下载…...

JavaSec-RCE
简介 RCE(Remote Code Execution),可以分为:命令注入(Command Injection)、代码注入(Code Injection) 代码注入 1.漏洞场景:Groovy代码注入 Groovy是一种基于JVM的动态语言,语法简洁,支持闭包、动态类型和Java互操作性,…...

AI Agent与Agentic AI:原理、应用、挑战与未来展望
文章目录 一、引言二、AI Agent与Agentic AI的兴起2.1 技术契机与生态成熟2.2 Agent的定义与特征2.3 Agent的发展历程 三、AI Agent的核心技术栈解密3.1 感知模块代码示例:使用Python和OpenCV进行图像识别 3.2 认知与决策模块代码示例:使用OpenAI GPT-3进…...

转转集团旗下首家二手多品类循环仓店“超级转转”开业
6月9日,国内领先的循环经济企业转转集团旗下首家二手多品类循环仓店“超级转转”正式开业。 转转集团创始人兼CEO黄炜、转转循环时尚发起人朱珠、转转集团COO兼红布林CEO胡伟琨、王府井集团副总裁祝捷等出席了开业剪彩仪式。 据「TMT星球」了解,“超级…...

【SQL学习笔记1】增删改查+多表连接全解析(内附SQL免费在线练习工具)
可以使用Sqliteviz这个网站免费编写sql语句,它能够让用户直接在浏览器内练习SQL的语法,不需要安装任何软件。 链接如下: sqliteviz 注意: 在转写SQL语法时,关键字之间有一个特定的顺序,这个顺序会影响到…...
python如何将word的doc另存为docx
将 DOCX 文件另存为 DOCX 格式(Python 实现) 在 Python 中,你可以使用 python-docx 库来操作 Word 文档。不过需要注意的是,.doc 是旧的 Word 格式,而 .docx 是新的基于 XML 的格式。python-docx 只能处理 .docx 格式…...

优选算法第十二讲:队列 + 宽搜 优先级队列
优选算法第十二讲:队列 宽搜 && 优先级队列 1.N叉树的层序遍历2.二叉树的锯齿型层序遍历3.二叉树最大宽度4.在每个树行中找最大值5.优先级队列 -- 最后一块石头的重量6.数据流中的第K大元素7.前K个高频单词8.数据流的中位数 1.N叉树的层序遍历 2.二叉树的锯…...
在树莓派上添加音频输入设备的几种方法
在树莓派上添加音频输入设备可以通过以下步骤完成,具体方法取决于设备类型(如USB麦克风、3.5mm接口麦克风或HDMI音频输入)。以下是详细指南: 1. 连接音频输入设备 USB麦克风/声卡:直接插入树莓派的USB接口。3.5mm麦克…...

springboot 日志类切面,接口成功记录日志,失败不记录
springboot 日志类切面,接口成功记录日志,失败不记录 自定义一个注解方法 import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target;/***…...
vue3 daterange正则踩坑
<el-form-item label"空置时间" prop"vacantTime"> <el-date-picker v-model"form.vacantTime" type"daterange" start-placeholder"开始日期" end-placeholder"结束日期" clearable :editable"fal…...

篇章二 论坛系统——系统设计
目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...