• 多进程并发服务器


    • TCP三次握手建立连接
    • 错误处理模块:wrap.c,函数声明:wrap.h
    • 并发服务器模型(多进程,多线程)

    转换大小写程序

    服务端
    #include 
    #include 
    #include         
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define PORT 6799
    #define IP "127.0.0.1"
    void print_err(char* str){
        perror(str);
        exit(-1);
    }
    int main(){
        int res_bind=0;
        int cfd=0;
        struct sockaddr_in serverAddr;
        struct sockaddr_in clientAddr;
        socklen_t cli_addr_len;
        int sfd=socket(AF_INET,SOCK_STREAM,0);
        if(sfd==-1) print_err("socket fails\n");
        //把内存清零,在使用结构体赋值前将缓冲区清零
        bzero(&serverAddr,sizeof(serverAddr));
        serverAddr.sin_family=AF_INET;
        serverAddr.sin_port=htons(PORT);
        //serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
        inet_pton(sfd,IP,&serverAddr.sin_addr.s_addr);
        res_bind=bind(sfd,(struct sockaddr*)&serverAddr,\
                      sizeof(serverAddr));
        if(res_bind==-1) print_err("bind fails\n");
        listen(sfd,120);
        cli_addr_len=sizeof(clientAddr);
        cfd=accept(sfd,(struct sockaddr*)&clientAddr,&cli_addr_len);
        if(cfd==-1) print_err("accept fails\n");
        /*显示一下哪个客户端连接了*/
        char client_IP[100]={0};
        printf("client IP=%s,client PORT=%d\n",\
                inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,\
                                  client_IP,sizeof(client_IP)),\
                ntohs(clientAddr.sin_port));
        /*转换大小写*/
        char buf[30]={0};
        int n=read(cfd,buf,sizeof(buf));
        int i;
        for(i=0;i<n;i++){
            buf[i]=toupper(buf[i]);}
        write(cfd,buf,sizeof(buf));
        close(sfd);
        close(cfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    用户端
    #include 
    #include 
    #include         
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define PORT 6799
    #define IP "127.0.0.1"
    void print_err(char* str){
        perror(str);
        exit(-1);
    }
    int main(){
        int cfd=socket(AF_INET,SOCK_STREAM,0);
        if(cfd==-1) print_err("socket fails\n");
        struct sockaddr_in server_addr;
        int connect_res=0;
        memset(&server_addr,0,sizeof(server_addr));
        server_addr.sin_family=AF_INET;
        server_addr.sin_port=htons(PORT);
        inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);
        connect_res=connect(cfd,(struct sockaddr*)&server_addr,\
                                        sizeof(server_addr));
        if(connect_res==-1) print_err("connect fails\n");
        /*执行程序*/
        //把用户输入读到buf
        char buf[100]={0};
        fgets(buf,sizeof(buf),stdin);
        //把buf内容写到用户端
        write(cfd,buf,strlen(buf));
        //把返回结果写回屏幕
        int n=read(cfd,buf,sizeof(buf));
        write(1,buf,sizeof(buf));
        close(cfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    执行结果

    程序分析

    • 每次创建出一个socket,socket的描述符都指向两个缓冲区,一个用来读,一个用来写
    • 两个套接字想进行网络通信,必须通过ip地址+端口号,才能建立网络连接

    套接字的读写缓冲区在内核中定义,所以用户定义的缓冲区,需要借助write和read进行读写

    用户定义的缓冲区在栈中

    在网络通信中,套接字一定是成对出现的

    • 一端的发送缓冲区对应另一端的接收缓冲区
    服务器端
    • 接收端也就是读缓冲区,需要从用户端套接字的写缓冲区读数据

      read(cfd,buf,sizeof(buf));

    • 然后本地实现大小写功能后,需要把数据写到用户端的接收端

      write(cfd,buf,sizeof(buf));

    用户端
    • 首先需要把用户的键盘输入的内容读到自己定义的用户缓冲区buf

      fgets(buf,sizeof(buf),stdin);

    • 然后自己的发送端的内容(客户端写的)写到自己定义的buf,以便输出

      write(cfd,buf,strlen(buf));

    • 最后buf的数据,写出到屏幕上

      write(1,buf,sizeof(buf));

    查看端口的命令
    netstat -apn | grep 具体端口号
    
    • 1

    把错误处理进行封装

    wrap.c(没有主函数)
    #include 
    #include 
    #include 
    #include          
    #include 
    #include 
    #include 
    void print_err(char* str){
        perror(str);
        exit(-1);
    }
    /*socket封装*/
    int Socket(int domain, int type, int protocol){
        int n=socket(domain,type,protocol);
        if(n==-1) print_err("socket fails\n");
        return n;
        }
        /*bind封装*/
    int Bind(int sockfd, const struct sockaddr *addr,\
        socklen_t addrlen){
        int n=bind(sockfd,addr,addrlen);
        if(n==-1) print_err("bind fails\n");
        return n;
    }
    /*listen封装*/
    int Listen(int sockfd, int backlog){
        int n=listen(sockfd,backlog);
        if(n==-1) print_err("listen fails\n");
        return n;
    }
    /*accept封装*/
    int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
        int n=accept(sockfd,addr,addrlen);
        if(n==-1) print_err("accept fails\n");
        return n;
    }
    /*connect封装*/
    int Connect(int sockfd, const struct sockaddr *addr,\
                       socklen_t addrlen){
        int n=connect(sockfd,addr,addrlen);
        if(n==-1) print_err("connect fails\n");
        return n;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    wrap.h(头文件声明)
    #ifndef MY_WRAP
    #define MY_WRAP
    extern void print_err(char* str);
    extern int Socket(int domain, int type, int protocol);
    extern int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    extern int Listen(int sockfd, int backlog);
    extern int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    extern int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    server.c(调用大写程序)
    #include 
    #include 
    #include         
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "wrap.h"
    #define PORT 6799
    #define IP "127.0.0.1"
    int main(){
        int cfd,sfd;
        char buf[30];
        int n;
        struct sockaddr_in serverAddr;
        struct sockaddr_in clientAddr;
        socklen_t cli_addr_len;
        //socket()
        sfd=Socket(AF_INET,SOCK_STREAM,0);
        //bind()
        bzero(&serverAddr,sizeof(serverAddr));
        serverAddr.sin_family=AF_INET;
        serverAddr.sin_port=htons(PORT);
        inet_pton(sfd,IP,&serverAddr.sin_addr.s_addr);
        Bind(sfd,(struct sockaddr*)&serverAddr,\
                      sizeof(serverAddr));
        //listen()
        Listen(sfd,12);
        //accept()
        cli_addr_len=sizeof(clientAddr);
        cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&cli_addr_len);
        /*显示一下哪个客户端连接了*/
        char client_IP[100]={0};
        printf("client IP=%s,client PORT=%d\n",\
                inet_ntop(AF_INET,&clientAddr.sin_addr.s_addr,\
                                  client_IP,sizeof(client_IP)),\
                ntohs(clientAddr.sin_port));
        /*转换大小写*/
        n=read(cfd,buf,sizeof(buf));
        int i;
        for(i=0;i<n;i++){
            buf[i]=toupper(buf[i]);}
        write(cfd,buf,n);
        close(sfd);
        close(cfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51

    client.c(调用小写函数)

    #include 
    #include 
    #include         
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "wrap.h"
    #define PORT 6799
    #define IP "127.0.0.1"
    int main(){
        int n;
         char buf[100];
        int cfd=Socket(AF_INET,SOCK_STREAM,0);
        struct sockaddr_in server_addr;
        memset(&server_addr,0,sizeof(server_addr));
        server_addr.sin_family=AF_INET;
        server_addr.sin_port=htons(PORT);
        inet_pton(AF_INET,IP,&server_addr.sin_addr.s_addr);
        Connect(cfd,(struct sockaddr*)&server_addr,\
                                        sizeof(server_addr));
        //把用户输入读到buf
        fgets(buf,sizeof(buf),stdin);
        //把buf内容写到用户端
        write(cfd,buf,strlen(buf));
        //把返回结果写回屏幕
        n=read(cfd,buf,sizeof(buf));
        write(1,buf,n);
        close(cfd);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    程序运行

    accept()函数错误处理

    int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
        int n=accept(sockfd,addr,addrlen);
        /*异常处理和错误都会返回-1,所以要单独分析
            ---比如:一个连接被断开了,错误号:ECONNABORTED
            ---比如:因为收到信号被断开连接,错误号:EINTR
            这时候要么重新连接,要么结束连接,这里选“重新连接”
        */
    reconnectted:
        if(n==-1){
            if(errno==ECONNABORTED||(errno==EINTR))
               goto reconnectted;
            else 
               print_err("accept fails\n");
        }
        return n;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16

    需要考虑是正常退出(信号主动退出)还是异常退出

    read()函数返回值

    • 正常情况大于0—实际读到字节数

      • 比如buf[1024],read恰好读到那么大,==1024
      • 如果不足,读到多少字节就返回多少字节
    • 恰好返回0----读到了文件末尾,管道被关闭了,socket被关闭

      如果没被关闭,还没写数据,不会读到0,因为会阻塞,直到读到或被终止

    • 等于-1

      • 正常退出
        • errno==EINTR,被信号终止,退出或重启
        • errno==EAGAIN,非阻塞方式读,但是还没有数据的情况
        • 。。。
      • 异常—(-1,出现错误)
    /*read封装*/
    ssize_t Read(int fd, void *buf, size_t count){
        int n=read(fd,buf,count);
        if(n==-1){
        again:
            if(errno==EINTR||errno==EAGAIN)
                 goto again;
            else 
               print_err("read fails\n");
        }
        return n;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    TCP协议

    由于网络层与硬件连接紧密,所以具有不稳定性(路由器宕机,网络传输慢)

    传输层:

    • 如果完全不弥补,尽力而为

    选用:无连接不可靠的报文传输—UDP

    • 如果完全弥补

    选用:面向连接的可靠数据包传输—TCP

    TCP的通信时序图

    • 两条竖线表示通讯的两端
    • 从上到下表示时间的先后顺序
    • 图中的箭头都是斜的—数据从一端传到网络的另一端也需要时间
    • 请求标志 数据包编号(携带数据大小) 序号

    • 这个序号在网络通讯中用作临时地址

      • 每发一个数据字节,这个序号要加1
      • 在接收端可以根据序号排出数据包的正确顺序,也可以发现丢包的情况
      • 规定SYN位FIN位也要占一个序号
    1. 首先客户端主动发起连接、发送请求
    2. 然后服务器端响应请求
    3. 然后客户端主动关闭连接
    三次握手----建立连接

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-grYQqYNp-1669650410479)(/home/guojiawei/.config/Typora/typora-user-images/image-20221128154849051.png)]

    miss表示最大段尺寸

    • 如果一个段太大,封装成帧后超过了链路层的最大帧长度,就必须在IP层分片
    • 为了避免这种情况,客户端声明自己的最大段尺寸,建议服务器端发来的段不要超过这个长度

    在建立连接的同时,双方协商了一些信息,例如双方发送序号的初始值最大段尺寸

    数据传输:三次握手后,四次握手前

    在这里插入图片描述

    1. 客户端发出段4

    从序号1001开始的20个字节数据,发送数据

    1. 服务器发出段5
    • 确认序号为1021,对序号为1001-1020的数据表示确认收到
    • 服务器在应答的同时也向客户端发送从序号8001开始的10个字节数据
    1. 客户端发出段6

    对服务器发来的序号为8001-8010的数据表示确认收到

    在数据传输过程中,ACK和确认序号是非常重要的,

    • 应用程序交给TCP协议发送的数据会暂存在TCP层的发送缓冲区
    • 发出数据包给对方之后,只有收到对方应答的ACK段才知道该数据包确实发到了对方
    • 如果因为网络故障丢失了数据包或者丢失了对方发回的ACK段,经过等待超时后TCP协议自动将发送缓冲区中的数据包重发

    如图,不是发送一个数据就会得到一个应答请求

    比如发送端发送数据快,接收端可以一下接收三四个数据,再答复ack

    只要数据包序号是累加的即可
    在这里插入图片描述

    四次握手----关闭连接

    TCP连接是全双工的,因此每个方向都必须单独进行关闭

    一个 FIN只能终止这个方向的连接,只意味着这一方向上没有数据流动

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-TtLvsB3M-1669650410483)(/home/guojiawei/.config/Typora/typora-user-images/image-20221128173307143.png)]

    链路层的以太网帧大小很小,目的就是封装的包小,方便丢包后重传

    如果数据过大,就只能分割数据,获得多个传输的数据包

    多进程并发服务器

    父进程应答请求,fork子进程通信
    //父进程不断通过复制子进程实现多个连接
        while(1){
           //accept()----别忘了对第三个参数取地址
           cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&client_addr_len);
           //fork()
           pid=fork();
           if(pid >0){//父进程
               close(cfd);//没有请求了,关闭cfd
           }
           else if(pid==0){
                close(sfd);
                break;//跳出进入子进程交互
           }
        }//结束应答
       if(pid==0){//子进程去交互
          while(1){
              //转换大小写
              n=Read(cfd,buf,sizeof(buf));
              if(n==0){//读到了末尾
                close(cfd);
                return 0;
              }
              for(i=0;i<n;i++)
                 buf[i]=toupper(buf[i]);
              write(cfd,buf,n);
          }
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    运行结果(nc)

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-m6MOhdQp-1669650410484)(/home/guojiawei/.config/Typora/typora-user-images/image-20221128214210678.png)]

    僵尸进程问题

    子进程结束,但是父进程没有回收

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3NZlnApu-1669650410485)(/home/guojiawei/.config/Typora/typora-user-images/image-20221128221409998.png)]

     #include 
     #include 
    pid_t wait(int *wstatus);
    pid_t waitpid(pid_t pid, int *wstatus, int options);
    
    • 1
    • 2
    • 3
    • 4

    实现:

    void wait_child(int singo){
        //0---所有子进程无差别回收
        while(waitpid(0,NULL,WNOHANG)>0);
        exit(-1);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    if(pid >0){//父进程
               close(cfd);//没有请求了,关闭cfd
               signal(SIGINT,wait_child);//子进程回收
           }
    
    • 1
    • 2
    • 3
    • 4
    打印信息:网络字节序to本机
    printf("client ip=%s,port=%d\n",inet_ntop(AF_INET,\
                 &serverAddr.sin_addr.s_addr,client_IP,sizeof(client_IP)),\
                 ntohs(serverAddr.sin_port));
    
    • 1
    • 2
    • 3
    • inet_ntop(AF_INET**,&地址.s_addr,写入的缓存地址,**缓存长度);
    • ntohs(地址.sin_port);

    实现了多进程并发的完整程序

    wrap.c
    #include 
    #include 
    #include 
    #include          
    #include 
    #include 
    #include 
    #include 
    void print_err(char* str){
        perror(str);
        exit(-1);
    }
    /*socket封装*/
    int Socket(int domain, int type, int protocol){
        int n=socket(domain,type,protocol);
        if(n==-1) print_err("socket fails\n");
        return n;
        }
        /*bind封装*/
    int Bind(int sockfd, const struct sockaddr *addr,\
        socklen_t addrlen){
        int n=bind(sockfd,addr,addrlen);
        if(n==-1) print_err("bind fails\n");
        return n;
    }
    /*listen封装*/
    int Listen(int sockfd, int backlog){
        int n=listen(sockfd,backlog);
        if(n==-1) print_err("listen fails\n");
        return n;
    }
    /*accept封装*/
    int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen){
        int n=accept(sockfd,addr,addrlen);
        /*异常处理和错误都会返回-1,所以要单独分析
            ---比如:一个连接被断开了,错误号:ECONNABORTED
            ---比如:因为收到信号被断开连接,错误号:EINTR
            这时候要么重新连接,要么结束连接,这里选“重新连接”
        */
    reconnectted:
        if(n==-1){
            if(errno==ECONNABORTED||(errno==EINTR))
               goto reconnectted;
            else 
               print_err("accept fails\n");
        }
        return n;
    }
    /*connect封装*/
    int Connect(int sockfd, const struct sockaddr *addr,\
                       socklen_t addrlen){
        int n=connect(sockfd,addr,addrlen);
        if(n==-1) print_err("connect fails\n");
        return n;
    }
    /*read封装*/
    ssize_t Read(int fd, void *buf, size_t count){
        int n=read(fd,buf,count);
        if(n==-1){
        again:
            if(errno==EINTR||errno==EAGAIN)
                 goto again;
            else 
               print_err("read fails\n");
        }
        return n;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    wrap .o
    #ifndef MY_WRAP
    #define MY_WRAP
    extern void print_err(char* str);
    extern int Socket(int domain, int type, int protocol);
    extern int Bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    extern int Listen(int sockfd, int backlog);
    extern int Accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    extern int Connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    extern ssize_t Read(int fd, void *buf, size_t count);
    #endif
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    server.c
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "wrap.h"
    #include 
    #include 
    #define PORT 6767
    void wait_child(int singo){
        //0---所有子进程无差别回收
        while(waitpid(0,NULL,WNOHANG)>0);
        exit(-1);
    }
    int main(){
        int sfd,cfd;
        struct sockaddr_in serverAddr;
        struct sockaddr_in clientAddr;
        socklen_t client_addr_len;
        pid_t pid;
        char buf[100],client_IP[100];
        int n,i;
        sfd=Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        /*bind()*/
        bzero(&serverAddr,sizeof(serverAddr));
        serverAddr.sin_family=AF_INET;
        serverAddr.sin_addr.s_addr=htonl(INADDR_ANY);
        //inet_pton(AF_INET,自己的IP,&地址.sin_adds.s_addr)
        serverAddr.sin_port=htons(PORT);
        Bind(sfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
        Listen(sfd,12);
        client_addr_len=sizeof(clientAddr);
        /*利用多进程实现*/
        //父进程不断通过复制子进程实现多个连接
        while(1){
           //accept()----别忘了取地址
           cfd=Accept(sfd,(struct sockaddr*)&clientAddr,&client_addr_len);
           //打印信息:ip+端口号
           printf("client ip=%s,port=%d\n",inet_ntop(AF_INET,\
                 &serverAddr.sin_addr.s_addr,client_IP,sizeof(client_IP)),\
                 ntohs(serverAddr.sin_port));
           //fork()
           pid=fork();
           if(pid >0){//父进程
               close(cfd);//没有请求了,关闭cfd
               signal(SIGINT,wait_child);//子进程回收
           }
           else if(pid==0){
                close(sfd);
                break;//跳出进入子进程交互
           }
        }
       if(pid==0){//子进程去交互
          while(1){
              n=Read(cfd,buf,sizeof(buf));
              if(n==0){//读到了末尾
                close(cfd);
                return 0;
              }
              for(i=0;i<n;i++)
                 buf[i]=toupper(buf[i]);
              write(cfd,buf,n);
          }
       }
       return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    client.c
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include "wrap.h"
    #define PORT 6767
    int main(int argc,char** argv){
        int cfd;
        char* ip_ADDR=argv[1];//不可以用char ip_ADDR[100]
        char buf[100];
        struct sockaddr_in serverAddr;
        cfd=Socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
        bzero(&serverAddr,sizeof(serverAddr));
        serverAddr.sin_family=AF_INET;
        serverAddr.sin_port=htons(PORT);
        inet_pton(AF_INET,ip_ADDR,&serverAddr.sin_addr.s_addr);
        Connect(cfd,(struct sockaddr*)&serverAddr,sizeof(serverAddr));
        /*程序实现*/
        while(1){
        fgets(buf,sizeof(buf),stdin);
        write(cfd,buf,strlen(buf));
        int n=read(cfd,buf,sizeof(buf));
        write(1,buf,n);}
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27

    #include"wrap .h"的头文件需要放到后面,不然编译不通过

    基础补充

    典型协议

    传输层 常见协议有TCP/UDP协议

    • TCP传输控制协议(Transmission Control Protocol)

    是一种面向连接的可靠的基于字节流的传输层通信协议

    • UDP用户数据报协议(User Datagram Protocol)

    是OSI参考模型中一种无连接的传输层协议,提供面向事务简单不可靠信息传送服务

    应用层常见的协议有HTTP协议,FTP协议

    • HTTP超文本传输协议(Hyper Text Transfer Protocol)
    • FTP文件传输协议(File Transfer Protocol)

    网络层 常见协议有IP协议ICMP协议IGMP协议

    • IP协议是因特网互联协议(Internet Protocol)
    • ICMP协议是Internet控制报文协议(Internet Control Message Protocol)

    是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息

    • IGMP协议是 Internet 组管理协议(Internet Group Management Protocol)

    是因特网协议家族中的一个组播协议。该协议运行在主机和组播路由器之间。

    网络接口层 常见协议有ARP协议RARP协议、以太网帧协议

    • [ARP]协议是正向地址解析协议(Address Resolution Protocol)

    通过已知的IP,寻找对应主机的MAC地址

    • [RARP]是反向地址转换协议

      通过MAC地址确定IP地址

    网络应用程序设计模式

    C/S模式
    • 传统的网络应用设计模式,客户机(client)/服务器(server)模式
    • 需要在通讯两端各自部署客户机和服务器来完成数据通信
    B/S模式
    • 浏览器()/服务器(server)模式
    • 只需在一端部署服务器,而另外一端使用每台PC都默认配置的浏览器即可完成数据的传输。
    优缺点

    C/S模式

    1. 优点
    • 将数据缓存至客户端本地,从而提高数据传输效率,可以保证性能
    • 所采用的协议相对灵活,可以在标准协议的基础上根据需求裁剪及定制
    1. 缺点
    • 工作量将成倍提升,开发周期较长
    • 从用户角度出发,需要将客户端安装至用户主机上,对用户主机的安全性构成威胁

    B/S模式

    • 没有独立的客户端,使用标准浏览器作为客户端,其工作开发量较小
    • 移植性非常好,不受平台限制

    缺点:

    • 协议选择不灵活,必须采用标准协议
    • 缓存数据慢,传输数据量受限制

    分层模型

    OSI七层模型

    TCP/IP四层模型

    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f3CCDmtA-1669650410486)(/home/guojiawei/.config/Typora/typora-user-images/image-20221124212002846.png)]

    TCP/IP网络协议栈:

    应用层(Application),传输层(Transport),网络层(Network)和链路层(Link)四层

    目的主机收到数据包后,如何经过各层协议栈最后到达应用程序

    1. 以太网驱动程序根据以太网首部中的“上层协议”字段确定该数据帧的有效载荷是IP、ARP还是RARP协议的数据报

    有效载荷:payload,指除去协议首部之外实际传输的数据

    1. 假如是IP数据报,IP协议再根据IP首部中的“上层协议”字段确定该数据报的有效载荷是TCP、UDP、ICMP还是IGMP
    2. 假如是TCP段或UDP段,TCP或UDP协议再根据TCP首部或UDP首部的“端口号”字段确定应该将应用层数据交给哪个用户进程
    • IP地址是标识网络中不同主机的地址

    • 而端口号就是同一台主机上标识不同进程的地址

    • IP地址和端口号合起来标识网络中唯一的进程。

    • 虽然IP、ARP和RARP数据报都需要以太网驱动程序来封装成

      • 但是从功能上划分,ARP和RARP属于链路层,IP属于网络层
    • 虽然ICMP、IGMP、TCP、UDP的数据都需要IP协议来封装成数据报

      • 但是从功能上划分,ICMP、IGMP与IP同属于网络层,TCP和UDP属于传输层。
  • 相关阅读:
    C++实现高并发内存池
    【深入浅出玩转FPGA学习12----Testbench书写技巧2】
    QT调用python程序出现问题Failed to get function
    MySQL中explain的用法
    2023年【北京市安全员-A证】考试报名及北京市安全员-A证考试资料
    没有杯子的世界:OOP设计思想的应用实践
    vue3 pinia
    C++模板介绍
    股票交易数据接口获取股票基础信息数据的过程
    C++ — 程序、进程、线程
  • 原文地址:https://blog.csdn.net/weixin_47173597/article/details/128089698