Linux:TCP Socket编程(代码实战)


1. TCP的Socket编程

1.1 TCP的编程流程

和UDP编程一样,我们在考虑TCP的时候也要考虑到接收方和发送方,也就是是说要考虑接收方和发送方的,但是由于TCP是有连接,可靠有序且面向字节流的传输层协议,因此,TCP的编程流程要比UDP复杂一些。

用图来表示的话如下:
在这里插入图片描述

1.2 TCP Socket的接口

创建套接字socket()、关闭套接字close()和绑定地址信息bind()这三个函数均和UDP的一样,可以看我之前写的这篇文章

1.2.1 服务端监听连接接口

int listen(int sockfd, int backlog);

参数:

  • sockfd:套接字描述符
  • backlog:已完成连接队列的大小

    注:服务端和客户端在进行三次握手的过程中,会存在两个队列,一个是已完成连接的队列,一个是未完成连接的队列,未完成连接的队列中存储的是没有完成三次握手的连接;而已完成连接的队列中存储的是已经完成三次连接,并且等待被服务端"accept"的连接,或者也可以理解为当前连接状态为"ESTABLISHED"的连接。

backlog参数的具体理解:

由于TCP连接首先要进行三次握手,而想要和服务端建立连接的客户端却有很多个,在同一时间内可能会有多个客户端再和服务端进行三次握手。而backlog控制的就是已完成连接队列的大小,因此backlog影响了服务端在一瞬间的连接的接收能力,或者说影响了服务端并发接收新连接的能力,注意不能将其理解为 服务端建立连接的数量。

1.2.2 服务端获取新连接接口

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

作用:

从已完成连接队列中获取已经完成三次握手的连接。(当没有连接的时候,调用该接口会发送阻塞)

参数:

  • sockfd:套接字描述符
  • addr:客户端的地址信息结构
  • addrlen:客户端的地址信息结构的长度

返回值:(非常重要)

成功则返回新连接的套接字描述符,失败则返回-1。

accept返回值的深入理解:

如图:
在这里插入图片描述
这就是为什么返回值如此重要原因,因为他关系着后面收发数据时所对应在服务端中的缓冲区时哪一个。

1.2.3 客户端发起连接接口

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

参数:

  • sockfd:套接字描述符
  • addr:服务端的地址信息结构
  • addrlen:服务端的地址信息结构的长度

返回值:

成功则返回0,失败则返回小于0的数。

1.2.4 发送数据接口

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

参数:

  • sockfd:套接字描述符
  • buf:待要发送的数据
  • len:待要发送数据的长度
  • flags:0为阻塞发送,MSG_OOB为发送带外数据

带外数据:即在紧急情况下所产生的数据,会越过前面进行排队的数据优先进行发送。

返回值:

成功则返回的字节数量,失败则返回-1。

1.2.5 接收数据接口

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

参数:

  • sockfd:套接字描述符
  • buf:将接收到的数据放到buf中
  • len:接收数据的长度
  • flags:0为阻塞接收,即如果没有发送数据,则recv接口会阻塞

返回值:

  • >0:正常接收了多少字节的数据
  • ==0:对端将连接关闭(即对端机器调用了close函数关闭了套接字)
  • >0:接收失败

1.3 三次握手的简单验证

由于三次握手是操作系统内核自动完成的,因此我们只需要给出服务端的的代码到listen接口即可。

#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <iostream>

using namespace std;


int main()
{
    //1. 创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd < 0)
    {
        cout << "TCP socket failed" << endl;
        return 0;
    }

    //2.绑定地址信息
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(18989);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
    if(ret < 0)
    {
        cout << "bind failed" << endl;
        return 0;
    }

    //3.进行侦听(告诉OS内核可以与客户端建立连接了)
    //listen函数中blocklog参数,设定的是已完成三次握手并已建立连接的队列的容量
    ret = listen(sockfd,1);
    if(ret < 0)
    {
        cout << "listen failed" << endl;
        return 0;
    }
    while(1)
    {};
    return 0;
}

要测试三次握手是否成功,我们不需要编写客户端代码进行验证,而是借助windows下的一个工具telnettelnet可以模仿TCP进行三次握手,就只是单纯的建立连接,其他什么都不会做。

使用telnet + 公网ip + 端口号即可对其进行测试,需要注意的是,这条命令是在windows下的cmd窗口运行的。

首先我们需要将服务端运行起来然后再使用telnet进行测试
在这里插入图片描述
当一回车之后,会新弹出一个telnet的窗口,代表三次握手成功。
在这里插入图片描述
下面给出两种失败的情况
情况1:端口不存在
在这里插入图片描述
情况2:公网ip错误
在这里插入图片描述

我们多使用telnet建立几次连接,然后看看当前服务器端口的使用情况。
这里总共使用telnet与当前服务器进行了三次的连接,即进行了三个三次握手,而我们在代码中设置的已完成连接的队列的大小为1,我们可以使用netstat -anp | grep 18989查看18989端口的使用情况。
在这里插入图片描述
我们可以清楚的看到该服务端建立了两次连接,但是我们设置的backlog为1,按理说只能连接一次,那这是什么情况呢?原因是操作系统内核的问题,内核中的代码逻辑是这样的

// q为 已完成连接队列   ,capacity_为当前我们设置的backlog的大小
if(q.size() < capacity_)
{
	//不建立连接 
}

因此,根据这个逻辑来分析,他会建立两次连接,是正常的,并不是我们代码的问题。
但是有一点需要注意的是,在验证三次握手的时候,我们服务器端的代码绝对不能用accept来接收,因为一旦用accept接收之后,就会从当前已完成队列中取走一个连接,导致我们看到的连接结果会多一条,如图:
在这里插入图片描述

