linux系统编程-网络-tcp(29)

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 函数创建,并且已经通过 bindlisten 函数设置为监听状态。

  • addr :指向 sockaddr 结构体的指针,用于存储客户端的地址信息。此参数可以是 NULL,如果不需要获取客户端地址信息的话。

  • addrlen :指向 socklen_t 类型的变量的指针,表示 addr 参数指向的地址结构体的长度。在调用 accept 前,addrlen 应该包含 addr 所指向的结构体的大小(例如 sizeof(struct sockaddr_in))。调用成功后,addrlen 将被设置为实际返回的地址结构体的长度。

返回值

  • 成功:返回一个新的套接字描述符,用于与客户端通信。这个描述符是从监听套接字中接受到的连接的专用套接字。你可以使用这个描述符进行读写操作。

  • 失败:返回 -1,并设置 errno 以指示错误原因。

总结

  • accept 函数用于从监听套接字中接受客户端的连接请求,并返回一个新的套接字描述符用于与客户端进行数据交换。

  • 它是 TCP 连接的核心部分,与 socketbind、和 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_RCVBUFSO_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连接建立)

  1. 第一次握手:客户端发送 SYN

    1. 客户端向服务器发送一个SYN(同步)包,以初始化一个连接请求。这个包的SYN标志位被设置为1,并且客户端会选择一个初始的序列号(SEQ)。
  2. 第二次握手:服务器回应SYN-ACK

    1. 服务器收到客户端的SYN包后,确认客户端的请求,向客户端发送一个SYN-ACK包。这个包中SYN和ACK标志位都被设置为1。服务器也会选择一个自己的序列号,并确认客户端的序列号。
  3. 第三次握手:客户端确认 ACK

    1. 客户端收到服务器的SYN-ACK包后,发送一个ACK(确认)包给服务器。此包中ACK标志位被设置为1,并确认服务器的序列号。到此为止,客户端和服务器之间的连接就建立起来了。

四次挥手(TCP连接断开)

  1. 第一次挥手:主动关闭方发送FIN

    1. 主动关闭连接的一方(通常是客户端)发送一个FIN(终止)包给对方,表示数据已经发送完毕,希望关闭连接。此时客户端进入FIN_WAIT_1状态。
  2. 第二次挥手:被动关闭方回应FIN-ACK

    1. 被动关闭方(通常是服务器)收到FIN包后,发送一个FIN-ACK包来确认收到终止请求,并表示自己也准备关闭连接。此时服务器进入CLOSE_WAIT状态。
  3. 第三次挥手:主动关闭方接收 ACK

    1. 主动关闭方收到服务器的FIN-ACK包后,发送一个ACK包确认收到了关闭请求。此时客户端进入FIN_WAIT_2状态。
  4. 第四次挥手:被动关闭方关闭连接

    1. 被动关闭方在发送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;
}
相关推荐
厨 神4 分钟前
vmware中的ubuntu系统扩容分区
linux·运维·ubuntu
Karoku0668 分钟前
【网站架构部署与优化】web服务与http协议
linux·运维·服务器·数据库·http·架构
geek_Chen0110 分钟前
虚拟机共享文件夹开启后mnt/hgfs/下无sharefiles? --已解决
linux·运维·服务器
(⊙o⊙)~哦41 分钟前
linux 解压缩
linux·运维·服务器
牧小七2 小时前
Linux命令---查看端口是否被占用
linux
衍生星球2 小时前
【网络安全】对称密码体制
网络·安全·网络安全·密码学·对称密码
掘根2 小时前
【网络】高级IO——poll版本TCP服务器
网络·数据库·sql·网络协议·tcp/ip·mysql·网络安全
鸡鸭扣3 小时前
虚拟机:3、(待更)WSL2安装Ubuntu系统+实现GPU直通
linux·运维·ubuntu
友友马3 小时前
『 Linux 』HTTP(一)
linux·运维·服务器·网络·c++·tcp/ip·http