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

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

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

协议

在这里插入图片描述
在这里插入图片描述
物联网-集线器:就是hub
ppp:宽带拨号

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

在这里插入图片描述

数据链路层

在这里插入图片描述
解决第一个问题就牵扯到第二层的网络包格式。对于以太网,第二层的最开始,就是目标的MAC地址和源的MAC地址。
在这里插入图片描述

04. ARP请求、IGMP请求

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
交换机主要是记住Mac地址用来减少不必要的广播
在这里插入图片描述

ICMP与ping:网络层

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

05. NAT网关

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

06. 动态路由算法

# 网络编程基础

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

01 UDP协议

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

02 TCP协议

在这里插入图片描述

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

在这里插入图片描述

三次握手

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

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

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

03 套接字编程

网络字节序

在这里插入图片描述

test.c:测试大小端
#include<stdio.h>

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<stdio.h>
#include<arpa/inet.h>

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地址的数据类型

在这里插入图片描述

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

在这里插入图片描述

基于TCP协议的网络程序

在这里插入图片描述

server.c
#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<strings.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<ctype.h>

#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<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<string.h>
#include<strings.h>
#include<arpa/inet.h>
#include<unistd.h>

#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;
}

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

# 高并发服务器

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

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

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<strings.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<pthread.h>

#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<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<string.h>
#include<strings.h>
#include<arpa/inet.h>
#include<unistd.h>

#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个客户端的时候,已经处于排队的状态了,当结束任何一个时,就立马启动下一个服务。
    在这里插入图片描述

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

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

在这里插入图片描述

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

#include<stdio.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netinet/ip.h>
#include<strings.h>
#include<unistd.h>
#include<arpa/inet.h>
#include<ctype.h>
#include<stdlib.h>
#include<string.h>
#include<sys/types.h>
#include<sys/wait.h>
#include<sys/epoll.h>
#include<pthread.h>

#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服务器的实现

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

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

HTTP请求方法

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

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

后记

  • 发现网络编程这是个大坑呀,还很深,这篇博客先记录到这。
  • 1
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

杰之行

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值