Linux网络编程

前言

  • 这篇文章承接Linux系统编程,简单记录了一下,顺便把实现的代码都贴在文章中了。
  • 服务器的底层实现,好像Get了一丢丢。
  • 加油,少年!

# 网络基础简介

  • 学完本节课程后,同学将掌握网络结构的基本概念,如何查看IP,DHCP协议原理,ARP请求、1MP请求分析,NAT网关原理,动态路算法的相关知识
  1. 网络结构
  2. 查看IP
  3. DHCP
  4. ARP请求、IGMP请求
  5. NAT网关
  6. 动态路由算法

01. 网络结构

国家或全球ISP
移动网络
本地ISP
家庭网络

路由器是跨网段的,交换机是同一个网段的
hub是扩展节点,与其他都连接上。
C类局域网,只有0~254个端口可以使用,容易打架。

云环境?

数字用户线路DSL

02. 查看IP

03. DHCP

Linux网络编程_第1张图片
Linux网络编程_第2张图片
在这里插入图片描述
Linux网络编程_第3张图片
Linux网络编程_第4张图片
Linux网络编程_第5张图片

在这里插入图片描述
Linux网络编程_第6张图片

协议

Linux网络编程_第7张图片
Linux网络编程_第8张图片
物联网-集线器:就是hub
ppp:宽带拨号

物理层:如何在宿舍里自己组网玩联机游戏

Linux网络编程_第9张图片

数据链路层

Linux网络编程_第10张图片
解决第一个问题就牵扯到第二层的网络包格式。对于以太网,第二层的最开始,就是目标的MAC地址和源的MAC地址。
Linux网络编程_第11张图片

04. ARP请求、IGMP请求

在这里插入图片描述
Linux网络编程_第12张图片
Linux网络编程_第13张图片
交换机主要是记住Mac地址用来减少不必要的广播
Linux网络编程_第14张图片

ICMP与ping:网络层

Linux网络编程_第15张图片
Linux网络编程_第16张图片
Linux网络编程_第17张图片

05. NAT网关

Linux网络编程_第18张图片
Linux网络编程_第19张图片
Linux网络编程_第20张图片
Linux网络编程_第21张图片

06. 动态路由算法

  • 网络中的「动态路由算法」,你了解吗?

# 网络编程基础

学完本节课程后,同学将掌握UDP协议和TCP协议的基本概念、特点和使用常见,掌握TCP协议中的流量控制、丢包问题和拥塞问题,掌握编写简单服务器/客户端程序的的基础知识

01 UDP协议

UDP(User Datagram Protocol:用户数据报协议)
UDP相对于TCP来说,是一个非常简单的协议

  • UDP协议简介
    Linux网络编程_第22张图片
    Linux网络编程_第23张图片

02 TCP协议

Linux网络编程_第24张图片

  • 源端口号和目标端口号是不可少的,这一点和UDP是一样的。如果没有这两个端口数据就不知道应该发给哪个应用接下来是包的序号。为什么要给包编号呢?
  • 当然是为了解决乱序的问题。不编好号怎么确认哪个应该先来,哪个应该后到呢。编号是为了解决乱序问题。既然是社会老司机,做事当然要稳重,一件件来,面临再复杂的情况,也临危不乱
  • 还应该有的就是确认序号。发出去的包应该有确认,要不然我怎么知道对方有没有收到呢?如果没有收到就应该重新发送,直到送达。这个可以解决不丢包的问题。作为老司机,做事当然要靠谱,答应了就要做到,暂时做不到也要有个回复
  • 接下来有一些状态位。例如SYN是发起一个连接,ACK是回复,RST是重新连接,FIN是结束连接等。TCP是面向连接的,因而双方要维护连接的状态,这些带状态位的包的发送,会引起双方的状态变更。

Linux网络编程_第25张图片

三次握手

首先要先建立一个连接,所以我们先来看连接维护问题。
TCP的连接建立,我们常常称为三次握手。

  • A:您好,我是A.
  • B:您好A,我是B.
  • A:您好B.
    首先,为什么要三次,而不是两次?按说两个人打招呼,一来一回就可以了啊?为
    了可靠,为什么不是四次?

