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.mzph.cn/pingmian/42063.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Apispec,一个用于生成 OpenAPI(Swagger)规范的 Python 库

目录 01什么是 Apispec&#xff1f; 为什么选择 Apispec&#xff1f; 安装与配置 02Apispec 的基本用法 生成简单的 API 文档 1、创建 Apispec 实例 2、定义 API 路由和视图 3、添加路径到 Apispec 集成 Flask 和 Apispec 1、安装…

FreeU: Free Lunch in Diffusion U-Net——【代码复现】

这篇文章发表于CVPR 2024&#xff0c;官网地址&#xff1a;ChenyangSi/FreeU: FreeU: Free Lunch in Diffusion U-Net (CVPR2024 Oral) (github.com) 一、环境准备 提前准备好python、pytorch环境 二、下载项目依赖 demo下有一个requirements.txt文件&#xff0c; pip inst…

【WEB前端2024】3D智体编程:乔布斯3D纪念馆-第55课-芝麻开门(语音 识别 控制3D纪念馆开门 和 关门)

【WEB前端2024】3D智体编程&#xff1a;乔布斯3D纪念馆-第55课-芝麻开门&#xff08;语音识别控制3D纪念馆开门和关门&#xff09; 使用dtns.network德塔世界&#xff08;开源的智体世界引擎&#xff09;&#xff0c;策划和设计《乔布斯超大型的开源3D纪念馆》的系列教程。dtn…

2.pwn的linux基础(计算机内部数据结构存储形式)

linux基础 保护层级: 分为四个ring0-ring3 一般来说就两个&#xff0c;0和3 0为内核 3为用户 权限: 用户分为多个组 文件和目录等等的权限一般都是三个&#xff0c;即可读可写可执行。 读:R&#xff0c;写:W&#xff0c;执行:X 赋予一个可执行文件执行权限就是chmod x file…

qt 如何添加子项目

首先我们正常流程创建一个项目文件&#xff1a; 这是我已经创建好的&#xff0c;请无视红线 然后找到该项目的文件夹&#xff0c;在文件夹下创建一个文件夹&#xff0c;再到创建好的文件夹下面创建一个 .pri 文件&#xff1a; &#xff08;创建文件夹&#xff09; &#xff08…

字节跳动与南开联合开源 StoryDiffusion:一键生成漫画和视频故事的神器!完全免费!

大家好&#xff0c;我是程序员X小鹿&#xff0c;前互联网大厂程序员&#xff0c;自由职业2年&#xff0c;也一名 AIGC 爱好者&#xff0c;持续分享更多前沿的「AI 工具」和「AI副业玩法」&#xff0c;欢迎一起交流~ 漫画&#xff0c;是多少人童年的回忆啊&#xff01; 记得小学…

#数据结构 链式栈

1. 概念 链式栈LinkStack 逻辑结构&#xff1a;线性结构物理结构&#xff1a;链式存储栈的特点&#xff1a;后进先出 栈具有后进先出的特点&#xff0c;我们使用链表来实现栈&#xff0c;即链式栈。那么栈顶是入栈和出栈的地方&#xff0c;单向链表有头有尾&#xff0c;那我…

Http中get与post的区别,99%的人都理解错了吧

Get和Post是HTTP请求的两种基本方法&#xff0c;要说它们的区别&#xff0c;接触过WEB开发的人都能说出一二。 最直观的区别 就是Get把参数包含在URL中&#xff0c;Post通过request body传递参数。 你可能自己写过无数个Get和Post请求&#xff0c;或者已经看过很多权威网站总…

容器:stack

以下是关于stack容器的一些总结&#xff1a; stack容器比较简单&#xff0c;主要包括&#xff1a; 1、构造函数&#xff1a;stack [staName] 2、添加、删除元素: push() 、pop() 3、获取栈顶元素&#xff1a;top() 4、获取栈的大小&#xff1a;size() 5、判断栈是否为空&#x…

Linux运维之管道符、重定向与环境变量

