linux下的网络编程

news2024/9/22 15:38:46

网络编程

  • 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,类似于memset
	local.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.coloradmin.cn/o/1901611.html

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

相关文章

解决 MEX 文件 ‘xxx.mexw64‘ 无效: 找不到指定的模块。的问题

1.问题描述 在matlab R2021b中运行编译好后的gptoolbox工具箱中的函数[SVtemp,SFtemp,IF] selfintersect(V,F);报错如下 MEX 文件 E:\MATLAB_File\gptoolbox\mex\selfintersect.mexw64 无效: 找不到指定的模块。出错 offset_bunny (第 15 行) [SVtemp,SFtemp,IF] selfinter…

【pyqt-实训训练】串口助手

串口助手 前言一、ui设计二、ui的控件命名三、ui转py使用类的方法【扩展】使用ui文件导入&#xff01;P7的小错误解决办法 总结 前言 我的惯例就是万物之始&#xff0c;拜见吾师&#x1f970;⇨pyqt串口合集 最开始的时候我想的是&#xff0c;学了那么久的pyqt&#xff0c;我…

加入新数据预测,基于黏菌优化算法SMA优化SVM支持向量机回归预测(多输入单输出)

加入新数据预测&#xff0c;基于黏菌优化算法SMA优化SVM支持向量机回归预测&#xff08;多输入单输出&#xff09; 1.数据均为Excel数据&#xff0c;直接替换数据就可以运行程序。 2.所有程序都经过验证&#xff0c;保证程序可以运行。 3.具有良好的编程习惯&#xff0c;程序…

Spark查询当前用户下所有账号的余额,如果当天没有余额则使用最近的余额

在使用Apache Spark进行数据分析时&#xff0c;你可能会处理一个包含用户账户和余额信息的数据集。如果你想要查询当前用户下所有账号的余额&#xff0c;并且如果当天没有余额记录&#xff0c;则使用最近的余额&#xff0c;你可以按照以下步骤进行&#xff1a; 数据准备&#x…

要想贵人相助,首先自己得先成为贵人!

点击上方△腾阳 关注 转载请联系授权 在金庸江湖里&#xff0c;有两位大侠&#xff0c;一个是萧峰&#xff0c;一个是郭靖。 郭靖在《射雕英雄传》里是绝对的主角&#xff0c;在《神雕侠侣》当中也是重要的配角&#xff0c;甚至可以说是第二主角。 谈起郭靖&#xff0c;很多…

【Linux进阶】文件系统7——文件系统简单操作

1.磁盘与目录的容量 现在我们知道磁盘的整体数据是在超级区块中&#xff0c;但是每个文件的容量则在inode 当中记载。 那在命令行模式下面该如何显示这几个数据&#xff1f;下面就让我们来谈一谈这两个命令&#xff1a; df&#xff1a;列出文件系统的整体磁盘使用量&#xf…

使用Vue实现前后端分离 spring框架返回json数据中文乱码

java json数据返回值中文乱码 出现&#xff1f;&#xff1f;&#xff1f; - _xkoko - 博客园 (cnblogs.com) 引入js的script标签到底是放在head还是body中_html页面中用<script>标签引入js代码,该标签放在<head>标签中和放在<body>标签-CSDN博客 vue.js 的问…

进程的控制-ps和kill命令

ps 查看进程信息 部分参数&#xff1a; a : 显示现行终端机下的所有程序&#xff0c;包括其他用户的程序 u: 以用户为主的格式来显示程序状况 x: 显示所有程序&#xff0c;不以 终端机来区分 kill 向指定的进程发送信号 kill 可将指定的信息送至程序。预设的信息为 SIG…

最新版情侣飞行棋dofm,已解锁高阶私密模式,单身狗务必绕道!(附深夜学习资源)

今天阿星要跟大家聊一款让阿星这个大老爷们儿面红耳赤的神奇游戏——情侣飞行棋。它的神奇之处就在于专为情侣设计&#xff0c;能让情侣之间感情迅速升温&#xff0c;但单身狗们请自觉绕道&#xff0c;不然后果自负哦&#xff01; 打开游戏&#xff0c;界面清新&#xff0c;操…