如果是两次的话,只能确定A说的B能听到,但是B不能确定B说的A听到了吗。

03 套接字编程

网络字节序

Linux网络编程_第26张图片

test.c:测试大小端

#include

int main(){
    
    int a = 321;
    char *p = (char *)&a;


    printf("addr = %p, data = %#x\n", p, *p);
    printf("addr = %p, data = %#x\n", p + 1, *(p + 1));
    printf("addr = %p, data = %#x\n", p + 2, p[2]);
    printf("addr = %p, data = %#x\n", p + 3, p[3]);

    return 0;
}

  • 运行结果:你会发现,低地址存储的是低字节,这就是小端模式;
./a.out                                                                       [0]
addr = 0x7ffff8f0762c, data = 0x41
addr = 0x7ffff8f0762d, data = 0x1
addr = 0x7ffff8f0762e, data = 0
addr = 0x7ffff8f0762f, data = 0

test.c:网络字节序是大端模式

#include
#include

int main(){
    
    int a = 0x12345678;
    printf("%#x\n", a);
    int b = htonl(a);
    printf("%#x\n", b);


    /*
    int a = 321;
    char *p = (char *)&a;

    
    printf("addr = %p, data = %#x\n", p, *p);
    printf("addr = %p, data = %#x\n", p + 1, *(p + 1));
    printf("addr = %p, data = %#x\n", p + 2, p[2]);
    printf("addr = %p, data = %#x\n", p + 3, p[3]);
    */
    return 0;
}

  • 运行结果:
@DESKTOP-NA5RGM7 net % ./a.out                                       [0]
0x12345678
0x78563412

socket地址的数据类型

Linux网络编程_第27张图片

三次握手,四次挥手(传输层)

Linux网络编程_第28张图片

基于TCP协议的网络程序

Linux网络编程_第29张图片

server.c

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define SERV_PORT 8000
#define MAXLINE 80

int main() {

    struct sockaddr_in serveraddr, cliaddr; 
    int listenfd, connfd;  
    socklen_t cliaddr_len;

    char buf[MAXLINE];
    char str[INET_ADDRSTRLEN];
    int n, i;

    listenfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&serveraddr, sizeof(serveraddr));
   
    //服务器 ip地址:端口初始化
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    serveraddr.sin_addr.s_addr = htons(INADDR_ANY);

    bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    listen(listenfd, 3);

    printf("Accepting connections...\n");
    while(1) {
        cliaddr_len = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);

        printf("received from %s:%d\n",\
              inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
              ntohs(cliaddr.sin_port));

        n = read(connfd, buf, MAXLINE);
        for (i = 0; i < n; i++) {
            buf[i] = toupper(buf[i]);
        }

        write(connfd, buf, n);
        close(connfd);
    }

    return 0;
}

client.c

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define SERV_PORT 8000
#define MAXLINE 80

int main() {

    struct sockaddr_in serveraddr; 

    char buf[MAXLINE] = {"hello tcp"};
    char str[INET_ADDRSTRLEN];

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);

    connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));

    write(sockfd, buf, strlen(buf));

    int n = read(sockfd, buf, MAXLINE);
    printf("reponse from server:\n");
    write(1, buf, n);
    putchar(10);
    close(sockfd);

    return 0;
}

Linux网络编程_第30张图片
Linux网络编程_第31张图片

# 高并发服务器

01. 基于链队实现的线程池, TCP 服务端+客户端

server.c: 编译用gcc server.c -o server -lpthread,

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define SERV_PORT 8000
#define MAXLINE 80

#define prrexit(msg) {\
    perror(msg);\
    exit(1);\
}

typedef struct Task {
    int fd;
    struct Task *next;
} Task;

typedef struct Task_pool {
    Task *head;
    Task *tail;
    pthread_mutex_t lock;
    pthread_cond_t havetask;
} Task_pool;

