8. 网络编程

news2025/1/30 7:42:38

网络的基本概念

在这里插入图片描述

TCP/IP协议概述

在这里插入图片描述

OSI和TCP/IP模型

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

socket(套接字)

在这里插入图片描述

创建socket

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

字节序

在这里插入图片描述

字节序转换函数

在这里插入图片描述

通用地址结构

在这里插入图片描述

因特网地址结构

在这里插入图片描述

IPV4地址族和字符地址间的转换(点分十进制->网络字节序)

在这里插入图片描述

填写IPV4地址族结构案例

在这里插入图片描述

掌握TCP协议网络基础编程

在这里插入图片描述

在这里插入图片描述

相关函数

在这里插入图片描述
在这里插入图片描述

特殊bind地址

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

案例1:time

  • 先启动一个服务端,客户链接上来之后,服务端返回一个系统时间

  • time_tcp_server.c

#include <stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>

int sockfd;


void sig_handler(int signo)
{
    if(signo==SIGINT)
    {
        printf("server close\n");
        /*步骤6:
         *  关闭socket
         * */
        close(sockfd);
        exit(1);
    }
}

/*输出连接上来的客户端相关信息
 * */
void out_addr(struct sockaddr_in *clientaddr)
{
    // 将端口从网络字节序转换成主机字节序
    int port = ntohs(clientaddr->sin_port);
    char ip[16];
    memset(ip,0,sizeof(ip));
    // 网络字节序-> 点分十进制
    inet_ntop(AF_INET,
              &clientaddr->sin_addr.s_addr,ip,sizeof(ip));
    printf("client:%s(%d) connected \n",ip,port); 
}
void do_service(int fd)
{
    // 获得系统时间
    long t = time(0);
    char *s = ctime(&t);
    size_t size = strlen(s)*sizeof(char);
    // 将服务器端获得的系统时间写到客户端
    if(write(fd,s,size)!=size)
    {
        perror("write  error");
    }
}
int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(0);
    }

    // 绑定一个信号,ctrl+c 终止服务端
    if(signal(SIGINT,sig_handler)==SIG_ERR)
    {
        perror("signal sigint error");
        exit(1);
    }

    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
       sockfd = socket(AF_INET,SOCK_STREAM,0);

    /*
     * 步骤2:调用bind函数将socket和地址(ip和port)进行绑定  
     * */
    // 专用地址,绑定的时候在强转为通用地址
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
    serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
    if(bind(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        perror("bind error");
        exit(1);
    }

    /*
     *步骤3:调用listen函数启动监听(指定port监听)
            通知系统去接受来自客户端的连接请求
            将接受到的客户端连接请求放置到对应的队列中
        第二个参数:
            10:队列的长度
     * */
    if(listen(sockfd,10)<0)
    {
        perror("listen error");
        exit(1);
    }

    /*
     *步骤4:
        调用accept函数,从队列中获得一个客户端的请求连接
     *  并返回一个新的socket描述符
     *注意:
        若没有客户端连接,调用此函数后会阻塞
        直到获得一个客户端的连接
     * */
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);
    while(1)
    {

        // 第二个参数可以设置为NULL,如果不想知道客户端的信息的话
        int fd = accept(sockfd,
                        (struct sockaddr*)&clientaddr,
                        &clientaddr_len);
        if(fd<0)
        {
            perror("accept error");
            continue;
        }
        /*
         *步骤5:
            调用IO函数和客户端进行双向的通信
         */
        out_addr(&clientaddr);
        do_service(fd);
        /*步骤5:
         *  关闭 socket
         * */ 
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/d5dcdb3be95a4bbdb7566cb9994daf40.png)
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/24b23c1c7771411493b1380af83b23af.png)
![在这里插入图片描述](https://i-blog.csdnimg.cn/direct/910db7d627b54bada7cd3b98047f0cf8.png)

- time_tcp_client.c

```c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>



int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(0);
    }


    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
      int  sockfd = socket(AF_INET,SOCK_STREAM,0);
       if(sockfd<0)
       {
           perror("sockek error");
           exit(1);
       }
    /*
     * 步骤2:创建socket
     * */
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主机字节序->网络字节序
    inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);
    if(connect(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        perror("connect error");
        exit(1);
    }

    // 步骤3:调用IO函数,和服务器进行双向通讯
    char buffer[1024];
    memset(buffer,0,sizeof(buffer));
    size_t size;
    if((size=read(sockfd,buffer,sizeof(buffer)))<0)
    {
        perror("read error");
    }
    if(write(STDOUT_FILENO,buffer,size)!=size)
    {
        perror("write error");
    }

    // 步骤4:关闭socket
    close(sockfd);
    
    return 0;
}

在这里插入图片描述

自定义协议

在这里插入图片描述

  • msg.h
#ifndef __MSG_H__
#define __MSG_H__
#include<sys/types.h>
typedef struct{
    // 协议头部
    char head[10];
    // 验证码
    char checknum;
    //协议体部
    char buff[512] ;//数据
}MSG;
/*
 * 发送一个基于自定义协议的message,发送的数据存放在buff中
 * */
extern int write_msg(int sockfd,char *buff,size_t len);
/*读取一个基于自定义协议的message。读取的数据存放在buff中
 * */
extern int read_msg(int sockfd,char *buff,size_t len);

#endif

  • msg.c
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<sys/types.h>
#include"msg.h"

// 计算校验码
static unsigned  char msg_check(Msg *message)
{
    unsigned char s = 0;
    for(int i=0;i<sizeof(message->head);i++)
    {
        s+=message->head[i];
    }
    for(int i=0;i<sizeof(message->buff);i++)
    {
        s+=message->buff[i];
    }
    return s;
}

/*
 * 发送一个基于自定义协议的message,发送的数据存放在buff中
 * */

int write_msg(int sockfd,char *buff,size_t len)
{
    Msg message;
    memset(&message,0,sizeof(message));
    strcpy(message.head,"iotek2025");
    memcpy(message.buff,buff,len);
    message.checknum = msg_check(&message);
    if(write(sockfd,&message,sizeof(message))!=sizeof(message))
    {
        return -1;
    }
}

/*读取一个基于自定义协议的message。读取的数据存放在buff中
 * */
int read_msg(int sockfd,char *buff,size_t len)
{
    Msg message;
    memset(&message,0,sizeof(message));
    size_t size;
    if((size=read(sockfd,&message,sizeof(message)))<0)
    {
        return -1;
    }
    else if(size==0)
    {
        return 0;
    }

    // j进行校验码的验证
    unsigned char s =msg_check(&message);
    if((s==(unsigned char)message.checknum) && (!strcmp("iotek2025",message.head)))
    {
        memcpy(buff,message.buff,len);
        return sizeof(message);
    }
    return -1;
}

  • 在服务端的do_service中增加
void do_service(int fd)
{
//----------------------------------------
    size_t len;
    char buff[20];
    if((len=read(fd,buff,20))<0) // 注意由于客户端没有给服务端写数据,所以会在此阻塞
    {
        perror("do_service server read error");
    }

//----------------------------------------
    // 获得系统时间
    long t = time(0);
    char *s = ctime(&t);
    size_t size = strlen(s)*sizeof(char);
    // 将服务器端获得的系统时间写到客户端
    if(write(fd,s,size)!=size)
    {
        perror("write  error");
    }
}

在这里插入图片描述
在这里插入图片描述

服务器并发性处理

多进程模型

  • echo_tcp_server
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include"msg.h"
int sockfd;


void sig_handler(int signo)
{
    if(signo==SIGINT)
    {
        printf("server close\n");
        /*步骤6:
         *  关闭socket
         * */
        close(sockfd);
        exit(1);
    }
    if(signo==SIGCHLD)
    {
        printf("child process dead.....\n");
        wait(0);
    }
}

/*输出连接上来的客户端相关信息
 * */
void out_addr(struct sockaddr_in *clientaddr)
{
    // 将端口从网络字节序转换成主机字节序
    int port = ntohs(clientaddr->sin_port);
    char ip[16];
    memset(ip,0,sizeof(ip));
    // 网络字节序-> 点分十进制
    inet_ntop(AF_INET,
              &clientaddr->sin_addr.s_addr,ip,sizeof(ip));
    printf("client:%s(%d) connected \n",ip,port); 
}
void do_service(int fd)
{
    // 和客户端进行读写操作(双向通信)
    char buff[512];
    while(1)
    {
        memset(buff,0,sizeof(buff));
        printf("start read and write....\n");
        size_t size;
        if((size=read_msg(fd,buff,sizeof(buff)))<0)
        {
            perror("protocal error");
            break;
        }
        else if(size==0)
        {
            break; // 写端突然挂了
        }
        else
        {
            printf("%s\n",buff);
            if(write_msg(fd,buff,sizeof(buff))<0)
            {
                if(errno==EPIPE)// 读端突然关闭时,类似于管道通信
                {
                    break;
                }
                perror("protocal error");
            }
        }
    }
}
int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(0);
    }

    // 绑定一个信号,ctrl+c 终止服务端
    if(signal(SIGINT,sig_handler)==SIG_ERR)
    {
        perror("signal sigint error");
        exit(1);
    }

    if(signal(SIGCHLD,sig_handler)==SIG_ERR)// 等子进程终止发送的信号
    {
        perror("signal sigchild error");
        exit(1);
    }
    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
       sockfd = socket(AF_INET,SOCK_STREAM,0);
       if(sockfd<0)
       {
           perror("sockek error");
           exit(1);
       }
    /*
     * 步骤2:调用bind函数将socket和地址(ip和port)进行绑定  
     * */
    // 专用地址,绑定的时候在强转为通用地址
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
    serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
    if(bind(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        perror("bind error");
        exit(1);
    }

    /*
     *步骤3:调用listen函数启动监听(指定port监听)
            通知系统去接受来自客户端的连接请求
            将接受到的客户端连接请求放置到对应的队列中
        第二个参数:
            10:队列的长度
     * */
    if(listen(sockfd,10)<0)
    {
        perror("listen error");
        exit(1);
    }

    /*
     *步骤4:
        调用accept函数,从队列中获得一个客户端的请求连接
     *  并返回一个新的socket描述符
     *注意:
        若没有客户端连接,调用此函数后会阻塞
        直到获得一个客户端的连接
     * */
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);
    while(1)
    {

        // 第二个参数可以设置为NULL,如果不想知道客户端的信息的话
        int fd = accept(sockfd,
                        (struct sockaddr*)&clientaddr,
                        &clientaddr_len);
        if(fd<0)
        {
            perror("accept error");
            continue;
        }
        /*
         *步骤5:
                启动子进程调用IO函数   
         */
        pid_t pid = fork();
        if(pid<0)
        {
            continue;
        }
        else if(pid==0)
        {
            out_addr(&clientaddr);
            do_service(fd);
          /*步骤6:
           *  关闭 socket
           * */
            close(fd); /// 子进程会复制父进程的fd
        }
        else
        {
            close(fd); 

        }

    }
    return 0;
}

  • echo_tcp_client.c

#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include"msg.h"


int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(0);
    }


    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
      int  sockfd = socket(AF_INET,SOCK_STREAM,0);
       if(sockfd<0)
       {
           perror("sockek error");
           exit(1);
       }
    /*
     * 步骤2:创建socket
     * */
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主机字节序->网络字节序
    inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);
    if(connect(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        perror("connect error");
        exit(1);
    }

    // 步骤3:调用IO函数,和服务器进行双向通讯
    char buff[512];
    size_t size;
    char * prompt = ">" ;

    while(1)
    {
        memset(buff,0,sizeof(buff));
        write(STDOUT_FILENO,prompt,1);
        size = read(STDIN_FILENO,buff,sizeof(buff));
        if(size<0) 
        {
            continue;
        }
        buff[size-1] = '\0';
        if(write_msg(sockfd,buff,sizeof(buff))<0)
        {
            perror("write msg error");
            continue;
        }
        else
        {
            if(read_msg(sockfd,buff,sizeof(buff))<0)
            {
                perror("read msg error");
                continue;
            }
            else
            {
                printf("%s\n",buff);
            }
        }
    }
    // 步骤4:关闭socket
    close(sockfd);
    
    return 0;
}

在这里插入图片描述

  • 下面这个没有出现
    在这里插入图片描述

多线程模型

  • ehco_tcp_server_th.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include"msg.h"

int sockfd;


void sig_handler(int signo)
{
    if(signo==SIGINT)
    {
        printf("server close\n");
        /*步骤6:
         *  关闭socket
         * */
        close(sockfd);
        exit(1);
    }
}

void do_service(int fd)
{
    // 和客户端进行读写操作(双向通信)
    char buff[512];
    while(1)
    {
        memset(buff,0,sizeof(buff));
        printf("start read and write....\n");
        size_t size;
        if((size=read_msg(fd,buff,sizeof(buff)))<0)
        {
            perror("protocal error");
            break;
        }
        else if(size==0)
        {
            break; // 写端突然挂了
        }
        else
        {
            printf("%s\n",buff);
            if(write_msg(fd,buff,sizeof(buff))<0)
            {
                if(errno==EPIPE)// 读端突然关闭时,类似于管道通信
                {
                    break;
                }
                perror("protocal error");
            }
        }
    }
}

void out_fd(int fd)
{
    struct sockaddr_in addr;
    socklen_t len = sizeof(addr);
    // 从fd中获得连接的客户端的相关信息
    if(getpeername(fd,(struct sockaddr*)&addr,&len)<0)
    {
        perror("getpeername error");
        return;
    }
    char ip[16];
    memset(ip,0,sizeof(ip));
    int port = ntohs(addr.sin_port);
    inet_ntop(AF_INET,&addr.sin_addr.s_addr,ip,sizeof(ip));
    printf("%16s(%5d) closed\n",ip,port);
}

void * th_fn(void *arg)
{
    int fd = (int)arg;
    do_service(fd);
    out_fd(fd);// 输出客户端的信息
    close(fd);
    return (void*)0;
}


int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(0);
    }

    // 绑定一个信号,ctrl+c 终止服务端
    if(signal(SIGINT,sig_handler)==SIG_ERR)
    {
        perror("signal sigint error");
        exit(1);
    }

    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
       sockfd = socket(AF_INET,SOCK_STREAM,0);
       if(sockfd<0)
       {
           perror("sockek error");
           exit(1);
       }
    /*
     * 步骤2:调用bind函数将socket和地址(ip和port)进行绑定  
     * */
    // 专用地址,绑定的时候在强转为通用地址
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
    serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
    if(bind(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        perror("bind error");
        exit(1);
    }

    /*
     *步骤3:调用listen函数启动监听(指定port监听)
            通知系统去接受来自客户端的连接请求
            将接受到的客户端连接请求放置到对应的队列中
        第二个参数:
            10:队列的长度
     * */
    if(listen(sockfd,10)<0)
    {
        perror("listen error");
        exit(1);
    }

    /*
     *步骤4:
        调用accept函数,从队列中获得一个客户端的请求连接
     *  并返回一个新的socket描述符
     *注意:
        若没有客户端连接,调用此函数后会阻塞
        直到获得一个客户端的连接
     * */
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);
    
    // 设置线程的分离属性
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    while(1)
    {

        // 第二个参数可以设置为NULL,如果不想知道客户端的信息的话
        // 主控线程负责调用accept去获得客户端的连接 
        int fd = accept(sockfd,NULL,NULL);
        if(fd<0)
        {
            perror("accept error");
            continue;
        }
        /*
         *步骤5:
                启动子线程调用IO函数   
         */
        pthread_t th;
        int err;
        // 以分离状态启动子线程
        if((err=pthread_create(&th,&attr,
                               th_fn,(void*)fd))!=0)
        {
            perror("pthread create error");
        }
        pthread_attr_destroy(&attr);
    }
    return 0;
}

在这里插入图片描述

I/O多路转换(select)

掌握UDP协议网络基础编程

在这里插入图片描述

发生数据

在这里插入图片描述

接收数据

在这里插入图片描述

  • time_udp_server
#include <stdio.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<signal.h>
#include<time.h>
#include<netdb.h>

int sockfd;

void sig_handler(int signo)
{
    if(signo==SIGINT)
    {
        printf("server close\n");
        close(sockfd);
        exit(1);
    }

}

// 输出客户端的信息
void out_addr(struct sockaddr_in *clientaddr)
{
    char ip[16];
    memset(ip,0,sizeof(ip));
    inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
    int port = ntohs(clientaddr->sin_port);
    printf("client : %s(%d)\n",ip,port);
}

// 和客户端进行通信
void do_service()
{
    struct sockaddr_in clientaddr;
    socklen_t len = sizeof(clientaddr);
    char buffer[1024];
    memset(buffer,0,sizeof(buffer));
    // 接受客户端的数据报文
    if(recvfrom(sockfd,buffer,sizeof(buffer),0,
                (struct sockaddr*)&clientaddr,&len)<0)
    {
        perror("recvfrom error");
    }
    else
    {
        out_addr(&clientaddr);
        printf("client send into : %s\n",buffer);

        //向客户端发送数据报文
        long int t = time(0);
        char * ptr = ctime(&t);
        size_t size = strlen(ptr)*sizeof(char);
        if(sendto(sockfd,ptr,size,0,
                  (struct sockaddr*)&clientaddr,len)<0)
        {
            perror("sendto error");
        }
    }

}
int main(int argc,char *argv[])
{
    if(argc<0)
    {
        printf("usage : %s port \n",argv[0]);
        exit(1);
    }
    if(signal(SIGINT,sig_handler)==SIG_ERR)
    {
        perror("signal sigint error");
        exit(1);
    }

    /*步骤1:
     *
     * */
    sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        perror("sockfd error");
        exit(1);
    }
    int res;
    int opt = 1;
    // 设置套接字选项:让刚才实现的端口立即启动
    if((res=setsockopt(sockfd,SOL_SOCKET,SO_REUSEADDR,&opt,sizeof(opt)))<0)
    {
        perror("setsockopt error");
        exit(1);
    }
    /*步骤2:调用bind函数对socket和地址进行绑定
     * */
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family =  AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[1])); // port
    serveraddr.sin_addr.s_addr = INADDR_ANY; //ip
    if(bind(sockfd,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("bind error");
        exit(1);
    }
    /*步骤3:和客户端进行双向的数据通信
     * */
    while(1)
    {
        do_service();
    }
    return 0;
}

  • time_udp_client
   #include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>
#include<memory.h>
#include<unistd.h>


int main(int argc,char *argv[])
{
    if(argc<3)
    {
        printf("usgae : %s ip port \n",argv[0]);
        exit(1);
    }
    /*步骤1:创建socket
     * */
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        perror("socket error");
        exit(1);
    }
    /*步骤2:调用recvfrom和sendto等函数
     * 和服务器进行双向通信
     * */
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr); //ip

    char buffer[1024] = "hello iotek";

    // 向服务器端发送数据报文
    
    if(sendto(sockfd,buffer,sizeof(buffer),0,
              (struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("sendto error");
        exit(1);
    }
    else
    {
        //接受服务器发送的报文
        memset(buffer,0,sizeof(buffer));
        if(recv(sockfd,buffer,sizeof(buffer),0)<0)
        {
            perror("recv error");
            exit(1);
        }
        else
        {
            printf("%s",buffer);
        }
        close(sockfd);
    }
        return 0;
}

在这里插入图片描述

  • 在客户端可以使用connect进行连接
    在这里插入图片描述

这里的connect并没有和服务端进行三次握手,只是在内核中记录了服务端的地址信息和端口,所以可以直接使用send不用指出服务端的地址等信息了。而且调用connect可以保证只是接受来自服务端发送的数据,不接受其他的信息

  • 多次绑定同一个端口
    在这里插入图片描述

在这里插入图片描述在这里插入图片描述
第二个端口起作用
在这里插入图片描述

域名

在这里插入图片描述

  • gethostent获取所有
    在这里插入图片描述
  • gethostbuname获取某一个
    在这里插入图片描述
    在这里插入图片描述

在这里插入图片描述

  • 在Linux中的,位于/etc/hosts
    在这里插入图片描述
    在这里插入图片描述

  • gethost.c

#include <stdio.h>
#include<netdb.h>
#include<stdlib.h>
#include<memory.h>

void out_addr(struct hostent *h)
{
    printf("hostbyname: %s\n",h->h_name);
    printf("addrtype: %s\n",h->h_addrtype==AF_INET ? "IPV4" : "IPV6");
    char ip[16];
    memset(ip,0,sizeof(ip));
    inet_ntop(h->h_addrtype,h->h_addr_list[0],ip,sizeof(ip));
    printf("ip addrress : %s\n",ip);

    int i = 0;
    while(h->h_aliases[i] != NULL)
    {
        printf("alias : %s \n",h->h_aliases[i]);
        i++;
    }
}

int main(int argc,char *argv[])
{
    if(argc<2)
    {
        printf("usage  %s host \n",argv[0]);
        exit(1);
    }
    struct  hostent *h;

    h = gethostbyname(argv[1]);
    if(h!=NULL)
    {
        out_addr(h);
    }
    else
    {
        printf("no %s exits\n",argv[1]);
    }
    return 0;
      endhostent();
}

在这里插入图片描述

注意gethostbyname应该避免在多线程下使用。

  • 使用gethostent

#include <stdio.h>
#include<netdb.h>
#include<stdlib.h>
#include<memory.h>

void out_addr(struct hostent *h)
{
    printf("hostbyname: %s\n",h->h_name);
    printf("addrtype: %s\n",h->h_addrtype==AF_INET ? "IPV4" : "IPV6");
    char ip[16];
    memset(ip,0,sizeof(ip));
    inet_ntop(h->h_addrtype,h->h_addr_list[0],ip,sizeof(ip));
    printf("ip addrress : %s\n",ip);

    int i = 0;
    while(h->h_aliases[i] != NULL)
    {
        printf("alias : %s \n",h->h_aliases[i]);
        i++;
    }
}

int main(int argc,char *argv[])
{
    if(argc<2)
    {
        printf("usage  %s host \n",argv[0]);
        exit(1);
    }
    struct  hostent *h;
    while((h=gethostent()) != NULL)
    {
        if(!strcmp(argv[1],h->h_name))
        {
            out_addr(h);
            exit(0);
        }
        else
        {
            int i = 0;
            while(h->h_aliases[i] != NULL)
            {
                if(!strcmp(argv[1],h->h_aliases[i]))
                {
                    out_addr(h);
                    exit(0);
                }
                i++;
            }
        }
    }
    
    endhostent();
    return 0;
}

#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>
#include<memory.h>
#include<unistd.h>

int  is_host(struct hostent *host,char *name)
{
    if(!strcmp(host->h_name,name))
        return 1;
    int i =0;
    while(host->h_aliases[i] != NULL)
    {
        if(!strcmp(host->h_aliases[i],name))
            return 1;
        i++;
    }       
}
unsigned int get_ip_by_name(char *name)
{
    unsigned int ip = 0;
    struct hostent *host;
    while((host = gethostent())!=NULL)
    {
        if(is_host(host,name))
        {
            memcpy(&ip,host->h_addr_list[0],4);
            break;
        }
    }
    endhostent();
    return ip;
}

int main(int argc,char *argv[])
{
    if(argc<3)
    {
        printf("usgae : %s ip port \n",argv[0]);
        exit(1);
    }
    /*步骤1:创建socket
     * */
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        perror("socket error");
        exit(1);
    }
    /*步骤2:调用recvfrom和sendto等函数
     * 和服务器进行双向通信
     * */
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[2]));
    
    unsigned int ip = get_ip_by_name(argv[1]);
    if(ip!=0)
    {
        serveraddr.sin_addr.s_addr = ip;
    }
    else
    {
        inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);
    }
    //inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr); //ip

    char buffer[1024] = "hello iotek";

    // 向服务器端发送数据报文
    
    if(sendto(sockfd,buffer,sizeof(buffer),0,
              (struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("sendto error");
        exit(1);
    }
    else
    {
        //接受服务器发送的报文
        memset(buffer,0,sizeof(buffer));
        if(recv(sockfd,buffer,sizeof(buffer),0)<0)
        {
            perror("recv error");
            exit(1);
        }
        else
        {
            printf("%s",buffer);
        }
        close(sockfd);
    }
        return 0;
}

在这里插入图片描述

网络高级编程

广播

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

套接字选项

在这里插入图片描述在这里插入图片描述

TCP不支持广播,只有UDP才能支持广播

缓存区

在这里插入图片描述

  • receiver.c
#include <stdio.h>
#include<netdb.h>
#include<sys/socket.h>
#include<string.h>
#include<signal.h>
#include<stdlib.h>

int sockfd;

void sig_handler(int signo)
{
    if(signo == SIGINT)
    {
        printf("receiver will exited");
        close(sockfd);
        exit(1);
    }
}

int main(int argc,char *argv[])
{
    if(argc<2)
    {
        fprintf(stderr,"usage : %s port \n",argv[0]);
        exit(1);
    }
    if(signal(SIGINT,sig_handler)==SIG_ERR)
    {
        perror("signal sigint errro");
        exit(1);
    }
    
    
    sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        perror("socket error");
        exit(1);
    }
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(atoi(argv[1]));
    serveraddr.sin_addr.s_addr = INADDR_ANY;
    if(bind(sockfd,(struct sockadd*)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("bind error");
        exit(1);
    }
    
    char buffer[1024];
    struct sockaddr_in  clientaddr;
    socklen_t len  = sizeof(clientaddr);
    while(1)
    {
        memset(buffer,0,sizeof(buffer));
        memset(&clientaddr,0,sizeof(clientaddr));
        if(recvfrom(sockfd,buffer,sizeof(buffer),0,(struct sockaddr*)&clientaddr,&len)<0)
        {
            perror("recvfrom error");
            exit(1);
        }
        else
        {
            char ip[16];
            inet_ntop(AF_INET,&clientaddr.sin_addr.s_addr,ip,sizeof(ip));
            int port = ntohs(clientaddr.sin_port); 
            printf("%s{%d}:%s\n",ip,port,buffer);
        }
    }
        return 0;
}


  • broadcast.c
#include <stdio.h>
#include<stdlib.h>
#include<string.h>
#include<netdb.h>
#include<sys/socket.h>



int main(int argc,char * argv[])
{
    if(argc<3)
    {
        fprintf(stderr,"usage: %s ip port\n",argv[0]);
        exit(1);
    }

    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd<0)
    {
        perror("sockdt error");
        exit(1);
    }

    int opt = 1;
    // 采用广播地址发送
    setsockopt(sockfd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt));
    struct sockaddr_in serveraddr;
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family =  AF_INET;
    serveraddr.sin_port  = htons(atoi(argv[2]));
    inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);

    printf("I will broadcast....\n");
    char *info = "helo yangpipi...";
    size_t size = strlen(info)*sizeof(char);
    if(sendto(sockfd,info,size,0,(struct sockaddr*)&serveraddr,sizeof(serveraddr))<0)
    {
        perror("sendto error");
        exit(1);
    }
    else
    {
            printf("boradcast success\n");
    }
    close(sockfd);
    
    return 0;

在这里插入图片描述

多路复用之fcntl

在这里插入图片描述
在这里插入图片描述

  • server.c

#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include"vector_fd.h"

VectorFD *vfd;
int sockfd;


void sig_handler(int signo)
{
    if(signo==SIGINT)
    {
        printf("server close----\n");
        /*步骤6:
         *  关闭socket
         * */
        close(sockfd);
      // 销毁动态数组
        destroy_vector_fd(vfd);
        exit(1);
    }
}

/*
 *fd对应于某个连接的客户端,和某个连接的客户端进行双向通信(非阻塞方式)
 * */
void do_service(int fd)
{
    // 和客户端进行读写操作(双向通信)
    char buff[512];
    while(1)
    {
        memset(buff,0,sizeof(buff));
        // 以非阻塞方式读,读不到数据就返回了,直接服务于下一个客户端,因此不需要判断size<0的情况
        size_t size=read(fd,buff,sizeof(buff));
        if(size==0)
        {
            char info[] = "client closed";
            write(STDOUT_FILENO,info,sizeof(info));
            // 从动态数组中删除对应的fd
            remove_fd(vfd,fd);
            // 关闭对应的客户端的socket
            close(fd);
        }
        else if(size > 0)
        {
            write(STDOUT_FILENO,buff,sizeof(buff));
            if(write(fd,buff,size)<size)
            {
                if(errno==EPIPE)// 客户端关闭连接
                {
                    perror("write error");
                    remove_fd(vfd,fd);
                    close(fd);
                }
            }
        }
    }
}


void * th_fn(void *arg)
{
    int i;
    while(1)
    {
        // 遍历动态数中的socket描述符
        for(int i = 0;i<vfd->counter;i++)
        {
            do_service(get_fd(vfd,i));
        }
    }
    
    return (void*)0;
}

void out_addr(struct sockaddr_in *clientaddr)
{
    char ip[16];
    memset(ip,0,sizeof(ip));
    int port = ntohs(clientaddr->sin_port);
    inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
    printf("%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(0);
    }

    // 绑定一个信号,ctrl+c 终止服务端
    if(signal(SIGINT,sig_handler)==SIG_ERR)
    {
        perror("signal sigint error");
        exit(1);
    }

    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
       sockfd = socket(AF_INET,SOCK_STREAM,0);
       if(sockfd<0)
       {
           perror("sockek error");
           exit(1);
       }
    /*
     * 步骤2:调用bind函数将socket和地址(ip和port)进行绑定  
     * */
    // 专用地址,绑定的时候在强转为通用地址
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
    serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
    if(bind(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        perror("bind error");
        exit(1);
    }

    /*
     *步骤3:调用listen函数启动监听(指定port监听)
            通知系统去接受来自客户端的连接请求
            将接受到的客户端连接请求放置到对应的队列中
        第二个参数:
            10:队列的长度
     * */
    if(listen(sockfd,10)<0)
    {
        perror("listen error");
        exit(1);
    }

    /*
     *步骤4:
        调用accept函数,从队列中获得一个客户端的请求连接
     *  并返回一个新的socket描述符
     *注意:
        若没有客户端连接,调用此函数后会阻塞
        直到获得一个客户端的连接
     * */
    
    // 创建放置套接字描述符fd的动态数组
    vfd = create_vector_fd();

    // 设置线程的分离属性
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    
    int err;
    pthread_t th; 
    if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0)
    {
        perror("pthread create error");
        exit(1);
    }
    pthread_attr_destroy(&attr);
    /*
     * 1) 主控线程获得客户端的连接,将新的socket描述符放置到动态数组中
     * 2) 启动的子线程负责遍历动态数组中socket描述符并和对应的客户端进行双向通信(采用非阻塞方式读写)
     *
     * */
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);

    while(1)
    {

        int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
        if(fd<0)
        {
            perror("accept error");
            continue;
        }
        out_addr(&clientaddr);

        // 将读写方式修改为非阻塞方式
        int val;
        fcntl(fd,F_GETFL,&val);
        val |= O_NONBLOCK;
        fcntl(fd,F_SETFL,val);        
        // 将返回新的socket描述符加入到动态数组中
        add_fd(vfd,fd);

    }
    return 0;

  • client.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>


int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(0);
    }


    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
      int  sockfd = socket(AF_INET,SOCK_STREAM,0);
       if(sockfd<0)
       {
           perror("sockek error");
           exit(1);
       }
    /*
     * 步骤2:创建socket
     * */
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[2]));// 字符串-> 整型,主机字节序->网络字节序
    inet_pton(AF_INET,argv[1],&serveraddr.sin_addr.s_addr);
    if(connect(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        perror("connect error");
        exit(1);
    }

    // 步骤3:调用IO函数,和服务器进行双向通讯
    char buff[512];
    size_t size;
    char * prompt = ">" ;

    while(1)
    {
        memset(buff,0,sizeof(buff));
        write(STDOUT_FILENO,prompt,1);
        size = read(STDIN_FILENO,buff,sizeof(buff));
        if(size<0) 
        {
            continue;
        }
        buff[size-1] = '\0';
        if(write(sockfd,buff,sizeof(buff))<0)
        {
            perror("write msg error");
            continue;
        }
        else
        {
            if(read(sockfd,buff,sizeof(buff))<0)
            {
                perror("read msg error");
                continue;
            }
            else
            {
                printf("%s\n",buff);
            }
        }
    }
    // 步骤4:关闭socket
    close(sockfd);
    
    return 0;
}

在这里插入图片描述

多路复用之select

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

  • echo_tcp_sever_select.c
#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include"vector_fd.h"

VectorFD *vfd;
int sockfd;


void sig_handler(int signo)
{
    if(signo==SIGINT)
    {
        printf("server close----\n");
        /*步骤6:
         *  关闭socket
         * */
        close(sockfd);
      // 销毁动态数组
        destroy_vector_fd(vfd);
        exit(1);
    }
}

/*
 *fd对应于某个连接的客户端,和某个连接的客户端进行双向通信(非阻塞方式)
 * */
void do_service(int fd)
{
    // 和客户端进行读写操作(双向通信)
    char buff[512];
    while(1)
    {
        memset(buff,0,sizeof(buff));
        // 以非阻塞方式读,读不到数据就返回了,直接服务于下一个客户端,因此不需要判断size<0的情况
        size_t size=read(fd,buff,sizeof(buff));
        if(size==0)
        {
            //char info[] = "client closed";
            //write(STDOUT_FILENO,info,sizeof(info));
            printf("client closed\n"); // 这里由于内核设置,所以可以使用带缓存的IO
            // 从动态数组中删除对应的fd
            remove_fd(vfd,fd);
            // 关闭对应的客户端的socket
            close(fd);
        }
        else if(size > 0)
        {
            //write(STDOUT_FILENO,buff,sizeof(buff));
            printf("%s\n",buff);
            if(write(fd,buff,size)<size)
            {
                if(errno==EPIPE)// 客户端关闭连接
                {
                    perror("write error");
                    remove_fd(vfd,fd);
                    close(fd);
                }
            }
        }
    }
}


/*遍历出动态数组中所有的描述符并加入到描述符集set中
 * 同时此函数返回动态数组中最大的那个描述符
 * */
int add_set(fd_set *set)
{
    FD_ZERO(set); // 清空描述符
    int max_fd = vfd->fd[0];
    for(int i = 0;i < vfd->fd[0];i++)
    {
        int fd = get_fd(vfd,i);
        if(fd>max_fd)
        {
            max_fd = fd;
        }
        FD_SET(fd,set);//将fd加入到描述符集中
    }
    return max_fd;
}
void * th_fn(void *arg)
{
    struct timeval t;
    t.tv_sec = 2;
    t.tv_usec = 0;
    int n = 0;
    int maxfd;
    fd_set set; // 描述符集
    maxfd = add_set(&set);

    /*调用select函数会阻塞,委托内核去检查传入的描述符是否准备好,
     * 如有则返回准备好的描述符,
     * 超时则返回0
     * 第一个参数为描述符集中描述符的范围(最大描述符+1)
     * */
    while((n=select(maxfd+1,&set,NULL,NULL,&t))>=0)
    {
        if(n>0)
        {
            /*检测那些描述符准备好,并和这些准备好的描述符对应的客户端进行数据的双向通信
             * */
            for(int i=0;i<vfd->counter;i++)
            {
                int fd = get_fd(vfd,i);
                if(FD_ISSET(fd,&set))
                {
                    do_service(fd);
                }
            }
        }

        //超时
        // 重新设置时间和清空描述符集
        t.tv_sec = 2;
        t.tv_usec = 0;
        // 重新遍历动态数组中最新的描述符放置到描述符集中
        maxfd = add_set(&set);
    }
}

void out_addr(struct sockaddr_in *clientaddr)
{
    char ip[16];
    memset(ip,0,sizeof(ip));
    int port = ntohs(clientaddr->sin_port);
    inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
    printf("%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(0);
    }

    // 绑定一个信号,ctrl+c 终止服务端
    if(signal(SIGINT,sig_handler)==SIG_ERR)
    {
        perror("signal sigint error");
        exit(1);
    }

    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
       sockfd = socket(AF_INET,SOCK_STREAM,0);
       if(sockfd<0)
       {
           perror("sockek error");
           exit(1);
       }
    /*
     * 步骤2:调用bind函数将socket和地址(ip和port)进行绑定  
     * */
    // 专用地址,绑定的时候在强转为通用地址
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
    serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
    if(bind(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        perror("bind error");
        exit(1);
    }

    /*
     *步骤3:调用listen函数启动监听(指定port监听)
            通知系统去接受来自客户端的连接请求
            将接受到的客户端连接请求放置到对应的队列中
        第二个参数:
            10:队列的长度
     * */
    if(listen(sockfd,10)<0)
    {
        perror("listen error");
        exit(1);
    }

    /*
     *步骤4:
        调用accept函数,从队列中获得一个客户端的请求连接
     *  并返回一个新的socket描述符
     *注意:
        若没有客户端连接,调用此函数后会阻塞
        直到获得一个客户端的连接
     * */
    
    // 创建放置套接字描述符fd的动态数组
    vfd = create_vector_fd();

    // 设置线程的分离属性
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    
    int err;
    pthread_t th; 
    if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0)
    {
        perror("pthread create error");
        exit(1);
    }
    pthread_attr_destroy(&attr);
    /*
     * 1) 主控线程获得客户端的连接,将新的socket描述符放置到动态数组中
     * 2) 
     *      a) 启动的子线程调用select函数委托内核去检查传入到select中的描述符是否准备好
     *      b) 利用FD_ISSET来找出准备好的那些描述符并和对应的客户端进行双向通信(非阻塞)
     *
     * */
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);

    while(1)
    {

        int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
        if(fd<0)
        {
            perror("accept error");
            continue;
        }
        out_addr(&clientaddr);
        // 将返回新的socket描述符加入到动态数组中
        add_fd(vfd,fd);

    }
    return 0;
}

在这里插入图片描述

守护进程

在这里插入图片描述

  • 编程步骤
    在这里插入图片描述
  • 出错处理
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

#include<stdio.h>
#include<stdlib.h>
#include<netdb.h>
#include<sys/socket.h>
#include<unistd.h>
#include<string.h>
#include<memory.h>
#include<signal.h>
#include<time.h>
#include<errno.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>
#include<fcntl.h>
#include<syslog.h>
#include<sys/stat.h>
#include"vector_fd.h"

VectorFD *vfd;
int sockfd;



/*
 *fd对应于某个连接的客户端,和某个连接的客户端进行双向通信(非阻塞方式)
 * */
void do_service(int fd)
{
    // 和客户端进行读写操作(双向通信)
    char buff[512];
    while(1)
    {
        memset(buff,0,sizeof(buff));
        // 以非阻塞方式读,读不到数据就返回了,直接服务于下一个客户端,因此不需要判断size<0的情况
        size_t size=read(fd,buff,sizeof(buff));
        if(size==0)
        {
            char info[] = "client closed\n";
            syslog(LOG_DEBUG,"client closed");
            //printf("client closed\n"); // 这里由于内核设置,所以可以使用带缓存的IO
            // 从动态数组中删除对应的fd
            remove_fd(vfd,fd);
            // 关闭对应的客户端的socket
            close(fd);
        }
        else if(size > 0)
        {
            //write(STDOUT_FILENO,buff,sizeof(buff));
           // printf("%s\n",buff);
            syslog(LOG_DEBUG,"%s\n",buff);
            if(write(fd,buff,size)<size)
            {
                if(errno==EPIPE)// 客户端关闭连接
                {
                    syslog(LOG_DEBUG,"write:%s\n",strerror(errno));
                    remove_fd(vfd,fd);
                    close(fd);
                }
            }
        }
    }
}


/*遍历出动态数组中所有的描述符并加入到描述符集set中
 * 同时此函数返回动态数组中最大的那个描述符
 * */
int add_set(fd_set *set)
{
    FD_ZERO(set); // 清空描述符
    int max_fd = vfd->fd[0];
    for(int i = 0;i < vfd->fd[0];i++)
    {
        int fd = get_fd(vfd,i);
        if(fd>max_fd)
        {
            max_fd = fd;
        }
        FD_SET(fd,set);//将fd加入到描述符集中
    }
    return max_fd;
}
void * th_fn(void *arg)
{
    struct timeval t;
    t.tv_sec = 2;
    t.tv_usec = 0;
    int n = 0;
    int maxfd;
    fd_set set; // 描述符集
    maxfd = add_set(&set);

    /*调用select函数会阻塞,委托内核去检查传入的描述符是否准备好,
     * 如有则返回准备好的描述符,
     * 超时则返回0
     * 第一个参数为描述符集中描述符的范围(最大描述符+1)
     * */
    while((n=select(maxfd+1,&set,NULL,NULL,&t))>=0)
    {
        if(n>0)
        {
            /*检测那些描述符准备好,并和这些准备好的描述符对应的客户端进行数据的双向通信
             * */
            for(int i=0;i<vfd->counter;i++)
            {
                int fd = get_fd(vfd,i);
                if(FD_ISSET(fd,&set))
                {
                    do_service(fd);
                }
            }
        }

        //超时
        // 重新设置时间和清空描述符集
        t.tv_sec = 2;
        t.tv_usec = 0;
        // 重新遍历动态数组中最新的描述符放置到描述符集中
        maxfd = add_set(&set);
    }
}

void out_addr(struct sockaddr_in *clientaddr)
{
    char ip[16];
    memset(ip,0,sizeof(ip));
    int port = ntohs(clientaddr->sin_port);
    inet_ntop(AF_INET,&clientaddr->sin_addr.s_addr,ip,sizeof(ip));
    syslog(LOG_DEBUG,"%s(%d) connected!\n",ip,port);
}
int main(int argc,char *argv[])
{
    // 参数:服务器端绑定的端口
    if(argc<2)
    {
        printf("usage :%s #port\n",argv[0]);
        exit(1);
    }

    // 守护进程编程的5个步骤
    // 步骤1:创建屏蔽字为0
    umask(0);
    // 步骤2:调用fork函数创建子进程,然后父进程退出
    pid_t pid = fork();
    if(pid>0) exit(0);
    //步骤3:调用setsid函数创建一个新的会话
    setsid();
    //步骤4:将当前工作目录更改为根目录
    chdir("/");
    //步骤5:关闭不需要的文件描述符
    close(STDIN_FILENO);
    close(STDOUT_FILENO);
    close(STDERR_FILENO);

    //打开系统日志服务的一个连接
    openlog(argv[0],LOG_PID,LOG_SYSLOG);



    /* 
     * 步骤1. 创建socket,套接字socket创建在内核,是一个结构体
     * AF_INET : IPV4
     * SOCK_STREAM:TCP协议
    */
       sockfd = socket(AF_INET,SOCK_STREAM,0);
       if(sockfd<0)
       {
           syslog(LOG_DEBUG,"socket:%s\n",strerror(errno));
           exit(1);
       }
    /*
     * 步骤2:调用bind函数将socket和地址(ip和port)进行绑定  
     * */
    // 专用地址,绑定的时候在强转为通用地址
    struct sockaddr_in serveraddr; 
    memset(&serveraddr,0,sizeof(serveraddr));
    serveraddr.sin_family = AF_INET; // IPV4
    serveraddr.sin_port = htons(atoi(argv[1]));// 字符串-> 整型,主机字节序->网络字节序
    serveraddr.sin_addr.s_addr = INADDR_ANY;//响应所有请求
    if(bind(sockfd,(struct sockaddr*)&serveraddr,
            sizeof(serveraddr))<0)
    {
        // 将日志信息写入到系统日志文件中(/var/log/syslog)
        syslog(LOG_DEBUG,"bind:%s\n",strerror(errno));
        exit(1);
    }

    /*
     *步骤3:调用listen函数启动监听(指定port监听)
            通知系统去接受来自客户端的连接请求
            将接受到的客户端连接请求放置到对应的队列中
        第二个参数:
            10:队列的长度
     * */
    if(listen(sockfd,10)<0)
    {
        syslog(LOG_DEBUG,"listen:%s\n",strerror(errno));
        exit(1);
    }

    /*
     *步骤4:
        调用accept函数,从队列中获得一个客户端的请求连接
     *  并返回一个新的socket描述符
     *注意:
        若没有客户端连接,调用此函数后会阻塞
        直到获得一个客户端的连接
     * */
    
    // 创建放置套接字描述符fd的动态数组
    vfd = create_vector_fd();

    // 设置线程的分离属性
    pthread_attr_t attr;
    pthread_attr_init(&attr);
    pthread_attr_setdetachstate(&attr,PTHREAD_CREATE_DETACHED);
    
    int err;
    pthread_t th; 
    if((err=pthread_create(&th,&attr,th_fn,(void*)0))!=0)
    {
        syslog(LOG_DEBUG,"pthread:%s\n",strerror(errno));
        exit(1);
    }
    pthread_attr_destroy(&attr);
    /*
     * 1) 主控线程获得客户端的连接,将新的socket描述符放置到动态数组中
     * 2) 
     *      a) 启动的子线程调用select函数委托内核去检查传入到select中的描述符是否准备好
     *      b) 利用FD_ISSET来找出准备好的那些描述符并和对应的客户端进行双向通信(非阻塞)
     *
     * */
    struct sockaddr_in clientaddr;
    socklen_t clientaddr_len = sizeof(clientaddr);

    while(1)
    {

        int fd = accept(sockfd,(struct sockaddr*)&clientaddr,&clientaddr_len);
        if(fd<0)
        {
            syslog(LOG_DEBUG,"accept:%s\n",strerror(errno));
            continue;
        }
        out_addr(&clientaddr);
        // 将返回新的socket描述符加入到动态数组中
        add_fd(vfd,fd);

    }
    return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2286332.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

关于opencv环境搭建问题:由于找不到opencv_worldXXX.dll,无法执行代码,重新安装程序可能会解决此问题

方法一&#xff1a;利用复制黏贴方法 打开opencv文件夹目录找到\opencv\build\x64\vc15\bin 复制该目录下所有文件&#xff0c;找到C:\Windows\System32文件夹&#xff08;注意一定是C盘&#xff09;黏贴至该文件夹重新打开VS。 方法二&#xff1a;直接配置环境 打开opencv文…

Git Bash 配置 zsh

博客食用更佳 博客链接 安装 zsh 安装 Zsh 安装 Oh-my-zsh github仓库 sh -c "$(curl -fsSL https://install.ohmyz.sh/)"让 zsh 成为 git bash 默认终端 vi ~/.bashrc写入&#xff1a; if [ -t 1 ]; thenexec zsh fisource ~/.bashrc再重启即可。 更换主题 …

DeepSeek-R1 本地部署模型流程

DeepSeek-R1 本地部署模型流程 ***************************************************** 环境准备 操作系统&#xff1a;Windows11 内存&#xff1a;32GB RAM 存储&#xff1a;预留 300GB 可用空间 显存: 16G 网络: 100M带宽 ********************************************…

C++ unordered_map和unordered_set的使用,哈希表的实现

文章目录 unordered_map&#xff0c;unorder_set和map &#xff0c;set的差异哈希表的实现概念直接定址法哈希冲突哈希冲突举个例子 负载因子将关键字转为整数哈希函数除法散列法/除留余数法 哈希冲突的解决方法开放定址法线性探测二次探测 开放定址法代码实现 哈希表的代码 un…

C#通过3E帧SLMP/MC协议读写三菱FX5U/Q系列PLC数据案例

C#通过3E帧SLMP/MC协议读写三菱FX5U/Q系列PLC数据案例&#xff0c;仅做数据读写报文测试。附带自己整理的SLMP/MC通讯协议表。 SLMP以太网读写PLC数据20191206/.vs/WindowsFormsApp7/v15/.suo , 73216 SLMP以太网读写PLC数据20191206/SLMP与MC协议3E帧通讯协议表.xlsx , 10382…

Unity|小游戏复刻|见缝插针1(C#)

准备 创建Scenes场景&#xff0c;Scripts脚本&#xff0c;Prefabs预制体文件夹 修改背景颜色 选中Main Camera 找到背景 选择颜色&#xff0c;一种白中透黄的颜色 创建小球 将文件夹里的Circle拖入层级里 选中Circle&#xff0c;位置为左右居中&#xff0c;偏上&…

数据结构的队列

一.队列 1.队列&#xff08;Queue&#xff09;的概念就是先进先出。 2.队列的用法&#xff0c;红色框和绿色框为两组&#xff0c;offer为插入元素&#xff0c;poll为删除元素&#xff0c;peek为查看元素红色的也是一样的。 3.LinkedList实现了Deque的接口&#xff0c;Deque又…

HTML-新浪新闻-实现标题-排版

标题排版 图片标签&#xff1a;<img> src&#xff1a;指定图片的url&#xff08;绝对路径/相对路径&#xff09; width&#xff1a;图片的宽度&#xff08;像素/相对于父元素的百分比&#xff09; heigth&#xff1a;图片的高度&#xff08;像素/相对于父元素的百分比&a…

C语言二级题解:查找字母以及其他字符个数、数字字符串转双精度值、二维数组上下三角区域数据对调

目录 一、程序填空题 --- 查找字母以及其他字符个数 题目 分析 二、程序修改 --- 数字字符串转双精度值 题目 分析 小数位字符串转数字 三、程序设计 --- 二维数组上下三角区域数据对调 题目 分析 前言 本文来讲解&#xff1a; 查找字母以及其他字符个数、数字字符串…

VPR概述、资源

SOTA网站&#xff1a; Visual Place Recognition | Papers With Code VPR&#xff08;Visual Place Recognition&#xff09; 是计算机视觉领域的一项关键任务&#xff0c;旨在通过图像匹配和分析来识别场景或位置。它的目标是根据视觉信息判断某个场景是否与数据库中的场景匹…

Electron学习笔记,安装环境(1)

1、支持win7的Electron 的版本是18&#xff0c;这里node.js用的是14版本&#xff08;node-v14.21.3-x86.msi&#xff09;云盘有安装包 Electron 18.x (截至2023年仍在维护中): Chromium: 96 Node.js: 14.17.0 2、安装node环境&#xff0c;node-v14.21.3-x86.msi双击运行选择安…

58.界面参数传递给Command C#例子 WPF例子

界面参数的传递&#xff0c;界面参数是如何从前台传送到后台的。 param 参数是从界面传递到命令的。这个过程通常涉及以下几个步骤&#xff1a; 数据绑定&#xff1a;界面元素&#xff08;如按钮&#xff09;的 Command 属性绑定到视图模型中的 RelayCommand 实例。同时&#x…

Git图形化工具【lazygit】

简要介绍一下偶然发现的Git图形化工具——「lazygit」 概述 Lazygit 是一个用 Go 语言编写的 Git 命令行界面&#xff08;TUI&#xff09;工具&#xff0c;它让 Git 操作变得更加直观和高效。 Github地址&#xff1a;https://github.com/jesseduffield/lazygit 主要特点 主要…

三个不推荐使用的线程池

线程池的种类 其实看似这么多的线程池&#xff0c;都离不开ThreadPoolExecutor去创建&#xff0c;只不过他们是简化一些参数 newFixedThreadPool 里面全是核心线程 有资源耗尽的风险&#xff0c;任务队列最大长度为Integer.MAX_VALUE&#xff0c;可能会堆积大量的请求&#xff…

星际战争模拟系统:新月的编程之道

星际战争模拟系统&#xff1a;新月的编程之道 作为一名在 25 世纪星际时代成长起来的科学家和军事战略家&#xff0c;我对编程和人工智能的热爱始于童年。我的父亲是一位著名的物理学家&#xff0c;母亲是一位杰出的生物工程师。在他们的影响下&#xff0c;我从小就对科学和技术…

【CS61A 2024秋】Python入门课,全过程记录P4(Week7 Generators开始,更新于2025/1/29)

文章目录 关于基本介绍&#x1f44b;新的问题更好的解决方案Week7Mon Generators阅读材料Lab 05: Iterators, MutabilityQ1: WWPD: List-MutationQ2: Insert Items 关于 个人博客&#xff0c;里面偶尔更新&#xff0c;最近比较忙。发一些总结的帖子和思考。 江湖有缘相见&…

Fort Firewall:全方位守护网络安全

Fort Firewall是一款专为 Windows 操作系统设计的开源防火墙工具&#xff0c;旨在为用户提供全面的网络安全保护。它基于 Windows 过滤平台&#xff08;WFP&#xff09;&#xff0c;能够与系统无缝集成&#xff0c;确保高效的网络流量管理和安全防护。该软件支持实时监控网络流…

【数据结构】_C语言实现不带头非循环单向链表

目录 1. 链表的概念及结构 2. 链表的分类 3. 单链表的实现 3.1 SList.h头文件 3.2 SList.c源文件 3.3 Test_SList.c测试文件 关于线性表&#xff0c;已介绍顺序表&#xff0c;详见下文&#xff1a; 【数据结构】_顺序表-CSDN博客 本文介绍链表&#xff1b; 基于顺序表…

【Qt】06-对话框

对话框 前言一、模态和非模态对话框1.1 概念1.2 模态对话框1.2.1 代码QAction类 1.2.2 模态对话框运行分析 1.3 非模态对话框1.3.1 代码局部变量和成员变量setAttribute 类 1.3.2 现象解释 二、标准对话框2.1 提示对话框 QMessageBox2.1.1 现象及解释 2.2 问题对话框2.2.1 现象…

特征缩放:数据归一化

First&#xff0c;新年到了&#xff01;感谢CSDN一路相伴&#xff0c;成为技术交流的温馨港湾。值此蛇年新春&#xff0c;祝平台人气蒸蒸日上&#xff0c;活动精彩纷呈&#xff0c;助力更多开发者突破技术瓶颈&#xff0c;在新的一年创造无限可能&#xff0c;新年快乐&#xff…