【华清远见】培训的第9周

复习:
网络编程
重要知识点:
1 udp 和 tcp 的区别
 
2 如何实现udp传输
int fd = socket(, SOCK_DGRAM, );
bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr));
recvfrom();
sendto();
close(fd);
 
3 如何实现tcp 服务器
int fd = socket(, SOCK_STREAM, );
bind();
listen();
accept
recv
send
 
4 服务器多采用并发服务器
服务器有一个单独线程,accept, 只要有一个客户端连接,就先建一个线程,此线程单独recv     

5 I/O 多路复用
select
     
6 udp 广播
192.168.31.255

setsockopt 允许广播     

7 udp 组播
224.10.10.10     

tcp / ip 3卷
卷1   协议的内容
卷2   协议的实现

面试题高级(比较难,代码理解即可):
1 抓取网络数据包
wireshark 
注意: 安装过程中会提示   安装一个组件 winpcap (需要安装)

作用:
1) 实时分析数据报 : 捕获网卡数据
     
2) 打开历史报文

重点: wireshark问题

1.1 分析以太网包头(15min)

1.2 分析ip报文、tcp报文(15min)
重点: TCP 报文头格式
源端口号:   发送方使用的端口号(8888)
目的端口号: 接收方的端口号    (8888)
32位序号  : 报文的编号(1,2,3,4,5......)
32位确认号: 应答的编号(收到了哪个编号的数据)
4位首部长度: 
6位报文类型:  
    ACK  应答报文
    SYN  请求连接报文(connect + accept)
    FIN  断开连接报文(close)
16位窗口大小:  
    因为发送方和接收方 有可能 网速不同, 接收方如果是一个慢速设备,
        那么接收方用窗口大小提示发送方我还能收多少
16位校验和: 采用的是求和校验


重点
1.3 tcp连接的过程(accept 和 connect怎么搭配的)   
    使用的 tcp 3次握手

1.4 tcp 4次挥手

练习1:  客户端连接服务器时发送(   C   )包,服务器收到客户端连接请求发送(  D   )包
    A  ACK    B   FIN     C  SYN     D SYN+ACK

练习2: 客户端和服务器的初始序号为(2000, 4000), 那么客户端SYN包序号为( A   ), 服务器的SYN包序号为(  C   )
    A 2000    B 2001     C 4000   D 4001


作业: 重画 3 次握手,4次挥手

2 客户端如何判断服务器断开

方法1: 通过recv的返回值 <= 0,如果 <=0 说明服务器出问题了

    但recv 返回值 <= 0的判断可能会漏掉某些情况

方法2: 因为recv <= 0 并不能检测到所有断开情况,可以使用心跳包实现

原理: 客户端  定期(每隔1秒)给服务器发送1包数据,服务收到后回应,
    如果客户端检测到,发送了多包数据,服务器仍然没有回应,说明,服务器出问题了,
    客户端重连,直到服务器恢复为止

流程:
    服务器功能: 等待客户端连接
            一旦有客户端发送connect, 回应ok
    客户端功能: 连接成功后,每隔1秒钟发送connect, 接收服务器的ok
            如果长时间收不到ok, 客户端重新连接服务器

/client.c/
#include
#include
#include
#include
#include
int count = 3;        //客户端接收不到服务器应答的次数,如果 == 0, 说明服务器不在线
int server_on = 0;    //是否连接成功标志  0,断开     1 连接
int i = 0;
int main(int argc, char *argv[])
{
    int fd;
    struct sockaddr_in youaddr;    
    youaddr.sin_family = AF_INET;
    youaddr.sin_port = htons(atoi(argv[2]));
    youaddr.sin_addr.s_addr = inet_addr(argv[1]);
    char buf[100] = "connect";
    while(1)
    {    
        sleep(1);
        if(!server_on)
        {
            fd = socket(AF_INET, SOCK_STREAM, 0);
            int ret = connect(fd, (struct sockaddr *)&youaddr, sizeof(youaddr));
            if(ret == 0)
                server_on = 1;    //连接成功        
            else
            {
                printf("connect error %d\n", i);
                close(fd);    //连接失败,因为是无限循环,1秒钟后再重连
                i++;
                continue;
            }
        }
        strcpy(buf, "connect");
        send(fd, buf, sizeof(buf), 0);        //发送connect
        memset(buf, 0, sizeof(buf));
        usleep(100000);
        if(server_on)
        {
            if(recv(fd, buf, sizeof(buf), MSG_DONTWAIT) > 0)
            {
                printf("buf is %s\n", buf);
                if(strcmp(buf, "ok") != 0)    //没收到ok
                {
                    count--;
                    if(count == 0)
                    {
                        printf("server is disconnect\n");
                        server_on = 0;
                    }
                }
                else
                    count = 3;
            }
            else
            {
                count = 0;
                printf("server is no answer\n");            
                server_on = 0;
                close(fd);
            }
        }
    }
    close(fd);
}

