linux下的网络编程

网络编程

  • 1. 网络基础编程知识
    • 1.1网络字节序问题
    • 1.2 常用socket编程接口
      • 1.2.1 sockaddr
      • 1.2.2 ip地址转换函数
      • 1.2.4 socket()
      • 1.2.3 bind()
      • 1.2.4 listen()
      • 1.2.5 accept()
      • 1.2.6 connect()
    • 1.3 以udp为基础的客户端连接服务器的demo
    • 1.4 以udp为基础的的服务器聊天室功能demo
    • 1.5 基于TCP连接的具有线程池功能的服务器客户端demo
      • tcp_test目录
      • sing_fock_test目录
      • thread_tcp目录
      • tcpthreadpool目录
  • 网络的理论部分

1. 网络基础编程知识

1.1网络字节序问题

已知计算机的数据存储有大小端之分,网络流数据同样有大小端之分。

  • 发送主机通常将发送缓冲区中的数据按内存地址从低到高的数据顺序发出
  • 接收主机把从网络上接到的字节依次保存在缓冲区中,也是按地址从低到搞的顺序保存
  • 因此网络数据流规定:先发出的数据是低地址,后发出的数据是高地址
  • TCP/IP协议规定:网络流数据应采取大端字节序,即低地址高字节(符合人类的阅读习惯)
  • 如果当前发送主机是小端,那么需要改成大端再发送!

为了解决这个问题,使网络具有可移植性,使同样的代码在大小端机器上都能运行,需要使用下面的库函数做网络字节序和主机字节序的转换。

# include<arpa/inet.h>uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);

解释:

  • 其中h表示host,n表示network,l表示32位长整数,s表示16位短整数。
  • htonl 表示将32位的长整形数从主机字节序转化为网络字节序。例如:将IP地址转化后发送。
  • 若主机是小端字节序,这些函数将做大小端转换再返回;否则原封不动返回。

从参数和返回值可以看出,这个函数是转换整型的函数,比如说port接口转换就会用到该函数
如图:atoi函数把string转化成整形,然后交给htons转化成网络字节流的数据格式!
在这里插入图片描述

1.2 常用socket编程接口

socket API是一层抽象的网络编程接口,适用于各种底层网络协议。如IPv4、v6等。

//网络编程常用的四个接口
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include <arpa/inet.h>// 创建 socket 文件描述符(TCP/UDP, 客户端+服务器)
int socket(int domain, int type, int protocol);//绑定端口号(TCP/UDP, 服务器)
int bind(int socket, const struct sockaddr *address, socklen_t address_len);// 开始监听socket (TCP,服务器)
int listen(int socket, int backlog);//接受请求(TCP,服务器)
int accept(int socket, struct sockaddr* address, socklen_t* addrlen);//建立连接(TCP,客户端)
int connect(int sockfd, const struct sockaddr* addr, socklen_t addrlen);

1.2.1 sockaddr

sockaddr可以认为是存放,将要访问的服务器的ip地址和端口号的结构体。

因为各种网络协议地址格式并不同,所以为了适配格式,产生了sockaddr(通用的地址结构)。
以bind为例(accept、connect都一样),AF_INET就指定了将要通信的地址类型,所以再传入sockaddr之后,程序会根据socket类型自动转化!
这个相当于c语言的多态。(调用同一个函数,会有不同的效果!)

在这里插入图片描述

  • IPv4和IPv6的地址格式定义在netinet/in.h中,IPv4地址用sockaddr_in结构体表示,包括16位地址类型,16位端口号地址和32位IP地址。
  • IPv4和IPv6地址类型分别定义为常数AF_INET、AF_INET6。这样,只要取得某种sockaddr结构体的首地址,不需要具体知道是哪种类型的sockaddr结构体,就可以根据16位类型字段确定结构体中的内容。
  • socket API可以都用struct sockaddr* 类型表示,在使用的时候需要强制转化成sockaddr_in;好处就是增加了程序的通用性。
    在这里插入图片描述
    下面是v4和v6的sockaddr地址结构:
    在这里插入图片描述
    在这里插入图片描述

1.2.2 ip地址转换函数

功能:实现点分十进制字符串和无符号32位整数之间相互转化!
在这里插入图片描述

inet_addr()函数功能介绍:
在这里插入图片描述

