C/S B/S P2p模型
在Linux系统编程中,C/S (Client/Server,客户端/服务器)和B/S(Browser/Server,浏览器/服务器)模型是两种常见的架构模式,用于构建分布式应用程序。它们在设计和实现上有不同的特点和应用场景。以下是它们的对比:
C/S(Client/Server)模型
1. 架构概述
-
客户端:运行在用户的计算机上,与服务器进行直接的通信。客户端可以是桌面应用程序、移动应用程序或其他类型的应用程序。
-
服务器:运行在服务器端,提供服务和资源。服务器处理客户端请求并返回响应。
2. 优点
-
高性能:客户端与服务器之间的通信直接,通常具有较高的效率和响应速度。
-
丰富的 用户界面:客户端可以提供复杂和丰富的用户界面,支持多种交互功能。
-
离线操作:客户端通常可以在没有网络的情况下运行,并在网络可用时进行数据同步。
3. 缺点
-
维护复杂:需要维护和更新客户端应用程序的多个版本,可能增加开发和维护的难度。
-
部署问题:客户端应用程序需要在每个用户的机器上安装,部署过程较为复杂。
-
平台依赖:客户端可能需要针对不同操作系统开发和测试,增加开发工作量。
4. 应用场景
-
桌面应用程序(如Office套件)
-
游戏客户端
-
专用业务应用程序
5. 示例技术
-
Socket编程:用于客户端与服务器之间的直接通信。
-
RPC ( 远程过程调用 ):用于客户端调用服务器上的方法。
-
GUI 库:如Qt、GTK+,用于构建客户端用户界面。
B/S(Browser/Server)模型
1. 架构概述
-
浏览器:运行在用户的计算机上,通过Web浏览器访问Web应用程序。浏览器与服务器进行通信,通过HTTP/HTTPS协议进行数据交换。
-
服务器:运行在服务器端,处理来自浏览器的请求并生成响应。响应通常是HTML、CSS和JavaScript代码。
2. 优点
-
跨平台:用户只需要一个支持Web的浏览器,不需要安装额外的软件。应用程序可以在不同的操作系统和设备上运行。
-
易于维护:更新和维护只需要在服务器端进行,用户端不需要进行软件更新。
-
简化部署:用户通过浏览器访问应用程序,减少了客户端的部署和安装复杂度。
-
广泛支持:支持各种终端设备,如PC、平板、手机等。
3. 缺点
-
性能限制:浏览器的性能受限于其处理能力和网络延迟,可能不如本地应用程序高效。
-
界面限制:Web应用程序的用户界面通常受限于浏览器的功能,可能不如桌面应用程序灵活。
-
离线功能:虽然现代Web技术如PWA(渐进式Web应用)可以提供有限的离线支持,但传统的Web应用程序通常依赖于持续的网络连接。
4. 应用场景
-
Web应用程序(如在线办公、电子商务网站)
-
在线服务(如银行服务、社交媒体)
-
互联网平台(如内容管理系统、论坛)
5. 示例技术
-
Web服务器:如Apache、Nginx,用于处理HTTP请求。
-
Web框架:如Django、Flask、Express,用于开发Web应用程序。
-
前端技术:如HTML、CSS、JavaScript,用于构建用户界面。
-
API:用于与前端和后端进行通信。
总结
-
C/S模型适用于需要高性能、复杂用户界面的应用,客户端和服务器之间通常通过专用协议进行通信。
-
B/S模型则更适用于需要广泛兼容和简化维护的应用,用户通过浏览器访问服务器上的应用程序。
P2P ( Peer-to-Peer )模型
是一种网络架构,其中每个节点(或称为对等方)在网络中既是客户端也是服务器。每个节点可以直接与其他节点通信并共享资源,而不依赖于中心服务器。
优点
-
去中心化:没有单一的故障点,增强了系统的可靠性和鲁棒性。
-
扩展性:节点数量增加时,系统性能通常会提升,因为每个新节点都可以提供更多资源。
-
资源共享:节点可以共享存储、计算能力或其他资源。
缺点
-
安全性:去中心化可能导致安全隐患,如数据泄露和恶意行为。
-
管理复杂:难以对每个节点进行统一管理和控制。
-
性能问题:网络性能依赖于每个节点的带宽和处理能力,可能会出现性能瓶颈。
应用场景
-
文件共享(如BitTorrent)
-
区块链和加密货币(如比特币)
-
去中心化应用(DApps)
示例技术
-
BitTorrent协议:用于文件共享,通过P2P网络分发文件。
-
区块链技术:使用P2P网络维护去中心化的账本
网络编程之TCP
1、模式 C/S 模式 ==》服务器/客户端模型
server:socket()-->bind()--->listen()-->accept()-->recv()-->close()
client:socket()-->connect()-->send()-->close();
int listen(int sockfd, int backlog);
功能:在参数1所在的套接字id上监听等待链接。
参数:
sockfd :套接字描述符,该套接字之前需要使用 socket
函数创建。
backlog:指定系统在处理连接之前,允许排队的最大连接数。
返回值:成功 0
失败 -1;
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
参数说明
-
sockfd :监听套接字的文件描述符。该套接字应该是已经通过
socket
函数创建,并且已经通过bind
和listen
函数设置为监听状态。 -
addr :指向
sockaddr
结构体的指针,用于存储客户端的地址信息。此参数可以是NULL
,如果不需要获取客户端地址信息的话。 -
addrlen :指向
socklen_t
类型的变量的指针,表示addr
参数指向的地址结构体的长度。在调用accept
前,addrlen
应该包含addr
所指向的结构体的大小(例如sizeof(struct sockaddr_in)
)。调用成功后,addrlen
将被设置为实际返回的地址结构体的长度。
返回值
-
成功:返回一个新的套接字描述符,用于与客户端通信。这个描述符是从监听套接字中接受到的连接的专用套接字。你可以使用这个描述符进行读写操作。
-
失败:返回
-1
,并设置errno
以指示错误原因。
总结
-
accept
函数用于从监听套接字中接受客户端的连接请求,并返回一个新的套接字描述符用于与客户端进行数据交换。 -
它是 TCP 连接的核心部分,与
socket
、bind
、和listen
函数配合使用,构成了 TCP 服务器的基本流程。 -
使用
accept
后,通常会进行数据的读取和处理,然后关闭套接字以释放资源。
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
在 Linux 系统编程中,recv
函数用于从已连接的套接字中接收数据。这是一个非常重要的函数,广泛应用于网络编程中,特别是在处理 TCP 套接字时。recv
函数从套接字接收数据并将其存储到指定的缓冲区中。
#include <sys/types.h> #include <sys/socket.h>
参数说明
-
sockfd :套接字描述符,指向一个已连接的套接字。该套接字必须已经通过
socket
函数创建,并且通过connect
函数建立了连接(对于 TCP 套接字)。 -
buf:指向缓冲区的指针,用于存储接收到的数据。
-
len :缓冲区的大小,以字节为单位。这个值表示
recv
函数最多可以读取len
字节的数据。 -
flags:接收数据的选项。这个参数可以是以下值之一,或其组合:
-
0
:没有特殊标志,函数将会阻塞,直到数据可读。 -
MSG_OOB
:接收带外数据。 -
MSG_PEEK
:查看数据但不移除它。下一次recv
调用将会再次返回这些数据。 -
MSG_DONTWAIT
:使recv
函数在非阻塞模式下工作。
-
返回值
-
成功 :返回接收到的字节数。如果返回值为
0
,表示对方关闭了连接(对于 TCP 套接字)。 -
失败 :返回
-1
,并设置errno
以指示错误原因。
ssize_t send(int sockfd, const void *buf, size_t len, int flags);
在 Linux 系统编程中,send
函数用于向已连接的套接字发送数据。它是网络编程中处理数据发送的重要函数,通常用于 TCP 套接字中。
#include <sys/types.h> #include <sys/socket.h>
参数说明
-
sockfd :套接字描述符,指向一个已连接的套接字。必须通过
socket
创建,并通过connect
连接(对于 TCP 套接字)。 -
buf:指向要发送的数据的指针。
-
len:要发送的数据的字节数。
-
flags:发送数据的选项。常见值包括:
-
0
:使用默认行为,函数可能会阻塞,直到数据被发送。 -
MSG_OOB
:发送带外数据。 -
MSG_DONTROUTE
:不通过路由发送数据,通常用于测试目的。 -
MSG_NOSIGNAL
:阻止在信号处理期间产生SIGPIPE
信号。
-
返回值
-
成功 :返回实际发送的字节数。如果发送的字节数小于
len
,说明数据可能被分段发送,需要在之后的send
调用中继续发送剩余数据。 -
失败 :返回
-1
,并设置errno
以指示错误原因。
总结
-
send
函数用于向已连接的套接字发送数据,并通过buf
指定的缓冲区传输数据。 -
它支持不同的标志来改变发送行为,例如防止产生
SIGPIPE
信号(MSG_NOSIGNAL
)。 -
处理返回值和错误对于确保数据成功发送和处理异常情况至关重要。
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int on = 1;
setsockopt(listfd, SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
setsockopt
函数是 Linux 系统编程中用于设置套接字选项的一个重要函数。它允许程序员对套接字进行配置,以便调整其行为和特性。
参数说明
-
sockfd
: 套接字描述符,是先前通过socket
函数创建的套接字。 -
level
: 指定选项的级别,通常是协议层次。常见的值包括:-
SOL_SOCKET
: 套接字级别选项 -
IPPROTO_TCP
: TCP 协议级别选项 -
IPPROTO_IP
: IP 协议级别选项
-
-
optname
: 要设置的选项名称,具体选项根据level
而不同。例如,SO_RCVBUF
、SO_KEEPALIVE
等。 -
optval
: 指向包含选项值的内存区域的指针。选项值的类型和大小依赖于optname
。 -
optlen
:optval
所指向的内存区域的大小(以字节为单位)。
SOL_SOCKET
层级:
SO_REUSEADDR
: 允许重用本地地址。(bind:address already in use)
eg:
Ser
cs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM,0 );
if(-1 ==listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr =inet_addr("127.0.0.1");
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 ==ret)
{
perror("bind");
exit(1);
}
//建立连接的排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
//通讯套接字
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
exit(1);
}
while(1)
{
char buf[512]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(rd_ret<=0)
{
break;
}
printf("cli:%s\n",buf);
time_t tm;
time(&tm);
sprintf(buf,"%s %s",buf,ctime(&tm));
send(conn,buf,strlen(buf),0);
}
close(listfd);
close(conn);
return 0;
}
Cli
cs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
int conn= socket(AF_INET,SOCK_STREAM,0);
if(-1 == conn)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr =inet_addr("127.0.0.1");
int ret = connect(conn,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
while(1)
{
char buf[512]="hello,this tcp test";
send(conn,buf,strlen(buf),0);
bzero(buf,sizeof(buf));
recv(conn,buf,sizeof(buf),0);
printf("ser:%s\n",buf);
sleep(1);
}
close(conn);
return 0;
}
三次握手,四次挥手
三次握手(TCP连接建立)
-
第一次握手:客户端发送 SYN
- 客户端向服务器发送一个SYN(同步)包,以初始化一个连接请求。这个包的SYN标志位被设置为1,并且客户端会选择一个初始的序列号(SEQ)。
-
第二次握手:服务器回应SYN-ACK
- 服务器收到客户端的SYN包后,确认客户端的请求,向客户端发送一个SYN-ACK包。这个包中SYN和ACK标志位都被设置为1。服务器也会选择一个自己的序列号,并确认客户端的序列号。
-
第三次握手:客户端确认 ACK
- 客户端收到服务器的SYN-ACK包后,发送一个ACK(确认)包给服务器。此包中ACK标志位被设置为1,并确认服务器的序列号。到此为止,客户端和服务器之间的连接就建立起来了。
四次挥手(TCP连接断开)
-
第一次挥手:主动关闭方发送FIN
- 主动关闭连接的一方(通常是客户端)发送一个FIN(终止)包给对方,表示数据已经发送完毕,希望关闭连接。此时客户端进入FIN_WAIT_1状态。
-
第二次挥手:被动关闭方回应FIN-ACK
- 被动关闭方(通常是服务器)收到FIN包后,发送一个FIN-ACK包来确认收到终止请求,并表示自己也准备关闭连接。此时服务器进入CLOSE_WAIT状态。
-
第三次挥手:主动关闭方接收 ACK
- 主动关闭方收到服务器的FIN-ACK包后,发送一个ACK包确认收到了关闭请求。此时客户端进入FIN_WAIT_2状态。
-
第四次挥手:被动关闭方关闭连接
- 被动关闭方在发送FIN-ACK包后,完成了数据的传输和关闭连接的准备。此时,服务器会发送一个ACK包来确认连接已关闭,然后进入LAST_ACK状态,直到确认ACK包被收到后才彻底关闭连接。
hw:tcp_cp
cs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM,0 );
if(-1 ==listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr =inet_addr("127.0.0.1");
int on = 1;// bind: Address already in use 异常断开,四次挥手顺序不对情况
setsockopt(listfd, SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 ==ret)
{
perror("bind");
exit(1);
}
//建立连接的排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
//通讯套接字
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
exit(1);
}
int fd = open("1.png",O_WRONLY|O_CREAT|O_TRUNC,0666);
if(-1 == fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[4096]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(rd_ret<=0)
{
break;
}
write(fd,buf,rd_ret);
strcpy(buf,"go on");
send(conn,buf,strlen(buf),0);
}
close(listfd);
close(conn);
close(fd);
return 0;
}
cs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <fcntl.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
int conn= socket(AF_INET,SOCK_STREAM,0);
if(-1 == conn)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr =inet_addr("127.0.0.1");
int ret = connect(conn,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
int fd = open("/home/linux/1.png",O_RDONLY);
if(-1 ==fd)
{
perror("open");
exit(1);
}
while(1)
{
char buf[4096]={0};
int rd_ret = read(fd,buf,sizeof(buf));
if(rd_ret<=0)
{
break;
}
send(conn,buf,rd_ret,0);
bzero(buf,sizeof(buf));
recv(conn,buf,sizeof(buf),0);
}
close(conn);
close(fd);
return 0;
}
hw:tcp_chat
cs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <signal.h>
typedef struct sockaddr* (SA);
int main(int argc, char *argv[])
{
//监听套接字
int listfd = socket(AF_INET,SOCK_STREAM,0 );
if(-1 ==listfd)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser,cli;
bzero(&ser,sizeof(ser));
bzero(&cli,sizeof(cli));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr =inet_addr("127.0.0.1");
int on = 1;
setsockopt(listfd, SOL_SOCKET,SO_REUSEADDR,&on,sizeof(on));
int ret = bind(listfd,(SA)&ser,sizeof(ser));
if(-1 ==ret)
{
perror("bind");
exit(1);
}
//建立连接的排队数
listen(listfd,3);
socklen_t len = sizeof(cli);
//通讯套接字
int conn = accept(listfd,(SA)&cli,&len);
if(-1 == conn)
{
perror("accept");
exit(1);
}
pid_t pid = fork();
if(pid>0)
{
while(1)
{
char buf[128]={0};
printf("to cli:");
fgets(buf,sizeof(buf),stdin);
int se_ret = send(conn,buf,strlen(buf),0);
if(0 == strcmp("#quit\n",buf)
||se_ret<0)
{
kill(pid,2);
exit(0);
}
}
}
else if(0 == pid)
{
while(1)
{
char buf[128]={0};
int rd_ret = recv(conn,buf,sizeof(buf),0);
if(0 == strcmp(buf,"#quit\n")
|| rd_ret<=0)
{
kill(getppid(),2);
exit(0);
}
printf("cli:%s",buf);
fflush(stdout);
}
}
else
{
perror("fork");
exit(1);
}
close(listfd);
close(conn);
return 0;
}
cs
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
#include <time.h>
#include <pthread.h>
typedef struct sockaddr* (SA);
void* th1(void* arg)
{
int sockfd = *(int*)arg;
while(1)
{
char buf[128]={0};
printf("to ser:");
fgets(buf,sizeof(buf),stdin);
int se_ret = send(sockfd,buf,strlen(buf),0);
if(0==strcmp(buf,"#quit\n")
|| se_ret<0)
{
exit(0);
}
}
return NULL;
}
void* th2(void* arg)
{
int sockfd = *(int*)arg;
while(1)
{
char buf[128]={0};
int rd_ret = recv(sockfd,buf,sizeof(buf),0);
if(0 == strcmp(buf,"#quit\n")
||rd_ret<=0)
{
exit(0);
}
printf("ser:%s",buf);
fflush(stdout);
}
return NULL;
}
int main(int argc, char *argv[])
{
int conn= socket(AF_INET,SOCK_STREAM,0);
if(-1 == conn)
{
perror("socket");
exit(1);
}
struct sockaddr_in ser;
bzero(&ser,sizeof(ser));
ser.sin_family = AF_INET;
ser.sin_port = htons(50000);
ser.sin_addr.s_addr =inet_addr("127.0.0.1");
int ret = connect(conn,(SA)&ser,sizeof(ser));
if(-1 == ret)
{
perror("connect");
exit(1);
}
pthread_t tid1,tid2;
pthread_create(&tid1,NULL,th1,&conn);
pthread_create(&tid2,NULL,th2,&conn);
pthread_join(tid1,NULL);
pthread_join(tid2,NULL);
close(conn);
return 0;
}