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;
}
相关推荐
A小辣椒1 天前
TShark:Wireshark CLI 功能
linux
A小辣椒2 天前
TShark:基础知识
linux
AlfredZhao2 天前
OCI 明明分配了 200G 系统盘,为什么 df 只看到 30G?
linux·oci
AlfredZhao2 天前
vi 删除指定范围的行,不用再反复按 dd
linux·vi
用户9718356334662 天前
银河麒麟 KY10 申威(SW64) 安装 nginx-1.16.1-2.p01.ky10.sw_64.rpm 详细步骤
linux
猪脚踏浪3 天前
linux 拷贝文件或目录到指定的位置
linux
摇滚侠3 天前
Linux CentOS7 rpm 安装 MySQL 5.7
linux·运维·mysql
bush43 天前
嵌入式linux学习记录十四、术语
linux·嵌入式
载数而行5203 天前
Linux 11 动态监控指令top
linux
网络研究院3 天前
2026年网络安全
网络·安全·法律·法规·趋势·发展