在1.2.1这一节我们发现,ipv4其实是无符号整数,然而我们在访问ip地址时,使用的是点分十进制的方式。这就要求我们把点分十进制的字符串转化成无符号整数。
比如:ip = “192.168.1.1” ->xxxxxxxx …xxxxxxxx 这种形式, 我们可以使用atoi这种函数一个一个转化。
但是可以使用inet_addr()接口,可以将点分十进制直接转化。
如下图所示:
在这里插入图片描述


inet_aton()函数介绍:将ascii码形式的点分十进制转化成网络需要的无符号数。
在这里插入图片描述

void func(){char* _ip="192.168.1.1";struct sockaddr_in local;inet_aton(_ip, &(local.sin_addr));std::cout<<"aton转化前_ip: "<<_ip<<std::endl;std::cout<<"aton转化后无符号整数"<<local.sin_addr.s_addr<<std::endl;}//aton转化前_ip: 192.168.1.1//aton转化后无符号整数16885952

inet_ntoa()函数介绍:将网络的无符号数转化成点分十进制:
在这里插入图片描述

int main()
{// initServer();struct sockaddr_in local1;struct sockaddr_in local2;local1.sin_addr.s_addr = 0;local2.sin_addr.s_addr = 0xffffffff;char *result1 = inet_ntoa(local1.sin_addr);char *result2 = inet_ntoa(local2.sin_addr);std::cout << "第一次调用ntoa:restult1: " << result1 << std::endl;  std::cout << "第二次调用ntoa:restult2: " << result2 << std::endl;return 0;//第一次调用ntoa:restult1: 255.255.255.255//第二次调用ntoa:restult2: 255.255.255.255
}
  • 为什么result1和result2的结果一样?
    因为手册上说了,ntoa函数是系统申请了一个静态地址空间,存放了返回值。当再次调用时,静态地址被覆盖了,因此就被改变了。这个例子变相的说明了它可能不是一个线程安全的函数!!!

验证一下是不是线程安全的?

#include <stdio.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
void *Func1(void *p)
{struct sockaddr_in *addr = (struct sockaddr_in *)p;while (1){char *ptr = inet_ntoa(addr->sin_addr);sleep(1);printf("addr1: %s\n", ptr);}return NULL;
}
void *Func2(void *p)
{struct sockaddr_in *addr = (struct sockaddr_in *)p;while (1){char *ptr = inet_ntoa(addr->sin_addr);sleep(1);printf("addr2:%s\n", ptr);}return NULL;
}
int main()
{pthread_t tid1 = 0;struct sockaddr_in addr1;struct sockaddr_in addr2;addr1.sin_addr.s_addr = 0;addr2.sin_addr.s_addr = 0xffffffff;pthread_create(&tid1, NULL, Func1, &addr1);pthread_t tid2 = 0;pthread_create(&tid2, NULL, Func2, &addr2);pthread_join(tid1, NULL);pthread_join(tid2, NULL);return 0;
}

根据结果可知,在centos7上,该函数是线程安全的,内部应该加了锁。
在这里插入图片描述
建议:

  • 在多线程下,推荐使用inet_ntop函数,这个函数由调用者提供一个缓冲区保存结果,可以规避线程安全问题。

1.2.4 socket()

将本机的网络号和端口号
在这里插入图片描述

  • socket()打开一个网络通讯端口,如果成功的话,就像open()一样,返回一个文件描述符;
  • 应用程序可以像读写文件一样用read/write 在网络上收发数据;
  • 如果调用出错,socket返回-1;
  • 对于IPv4,domain参数为AF_INET;IPv6为AF_INET6。
  • 对于TCP协议,type参数可以指定为SOCK_STREAM,表示面向流的传输协议;对于UDP协议,指定为SOCK_DGRAM。
  • 第三个参数默认为0即可。

1.2.3 bind()

在这里插入图片描述

  • 服务器程序所监听的网络地址和端口号通常是固定不变的,客户端程序得知服务器程序的地址和端口号后就可以向服务器发起连接;服务器需要调用bind绑定一个固定的网络地址和端口号;
  • bind()成功返回0,失败返回-1。
  • bind()的作用是将参数sockfd和myaddr绑定在一起,使sockfd这个用于网络通讯的文件描述符监听myaddr所描述的地址和端口号。
  • sockaddr* 存的是一个通用指针类型,存的是本地的IP和port,第三个参数是结构体的长度;
    初始化sockaddr可以这样初始化:
	struct sockaddr_in local;        bzero(&local, sizeof local);    //初始化为0,类似于memsetlocal.sin_family = AF_INET;     //指明famaily为ipv4地址协议//服务器的IP和端口未来也是要发送给对方主机的 ->先要将数据发送到网络!local.sin_port = htons(_port);    //将host的整形,转化为net的string类型//1.同上,将点分十进制字符串风格IP地址->4字节//2.  然后4字节主机序列->网络序列// 我们可以创建子进程帮我们完成这个工作,但是我们有一套接口,可以帮助我们完成这个工作local.sin_addr.s_addr = _ip.empty()?INADDR_ANY:inet_addr(_ip.c_str());    //如果我们没自己写ip地址,服务器会自动给分配一个!,这样,只要端口号正确,服务器就能收到消息!
  • INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址;