Task_pool *task_pool_init() {
    Task_pool *tp = (Task_pool *)malloc(sizeof(Task_pool));
    tp->head = NULL;
    tp->tail = NULL;
    pthread_mutex_init(&tp->lock, NULL);
    pthread_cond_init(&tp->havetask, NULL);

    return tp;
}

void task_pool_push(Task_pool *tp, int fd) {
    pthread_mutex_lock(&tp->lock);

    Task *t = malloc(sizeof(Task));
    t->fd = fd;
    t->next = NULL;

    if(!tp->tail) {
        tp->head = tp->tail = t;
    } else {
        tp->tail->next = t;
        tp->tail = t;
    }
    pthread_cond_broadcast(&tp->havetask);
    pthread_mutex_unlock(&tp->lock);
}

Task task_pool_pop(Task_pool *tp) {
    pthread_mutex_lock(&tp->lock);

    while(!tp->head) {
        pthread_cond_wait(&tp->havetask, &tp->lock);
    }

    Task tmp, *k;
    k = tp->head;
    tmp = *k;
    tp->head = tp->head->next;
    if (!tp->head) 
        tp->tail = NULL;
    free(k);

    pthread_mutex_unlock(&tp->lock);
    return tmp;
} 

void task_pool_free(Task_pool *tp) {
    
    pthread_mutex_lock(&tp->lock);
    Task *p = tp->head, *k;

    while(p) {
        k = p;
        p = p->next;
        free(k);
    }
    tp->head = NULL;
    pthread_mutex_unlock(&tp->lock);
    //pthread_mutex_destory(&tp->lock);
    //pthread_cond_destory(&tp->havetask);
    free(tp);
    return ;
}

void *up_server(void *arg) {

    pthread_detach(pthread_self());

    char buf[MAXLINE];
    int n, i;
    
    Task_pool *tp = arg;
    while(1){
        Task tmp = task_pool_pop(tp);
        int connfd = tmp.fd;
        printf("get task fd = %d\n", connfd);
        while(1) {
            n = read(connfd, buf, MAXLINE);
            if (!strncmp(buf, "quit", 4))
                break;
            write(1, buf, n);

            for (i = 0; i < n; i++) {
                buf[i] = toupper(buf[i]);
            }

            write(connfd, buf, n);
        }
        printf("finish task fd = %d\n", connfd);
        close(connfd);
    }
    return (void *)0;
}

int main() {

    struct sockaddr_in serveraddr, cliaddr; 
    int listenfd, connfd;  
    socklen_t cliaddr_len;

    char str[INET_ADDRSTRLEN];
    int i;
    Task_pool *tp = task_pool_init();

    /**多线程逻辑实现
     *
    */
    pthread_t tid;
    for (i = 0; i < 4; i++) {
        pthread_create(&tid, NULL,up_server,(void *)tp);
        printf("new thread is %#lx\n", tid);

    }


    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) 
        prrexit("socket");

    bzero(&serveraddr, sizeof(serveraddr));
   
    //服务器 ip地址:端口初始化
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    serveraddr.sin_addr.s_addr = htons(INADDR_ANY);

    if (bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) {
        prrexit("bind");
    }

    if(listen(listenfd, 3))
        prrexit("listen")

    printf("Accepting connections...\n");
    while(1) {
        cliaddr_len = sizeof(cliaddr);
        connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
        if (connfd < 0) 
            prrexit("accept");

        printf("received from %s:%d\n",\
              inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
              ntohs(cliaddr.sin_port));
        
        /**多进程逻辑实现
         *
        pid_t pid = fork();
        if (pid < 0) 
            prrexit("fork");

        //父进程:等待 创建链接
        if(pid > 0) {
            close(connfd);
            while(waitpid(-1, NULL, WNOHANG) > 0) {}

            continue;
        }

        close(listenfd);
        */

        task_pool_push(tp, connfd);


    }
    task_pool_free(tp);
    return 0;
}

client.c: 编译用gcc client.c -o client

#include
#include
#include
#include
#include
#include
#include
#include
#include

#define SERV_PORT 8000
#define MAXLINE 80