/server/
#include
#include
#include
#include
#include
int fd1;
int main()
{
    int newfd, ret;
    char buf[100] = { 0 }; 
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    fcntl(fd, F_SETFL, fcntl( fd, F_GETFD, 0 )|O_NONBLOCK );
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(55555);
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);     //192.168.20.252
    bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr));
    listen(fd, 5);
    while(1)
    {
        newfd = accept(fd, NULL, NULL);        //非阻塞等待客户端连接
        if(newfd > 0)
        {
            printf("fd %d connect newfd %d\n", fd, newfd);
            fd1 = newfd;
        }
        ret = recv(fd1, buf, sizeof(buf), MSG_DONTWAIT);//非阻塞等待客户端发送数据
        if(ret > 0)
        {
            printf("recv %s\n", buf);
            if(strcmp(buf, "connect") == 0)    //收到了connect
            {
                strcpy(buf, "ok");
                send(fd1, buf, sizeof(buf), MSG_DONTWAIT);    //回应ok
            }
        }
        usleep(100000);        //0.1 s (usleep 是以微秒为单位)
    }
    close(fd);
    close(newfd);
}

上面的服务器采用的是循环服务器,
循环服务器要求: accept 和  recv 都是  非阻塞的

非阻塞 方式接收数据:
二、非阻塞方式连接及接收(30min)
有些场合需要非阻塞
非阻塞:  accept  recv  read ....
         可以让这些函数不等
有时候,一个线程一直阻塞,效率有点低,
那么可以有两个解决办法
1)  不阻塞
2)  阻塞一小段时间(1秒)

2.1 如何实现非阻塞方式接收(15min)
文件I/O (不仅仅socket通信)

在打开文件后设置 O_NONBLOCK (非阻塞方式)
int fd = socket(AF_INET, SOCK_STREAM, 0);        //socket描述符

1) int flags = fcntl(fd, F_GETFD, 0);    //F_GETFD 取出socket默认属性, 给flag
    fcntl(file ----> control), 文件控制
        F_GETFD----> 读出文件处理方式标志位 (O_RDWR | O_TRUNC | .....)
   取出文件标志位
2) fcntl(fd, F_SETFL, flags | O_NONBLOCK);    
   给文件标志位增加  O_NONBLOCK( 非阻塞 )
        F_SETFL ---> 设置文件处理方式标志位
accept 函数变成非阻塞方式

flags (int 类型   32位)
    bit0    bit1    bit2    bit3    bit4 .... bit31
    可读    可写    新建    清空   非阻塞
flags = 0x05 ?----->   0000 0101  可读 + 新建  
如果再加上非阻塞权限

flags = flags | 0x10;        //0001 0000
#define O_NONEBLOCK 0x10
flags = flags | O_NONEBLOCK;

2.2 非阻塞方式练习(15min)
参照上面两个函数,实现并验证accept 非阻塞模式

#include
#include
#include
#include
#include

int main()
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);    //SOCK_STREAM 不要写成SOCK_DGRAM
    int flags = fcntl(fd, F_GETFD, 0);
    fcntl(fd, F_SETFL, flags | O_NONBLOCK);
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(33333);
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);     //192.168.30.252
    bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr));
    listen(fd, 5);        //设置同时连接最大值5个
    int newfd = accept(fd, NULL, NULL);        //非阻塞等待客户端连接
    printf("11111111111111 %d\n", newfd);        //-1 (正常  4), 因为没有客户端连接,所以-1
}

讲解:recv函数的第四个参数
recv / send
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
ssize_t send(int sockfd, const void *buf, size_t len, int flags);