2. 代码实战

本次TCP的代码有三种形式:单进程版本、多进程版本和多线程版本,但是这些版本均只是针对服务端而言,其中单进程和多进程的客户端的代码都是相同的,因此在这里我们首先给出客户端的代码。

#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <iostream>

using namespace std;

/*
 * 1. 创建套接字
 * 2. 建立连接请求
 * 3. TCP三次握手建立连接----OS会自动进行,不需要我们进行控制
 * 4. 接收数据、发送数据
 * 5. 关闭套接字
 */ 

int main()
{
    //1. 创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd < 0)
    {
        cout << "TCP socket failed" << endl;
        return 0;
    }

    //2.进行连接
    //0.0.0.0:任意网卡地址,注意不是任一,它的含义就是哪个网卡地址都行
    //当服务器端绑定的地址为0.0.0.0,那么我们客户端想要与服务端建立连接的时候
    //有两种方法: 
    //1.若是运行服务器的这台机器本身是能够上网的,
    // 那么我们只需要连接它的网卡地址中的任意一个就行,(0.0.0.0为任意地址)
    //2.若是运行服务器的这台机器是要经过类似于腾讯云、阿里云这样的
    // 云服务器读对私网ip进行转换才能上网的,那么我们就需要连接它的公网ip。
    
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(18989);
    //此处我们使用的公网ip
    addr.sin_addr.s_addr = inet_addr("118.89.67.215");

    int ret = connect(sockfd,(struct sockaddr *)&addr,sizeof(addr));
    if(ret < 0)
    {
        cout << "connnect failed" << endl;
        return 0;
    }

    //3.接收和发送数据
    while(1)
    {
        //这里接收数据和发送数据是没有顺序而言的
        //因为已经经过三次握手,客户端和服务端的连接以及建立成功了
        char buf[1024] = {0};
        
        //发送数据
        sprintf(buf,"hello server,i am client2\n");
        ssize_t send_ret = send(sockfd,buf,strlen(buf),0); 
        if(send_ret < 0)
        {
            cout << "send failed" << endl;
            continue;
        }
        memset(buf,'\0',sizeof(buf));

        ssize_t recv_size = recv(sockfd,buf,sizeof(buf)-1,0);
        if(recv_size < 0)
        {
            cout << "recv failed" << endl;
            continue;
        }
        else if(recv_size == 0)
        {
            cout << "Peer close" << endl;
            close(sockfd);
            return 0;
        }
        
        cout << "recv data is : " << buf << endl;


    }

    close(sockfd);
    return 0;
}

2.1 单进程版本的TCP代码

单进程版本的服务端代码如下:

#include <unistd.h>
#include <sys/socket.h>
#include <string.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <iostream>

using namespace std;

/*
 * 1. 创建套接字
 * 2. 绑定地址信息
 * 3. 进行监听--(告诉OS内核可以接收客户端发送的新请求了并且监听新连接的到来)
 * 4. TCP三次握手建立连接----OS会自动进行,不需要我们进行控制
 * 5. 接收新连接
 * 6. 接收数据、发送数据
 * 7. 关闭套接字
 */ 

