网络编程与IO模型

网络编程

TCP

创建服务端

创建一个socket文件描述符

 int socket(int domain, int type, int protocol);
//参数1:协议族(ipv4是AF_INET等)
//参数2:套接字类型(TCP->流式套接字,UDP->数据报套接字)
//参数3:0
//返回值:套接字描述符,-1

绑定套接字(ip+port)

 int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
 //参数1:socket描述符
 //参数2: man 7 ip 强转 (struct sockaddr *)
 //参数3:大小
 //返回值:0,-1

//参数2: man 7 ip

           struct sockaddr_in {
               sa_family_t    sin_family; /* address family: AF_INET */
               in_port_t      sin_port;   /* port in network byte order */
               struct in_addr sin_addr;   /* internet address */
           };
           //协议族 ipv4
           //端口号 转成网络字节序 htons()
           //ip地址 
           /* Internet address. */
           struct in_addr {
               uint32_t       s_addr;     /* address in network byte order */
           };
           //ip转换函数
           inet_addr()//把192.xxx转换为32位的(人看的 ->机器)
           inet_ntoa()//(机器 ->人)

启动监听

  int listen(int sockfd, int backlog);
  //参数1: socket描述符
  //参数2: 监听队列最大长度
  //返回值;0,-1

接收客户端请求(阻塞接口)

 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
 //参数1:socket描述符
 //参数2:客户端网络结构体的指针(对方地址)
 //参数3:客户端网络结构体的大小指针
 //返回值;已连接新的客户端的文件描述符,-1

示例代码

#include <stdio.h>

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#include <stdlib.h>

int main(int argc, char const *argv[])
{
    int sockfd; // 定义一个套接字的描述符

    if (argc < 3)
    {
        printf("usage : %s <ip> <port>\n", argv[0]);
        return -1;
    }

    // 创建一个socket文件描述符
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sock err\n");
        return -1;
    }

    // 绑定套接字(ip+port)
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2])); // 端口号   htons (主机序->网络序)
#if 0
    addr.sin_addr.s_addr = inet_addr(argv[1]);
#else
    addr.sin_addr.s_addr = INADDR_ANY; // 自动绑定本机所有网卡地址
#endif

    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    // 启动监听
    if (listen(sockfd, 5) < 0)
    {
        perror("lisetn err\n");
        return -1;
    }
    // 接收客户端请求(阻塞接口)
    printf("wait client connect\n");
    int clifd; // 返回已连接新的客户端的文件描述符,后续读写都要用这个新的进行
//客户端地址(传入的,不赋值)
    struct sockaddr_in cliaddr;

    int cli_addrlen = sizeof(cliaddr);

    clifd = accept(sockfd,  (struct sockaddr*)&cliaddr, &cli_addrlen);
    if (clifd < 0)
    {
        perror("accept err\n");
        return -1;
    }
    else
    {
        printf("new connect successful\n");
        printf ("ip = %s, port = %d\n",inet_ntoa(cliaddr.sin_addr),ntohs(cliaddr.sin_port));
    }
// 读写
#define N 64
    int ret;
    char buf[N] = {0};
    while (1)
    {
        ret = recv(clifd, buf, N, 0);
        if (ret < 0)
        {
            perror("recv err\n"); // 接收失败,继续
            continue;
        }
        else if (ret > 0)
        {
            printf("recive data = %s\n", buf); // 打印接收的字符
        }
        else // ret =0
        {
            printf("peer quit\n"); // 对方挂掉,退出
            break;
        }
    }
    close(clifd);
    close(sockfd);

    return 0;
}

创建客户端

在服务器中,0.0.0.0指的是本机上的所有IPV4地址,如果一个主机有两个IP地址,192.168.1.1 和 10.1.2.1,并且该主机上的一个服务监听的地址是0.0.0.0,那么通过两个ip地址都能够访问该服务。

127.0.0.1属于{127,}集合中的一个,而所有网络号为127的地址都被称之为回环地址,所以回环地址!=127.0.0.1,它们是包含关系,即回环地址包含127.0.0.1。

回环地址:所有发往该类地址的数据包都应该被loop back。

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
//参数1:
//参数2:
//参数3:
//返回值0,-1

代码

#include <stdio.h>

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{

    // 创建套接字
    int sockfd; // 定义一个套接字的描述符

    if (argc < 3)
    {
        printf("usage : %s <ip> <port>\n", argv[0]);
        return -1;
    }

    // 创建一个socket文件描述符
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sock err\n");
        return -1;
    }
    // 请求连接
    // 指定服务器地址
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2])); // 端口号   htons (主机序->网络序)
    addr.sin_addr.s_addr = inet_addr(argv[1]);

    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("connect err\n");
        return -1;
    }
    printf("connect sucessful\n");