flags ---> 通常是0  以阻塞方式接收
           如果以非阻塞方式接收数,flags  设置为 MSG_DONTWAIT
recv(newfd, buf, sizeof(buf), MSG_DONTWAIT);


/

3 线程池
多线程并发服务器 : 

缺点: 如果有多个客户端
    1) 耗费资源                                 (I/O 多路复用解决)
    2) 线程 可能会频繁的创建,消除 (效率低)    (线程池解决)

线程池原理:
    先把线程创建好,然后线程先休眠,一旦有要执行的任务,唤醒线程
问题
1)  线程池解决什么问题 : 线程  会频繁的创建,消除
2)  如何实现           :  先把线程创建好,然后线程先休眠,一旦有要执行的任务,唤醒线程

线程以队列+链表方式进行管理

///线程池的实现//
#include
#include
#include
#include
#include
#include
/*组成:
线程池管理 用于创建及管理线程池
工作线程 线程池中的线程--用于处理执行任务
任务接口  提供处理任务的接口 供工作线程调度执行任务
任务队列  存放待处理的任务 工作线程从中取出任务并执行 

*/
/*
*线程池里所有运行和等待的任务都是一个Thread_worker
*由于所有任务都在链表里,所以是一个链表结构
*/
typedef struct worker
{
    /*回调函数,任务运行时会调用此函数,注意也可声明成其它形式*/
    void *(*process) (void *arg);    //函数指针
    void *arg;/*回调函数的参数*/
    struct worker *next;
} Thread_worker;


/*线程池结构*/
typedef struct
{
     pthread_mutex_t queue_lock;
     pthread_cond_t queue_ready;
    /*链表结构,线程池中所有等待任务*/
     Thread_worker *queue_head;//任务1-》任务2-》任务3
    /*是否销毁线程池*/
    int shutdown;
    //线程池中所有线程的tid
     pthread_t *threadid;
   /*线程池中允许的活动线程数目*/
    int max_thread_num;
    /*当前等待队列的任务数目*/
    int cur_queue_size;
} Thread_pool;


int pool_add_worker (void *(*process) (void *arg), void *arg);
void *thread_routine (void *arg);


static Thread_pool *pool = NULL;
void   pool_init (int max_thread_num)
//创建线程池并初始化  
//参数:线程池最大线程数、任务队列最大任务数 
{
     pool = (Thread_pool *) malloc (sizeof (Thread_pool));
     pthread_mutex_init (&(pool->queue_lock), NULL);
     pthread_cond_init (&(pool->queue_ready), NULL);

     pool->queue_head = NULL;

     pool->max_thread_num = max_thread_num;
     pool->cur_queue_size = 0;

     pool->shutdown = 0;

     pool->threadid =(pthread_t *) malloc (max_thread_num * sizeof (pthread_t)); 
     int i = 0;
     for (i = 0; i < max_thread_num; i++)//批量创建线程
     { 
         pthread_create (&(pool->threadid[i]), NULL, thread_routine,NULL);
     }
}


/*向线程池中加入任务*/
//向任务队列中添加任务  
//参数 任务指针  
int pool_add_worker (void *(*process) (void *arg), void *arg)
{
    /*构造一个新任务*/
     Thread_worker *newworker =(Thread_worker *) malloc (sizeof (Thread_worker));
     newworker->process = process;
     newworker->arg = arg;
     newworker->next = NULL;

     pthread_mutex_lock (&(pool->queue_lock));
    /*将任务加入到等待队列中*/
     Thread_worker *member = pool->queue_head;
     if (member != NULL)//对列 尾插 执行任务 头删
     {
        while (member->next != NULL)
             member = member->next;
             
         member->next = newworker;
     }
     else
     {
         pool->queue_head = newworker;
     }

     pool->cur_queue_size++;
     pthread_mutex_unlock (&(pool->queue_lock));
     //唤醒一个等待线程 如果所有线程都在忙碌,这句没有任何作用
     // 唤醒阻塞在条件变量上的所有线程的顺序由调度策略决定,如果线程的调度策略是SCHED_OTHER类型的,系统将根据线程的优先级唤醒线程。
     //如果没有线程被阻塞在条件变量上,那么调用pthread_cond_signal()将没有作用。

     pthread_cond_signal (&(pool->queue_ready));    //发送信号给正在阻塞的线程,阻塞的线程解除阻塞,执行process
     return 0;
}