1.2.4 listen()

在这里插入图片描述

  • listen()声明sockfd处于监听状态,并且最多允许有backlog个客户端处于连接等待状态,如果接收到更多的连接请求就忽略,这里设置一般不会太大(一般是5)
  • listen() 成功返回0,失败返回-1;

1.2.5 accept()

在这里插入图片描述

  • 三次握手完成后,服务器调用accept()接受连接;
  • 如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来;
  • addr是一个输出型参数,accept()返回时传出客户端的地址和端口号;
  • 如果给addr传NULL,表示不关心客户端的地址。
  • addrlen参数时一个传入传出参数,传入的是调用者提供的缓冲区addr的长度,传出的时客户端地址结构体的实际长度。

简单理解一下流程:
客户端输入listen激活的sock的IP和port,然后服务器accept后,再产生一个sockfd来为客户端服务。
相当于门口有人把你领进来了之后,又分配了一个服务员来服务你,以后有什么事就直接叫服务员就好了!

1.2.6 connect()

在这里插入图片描述

  • 客户端需要调用connect()连接服务器;
  • connect和bind的参数一致,区别在于bind的参数是自己的地址,而connect的参数是对方的地址;
  • connect()成功返回0,出错返回-1;

1.3 以udp为基础的客户端连接服务器的demo

功能1:客户端输入消息,服务器收到消息,并返回给客户端。
功能2:客户端输入linux指令,服务器收到指令,并返回给客户端结果消息。

  • 功能1使用:udp_server copy.hpp需要编译这个文件
./server 8080   运行服务器的可执行程序
./client 123.0.0.1 8080  连接服务器ip地址,和端口号, 然后输入消息即可!
  • 功能2使用:udp_server .hpp需要编译这个文件
./server 8080   运行服务器的可执行程序
./client 127.0.0.1 8080  连接服务器ip地址,和端口号, 输入linux命令即可

源码地址

1.4 以udp为基础的的服务器聊天室功能demo

功能1:chat_no_thread文件夹,实现的是可以多个客户端连接服务器,但是都是各发各的消息,客户端不互通。
功能2: chat_thread_success 文件夹,实现的是可以多个客户端连接服务器,客户端消息互通,相当于群聊功能。

./server 8080   运行服务器的可执行程序
./client 123.0.0.1 8080  连接服务器ip地址,和端口号, 然后输入消息即可!
  • 功能2使用:udp_server .hpp需要编译这个文件
./server 8080   运行服务器的可执行程序
./client 127.0.0.1 8080  连接服务器ip地址,和端口号, 然后输入消息即可!

源码地址

1.5 基于TCP连接的具有线程池功能的服务器客户端demo

目录结构:
在这里插入图片描述

tcp_test目录

该目录下实现了基本的TCP连接的服务器功能,但是客户端没有写。
可以编译运行服务器成功后,使用telnet命令进行测试!
功能:服务器接受消息,并且返回给客户端。

./server 8080   运行服务器的可执行程序
telnet 127.0.0.1 8080  连接服务器ip地址,和端口号, 然后输入消息测试即可!

sing_fock_test目录

服务器端版本有三种:

/*
主要包含两个版本
版本1:单进程版,会阻塞
版本2:多进程版,会阻塞
版本2.1:多进程版本,变成个孤儿进程,不会阻塞!
*/

其中版本1:是单进程版,意思就是说服务器一次只能建立一个链接,断开后才能建立第二个链接。
版本2是多进程版,虽然子进程直接退出了,但是父进程得阻塞等待,所以说服务器也会阻塞等待它。
版本2.1:子进程再fork后,子进程立马退出(父进程就不会阻塞了),就会编程孤儿进程,孤儿进程被OS领养,因此就不会阻塞父进程。

使用:

./server 8080   运行服务器的可执行程序
./client 123.0.0.1 8080  连接服务器ip地址,和端口号, 然后输入消息即可!