转发服务器实验

首先先克隆一个虚拟机并完成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…

20240706 每日AI必读资讯

&#x1f680;Meta 发布 AI 重磅炸弹&#xff1a;多标记预测模型现已开放研究 - 新技术采用多标记预测方法&#xff0c;有望提高性能并缩短训练时间。 - 模型同时预测多个未来单词&#xff0c;可能改善语言结构和上下文理解。 - multi-token prediction模型是Facebook基于大…

大语言模型与知识图谱结合发展方向

引言 在人工智能的发展历程中&#xff0c;大语言模型&#xff08;LLM&#xff09;的出现标志着一个重要的转折点。随着深度学习技术的突破和计算能力的提升&#xff0c;LLM以其前所未有的规模和复杂性&#xff0c;开启了迈向人工通用智能&#xff08;AGI&#xff09;的新浪潮。…

打卡第4天----链表

通过学习基础,发现我的基本功还得需要再练练,思路得再更加清晰明了,这样子做算法题才能驾轻就熟。每天记录自己的进步。 一、两两交换 题目编号:24 题目描述: 给你一个链表,两两交换其中相邻的节点,并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本…

Linux 防火墙配置指南:firewalld 端口管理应用案例(二十个实列)

&#x1f3e1;作者主页&#xff1a;点击&#xff01; &#x1f427;Linux基础知识(初学)&#xff1a;点击&#xff01; &#x1f427;&#x1f427;Linux高级管理专栏&#xff1a;点击&#xff01; &#x1f510;Linux中firewalld防火墙&#xff1a;点击&#xff01; ⏰️…

解决vscode配置C++编译带有中文名称报错问题

在新电脑上安装vscode运行带有中文路径和中文名称的C代码时遇到报错 根据别人的教程将laugh.json文件中"program": "${fileDirname}\\${fileBasenameNoExtension}.exe",改成了"program": "${fileDirname}\\output\\test.exe",&#x…

c与c++的内存管理

给出内存四个分区名字&#xff1a;栈区、堆区、全局区&#xff08;俗话也叫静态变量区&#xff09;、代码区&#xff08;也叫代码段&#xff09;&#xff08;代码段又分很多种&#xff0c;比如常量区&#xff09; 当然也会看到别的定义如&#xff1a; 两者都正确&#xff0c;记…

suricata7 rule加载(一)加载 action

suricata7.0.5 一、前提条件 1.1 关键字注册 main | --> SuricataMain|--> PostConfLoadedSetup|--> SigTableSetupsigmatch_table是一个全局数组&#xff0c;每个元素就是一个关键字节点&#xff0c;是对关键字如何处理等相关回调函数。非常重要的一个结构&#x…

【论文解读】AGENTLESS:揭开基于LLM的软件工程代理的神秘面纱,重塑软件工程自动化新基线

&#x1f4dc; 文献卡 英文题目: Agentless: Demystifying LLM-based Software Engineering Agents;作者: Chunqiu Steven Xia; Yinlin Deng; Soren Dunn; Lingming ZhangDOI: 10.48550/arXiv.2407.01489摘要翻译: 大型语言模型&#xff08;LLM&#xff09;的最新进展显著推进…

【pytorch19】交叉熵

分类问题的loss MSECross Entropy LossHinge Loss &#xff08;SVN用的比较多&#xff09; ∑ i m a x ( 0 , 1 − y i ∗ h θ ( x i ) ) \sum_imax(0,1-y_i*h_\theta(x_i)) ∑i​max(0,1−yi​∗hθ​(xi​)) Entropy&#xff08;熵&#xff09; Uncertainty&#xff08;…

使用 ESP32-WROOM + DHT11 做个无屏温湿度计

最近梅雨天&#xff0c;有个房间湿度很大&#xff0c;而我需要远程查看温湿度&#xff0c;所以无所谓有没有显示屏&#xff0c;某宝上的温湿度计都是带屏的&#xff0c;如果连WIFI查看温湿度操作也比较麻烦&#xff0c;还需要换电池&#xff0c;实在不能满足我的需求&#xff0…