/*销毁线程池,等待队列中的任务不会再被执行,但是正在运行的线程会一直
把任务运行完后再退出*/
int pool_destroy ()
{
    if (pool->shutdown)
        return -1;/*防止两次调用*/
     pool->shutdown = 1;

     //唤醒所有等待线程,线程池要销毁
     pthread_cond_broadcast (&(pool->queue_ready));

     /*阻塞等待线程退出,否则就成僵尸了*/
     int i;
     for (i = 0; i < pool->max_thread_num; i++)
     { 
    pthread_join (pool->threadid[i], NULL);
     }
     free (pool->threadid);

     /*销毁等待队列*/
     Thread_worker *head = NULL;
     while (pool->queue_head != NULL)
     {
         head = pool->queue_head;
         pool->queue_head = pool->queue_head->next;
         free (head);
     }
     /*条件变量和互斥量也别忘了销毁*/
     pthread_mutex_destroy(&(pool->queue_lock));
     pthread_cond_destroy(&(pool->queue_ready));
    
     free (pool);
     /*销毁后指针置空是个好习惯*/
     pool=NULL;
     return 0;
}


//工作线程的主要工作为处理任务,当任务队列不为空的时候,工作线程直接从队列头取出一个任务并执行;
//当任务队列为空的时候,工作线程将阻塞直到有任务添加进来;
void *thread_routine (void *arg)
{
     printf ("starting thread 0x%x\n", (unsigned int)pthread_self ());
     while (1)
     {
         pthread_mutex_lock (&(pool->queue_lock));
         //如果等待队列为0并且不销毁线程池,则处于阻塞状态
         while (pool->cur_queue_size == 0 && !pool->shutdown)
         {
             printf ("thread 0x%x is waiting\n", (unsigned int)pthread_self ());
             pthread_cond_wait (&(pool->queue_ready), &(pool->queue_lock));        //让线程休眠
         }

         //线程池销毁
         if (pool->shutdown)
         {
             pthread_mutex_unlock (&(pool->queue_lock));
             printf ("thread 0x%x will exit\n", (unsigned int)pthread_self ());
             pthread_exit (NULL);
         }

         printf ("thread 0x%x is starting to work\n", (unsigned int)pthread_self ());
        
         //等待队列长度减去1,并取出链表中的头元素
         pool->cur_queue_size--;
         Thread_worker *worker = pool->queue_head;//头删 执行任务
         pool->queue_head = worker->next;
         pthread_mutex_unlock (&(pool->queue_lock));

         /*调用回调函数,执行任务*/
         (*(worker->process)) (worker->arg);
         free (worker);
         worker = NULL;
     }
     pthread_exit (NULL);
}
   
void *myprocess (void *arg)
{
     printf ("threadid is 0x%x, working on task %d\n", (unsigned int)pthread_self (),*(int *) arg);
     sleep (1);
     return NULL;
}

int  main (int argc, char **argv)
{
     pool_init (3);    //创建3个线程,默认是休眠的
     int *workingnum = (int *) malloc (sizeof (int) * 10);
     int i;
     for (i = 0; i < 10; i++)
     {
         workingnum[i] = i;
         pool_add_worker (myprocess, &workingnum[i]);    //给线程添加任务,执行myprocess
     }
    
     sleep (5);
    
     pool_destroy ();        //销毁线程

     free (workingnum);
     return 0;
}

作业讲解:
一、ftp文件服务器作业讲解(60min)  取文件列表、下载文件、上传文件
用到的知识点:
目录操作(list)、文件I/O(get put)、为了支持多客户端,用到了多线程、socket通信(tcp)
     
公共结构体讲解
struct data_info
{
    int type;    //类型  1 list    2 get    3  put     
    char buf1[50];    //list 文件名   get  文件名   put  文件名 
    char buf2[500];    //list 无效    get 文件内容   put  文件内容 
    int len;    //get  put  文件长度      
};