#define N 64
    char buf[N] = {0};
    while (1)
    {
        gets(buf);
        if (strcmp(buf, "quit") == 0)
        {
            break;
        }
        send(sockfd, buf, N, 0);
    }
    close(sockfd);

    return 0;
}

FTP传输文件

客户端:连接成功 打开文件 循环读写文件 文件数据帧发送到服务器 当读完退出循环 关闭

服务端:收到客户端连接 打开文件(清空 创建) 循环写入数据帧 recv() == 0//客户端退出

客户端
#include <stdio.h>

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#include <stdlib.h>
#include <string.h>

#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>

int main(int argc, char const *argv[])
{

    // 创建套接字
    int sockfd; // 定义一个套接字的描述符

    if (argc < 4)
    {
        printf("usage : %s <ip> <port> <file>\n", argv[0]);
        return -1;
    }

    // 创建一个socket文件描述符
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sock err\n");
        return -1;
    }
    
    // 文件的创建打开
    int fd;
    fd = open(argv[3], O_RDONLY);
    if (fd < 0)
    {
        perror("open err\n");
        return -1;
    }

    // 请求连接
    // 指定服务器地址
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2])); // 端口号   htons (主机序->网络序)
    addr.sin_addr.s_addr = inet_addr(argv[1]);

    if (connect(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("connect err\n");
        return -1;
    }
    printf("connect sucessful\n");

#define N 64
    char buf[N] = {0};
    while (1)
    {
        // 读取文件
        int ret;
        ret = read(fd, buf, N);
        if (ret < 0)
        {
            perror("read err\n");
            break;
        }
        else if (ret > 0)
        {
            send(sockfd, buf, ret, 0); // 按照实际读到的字节数发送
        }
        else
            //文件读取完成,退出循环
            {
                break;
            }

        // gets(buf);
        // if (strcmp(buf, "quit") == 0)
        // {
        //     break;
        // }
    }
    close(sockfd);

    return 0;
}

服务端
#include <stdio.h>

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(int argc, char const *argv[])
{
    int sockfd; // 定义一个套接字的描述符

    if (argc < 3)
    {
        printf("usage : %s <ip> <port>\n", argv[0]);
        return -1;
    }

    // 创建一个socket文件描述符
    sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if (sockfd < 0)
    {
        perror("sock err\n");
        return -1;
    }

    // 绑定套接字(ip+port)
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2])); // 端口号   htons (主机序->网络序)
#if 0
    addr.sin_addr.s_addr = inet_addr(argv[1]);
#else
    addr.sin_addr.s_addr = INADDR_ANY; // 自动绑定本机所有网卡地址
#endif

    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    // 启动监听
    if (listen(sockfd, 5) < 0)
    {
        perror("lisetn err\n");
        return -1;
    }
    // 接收客户端请求(阻塞接口)
    printf("wait client connect\n");
    int clifd; // 返回已连接新的客户端的文件描述符,后续读写都要用这个新的进行
               // 客户端地址(传入的,不赋值)
    struct sockaddr_in cliaddr;

    int cli_addrlen = sizeof(cliaddr);

    clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &cli_addrlen);
    if (clifd < 0)
    {
        perror("accept err\n");
        return -1;
    }
    else
    {
        printf("new connect successful\n");
        printf("ip = %s, port = %d\n", inet_ntoa(cliaddr.sin_addr), ntohs(cliaddr.sin_port));
    }

    int fd;//没有则创建
    fd = open("new.txt", O_WRONLY | O_CREAT | O_TRUNC,0666);
    if (fd < 0)
    {
        perror("open err\n");
        return -1;
    }
// 读写
#define N 64
    int ret;
    char buf[N] = {0};
    while (1)
    {
        ret = recv(clifd, buf, N, 0);
        if (ret < 0)
        {
            perror("recv err\n"); // 接收失败,继续
            continue;
        }
        else if (ret > 0)
        {
            write(fd ,buf,ret);
            // printf("recive data = %s\n", buf); // 打印接收的字符
        }
        else // ret =0
        {
            printf("file recv sucessful\n"); // 对方挂掉,退出
            break;
        }
    }
    close(clifd);
    close(sockfd);
    return 0;
}

UDP

区别

同: 同属传输层协议

tcp 面向连接 可靠 (流式套接字)

udp 无连接 不可靠 (数据报套接字)

client.c