前言&#xff1a;本博客仅作记录学习使用&#xff0c;部分图片出自网络&#xff0c;如有侵犯您的权益&#xff0c;请联系删除 目录 一、输入输出重定向 二、管道命令符 三、命令行的通配符 四、常用的转义字符 五、重要的环境变量 致谢 一、输入输出重定向 输入重定向是…

Python统计实战:时间序列分析之一元线性回归预测和指数曲线预测

为了解决特定问题而进行的学习是提高效率的最佳途径。这种方法能够使我们专注于最相关的知识和技能&#xff0c;从而更快地掌握解决问题所需的能力。 &#xff08;以下练习题来源于《统计学—基于Python》。请在Q群455547227下载原始数据。&#xff09; 练习题 下表是某只股票…

Mysql数据库基础操作

Mysql数据库 基本概念 内核的作用&#xff1a;调用硬件资源 数据库的作用 使用数据库可以高效且条理分明地存储数据&#xff0c;使人们能够更加迅速、方便的管理数据。 数据、表、数据库 数据 描述事物的符号记录&#xff0c;包括数字&#xff0c;文字&#xff0c;图形&…

nginx的正向代理和反向代理以及tomcat

nginx的正向代理和反向代理&#xff1a; 正向代理以及缓存配置&#xff1a; 代理&#xff1a;客户端不再是直接访问服务端&#xff0c;通过代理服务器访问服务端。 正向代理&#xff1a;面向客户端&#xff0c;我们通过代理服务器的IP地址访问目标范围端。 服务端只知道代理…

如何利用算法优化广告效果

效果广告以超过67%的占比&#xff0c;成为了中国互联网广告预算的大头。在BAT、字节等大的媒体平台上&#xff0c;效果广告以CPC实时竞价广告为主。在这种广告产品的投放中&#xff0c;广告主或其代理公司通过针对每个广告点击出价&#xff0c;系统自动把这些点击出价换算成eCP…

Java + MySQL 实现存储完整 Json

Java MySQL 实现存储完整 Json 一、应用场景二、数据库配置三、后端代码配置1、maven 依赖2、实体类3、Service 实现类4、xml 文件 四、测试1、新增接口2、查询接口3、数据表内容 一、应用场景 将前端传过来的 Json 完整存储到 MySQL 中&#xff0c;涉及技术栈为 Java、MyBat…

从资金管理的角度 谈谈伦敦金投资技巧

刚进入伦敦金市场的时候&#xff0c;笔者认为技术分析是很重要的&#xff0c;所以将学习伦敦金投资技巧的精力全部投入到技术分析的学习中。经过一系列交易的亏损&#xff0c;笔者才发现&#xff0c;其实交易管理才是最重要的。如果管理得好&#xff0c;30%的胜率&#xff0c;投…

嵌入式C语言面试相关知识——关键字(不定期更新)

嵌入式C语言面试相关知识——关键字 一、博客声明二、C语言关键字1、sizeof关键字2、static关键字3、const关键字4、volatile关键字5、extern关键字 一、博客声明 又是一年一度的秋招&#xff0c;怎么能只刷笔试题目呢&#xff0c;面试题目也得看&#xff0c;想当好厂的牛马其实…

Charles拦截发送数据包-cnblog

Charles拦截发送数据包 打开允许断点 右键要打断点的数据包&#xff0c;打断点 重新发请求进入断点模式 修改完毕后发送

C++实现简化版Qt的QObject(3):增加父子关系、属性系统

前几天写了文章&#xff1a; C实现一个简单的Qt信号槽机制 C实现简化版Qt信号槽机制&#xff08;2&#xff09;&#xff1a;增加内存安全保障 之后感觉还不够过瘾&#xff0c;Qt中的QObject体系里还有不少功能特性没有实现。为了提高QObject的还原度&#xff0c;今天我们将父子…

vscode远程连接linux(配置免密)

远程连接 1.首先保证物理机和虚拟机网络可以ping通 2.查看ubuntu得ip地址 ifconfig IP为&#xff1a;192.168.52.133 3.连接远程主机 配置免密 1.打开cmd运行ssh-keygen -t rsa 一路回车就行 2.打开window文件夹C:\Users\xbj\.ssh 3.用记事本打开id_rsa.pub文件复制公…