1.1 list的实现(15min)
客户端:
    struct data_info s;
    发送:
    scanf("%s", buf);    //list
    if(buf 是 "list")    //strcmp
    {
        s.type = 1;
        send(fd, &s, sizeof(s), 0);        //给服务器
    }
    收:
    recv(fd, &s, sizeof(s), 0);
    if(s.type == 1)
        printf("%s\n", s.buf1);
服务器端:
    struct data_info s;
    接收:
    recv(fd, &s, sizeof(s), 0);
    if(s.type == 1)
    {
        dp = opendir("aaaa");    
        ep = readdir(dp);
        while(ep != NULL)
        {
            strcpy(s.buf1, ep.d_name);
            send(fd, &s, sizeo(s), 0);
            ep = readdir(dp);
        }
    }
 

1.2 get file的实现(15min)
客户端: 
    发送:
    scanf("%s%s", buf, file_name);    //get hello.c
    if(buf 是 get)
    {
        s.type = 2;
        strcpy(s.buf1, file_name);
        send(fd, &s, sizeof(s), 0);
        FILE *fp = fopen("hello.c", "w");
    }
     接收:
    recv(fd, &s, sizeof(s), 0);
    if(s.type == 2)
    {    //写入文件
        FILE *fp = fopen("hello.c", "a+");    //追加写入
        fwrite(s.buf2, 1, sizeof(s.buf2), fp);
    }
服务器端:
    接收:
     recv(fd, &s, sizeof(s), 0);
    if(s.type == 2)
    {
        FILE *fp = fopen("aaaa/hello.c", "r");
        while(fread(s.buf2, 1, sizeof(s.buf2), fp) > 0)
            send(fd, &s, sizeof(s), 0);
    }
漏洞
1)  "a+"   追加输入,如果客户端原来就有这个文件  ??(在get时,客户端先新建并清空文件)
2)  如果文件很小,发送的buf是500字节,那么如何处理  (双发发送时要用len 存储实际发送的字节数)

1.3 put file的实现(15min)
客户端: 
    发送:
    scanf("%s%s", buf, file_name);      //put a.c
    if(buf 是 put)
    {
        s.type = 3;
        strcpy(s.buf1, file_name);
        FILE *fp = fopen("a.c", "r");
        while(fread(s.buf2, 1, sizeof(buf2), fp) > 0)
            send(fd, &s, sizeof(s), 0);
    }
服务器端:
    接收:
    recv(fd, &s, sizeof(s), 0);
    if(s.type == 3)
    {
        FILE *fp = fopen(s.buf1, "a+");
        fwrite(s.buf2, 1, sizeof(s.buf2), fp);
    }

/server.c//
#include
#include
#include
#include
#include
#include
struct data_info
{
    int type;    //1 list  2 get   3 put  4 quit
    char buf1[50];    //保存文件名
    char buf2[500];    //保存文件内容
    int len;        //加一个变量 number--->1 新建
};
void send_list(int fd, struct data_info *p)
{
    DIR *dp = NULL;  //定义一个结构体变量
    dp = opendir("aaaa");    //打开目录,fopen:打开文件,返回FILE *

    struct dirent *ep;   //用来保存一个文件
    ep = readdir(dp);    //readdir,执行一次从目录中读出一个文件
            //当读出所有文件时,返回值是NULL
    while( ep != NULL)
    {
        if(ep->d_name[0] != '.')    //以.开头是隐藏文件,不显示
        {
            strcpy(p->buf1, ep->d_name);
            send(fd, p, sizeof(struct data_info), 0);
        }
        ep = readdir(dp);
    }
}

void send_file(int fd, struct data_info *p)
{
    char name[100] = { 0 };
    sprintf(name, "%s/%s", "aaaa", p->buf1);
    FILE *fp = fopen(name, "r");    
    printf("file name is %s\n", p->buf1);
    if(fp != NULL)
    {
        int len;
        while((len = fread(p->buf2, 1, 500, fp)) > 0)    //p->len = fread(p->buf2, 1, 500, fp)
        {
            p->len = len;
            send(fd, p, sizeof(struct data_info), 0);
        }
        fclose(fp);
    }
}