#include <stdio.h>

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{

    // 创建套接字
    int sockfd; // 定义一个套接字的描述符

    if (argc < 3)
    {
        printf("usage : %s <ip> <port>\n", argv[0]);
        return -1;
    }

    // 创建一个socket文件描述符
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("sock err\n");
        return -1;
    }
    // 请求连接
    // 指定服务器地址
    struct sockaddr_in srvaddr;
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(atoi(argv[2])); // 端口号   htons (主机序->网络序)
    srvaddr.sin_addr.s_addr = inet_addr(argv[1]);

    int addrlen = sizeof(srvaddr);
    if (connect(sockfd, (struct sockaddr *)&srvaddr, sizeof(srvaddr)) < 0)
    {
        perror("connect err\n");
        return -1;
    }
    printf("connect sucessful\n");

    ssize_t len;
#define N 64
    char buf[N] = {0};
    while (1)
    {
        memset(buf, N, 0);
        fgets(buf, N, stdin);
        sendto(sockfd, buf, N, 0, (struct sockaddr *)&srvaddr, addrlen);
        len = recvfrom(sockfd, buf, N, 0, NULL, NULL);
        if (len > 0)
        {
            printf("recv data = %s\n", buf);
        }
    }
    return 0;
}

server.c

#include <stdio.h>

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include <netinet/in.h>
#include <netinet/ip.h> /* superset of previous */

#include <netinet/in.h>
#include <arpa/inet.h>

#include <unistd.h>

#include <stdlib.h>
#include <string.h>

int main(int argc, char const *argv[])
{
    int sockfd; // 定义一个套接字的描述符

    if (argc < 3)
    {
        printf("usage : %s <ip> <port>\n", argv[0]);
        return -1;
    }

    // 创建一个socket文件描述符
    sockfd = socket(AF_INET, SOCK_DGRAM, 0);
    if (sockfd < 0)
    {
        perror("sock err\n");
        return -1;
    }

    // 绑定套接字(ip+port)
    struct sockaddr_in addr;
    addr.sin_family = AF_INET;
    addr.sin_port = htons(atoi(argv[2])); // 端口号   htons (主机序->网络序)
#if 0
    addr.sin_addr.s_addr = inet_addr(argv[1]);
#else
    addr.sin_addr.s_addr = INADDR_ANY; // 自动绑定本机所有网卡地址
#endif
    int addrlen = sizeof(addr);
    if (bind(sockfd, (struct sockaddr *)&addr, sizeof(addr)) < 0)
    {
        perror("bind err");
        return -1;
    }
    printf("wait connect\n");
    // 接收
    ssize_t len;
#define N 64
    char buf[N] = {0};
    struct sockaddr_in cliaddr; // 接收客户端的地址
    while (1)
    {
        memset(buf,N,0);
        len = recvfrom(sockfd, buf, N, 0, (struct sockaddr *)&cliaddr, &addrlen);
        if (len > 0)
        {
            printf("recv data = %s\n", buf);
            sendto(sockfd, buf, N, 0, (struct sockaddr *)&cliaddr, addrlen); // 原样返回
        }
    }

    return 0;
}

查看端口占用 然后kill掉

netstat -ntlp

IO模型

阻塞模型:不占用CPU,但是不能同时处理多个设备
非阻塞模型:能同时处理多个设备,但是非常耗费CPU
异步IO/信号驱动IO:完全解放了主线程,有数据会主动通知,异步调用处理方法,但是也不
能同时处理多个设备。
IO多路复用:既能同时处理多个设备,又不占用CPU

以上这4种方法没有优劣之分,在不同的场合使用不同的方法。

阻塞不占用cpu资源

    while (1)
        {
            memset(buf, N, 0);
            gets(buf);//不占cpu
            printf("buf = %s\n", buf);
        }

修改

#if 1
    int flag;                  // 文件状态标志
    flag = fcntl(fd, F_GETFL); // 读
    flag |= O_NONBLOCK;        //   改 O_NONBLOCK = 0x00004000(非阻塞)
    fcntl(fd, F_SETFL, flag);  // 写
#endif

置位 清零

1那个位
|= 000001
& ~(00001)

非阻塞

#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>

#define N 64
char buf[N] = {0};

int main(int argc, char const *argv[])
{
    //1 非阻塞 0 阻塞
#if 1
    int flag;                  // 文件状态标志
    flag = fcntl(0, F_GETFL); // 读 fd = 0;标准输入
    flag |= O_NONBLOCK;        //   改 O_NONBLOCK = 0x00004000(非阻塞)
    fcntl(0, F_SETFL, flag);  // 写
#endif
    while (1)
    {
        memset(buf, N, 0);
        gets(buf);
        printf("buf = %s\n", buf);
        sleep(1);
    }

    return 0;
}