int main()
{
    //1. 创建套接字
    int sockfd = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(sockfd < 0)
    {
        cout << "TCP socket failed" << endl;
        return 0;
    }

    //2.绑定地址信息
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(18989);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int ret = bind(sockfd,(struct sockaddr *)&addr,sizeof(addr));
    if(ret < 0)
    {
        cout << "bind failed" << endl;
        return 0;
    }

    //3.进行侦听(告诉OS内核可以与客户端建立连接了)
    //listen函数中blocklog参数,设定的是已完成三次握手并已建立连接的队列的容量
    ret = listen(sockfd,1);
    if(ret < 0)
    {
        cout << "listen failed" << endl;
        return 0;
    }

    //4.接收新连接
    struct sockaddr_in client_addr;
    socklen_t socklen = sizeof(client_addr);
    int new_sockfd = accept(sockfd,
    			(struct sockaddr *)&client_addr,&socklen);

    if(new_sockfd < 0)
    {
        cout << "accept failed" << endl;
        return 0;
    }

    printf("accept success: %s:%d, and new_sockfd is %d\n",
    inet_ntoa(client_addr.sin_addr),ntohs(client_addr.sin_port),
    			new_sockfd);

    //5.接收和发送数据
    while(1)
    {
        //这里接收数据和发送数据是没有顺序而言的
        //因为已经经过三次握手,客户端和服务端的连接以及建立成功了
        
        char buf[1024] = {0};

        ssize_t recv_size = recv(new_sockfd,buf,sizeof(buf)-1,0);
        if(recv_size < 0)
        {
            cout << "recv failed" << endl;
            continue;
        }
        else if(recv_size == 0)
        {
            cout << "Peer close" << endl;
            close(new_sockfd);
            close(sockfd);
            return 0;
        }
        
        cout << "accrpt data is : " << buf << endl;

        //发送数据
        memset(buf,'\0',sizeof(buf));
        sprintf(buf,"hello lient,i am server,i am %s:%d\n",
        		inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
        		
        ssize_t send_ret = send(new_sockfd,buf,strlen(buf),0); 
        if(send_ret < 0)
        {
            cout << "send failed" << endl;
            continue;
        }

    }

    close(sockfd);
    return 0;
}

分析完代码后,我们来看看客户端与服务端之间的通信吧!!

分别运行客户端和服务端所得到的结果如下:
服务端的结果:
在这里插入图片描述
客户端的结果:
在这里插入图片描述
看上去好像成功了,但是这只是一个客户端与服务器通信的情况,如果是多个客户端呢?他的情况是怎样的?在上面的基础上,我们再运行一个客户端,看看其结果如何:
在这里插入图片描述
我们发现第二个客户端完全不运行,这是什么情况呢?我们使用ps aux | grep []pstack []命令来对其进行分析。
在这里插入图片描述
稍微对服务端代码进行分析就可知,因为服务端accept在while循环外面,所以只能从已完成连接队列中拿出一次连接进行通信,而第二个客户端进来后,虽然和服务端三次握手成功建立了连接,但是并没有被服务端接收其连接,因此当客户端代码走到recv逻辑的时候就会发生阻塞,因为客户端是不会给他回复任何消息的。

那能否将服务端accept代码放到while循环里面?答案肯定也是不行的,稍微分析一下,当while循环每次走到accept逻辑的时候,都要从已完成连接队列中获取连接,当队列为空的时候就会阻塞掉,因此,当有两个客户端的时候,它们之间只能通信一次,然后就会陷入到accept阻塞的逻辑中。

稍微总结一下,单进程版本的TCP代码当有多个客户端与其服务器进行通信的时候,依据代码逻辑的不同,可能会产生两种阻塞:recv阻塞和accept阻塞。

那么该如何解决呢?有三种解决办法。

  • 多进程版本的TCP代码
  • 多线程版本的TCP代码
  • 使用多路转接的技术(这个在后面会进行讲解)

2.2 多进程版本的TCP代码

再写多进程代码的时候,一定要注意的点有:父进程只管accept,子进程只管send和recv数据,因为父子进程的进程虚拟空间是几乎相同的,父进程从已完成连接队列中得到的套接字,子进程也能拥有。

但是一定要注意到进程等待,如果子进程先于父进程退出,且父进程没有进行相应的进程等待,那么该子进程就会变成僵尸进程。但是如果我们直接在父进程的逻辑中加上wait函数是行不通的,因为,随着客户端的增多,父进程创建出的子进程也逐渐变多,wait函数会捕捉子进程退出时所发出的SIGCHLD信号,但是是不知道捕捉的是哪一个进程的。

因此,在这里,我们使用signal函数将SIGCHLD该信号处理重新用我们自己的逻辑定义一下即可。

服务端的代码如下:

#include <unistd.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <netinet/in.h>
#include <string.h>
#include <iostream>

using namespace std;

void signalcallBack(int)
{
    wait(NULL);
    return;
}

int main()
{
    signal(SIGCHLD,signalcallBack);
   
    //创建套接字
    int listen_sock = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
    if(listen_sock < 0)
    {
        cout << "socket failed" << endl;
        return 0;
    }
    
    //绑定地址信息
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(18989);
    addr.sin_addr.s_addr = inet_addr("0.0.0.0");
    int ret = bind(listen_sock,(struct sockaddr *)&addr,sizeof(addr));
    if(ret < 0)
    {
        cout << "bind failed" << endl;
        return 0;
    }

    //帧听
    ret = listen(listen_sock,1);
    if(ret < 0)
    {
        cout << "listen failed" << endl;
        return 0;
    }

    while(1)
    {
        struct sockaddr_in peer_addr;
        socklen_t peerlen;
        int new_sockfd = accept(listen_sock,
        				(struct sockaddr *)&peer_addr,&peerlen);
        if(new_sockfd < 0)
        {
            cout << "accept failed" << endl;
            return 0;
        }
        pid_t fork_ret = fork();
        if(fork_ret < 0)
        {
            cout << "fork failed" << endl;
            continue;
        }
        else if(fork_ret == 0)
        {
            close(listen_sock);
            //child
            //在子进程中只实现接收和发送数据
            while(1)
            {
                char buf[1024] = {0};
             ssize_t recv_size = recv(new_sockfd,buf,sizeof(buf)-1,0);
                if(recv_size < 0)
                {
                    cout << "recv failed" << endl;
                    continue;
                }
                else if(recv_size == 0)
                {
                    cout << "peer failed" << endl;
                    break;
                }

                cout << "i recv: " << buf << endl;

                //接收数据
                memset(buf,'\0',sizeof(buf));
                sprintf(buf,"hello client,%s:%d\n",
                		inet_ntoa(peer_addr.sin_addr),
                		ntohs(peer_addr.sin_port));
                ssize_t send_size = send(new_sockfd,buf,strlen(buf),0);
                if(send_size < 0)
                {
                    cout << "send failed" << endl;
                    break;
                }
            }
        }

        else
        {
            //father
            //要进行进程等待,防止子进程变成僵尸进程
            //但是还存在着的问题是,该父进程可能会创建多个子进程
            //若是直接在父进程中进行进程等待的话,就意味着若只有一个子进程退出
            //父进程也会随之等待到资源,然后continue上去会陷入到accept的逻辑中
            //因此,我们需要设置信号,自定义信号的处理逻辑,每当有一个sigchild
            //信号发送出来的时候,我们让其在自定义的处理函数进行进程等待即可
            continue;
        }
    }

    return 0;
}

可能是我上传的图片太多了,上传图片总是失败,所以这里就不再进行结果的验证了。

2.3 多线程版本的TCP代码

多线程和多进程一样,也需要考虑几点,要将新的套接字传入线程入口函数,为此我们要在堆上申请一个空间用来存储新的套接字;为了不让线程进行等待,我们可以在线程入口函数中直接将自己线程分离掉即可;最后一点,一定要注意退出时对内存进行释放,关闭套接字,避免内存泄漏。

我在这里用一个类将TCP的各个接口给封装了起来,换句话说,到时候可以在客户端或服务端直接调用封装的类的接口即可实现逻辑。

封装的tcp类:

//tcp.hpp
#pragma once

#include <unistd.h>
#include <pthread.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>

#include <iostream>

using namespace std;

class tcp
{
    public:
        tcp() : sockfd_(-1)
        {}
        tcp(int sock) : sockfd_(sock)
        {}
        ~tcp()
        {
            close(sockfd_);
        }


        int createSockfd()
        {
            sockfd_ = socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
            if(sockfd_ < 0)
            {
                cout << "socket failed" << endl;
                return -1;
            }
            return sockfd_;
        }

        int Bind(string ip = "0.0.0.0",uint16_t port = 18989)
        {
            struct sockaddr_in addr;
            addr.sin_family = AF_INET;
            addr.sin_port = htons(port);
            addr.sin_addr.s_addr = inet_addr(ip.c_str());


            int ret = bind(sockfd_,
            			(struct sockaddr *)&addr,sizeof(addr));
            if(ret < 0)
            {
                cout << "bind failed" << endl;
                return -1;
            }
            return ret;
        }

        int Listen(int backlog = 2)
        {
            int ret = listen(sockfd_,backlog);
            if(ret < 0)
            {
                cout << "listen failed" << endl;
                return -1;
            }
            return ret;
        }

        int Accept(struct sockaddr_in* addr,socklen_t* socklen)
        {
            int new_sockfd = accept(sockfd_,
            			(struct sockaddr *)addr,socklen);
            if(new_sockfd < 0)
            {
                cout << "accept failed" << endl;
                return -1;
            }
            return new_sockfd;
        }

        ssize_t Recv(char* buf,size_t len)
        {
            ssize_t ret = recv(sockfd_,buf,len,0);
            if(ret < 0)
            {
                cout << "recv failed" << endl;
            }
            return ret;
        }

        ssize_t Send(char* buf,size_t len)
        {
            ssize_t ret =  send(sockfd_,buf,len,0);
            if(ret < 0)
            {
                cout << "send faild" << endl;
                return -1;
            }

            return ret;
        }
    
    private:
        int sockfd_;
};

服务端代码:

#include "tcp.hpp"

void* TcpEntryPthread(void* arg)
{ 
    pthread_detach(pthread_self());
    tcp *tc = (tcp*) arg; 

    while(1)
    {
        char buf[1024] = {0};
        ssize_t ret = tc->Recv(buf,sizeof(buf)-1);
        if(ret < 0)
        {
            cout << "recv failed" << endl;
            continue;
        }
        else if(ret == 0)
        {
            cout << "peer close" << endl;
            break;
        }

        printf("client say: %s\n",buf);

        memset(buf,'\0',sizeof(buf));
        sprintf(buf,"hello client, i am server");

        ret = tc->Send(buf,strlen(buf));
        if(ret < 0)
        {
            cout << "send failed" << endl;
            continue;
        }
    }

    delete tc;
    return nullptr;
}

int main()
{
    tcp tc;
    int ret = tc.createSockfd();
    if(ret < 0)
        return -1;
    //默认ip 0.0.0.0,默认端口18989
    ret = tc.Bind();
    if(ret < 0)
        return -1;
    //默认已完成连接队列大小为2
    ret = tc.Listen();
    if(ret < 0)
        return -1;
    
    while(1)
    {
        struct sockaddr_in addr;
        socklen_t socklen = sizeof(addr);
        int new_sockfd = tc.Accept(&addr,&socklen);
        if(new_sockfd < 0)
        {
            cout << "Please again to accept" << endl;
            continue;
        }
        
        //从这里开始创建工作线程
        //如果只是单纯的将new_sockfd传过去的话,是不行的
        //因为它是一个局部变量,因此,我们需要在堆上开辟出一个空间
        tcp *t = new tcp(new_sockfd);
        if(t == nullptr)
        {
            cout << "new class tcp failed" << endl;
            continue;
        }
        
        pthread_t tid;
        ret = pthread_create(&tid,NULL,TcpEntryPthread,t);
        if(ret < 0)
        {
            cout << "pthread_create failed" << endl;
            delete t;
            continue;
        }
    }
}
  • 9
    点赞
  • 18
    收藏
    觉得还不错? 一键收藏
  • 4
    评论
python入门到高级全栈工程师培训视频学习资料;本资料仅用于学习,请查看后24小时之内删除。 【课程内容】 第1章 01 计算机发展史 02 计算机系统 03 小结 04 数据的概念 05 进制转换 06 原码补码反码 07 物理层和数据链路层 08 网络层和arp协议 09 传输层和应用层 第2章 01 上节课复习 02 arp协议复习 03 字符编码 第3章 01 网络基础和dos命令 02 为何学习linux 03 课程内容介绍 04 操作系统内核与系统调用 05 操作系统安装原理 06 linux操作系统安装 07 初识linux命令 08 linux操作系统目录结构 09 目录及文件操作 第4章 01 上节课复习 02 创建用户相关的文件 03 用户增删该查及组相关操作 04 对文件的权限管理 05 对目录的权限管理 06 权限管理补充 07 属主属组及基于数字的权限管理 第5章 01 上节课复习 02 文件合并与文件归档 03 文件归档与两种压缩方式 04 vim编辑器 05 系统启动流程 06 grub加密 07 bios加密 08 top命令 09 free命令 10 进程管理 第6章 01 上节课复习 02 磁盘分区 03 文件系统与挂载 04 挂载信息讲解 05 磁盘用满的两种情况 06 软连接和硬链接 07 软连接和硬链接补充 第7章 01 ip地址与子网划分 02 ip地址配置 03 虚拟机网络模式 04 三层隔离验证试验 第8章 01 上节课复习 02 软件包介绍 03 rpm软件包管理 04 yum软件包管理 05 源码安装python3.5 06 ssh服务 07 apache服务 08 samba服务 第9章 01 Python开发系列课程概要 02 Python作业要求以及博客 03 编程语言介绍 04 Python种类介绍 05 Python安装以及环境变量的操作 06 Python初识以及变量 07 Python条件语句和基本数据类型 08 Python while循环语句以及练习题 09 练习题讲解 第10章 01 上节内容回顾以及补充 02 上周作业实现 03 Pycharm的安装和使用 04 Python 运算符 05 Python 运算符以及总结 06 Python 基本数据类型介绍 07 Python 整形的魔法 08 Python 字符串的魔法 第11章 01 Python 字符串的魔法 02 Python range的用法以及练习 03 Python 课上练习解释 04 Python 基础知识练习题试题 第12章 01 今日内容介绍以及基础测试题答案讲解 02 Python 列表的魔法 03 Python 元组的魔法 04 Python 字典的魔法 05 Python 错误更正:布尔值可以作为字典的key 06 Python 今日内容整理 第13章 第13章共1课 第14章 01 数据类型和变量总结 02 集合定义和基本操作方法 03 集合关系运算交,差,并集 04 集合的其他内置方法 05 集合补充 06 百分号字符串拼接 07 format字符串格式化 08 数学意义的函数与python中的函数 09 为何要有函数 10 函数返回值 11 可变长参数 第15章 01 上节课复习 02 全局变量与局部变量 03 风湿理论之函数即变量 04 函数递归 05 函数递归补充 第16章 01 上节课回顾 02 函数作用域 03 函数作用域补充 04 匿名函数 05 函数式编程介绍 06 函数式编程尾递归调用优化 07 map函数 08 map函数filter函数 09 reduce函数 10 map reduce filter总结 11 内置函数part1 第17章 01 课前吹牛 02 zip方法 03 max和min高级使用 04 其他内置函数 05 文件操作的其他模式 第18章 01 上节课复习 02 文件处理b模式 03 文件操作的其他方法 04 文件seek方法补充 05 迭代器协议和for循环工作机制 06 迭代器补充 07 三元运算,列表解析,生成器表达式 第19章 01 生成器函数 02 生成器函数的好处 03 母鸡下蛋的传说 04 生成器特性阐释 05 生产者消费者模型 06 第三次作业讲解 第20章 01 上节课回顾 02 装饰器基本理论 03 高阶函数使用 04 函数闭包 05 函数闭包装饰器基本实现 06 函数闭包加上返回值 07 函数闭包加上参数 08 函数闭包补充:解压序列 09 函数闭包为函数加上认证功能 10 函数闭包模拟session 11 函数闭包装饰器运行流程 12 函数闭包带参数装饰器 第21章 01 查询功能 02 修改功能 03 程序的解耦 04 module模块和包的介绍 05 模块的执行以及__name__ 06 关于模块的介绍 07 time时间模块 08 random模块 第22章 01 模块的补充 02 sys修改环境变量 03 BASEDIR的介绍 04 os模块的介绍 05 sys模块的介绍 06 json模块 07 pickle模块 08 shelve模块 09 XML模块 10 re模块简介 11 re模块之元字符 第23章 01 re模块之转义字符 02 re模块之分组 03 re模块之方法 04 re模块总结 05 logging模块 06 re模块补充 07 configparse模块 08 hashlib模块 09 计算器作业以及思路 10 模块导入补充 第24章 01 面向对象设计 02 类相关知识 03 对象相关知识 04 类属性增删改查 05 实例属性的增删改查 06 对象与实例属性 07 对象与实例属性补充 08 面向对象作业 第25章 01 上节课回顾 02 静态属性 03 类方法 04 静态方法 05 小结 06 组合 07 继承 08 接口继承与归一化设计 09 继承顺序之mro线性顺序列表 10 在python2中的继承顺序是什么 11 在子类中调用父类方法 12 super调用父类的方法 13 选择系统作业讲解 第26章 01 学生自主复习 02 分享列表 03 多态 04 封装 05 面向对象概念总结 06 反射 07 反射及动态导入模块 08 类的内置attr属性 09 类内置attr属性补充 10 继承的方式完成包装 11 组合的方式完成授权 第27章 01 os模块复习 02 上节课复习 03 内置函数补充及getattribute 04 getattribue补充 05 item系列 06 str与repr 07 自定制format 08 slots属性 09 doc属性 10 module和class 11 析构方法 12 call方法 13 迭代器协议 14 迭代器协议实现斐波那契数列 16 描述符答疑 17 描述符优先级 18 软件开发规范 19 pycharm干的好事 第28章 01 上节课复习 02 上下文管理协议 04 异常的构成简单了解 05 描述符应用 08 类的装饰器的基本原理 09 类的装饰器增强版 10 类的装饰器的应用 11 自定制property 12 自定制property流程分析 13 自定制property实现延迟计算功能 14 property补充 15 元类介绍 16 自定义元类 17 函数复习 18 文件操作复习 第29章 01 上节课复习 02 什么是异常处理及异常处理的两种方式对比 03 多分支与万能异常 04 异常处理的其他内容 05 什么时候用异常处理 06 什么是socket 07 套接字发展及分类 08 基于tcp协议的套接字编程 09 socket底层工作原理解释 10 tcp三次握手与四次挥手 第30章 01 上节课复习 02 客户端服务端循环收发消息 03 socket收发消息原理剖析 04 服务端循环链接请求来收发消息 05 补充 06 udp套接字 07 recv与recvfrom的区别及基于udp实现ntp服务 08 基于tcp实现远程执行命令 09 基于tcp实现远程执行命令测试结果 10 粘包现象 11 粘包解决方法 第31章 01 上节课复习 02 socketserver实现并发 03 socketserver模块介绍 04 socketserver源码分析tcp版本 05 socketserver源码分析udp版 06 ftp作业要求讲解 07 补充:认证客户端链接合法性 第32章 01 FTP之参数解析与命令分发 02 FTP之逻辑梳理 03 FTP之验证功能 05 FTP之文件上传 06 FTP之断点续传 08 FTP之进度条 09 FTP之cd切换 11 FTP之创建文件夹及MD5校验思路 第33章 01 操作系统历史 02 进程的概念 03 线程的概念 04 线程的调用以及join方法 05 setDaemon方法和继承式调用.baiduyun.downloading 05 setDaemon方法和继承式调用 第34章 01 上节知识回顾 02 并发并行与同步异步的概念 03 GIL的概念 04 同步锁 05 递归锁 06 同步对象event 07 信号量 08 线程队列 09 生产者消费者模型 10 多进程的调用 第35章 01 进程通信 02 进程池 03 协程 04 事件驱动模型 05 IO模型前戏 06 阻塞IO与非阻塞IO 07 select及触发方式 08 select监听多连接 09 select与epoll的实现区别 第36章 01 异步IO 02 selectors模块介绍 03 selectors模块应用 04 作业介绍 第37章 01 selctors实现文件上传与下载 02 html的介绍 03 html文档树的概念 04 meta标签以及一些基本标签 05 img标签和列表标签 06 form表单之input标签 07 通过form向server端发送数据 08 form表单之select标签 09 table标签 第38章 01 css的四种引入方式 02 css的四种基本选择器 03 css的组合选择器 04 css的属性选择器 05 css的伪类 06 css的选择器优先级 07 css的背景属性 第39章 01 css的文本属性与边框属性 02 css的列表属性与display属性 03 css的内外边距 04 css的内外边距补充 05 css的float属性 06 css的清除浮动 07 css的定位 08 css的margin定位 第40章 01 抽屉作业之head区域(导航条) 02 抽屉作业之置顶区域 03 抽屉作业之content部分 05 抽屉作业之页码部分 06 抽屉作业之footer部分 第41章 01 JS的历史以及引入方式 02 JS的基础规范 03 JS的基本数据类型 04 JS的运算符 05 JS的控制语句与循环 06 JS的循环与异常 07 JS的字符串对象 08 JS的数组对象 09 JS的函数对象 第42章 01 JS的函数作用域 02 JS的window对象之定时器 03 JS的history对象和location对象 04 JS的DOM节点 05 JS的DOM节点 第43章 01 上节知识回顾 02 js之onsubmit事件与组织事件外延 03 DOM节点的增删改查与属性设值 04 正反选练习 05 js练习之二级联动 06 jquery以及jquery对象介绍 07 jquery选择器 08 jquery的查找筛选器 09 jquery练习之左侧菜单 第44章 01 jquery属性操作之html,text,val方法 02 jquery循环方法和attr,prop方法 03 jquery模态对话框与clone的应用 04 jqueryCSS操作之offsets,position以及scrolltop 05 jquery事件绑定与事件委托 06 jquery动画效果 07 jquery扩展与插件 08 jquery扩展补充 09 本周作业轮播图以及思路 第45章 轮播图片css部分 轮播图片js部分 第46章 01 数据库与dbms的概念 02 sql规范 03 数据库操作DDL 04 python s3 day46 mysql的数据类型 05 数据表操作 06 表记录之增删改操作 07 表记录查询之查询 第47章 01 多表查询之连接查询 02 级联删除与set null 03 多表查询之连接查询 04 多表查询之复合查询与子查询 05 mysql之索引 第48章 01 python操作数据库pymysql 02 数据库之事务 03 mysql事务之savepoint 第49章 01 http协议之请求协议 02 http协议之响应协议 03 web框架的概念 04 做一个最简答web框架 05 MVC模式和MTV模式 06 django的一个简单应用 07 django静态文件之static 08 django的url控制系统 09 django的urlConf补充 第50章 01 django之视图函数的介绍 02 django视图之redirec 03 django模板之变量 04 django模板之过滤器 05 django模板之控制语句if和for循环 06 django模板之标签tag补充 07 django模板之自定义filter和simple_tag 08 django模板之继承标签extend和添加标签include 第51章 01 数据库表与表之间的一对多多对多的关系 02 Django的ORM的概念 03 ORM对单表的增删改操作 04 ORM查询API 05 模糊查询之万能的双下换线 第52章 01 上节知识回顾 02 ORM多表操作之一对多增加记录 03 ORM多表操作之一对多查询之对象查询 04 ORM多表操作之一对多查询之双下划线查询 05 ORM多表操作之多对多添加记录 06 ORM多表操作之多对多查询 07 ORM多表操作之F查询与Q查询 08 ORM的querySet集合对象的特性 第53章 01 admin介绍 02 alex首秀失败 03 自定义admin样式 04 admin补充 05 COOKIE介绍 06 COOKIE和SESSION配合使用 第54章 01 今日内容概要 02 Django内容回顾 03 Django请求生命周期之Http请求 04 Django请求生命周期之FBV和CBV 05 Django请求生命周期之CBV扩展 06 瞎扯淡 07 Django请求生命周期之响应内容 08 学员管理示例:数据库设计 09 学员管理示例:班级管理 10 学员管理示例:学员管理 第55章 01 Django的ORM基本操作补充之概要 02 Django的ORM基本操作补充之一对多 03 学员管理示例:编辑学生 04 Django的ORM基本操作补充之多对多 05 学员管理示例:为班级分配老师 06 初识Ajax以及简单应用 07 学员管理示例:Ajax删除学生 08 本节作业以及内容补充 第56章 01 上节内容回顾 02 创建Project以及表结构 03 基于BootStrap和FontAwesome制作页面 04 创建学生信息 05 删除学生信息 第57章 01 上节内容回顾 02 上节bug修复 03 编辑学生信息之前端功能 04 编辑学生信息之后台处理 05 以上内容总结 06 Ajax功能之dataType和traditional 第58章 01 今日内容概要 02 Ajax补充之serialize 03 分页功能介绍 04 分页基础知识 05 Django内置分页 06 扩展Django内置分页 07 自定义分页组件 08 DjangoForm组件初识 第59章 01 Form组件之生成HTML标签 02 Form组件之详解字段 03 Form组件之常用标签示例 04 Form组件之动态绑定数据 第60章 Django序列化共6课 第61章 01 上节内容回顾 02 上传文件 03 制作上传按钮 04 Form组件上传文件 05 上传相关内容梳理 06 Model操作知识提问 07 Model操作概述 08 Model字段 09 Model连表字段参数详解 10 Model自定义多对多第三张表 11 强插一道面试题 12 Model连表操作梳理 13 多对多自关联 14 Model操作补充 15 再插两道JavaScript面试题 16 Model操作之select_related以及prefetch_related 17 Model操作知识梳理以及补充 18 JavaScript两道面试题讲解 第62章 01 今日内容概要 02 创建基本项目 03 XMLHttpRequest对象发送请求 04 XMLHttpRequest对象发送POST请求 05 Iframe伪造Ajax请求 06 Iframe伪造回调函数 07 上述内容整理 08 FormData对象以及Ajax文件上传 09 Iframe文件上传 10 Iframe上传文件 11 JSONP实现AJax跨域 12 内容整理以及CORS简单介绍 第63章 01 项目以及学习介绍 02 企业官网示例功能介绍 03 企业官网示例以及数据库表结构 04 企业官网示例作业要求 05 Toando源码基本基本介绍 第64章 01 组合搜索 02 瀑布流 03 瀑布流作业 第65章 01 今日内容概要 02 瀑布流作业讲解 03 保障系统需求分析 04 保障系统数据库设计 05 保障系统目录结构规定 06 阶段任务安排 第66章 01 保障系统主页功能讲解 02 保障系统主页分类和分页的实现 03 阶段作业:保障系统登录注册 第67章 01 保障系统之登录注册功能讲解 02 保障系统之网站验证码 03 保障系统之一个月免登陆 04 任务安排 第68章 01 保障系统之个人知识库主页 02 保障系统之个人知识库内容筛选 03 保障系统之文章最终页 04 保障系统之KindEditor基本使用 05 保障系统之下节预告 06 今日作业以及下节预告 第69章 01 后台管理功能介绍 02 后台管理页面布局 03 今日作业以及下节预告 第70章 01 后台管理之创建报障单 02 后台管理之处理报障单 03 后台管理之画图流程 04 后台管理之Highchart统计保障单 第71章 01 权限管理要求 02 权限管理数据库表设计 03 填充权限数据 04 作业:获取权限以及菜单信息 05 作业思路讲解 06 权限管理之获取用户权限信息 07 权限管理之获取用户菜单信息 08 权限管理之用户权限挂靠到菜单上 09 权限管理之处理菜单等级关系 第72章 01 上节内容概要以及标记应该显示的菜单 02 权限管理之递归生成多级菜单 03 权限管理之标记当前以及激活菜单 04 权限管理之基本使用 05 权限管理之封装权限组件 06 下节预告 第73章 01 CMDB项目介绍 02 CMDB开发背景 03 CMDB开发目的 04 CMDB资产采集方式之agent 05 CMDB资产采集方式之ssh 06 CMDB资产采集方式之saltstack 07 CMDB资产采集方式之puppet 08 CMDB资产采集方式比较 09 CMDB资产采集内容梳理 10 CMDB资产采集功能实现之agent 11 CMDB资产采集功能实现之ssh 12 CMDB资产采集功能实现之saltstack 13 CMDB资产采集插件开发 14 内容回顾之面向对象继承 15 作业:基于配置文件加载插件 第74章 01 CMDB项目上节作业讲解 02 CMDB项目采集资产数据 03 知识拾遗之线程进程池 04 CMDB项目采集资产之日志记录 05 自定义JSON序列化类型 06 本节作业 第75章 01 基于requests模块汇报资产数据 02 基于requests模块汇报API验证 03 CMDB项目示例之API验证流程 04 CMDB项目之数据库表结构 05 CMDB项目之资产汇报并持久化 06 CMDB项目之持久化资产流程 07 本周作业 第76章 01 CMDB项目CURD组件之配置文件构造 02 CMDB项目CURD组件之神奇的单@符号 03 CMDB项目CURD组件之神奇的双@符号 04 CMDB项目CURD组件之自定义td属性 05 下节内容预习 第77章 01 CMDB项目CURD组件之进入编辑模式 02 CMDB项目CURD组件之全选取消反选和编辑模式 03 CMDB项目CURD组件之内容截图 04 CMDB项目CURD组件之进入编辑模式详细 05 CMDB项目CURD组件之属性中应用神奇的单@符号 06 CMDB项目CURD组件之退出编辑模式 07 CMDB项目CURD组件之更新数据 08 CMDB项目CURD组件之基于jQuery扩展封装组件 09 CMDB项目CURD组件之10分钟搞定页面基本操作 10 CMDB项目CURD组件之分页功能 11 CMDB项目CURD组件之搜索功能介绍 12 CMDB项目总结 第78章 01 剩余项目概览 02 CRM项目需求分析 04 CRM项目需求分析及架构设计 05 CRM项目表结构设计 第79章 01 CRM项目实战-前端页面布局 02 CRM项目实战-登录页面开发 04 CRM项目实战-动态菜单设计 05 CRM项目实战-kingadmin开发设计 06 CRM项目实战-kingadmin自动发现及注册功能开发 07 CRM项目实战-kingadmin model obj list页面开发 08 CRM项目实战-kingadmin 根据list_display配置生成数据列表 09 CRM项目实战-kingadmin 多条件过滤功能开发 第80章 01 课前鸡汤 02 分页功能开发 03 分页功能优化 04 排序功能开发 05 分页 排序 筛选组合使用 06 搜索功能开发 第81章 01 CRM项目实战 - 动态modelform的实现 02 CRM项目实战 - 动态modelform 增加自定义样式 03 CRM项目实战 - 实现任意表的增删改查 04 CRM项目实战 - 只读字段的处理 05 CRM项目实战 - filter_horizontal的实现 第82章 01 CRM项目实战 - kingadmin m2m filter_horizontal优化 02 CRM项目实战 - kingadmin 对象删除功能开发 03 CRM项目实战 - kingadmin+admin+action功能开发 04 CRM项目实战 - csrf+token验证原理 第83章 01 CRM项目-kingadmin批量删除 02 CRM项目-学员报名流程开发 03 CRM项目-本次作业需求 第84章 01 SSO介绍 02 用户自定义认证 03 万能通用权限框架设计 04 万能通用权限框架设计-自定义权限钩子实现 第85章 01 堡垒机项目实战-需求讨论 02 堡垒机项目实战-表结构设计 第86章 01 堡垒机项目实战-用户交互程序开发 02 堡垒机项目实战-通过paramiko记录ssh会话记录 03 堡垒机项目实战-把parmaiko代码嵌入用户交互程序 04 堡垒机项目实战-在数据库里记录用户会话数据 第87章 01 堡垒机项目实战-前端模板的选择 02 堡垒机项目实战-web ssh的使用 03 堡垒机项目实战-批量任务的思路 04 堡垒机项目实战-批量任务的前端页面开发 第88章 01 堡垒机实战-批量命令后端开发 02 堡垒机实战-批量命令前端获取执行结果 03 堡垒机实战-批量文件分发 第89章 01 版本管理工具介绍 02 git基本使用 03 github使用 04 git 分支开发流程 05 restful规范介绍 06 restful api设计指南 第90章 01 rabbitmq 消息安全接收 02 rabbitmq 消息持久化 03 rabbitmq 消息订阅发布 04 rabbitmq 消息组播 05 rabbitmq 消息RPC 第91章 01 阶段课程安排介绍 02 爬虫介绍 03 初识爬虫之采集汽车资讯信息 04 requests和beautfulsoup模块基本使用 05 示例:自动登录抽屉新热榜 06 requests模块详细介绍 第92章 01 BeautifulSoup模块详细介绍 02 示例:自动登录知乎 03 示例:自动登录博客园 04 作业之开发Web微信 第93章 01 开发Web微信前戏 02 Web微信流程介绍 03 Web微信之用户扫码 04 Web微信之用户信息初始化 05 Web微信开发总结 第94章 01 Web微信之获取用户初始化信息并展示 02 Web微信之发送微信消息 03 Web微信之获取用户消息 04 Web微信开发总结 第95章 01 多线程实现并发请求 02 多进程实现并发请求 03 异步IO模块的使用 04 自定义异步IO模块前戏 05 自定义异步IO模块开发 06 自定义异步IO模块开发 第96章 01 Scrapy爬虫介绍 02 抽屉示例:初识Scrapy并获取新闻标题 03 抽屉示例:递归获取所有页码 04 抽屉示例:基于pipeline实现标题和URL持久化 第97章 01 Scrapy上节回顾 02 去除重复URL 03 pipeline补充 04 自动登录抽屉并点赞 05 scrapy框架扩展 06 配置文件 第98章 01 Scrapy配置之自动限速以及缓存 02 Scrapy之默认代理以及扩展代理 03 Scrapy之自定义Https证书 04 Scrapy配置之下载中间件 05 Scrapy配置之爬虫中间件 06 Scrapy配置之自定义scrapy命令 07 Scrapy源码流程简述 第99章 01 今日内容概要 02 Scrapy源码剖析前戏之Twisted使用 03 Scrapy源码剖析之自定义Low版框架 04 Scrapy源码剖析之自定义TinyScrapy框架 05 Scrapy源码剖析流程解析 第100章 01 Tornado学习概要 02 Tornado基本操作 03 Tornado自定义Session知识前戏 04 Tornado自定义Session 第101章 01 WebSocket介绍 02 WebSocket握手过程分析 03 基于Python实现WebSocket握手过程 04 位运算补充 05 WebSocket数据解析过程 06 基于Tornado的WebSocket实现聊天室 第102章 01 今日内容复习目标 02 异步非阻塞框架介绍 03 Tornado异步非阻塞功能使用 04 自定义Web框架(同步) 05 自定义Web框架支持同步和异步非阻塞 06 复习计划 第103章 01 缓存数据库介绍 02 redis string操作 03 redis hash 操作 04 redis list操作 05 redis 集合操作 06 redis 其他常用命令 07 redis 发布订阅 08 celery介绍和基本使用 09 celery在项目中使用 10 celery定时任务 11 celery在项目中使用 12 celery 在django中实现定时任务 第104章 就业指导 01 简历制作 02 如何面试 03 最后的鸡汤 04 Nginx+uWSGI+Django部署

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 4
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值