void save_file(int fd, struct data_info *p)
{
    char name[100] = { 0 };
    sprintf(name, "%s/%s", "aaaa", p->buf1);
    FILE *fp = fopen(name, "a+");        //有bug 追加方式写入,内容会不一致
    printf("file name is %s\n", p->buf1);
    if(fp != NULL)
    {
        fwrite(p->buf2,1,p->len,fp);    //
        fclose(fp);
    }
    strcpy(p->buf1, "success");
    send(fd, p, sizeof(struct data_info), 0);    
}

void *recv_fun(void *p)
{
    int fd; 
    int *q = p;
    fd  = *q;        //fd = *((int *)p);
    struct data_info s;
    char buf[100] = { 0 };
    while(1)
    {
        if(recv(fd, &s, sizeof(s), 0) <= 0)
            return 0;
        printf("type is %d\n", s.type);
        if(s.type == 1)    //list
        {
            send_list(fd, &s);
        }
        else if(s.type == 2)    //get
        {
            send_file(fd, &s);
        }
        else if(s.type == 3)    //put
        {
            save_file(fd, &s);
        }
    }
}
int main(int argc, char *argv[])
{    
    pthread_t id;
    char buf[100] = { 0 };
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in myaddr;
    myaddr.sin_family = AF_INET;
    myaddr.sin_port = htons(55555);   //不要超过65535
    myaddr.sin_addr.s_addr = htonl(INADDR_ANY);  //inet_addr("127.0.0.1");
    bind(fd, (struct sockaddr *)&myaddr, sizeof(myaddr));
    listen(fd, 5);
    while(1)
    {
        int newfd =  accept(fd, NULL, NULL);        //阻塞等待客户端连接
        printf("client connect\n");
        pthread_create(&id, NULL, recv_fun, &newfd);    //一旦有客户端连接,就创建新线程
    }
}

/client.c//
#include
#include
#include
#include
#include

struct data_info
{
    int type;    //1 list  2 get   3 put  4 quit
    char buf1[50];    //保存文件名
    char buf2[500];    //保存文件内容
    int len;
};
int fd;
char buf[100] = { 0 };
void *recv_fun(void *p)
{
    while(1)
    {
        struct data_info s;
        int ret = recv(fd, &s, sizeof(s), 0);    //阻塞
        if(ret > 0)
        {
            if(s.type == 1)            //list
            {
                printf("%s\n", s.buf1);     //buf1 文件名
            }
            else if(s.type == 2)
            {
                FILE *fp = fopen(s.buf1, "a+");    //"r+"   "w+"    
                                //"a+"  以追加的方式打开文件并写入
                if(fp != NULL)
                {
                    fwrite(s.buf2,1,s.len,fp);    //
                    fclose(fp);
                }    
            }
        }
    }
}

void send_file(int fd, struct data_info *p)
{
    FILE *fp = fopen(p->buf1, "r");    
    printf("file name is %s\n", p->buf1);
    if(fp != NULL)
    {
        while((p->len = fread(p->buf2, 1, 500, fp)) > 0)    //
        {
            send(fd, p, sizeof(struct data_info), 0);
        }
        fclose(fp);
    }
}
int main(int argc, char *argv[])
{    
    pthread_t id;
    fd = socket(AF_INET, SOCK_STREAM, 0);
    struct sockaddr_in youaddr;
    youaddr.sin_family = AF_INET;
    youaddr.sin_port = htons(atoi(argv[2]));   //不要超过65535
    youaddr.sin_addr.s_addr = inet_addr(argv[1]);    //ip地址
    connect(fd, (struct sockaddr *)&youaddr, sizeof(youaddr));
    pthread_create(&id, NULL, recv_fun, NULL);
    while(1)
    {
        struct data_info s;
        gets(buf);                    //阻塞
        if(strcmp(buf, "list") == 0)    //strcmp 字符串比较
        {
            s.type = 1;
            send(fd, &s, sizeof(s), 0);    
        }
        else if(strcmp(buf, "get") == 0)    //strcmp 字符串比较
        {
            s.type = 2;
            gets(s.buf1);
            FILE *fp = fopen(s.buf1, "w");    //清空并新建文件
            fclose(fp);
            send(fd, &s, sizeof(s), 0);    
        }
        else if(strcmp(buf, "put") == 0)    //strcmp 字符串比较
        {
            s.type = 3;
            gets(s.buf1);
            send_file(fd, &s);    
        }        
    }
}


新内容: 数据库

你可能感兴趣的:(c++)