异步IO模型

以16进制打印设备数据 hexdump

hexdump /dev/input/mouse0

查看信号

kill -l

获取鼠标数据长度(异步 占用cpu不高)

#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

static int fd;
#define N 64

// 调用这个方法的时候,一定是SIGIO信号被触发,一定有数据
void handler(int signum)
{
    char buf[N] = {0};
    int ret = read(fd, buf, N);
    if (ret < 0)
    {
        perror("read err\n");
        return;
    }
    else
    {
        printf("len = %d\n", ret);
    }
}
// 从鼠标获取数据,打印
int main(int argc, char const *argv[])
{

    fd = open("/dev/input/mouse0", O_RDONLY);
    if (fd < 0)
    {
        perror("open err\n");
        return -1;
    }

#if 1
    // 开启异步通知
    fcntl(fd, F_SETOWN, getpid());
    // 使能异步通知
    int flag;
    flag = fcntl(fd, F_GETFL);
    flag |= FASYNC;
    fcntl(fd, F_SETFL, flag);
    signal(SIGIO, handler);
#endif

    while (1)
    {
        sleep(1);
    }

    return 0;
}

IO多路复用

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

#include <stdio.h>
#include <string.h>

#include <unistd.h>
#include <fcntl.h>
#include <signal.h>

#include <sys/select.h>

/**
 * 同时监听标准输入、鼠标
 * 如果两个设备都没有数据,那么进程睡眠
 * 如果任意一个有数据,那么读取其数据
 * */
#define MOUSE "/dev/input/mouse0"
#define N 64
char buf[N] = {0};

int main(int argc, char const *argv[])
{
    // 打开鼠标设备
    int fd;
    fd = open(MOUSE, O_RDONLY);
    if (fd < 0)
    {
        perror("open err\n");
        return -1;
    }
    // 定义一张描述符表
    fd_set rdfds;

    int maxfd = fd; // fd一定比标准输入大
    int ret;
    while (1)
    {
        //每次会修改表,所以在select前需要重新设置
        // 设置描述符表,增加标准输入与鼠标的监听
        FD_ZERO(&rdfds);    // 清空表
        FD_SET(0, &rdfds);  // 把标准输入增加到表,0是标准输入
        FD_SET(fd, &rdfds); // 鼠标
        // 如果监听设备无数据,select会阻塞等待
        ret = select(maxfd + 1, &rdfds, NULL, NULL, NULL);
        if (ret < 0)
        {
            perror("select err\n");
            continue;
        }
        else if (ret > 0) // 某一个设备有数据了
        {
            if (FD_ISSET(0, &rdfds)) // 如果是标准输入
            {
                gets(buf); // 这里一定不会阻塞,因为一定有数据了
                printf("buf = %s\n", buf);
            }
            if (FD_ISSET(fd, &rdfds)) // 如果是鼠标
            {
                int len = read(fd, buf, N);
                printf("MOUSE len = %d\n", len);
            }
        }
        else
        {
        }
    }
    return 0;
}

select、poll和epoll
select
一个进程最多只能监听1024个文件描述符(千级别)
select每次会清空表,每次都需要拷贝用户空间的表到内核空间,效率低
poll
优化文件描述符个数的限制根据poll函数第一个函数的参数来定,如果监听的事件为1个,
则结构体数组的大小为1,如果想监听100个,那么这个结构体数组的大小就为100,由程序员
自己来决定)
·poll不需要重新构造文件描述符表,只需要从用户空间向内核空间拷贝一次数据即可

epoll(了解即可)epoll详解
监听的最大的文件描述符没有个数限制
(理论上,取决与你自己的系统
·异步I/O,Epoll当有事件产生被唤醒之后,文件描述符主动调用callback(回调函数)函数直接
拿到唤醒的文件描述符,不需要轮询,效率极高

理论

网络模型
在这里插入图片描述在这里插入图片描述
区别
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

字节序

小端序(little-endian)·低序字节存储在低地址
大端序(big-endian)·高序字节存储在低地址
网络中传输的数据必须使用网络字节序,即大端字节序
写一个函数,判断当前主机的字节序?
在这里插入图片描述
主机字节序到网络字节序
在这里插入图片描述
网络字节序到主机字节序
在这里插入图片描述
IP地址转换
在这里插入图片描述
在这里插入图片描述

TCP编程

在这里插入图片描述

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

UDP

在这里插入图片描述

三次握手和四次挥手

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

网络超时检测

在这里插入图片描述
利用select/poll超时特性
如果使用select模型,最后一个参数可以直接实现超时检测动作。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值