int main() {

    struct sockaddr_in serveraddr; 

    char buf[MAXLINE] = {"hello tcp"};
    char str[INET_ADDRSTRLEN];

    int sockfd = socket(AF_INET, SOCK_STREAM, 0);

    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    inet_pton(AF_INET, "127.0.0.1", &serveraddr.sin_addr);

    connect(sockfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));
    
   // write(sockfd, buf, sizeof(buf));
    int n;
    while (n = read(0, buf, MAXLINE)){
        //printf("__LINE__");
        write(sockfd, buf, n);
        if(!strncmp(buf,"quit",4))
            break;
        n = read(sockfd, buf, MAXLINE);
        printf("reponse from server:\n");
        write(1, buf, n);
    }

    close(sockfd);

    return 0;
}

  • 在开启第5个客户端的时候,已经处于排队的状态了,当结束任何一个时,就立马启动下一个服务。
    Linux网络编程_第32张图片

02. 使用epoll + 线程池处理多个

  • epol I:通过注册 cal lback函数的方式,当某个文件描述符发送变化的时候,就会主动通知
  • 就是在前面的基础上加上epolll轮询机制,使得不再专门服务某个客户端;只需要轮询为连接的客户按顺序服务一下。

Linux网络编程_第33张图片

server.c:前面程序的升级版(client.c没有改动)

#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include

#define SERV_PORT 8000
#define MAXLINE 80

#define prrexit(msg) {\
    perror(msg);\
    exit(1);\
}

typedef struct Task {
    int fd;
    struct Task *next;
} Task;

typedef struct Task_pool {
    Task *head;
    Task *tail;
    pthread_mutex_t lock;
    pthread_cond_t havetask;
} Task_pool;

Task_pool *task_pool_init() {
    Task_pool *tp = (Task_pool *)malloc(sizeof(Task_pool));
    tp->head = NULL;
    tp->tail = NULL;
    pthread_mutex_init(&tp->lock, NULL);
    pthread_cond_init(&tp->havetask, NULL);

    return tp;
}

void task_pool_push(Task_pool *tp, int fd) {
    pthread_mutex_lock(&tp->lock);

    Task *t = malloc(sizeof(Task));
    t->fd = fd;
    t->next = NULL;

    if(!tp->tail) {
        tp->head = tp->tail = t;
    } else {
        tp->tail->next = t;
        tp->tail = t;
    }
    pthread_cond_broadcast(&tp->havetask);
    pthread_mutex_unlock(&tp->lock);
}

Task task_pool_pop(Task_pool *tp) {
    pthread_mutex_lock(&tp->lock);

    while(!tp->head) {
        pthread_cond_wait(&tp->havetask, &tp->lock);
    }

    Task tmp, *k;
    k = tp->head;
    tmp = *k;
    tp->head = tp->head->next;
    if (!tp->head) 
        tp->tail = NULL;
    free(k);

    pthread_mutex_unlock(&tp->lock);
    return tmp;
} 

void task_pool_free(Task_pool *tp) {
    
    pthread_mutex_lock(&tp->lock);
    Task *p = tp->head, *k;

    while(p) {
        k = p;
        p = p->next;
        free(k);
    }
    tp->head = NULL;
    pthread_mutex_unlock(&tp->lock);
    //pthread_mutex_destory(&tp->lock);
    //pthread_cond_destory(&tp->havetask);
    free(tp);
    return ;
}

void *up_server(void *arg) {

    pthread_detach(pthread_self());

    char buf[MAXLINE];
    int n, i;
    
    Task_pool *tp = arg;
    while(1){
        Task tmp = task_pool_pop(tp);
        int connfd = tmp.fd;
        printf("get task fd = %d\n", connfd);
        if (1) { //注意这里引入epoll之后只需要执行一次
            n = read(connfd, buf, MAXLINE);
            write(1, buf, n);

            for (i = 0; i < n; i++) {
                buf[i] = toupper(buf[i]);
            }

            write(connfd, buf, n);
        }
        printf("finish task fd = %d\n", connfd);
        if (!strncmp(buf, "QUIT", 4))
            close(connfd);
    }
    return (void *)0;
}