源码地址

thread_tcp目录

线程版本,服务器端通过线程来为客户端提供服务建立TCP链接,这样不会阻塞主进程,主进程只管监听,线程管进行和客户端通信。

使用:

./server 8080   运行服务器的可执行程序
./client 123.0.0.1 8080  连接服务器ip地址,和端口号, 然后输入消息即可!

源码地址

tcpthreadpool目录

该服务是线程池版本的,预先申请好线程,然后等待使用。这样可以降低频繁申请的时间。
客户端版本有三种:

/*
主要包含三个版本
版本1(tcp_client copy 2.cc):发消息就建立连接,发完自动断开,客户主动断开,服务器不会断开!
版本2(tcp_client copy.cc):常链接,一个线程为一个人服务,不会自动断开。
版本3(tcp_client.cc):发消息就建立连接,发完自动断开,change和英汉互译服务,客户主动断开,服务器也会主动断开!
*/

服务器有三个功能:小写转大写,英汉互译功能,都在server函数里面。
源码地址

网络的理论部分

理论部分介绍

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.xdnf.cn/news/1473795.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

文件上传(本地、OSS)

什么是文件上传&#xff1a;将文件上传到服务器。 文件上传-本地存储 前端 <template> <div><!-- 上传文件需要设置表单的提交方式为post&#xff0c;并设置enctype属性、表单项的type属性设置为file --><form action"http://localhost:8080/wedu/…

easyx图形库

目录 1、绘制简单的图形化窗口 2、设置窗口属性 2.1 颜色设置 2.2 刷新 3、基本绘图函数 3.1 绘制直线 3.2 绘制圆 3.3 绘制矩形 4、贴图 4.1 原样贴图 4.1.1 IMAGE变量去表示图片 4.1.2 加载图片 4.1.3 显示图片 4.2 透明贴图 4.2.1 认识素材 4.3 png贴图 5…

Redission分布式锁-源码解析(手把手解析)

文章目录 1.关于锁的重试机制&#xff1a;2.锁的超时问题 1.关于锁的重试机制&#xff1a; 进一步进入tryLock函数内部 2.锁的超时问题 加入目前获取锁成功了&#xff0c;我有一个剩余的有效期&#xff0c;万一业务阻塞了&#xff0c;TTL到期了&#xff0c;其他线程又进来拿锁…

[数据结构] 基于交换的排序 冒泡排序快速排序

标题&#xff1a;[数据结构] 基于交换的排序 冒泡排序&&快速排序 水墨不写bug &#xff08;图片来源于网络&#xff09; 目录 &#xff08;一&#xff09;冒泡排序 优化后实现&#xff1a; &#xff08;二&#xff09;快速排序 I、实现方法&#xff1a; &#…

移动应用开发课设——原神小助手文档(1)

2023年末&#xff0c;做的移动应用开发课设&#xff0c;分还算高&#xff0c;项目地址&#xff1a;有帮助的话&#xff0c;点个赞和星呗~ GitHub - blhqwjs/-GenShin_imp: 2023年移动应用开发课设 本文按照毕业论文要求来写&#xff0c;希望对大家有所帮助。 xxxx大学课程设计报…

一级指针 二级指针

目录 一级指针 二级指针 通过二级指针打印原数据 一级指针 一级指针就是存放变量的指针 代码演示&#xff1a; #include<stdio.h> int main() {int a 10;int* pa &a;return 0; } pa就是一级指针变量&#xff0c;是变量就会有地址&#xff0c;因为变量都是在…

警惕AI泡沫:巨额投资与回报失衡

尽管高科技巨头们在AI基础设施上投入巨资&#xff0c;但AI带来的收入增长尚未显现&#xff0c;揭示了生态系统末端用户价值的重大缺口。 红杉资本分析师David Cahn认为&#xff0c;AI企业需每年赚取约6000亿美元才能抵消其AI基础设施&#xff08;如数据中心&#xff09;的成本&…

Docker部署Seata与Nacos整合

本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 本文来自 Apache Seata官方文档&#xff0c;欢迎访问官网&#xff0c;查看更多深度文章。 Docker部署Seata与Nacos整合 Docker 部署 Seata 与 Nacos 整合 运行所使用的 demo项目地址 …

测试环境:使用OpenSSL生成证书并配置Https