int main() {

    struct sockaddr_in serveraddr, cliaddr; 
    int listenfd, connfd;  
    socklen_t cliaddr_len;

    char str[INET_ADDRSTRLEN];
    int i;
    Task_pool *tp = task_pool_init();

    /**多线程逻辑实现
     *
    */
    pthread_t tid;
    for (i = 0; i < 4; i++) {
        pthread_create(&tid, NULL,up_server,(void *)tp);
        printf("new thread is %#lx\n", tid);

    }


    listenfd = socket(AF_INET, SOCK_STREAM, 0);
    if (listenfd < 0) 
        prrexit("socket");

    /**
     *引入epoll机制
     */
    int epfd = epoll_create(256);
    struct epoll_event ev, events[256];
    ev.events = EPOLLIN | EPOLLET;
    ev.data.fd = listenfd;
    
    epoll_ctl(epfd, EPOLL_CTL_ADD,listenfd, &ev);
    


    //服务器 ip地址:端口初始化
    bzero(&serveraddr, sizeof(serveraddr));
    serveraddr.sin_family = AF_INET;
    serveraddr.sin_port = htons(SERV_PORT);
    serveraddr.sin_addr.s_addr = htons(INADDR_ANY);

    if (bind(listenfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr))) {
        prrexit("bind");
    }

    if(listen(listenfd, 3))
        prrexit("listen")

    printf("Accepting connections...\n");
    while(1) {
       int nfds =  epoll_wait(epfd, events, 256, -1);
        for (i = 0; i < nfds; i++) {
            if (events[i].data.fd == listenfd) {
                cliaddr_len = sizeof(cliaddr);
                connfd = accept(listenfd, (struct sockaddr*)&cliaddr, &cliaddr_len);
                if (connfd < 0) 
                    prrexit("accept");

                printf("received from %s:%d\n",\
                      inet_ntop(AF_INET, &cliaddr.sin_addr, str, sizeof(str)),
                      ntohs(cliaddr.sin_port));
                
                ev.events = EPOLLIN | EPOLLET;
                ev.data.fd = connfd;
                epoll_ctl(epfd, EPOLL_CTL_ADD, connfd, &ev);
            } else if (events[i].events & EPOLLIN) {
                int clifd = events[i].data.fd;
                if (clifd < 3) 
                    continue;

                task_pool_push(tp, connfd);
            }
        }
        
        /**多进程逻辑实现
         *
        pid_t pid = fork();
        if (pid < 0) 
            prrexit("fork");

        //父进程:等待 创建链接
        if(pid > 0) {
            close(connfd);
            while(waitpid(-1, NULL, WNOHANG) > 0) {}

            continue;
        }

        close(listenfd);
        */



    }
    task_pool_free(tp);
    return 0;
}

03.简单web服务器的实现

  • 简单web服务器的实现(C++)

超文本传输协议(英文:HyperTextTransferProtocol,缩写:HTTP)是一种用于分布式、协作式和超媒体信息系统的应用层协议。HTTP是万维网的数据通信的基础。

  1. HTTP协议采用了请求/响应模型。
    Linux网络编程_第34张图片
  2. 无状态保存。
    HTTP是一种不保存状态,即无状态(stateless)协议。HTTP协议自身不对请求和响应之间的通信状态进行保存。也就是说在HTTP这个级别,协议对于发送过的请求或响应都不做持久化处理。
    Linux网络编程_第35张图片
  3. 无连接。
    无连接的含义是限制每次连接只处理一个请求。服务器处理完客户的请求,并收到客户的应答后,即断开连接。采用这种方式可以节省传输时间,并且可以提高并发性能,不能和每个用户建立长久的连接,请求一次相应一次,服务端和客户端就中断了。

HTTP请求方法

HTTP/1.1协议中共定义了八种方法(也叫“动作”)来以不同方式操作指定的资源

  • GET
    向指定的资源发出“显示”请求。
  • POST 等…

后记

  • 发现网络编程这是个大坑呀,还很深,这篇博客先记录到这。

你可能感兴趣的:(C/C++,Java/Scala,linux,网络,服务器)