文章目录 需求1、安装OpenSSL1.1、安装包下载1.2、安装&#xff08;以window 64位为例&#xff09;1.3、配置环境变量&#xff08;非必须&#xff09; 2、生成证书2.1、新建文件夹2.2、生成根证书2.2.1、生成私钥2.2.2、生成根证书&#xff0c;并且自签名 2.3、服务端证书生成2…

自然之美无需雕琢

《自然之美&#xff0c;无需雕琢 ”》在这个颜值至上的时代&#xff0c;但在温馨氛围中&#xff0c;单依纯以一种意想不到的方式&#xff0c;为我们诠释了自然之美的真谛。而医生的回答&#xff0c;如同一股清流耳目一新。“我说医生你看我这张脸&#xff0c;有没有哪里要动的。…

论文回顾 | CVPR 2021 | How to Calibrate Your Event Camera | 基于图像重建的事件相机校准新方法

论文速览 | CVPR 2021 | How to Calibrate Your Event Camera | 基于图像重建的事件相机校准新方法 1 引言 在计算机视觉和机器人领域,相机校准一直是一个基础而又重要的问题。传统的相机校准方法主要依赖于从已知校准图案中提取角点,然后通过优化算法求解相机的内参和外参。这…

绝区叁--如何在移动设备上本地运行LLM

随着大型语言模型 (LLM)&#xff08;例如Llama 2和Llama 3&#xff09;不断突破人工智能的界限&#xff0c;它们正在改变我们与周围技术的互动方式。这些模型早已集成到我们的手机中&#xff0c;但到目前为止&#xff0c;它们理解和处理请求的能力还非常有限。然而&#xff0c;…

认识并理解webSocket

今天逛牛客&#xff0c;看到有大佬分享说前端面试的时候遇到了关于webSocket的问题&#xff0c;一看自己都没见过这个知识点&#xff0c;赶紧学习一下&#xff0c;在此记录&#xff01; WebSocket 是一种网络通信协议&#xff0c;提供了全双工通信渠道&#xff0c;即客户端和服…

TeXstudio对已加载宏包的命令标记为暗红色未知命令

宏包已正常加载&#xff0c;编译也正常&#xff0c;但却将某些命令标记为暗红色。 具体的原因可参考 https://sourceforge.net/p/texstudio/wiki/Frequently%20Asked%20Questions/#how-does-txs-know-about-valid-commandshttps://sourceforge.net/p/texstudio/wiki/Frequent…

非对称加密算法原理与应用2——RSA私钥加密文件

作者:私语茶馆 1.相关章节 (1)非对称加密算法原理与应用1——秘钥的生成-CSDN博客 第一章节讲述的是创建秘钥对,并将公钥和私钥导出为文件格式存储。 本章节继续讲如何利用私钥加密内容,包括从密钥库或文件中读取私钥,并用RSA算法加密文件和String。 2.私钥加密的概述…

【HICE】转发服务器实验

1.在本地主机上操作 2.在客户端操作设置主机的IP地址为dns 3.测试,客户机是否能ping通

Micron近期发布了32Gb DDR5 DRAM

Micron Technology近期发布了一项内存技术的重大突破——一款32Gb DDR5 DRAM芯片&#xff0c;这项创新不仅将存储容量翻倍&#xff0c;还显著提升了针对人工智能&#xff08;AI&#xff09;、机器学习&#xff08;ML&#xff09;、高性能计算&#xff08;HPC&#xff09;以及数…

转发服务器实验

首先先克隆一个虚拟机并完成ip地址的修改 nmcli connection modify ens160 ipv4.addresses 192.168.209.128/24 nmcli connection modify ens160 ipv4.method manual nmcli connection modify ens160 connection.autoconnect yes nmcli connection up ens160 nmcli connection…

基于深度学习LightWeight的人体姿态检测跌倒系统源码

一. LightWeight概述 light weight openpose是openpose的简化版本&#xff0c;使用了openpose的大体流程。 Light weight openpose和openpose的区别是&#xff1a; a 前者使用的是Mobilenet V1&#xff08;到conv5_5&#xff09;&#xff0c;后者使用的是Vgg19&#xff08;前10…

昇思25天学习打卡营第11天|MindSpore 助力下的 GPT2:数据集加载处理及模型全攻略

目录 环境配置 数据集下载和获取 数据集拆分 处理数据集 模型构建 ​​​​​​​模型训练 ​​​​​​​模型推理 环境配置 “%%capture captured_output”这一行指令通常旨在捕获后续整个代码块所产生的输出结果。首先&#xff0c;将已预装的 mindspore 库予以卸载。随后&a…