linux之网络通信
发布日期:2021-06-29 11:09:20 浏览次数:2 分类:技术文章

本文共 9818 字,大约阅读时间需要 32 分钟。

一、网络基础

1、网络通信概念

网络通信,就是支持位于网络中不同主机上面的2个进程进行通信。

2、一些网络相关的硬件设备

2.1、网卡

计算机上网必备硬件,cpu靠网卡来链接外部网络的

串行转并行的设备;因为cpu和网卡都是串行的,但是网络传输的数据是并行的,需要网卡中转
数据帧封包和拆包;
网络数据的缓存和速率适配;

2.2、中继器

中继并用来放大信号

2.3、集线器

相当于中继器有中继和放大信号的作用,并且还可以组成局域网,用广播的形式工作,注意集线器只能用来组成局域网而不能用来连接外网的

2.4、交换机

包含集线器的功能,但是其工作方式比广播更高级,交换机中存在地址表,会进行学习记录,有记录的能直接发生,没有的才广播后记录下次就不用再广播形式发送了。

本质功能;学习MAC地址进行帧转发

2.5、路由器

本质功能是数据包转发和路径选择

路由器对外实现联网,对内管理子网

对内管理子网:可以在路由器中上设置子网的网段,设置有线端口的IP地址,设置dhcp功能等,因此局域网的IP是由路由器决定的。
对外实现联网;联网方式取决于外部网络,此时路由器相当于一个节点。

3、网络中的一些技术协议

3.1、DNS 域名服务

提供域名和IP地址直接转换服务的

3.2、DHCP 动态主机配置协议

局域网内的个主机IP地址是动态分配的,而动态分配需要局域网内的DHCP服务器来协助才能完成,

DHCP动态分配的优势在于;方便接入和断开,有限的IP地址得到充分利用

3.3、NAT 网络地址转换协议

IP地址分为私网IP和公网IP,在局域网内为私有IP ,不同局域网IP地址可以相同,但是在互联网连接时为公网IP此时IP地址不能重复的,因此需要NAT进行转换。

NAT的作用就是缓冲IPV4的IP地址不够的问题

3.5、NAT穿透

P2P技术就是的,

因为NAT 网络地址转换协议则把不同局域网之间进行了隔离,而NAT穿透就是直接让两个不同局域网内的主机进行通信的技术。主要是通过一个服务器(寻找种子)作为中介完成不同局域网的两台主机通信的。

4、IP地址分类

IP地址 = 网络地址 + 主机地址;

网络地址位数决定这种网络有多少个子网络。
主机地址位数决定该子网络能有多少主机
子网掩码;用来说明网络地址和主机地址各站多少位的,网络地址位置置0置1,主机地址位置置0

二、网络编程

1、socket编程接口介绍

1.1、 socket

类似于文件IO中的open传入地址属性返回文件描述符,这里是用来打开一个网络连接,成功则返回一个网络描述符,之后操作这个网络连接就通过这个网络描述符即可。

NAME       socket - create an endpoint for communicationSYNOPSIS       #include 
/* See NOTES */ #include
int socket(int domain, int type, int protocol);domain;表示网络域,就是网络范围,ipv4还是ipv6有相关宏设置AF_INET、AF_INET6type;通信协议的类型,tcp,udp...也有相关的宏来设置protocol 一般传0表示无特例,默认协议

在这里插入图片描述

在这里插入图片描述

1.2、bind

类似于文件IO中的fcntl函数给打开的文件描述符操作其相关属性,而bind就是给打开的网络文件描述符绑定自己的IP地址

注意;这个函数IP地址可兼容IPV4\IPV6两种

NAME       bind - bind a name to a socketSYNOPSIS       #include 
/* See NOTES */ #include
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); sockfd;网络文件描述符const struct sockaddr * addr;一个指向特定协议的地址结构的指针socklen_t addrlen;该地址结构的长度

1.3、listen

isten函数使用主动连接套接口变为被连接套接口,使得一个进程可以接受其它进程的请求,从而成为一个服务器进程。

当调用listen之后,服务器进程就可以调用accept来等待接受一个外来的请求。

NAME       listen - listen for connections on a socketSYNOPSIS       #include 
/* See NOTES */ #include
int listen(int sockfd, int backlog);成功返回0 ,失败返回-1并设置erronbacklog 表示监听最大的排队数,也就是一起最多可以监听多少个客户端

1. 4、connect;连接

NAME       connect - initiate a connection on a socketSYNOPSIS       #include 
/* See NOTES */ #include
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);成功返回0 ,失败返回-1并设置erron const struct sockaddr *addr;表示要连接的ip地址

1.5、收发

发送接收send和write,recv和read 基本相同;只是send多个参数flag,但是一般传0,则两个一样

SYNOPSIS       #include 
#include
ssize_t send(int sockfd, const void *buf, size_t len, int flags); ssize_t recv(int sockfd, void *buf, size_t len, int flags);
NAME       write - write to a file descriptorSYNOPSIS       #include 
ssize_t write(int fd, const void *buf, size_t count); ssize_t read(int fd, void *buf, size_t count);

1.6、网络编程中大小端不匹配问题

一般操作系统为小端模式,高字节存放在高地址上,而在网络编程中网络字节序列(或是通信协议协议)中数据的顺序是大端模式,高字节都是放在低地址上。因此需要一些转换函数,

用来将主机short类型的字节顺序转为网络字节顺序。htons : h(host) to n(net) s (short): 用来将网络 short 类型的字节顺序转为主机字节顺序。ntohs : n(net) to h(host) s(short) :  用来将主机字节顺序转换为网络字节顺序,只不过转换变量的类型不同为 unsigned longhtonl : h(host) to n (net) l (long ) :用来将网络字节顺序转换为主机字节顺序,转换的变量为 unsigned longntohl:  n (net) to h(host) l (long) :

1.7、网络编程中ip地址的存放以及相关结构体

in_addr 、sockaddr_in 、sockaddr_in 6

都定义在/usr/include/netinet/in.h中

首先最底层的肯定还是一个存放23位进制的数字,至于我们所输入的带.的IP地址里面会有辅助函数来进行转换的。这个数字封装的变量为 typedef u32_t in_ddr_t;
然后将这个类型单独定义一个变量封装到一个结构体中;
注意;struct in_addr存储的ip地址是网路字节序即大端方式
struct in_addr {in_ddr_t s_addr;}
再然后就有了IPV4所表示的结构体
stuct sockaddr_in
{
in_port_t sin_port;//端口号
struct in_addr sin_addr;//IP地址
等一些其他参数
}
还有了IPV6的结构体stuct sockaddr_in6其中与ipv4不同的是它封装的是struct in6_addr sin_addr;//IP6地址.
还有一个抽象的结构体 struct sockaddr 标准网络地址结构体,类似于void*可以转化为
stuct sockaddr_in 或stuct sockaddr_in的

在这里插入图片描述

在这里插入图片描述

struct sockaddr

因此对struct sockaddr进行赋值,则要

sockaddr.sin_addr.s_addr = inet_addr(“192.168.1.10”);这样的层级

sockaddr格式

在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了,

struct sockaddr {
sa_family_t sin_family;//地址族   char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息    };

并且注意;在使用函数时需要把socketaddr_in强制类型转换为(struct sockaddr *)。

struct addrinfo

addrinfo结构体定义如下:

typedef struct addrinfo {
int        ai_flags;//指示在getaddrinfo函数中使用的选项的标志。
int         ai_family;
int        ai_socktype;
int         ai_protocol;
size_t        ai_addrlen;
char        *ai_canonname;
struct sockaddr   *ai_addr;
struct addrinfo   *ai_next;//指向链表中下一个结构的指针。此参数在链接列表的最后一个addrinfo结构中设置为NULL。
} ADDRINFOA, *PADDRINFOA;
复制代码
其中:

ai_addrlen: 指向的缓冲区的长度(以字节为单位)。

ai_canonname: 主机的规范名称。
ai_addr: 指向 sockaddr 结构的指针。每个返回的addrinfo结构中的ai_addr成员指向一个填充的套接字地址结构。
每个返回的addrinfo结构的长度(以字节为单位)在ai_addrlen成员中指定。

8、辅助性函数

ip地址转换 n表示net网络端;如何将输入的带.的ip地址转换为网络编程存储的32位数的ip地址

inet_aton、inet_addr、inet_ntoa 这些不支持IPV6

inet_ntop、inet_pton

int inet_aton ( const char *cp , struct in_addr *in ) ;参数:  cp 指向点分格式IP地址字符串指针,"xxx.xxx.xxx.xxx"  in 它是 值-结果 类型的参数,用做提取与存放函数转换后的结果,其中 struct in_addr 类型与            struct sockaddr_in 中的sin_addr  字段类型是一致的,struct in_addr 中的 s_addr 变量存放着           16进制网络字节顺序的IP地址返回值:执行成功返回非零数值,失败返回 0in_addr_t inet_addr(const char *cp);也是转换为32位的数char * inet_ntoa( struct in_addr_in ) ;参数:   in : 该参数存放用来转换的16进制网络顺序的IP地址结构体返回值:   如果函数 Inet_ntoa 成功将 16进制网络顺序的 IP地址转换为字符串类型的点分IP 地址,   则将该字符串作为返回值返回,转换失败,返回 NULL
#include 
int inet_pton(int af, const char *src, void *dst);const char *inet_ntop(, const void *src,char *dst, socklen_t size);p;表达式类型,就是带.的字符串IP地址n网络存储32位形式的int af表示使用ipV4还是IPV6有对应的宏表示AF_INET,AF_INET6实践#include
#include
#include
int main(){
struct in_addr addr; if(inet_pton(AF_INET, "127.0.0.1", &addr.s_addr) == 1) printf("Numeric IP: %x\n", addr.s_addr); char str[16]; if(inet_ntop(AF_INET, &addr.s_addr, str, sizeof(str))) printf("Presentation IP: %s\n", str); return 0;}

2、网络编程实践

完成一个cs架构的,客户端向服务器发送学生进行注册

服务端采用线程方式,主线程负责监听,子线程负责与连接过来的客户端通信,实现单服务器多客服端操作

//netw.h头文件#ifndef _NETW_H_#define _NETW_H_#define SERADDR		"192.168.91.129"		// 服务器开放给我们的IP地址和端口号#define SERPORT		9003					//设置服务器通信端口#define BACKLOG		100						//设置服务器端一次最多能接收多大#define CMD_REGISTER	1001	// 注册学生信息#define CMD_CHECK		1002	// 检验学生信息#define CMD_GETINFO		1003	// 获取学生信息#define STAT_OK			30		// 回复ok#define STAT_ERR		31		// 回复出错了typedef struct commu{
char name[20]; // 学生姓名 int age; // 学生年龄 int cmd; // 命令码 int stat; // 状态信息,用来回复}info;#endif
//服务器server.c文件#include 
#include
#include
#include
#include
#include
#include
#include "netw.h"#include
//使用子线程完成对客户端的通信操作void *func(void *arg){ int ret = -1; //进行读写操作 // 客户端反复给服务器发 while (1) { info st; // 回合中第1步:服务器收 ret = recv(*(int *)arg, &st, sizeof(info), 0); // 回合中第2步:服务器解析客户端数据包,然后干活, if (st.cmd == CMD_REGISTER) { printf("用户要注册学生信息\n"); printf("学生姓名:%s,学生年龄:%d\n", st.name, st.age); // 在这里服务器要进行真正的注册动作,一般是插入数据库一条信息 // 回合中第3步:回复客户端 st.stat = STAT_OK; ret = send(*(int *)arg, &st, sizeof(info), 0); } if (st.cmd == CMD_CHECK) { } if (st.cmd == CMD_GETINFO) { } } return 0;}int main(){ int sockfd = -1; int ret = -1, clifd = -1; socklen_t len = 0; //因为采用IPV4则可以直接使用struct sockaddr_in结构体 struct sockaddr_in seraddr = { 0}; struct sockaddr_in cliaddr = { 0}; pthread_t th[10] = { -1}; int cnt = 0; //1、创建socket接口 //AF_INET表示使用ipv4 SOCK_STREAM表示使用tcp连接协议 sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { printf("socket error\n"); return -1; } printf("socket success.\n"); //2、bind绑定本地IP和端口与socket描述符 seraddr.sin_family = AF_INET; seraddr.sin_port = htons(SERPORT); // 设置地址的端口号信息 seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址,注意层级 ret = bind(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr)); if (ret < 0) { printf("bind error\n"); return -1; } printf("bind success.\n"); //3、listen监听 ret = listen(sockfd, BACKLOG); //设置可以监听的最大个数 if (ret < 0) { printf("listen error\n"); return -1; } //服务器再用循环监听客服端连接,而与客户端通信采用开辟子线程的方式 while(1) { //4、accept阻塞等待连接,此时返回的fb描述符就是用于读写的文件描述符, //之前socket返回的描述符是用于网络通信的 clifd = accept(sockfd, (struct sockaddr *)&cliaddr, &len); printf("连接已经建立,client fd = %d.\n", clifd); cnt++; ret = pthread_create(&th[cnt], NULL, func, &clifd); if (ret != 0) { printf("pthread_create error.\n"); exit(-1); } } printf("等待回收子线程\n"); for(int i = 0; i < cnt; i++) { ret = pthread_join(th[i], NULL); if (ret != 0) { printf("pthread_join error.\n"); exit(-1); } } return 0;}
//客户端 client.c#include 
#include
#include
#include
#include
#include
#include
#include "netw.h"char sendbuf[100];char recvbuf[100];int main(){ int sockfd = -1, ret = -1; struct sockaddr_in seraddr = { 0}; struct sockaddr_in cliaddr = { 0}; // 第1步:socket sockfd = socket(AF_INET, SOCK_STREAM, 0); if (-1 == sockfd) { printf("socket error\n"); return -1; } printf("socketfd = %d.\n", sockfd); // 第2步:connect链接服务器 seraddr.sin_family = AF_INET; // 设置地址族为IPv4 seraddr.sin_port = htons(SERPORT); // 设置地址的端口号信息 seraddr.sin_addr.s_addr = inet_addr(SERADDR); // 设置IP地址 ret = connect(sockfd, (const struct sockaddr *)&seraddr, sizeof(seraddr)); if (ret < 0) { perror("listen"); return -1; } printf("成功建立连接\n"); //第3步;就是进行读写操作 while (1) { // 回合中第1步:客户端给服务器发送信息 info st1; printf("请输入学生姓名\n"); scanf("%s", st1.name); printf("请输入学生年龄"); scanf("%d", &st1.age); st1.cmd = CMD_REGISTER; //printf("刚才输入的是:%s\n", sendbuf); ret = send(sockfd, &st1, sizeof(info), 0); printf("发送了1个学生信息\n"); // 回合中第2步:客户端接收服务器的回复 memset(&st1, 0, sizeof(st1)); ret = recv(sockfd, &st1, sizeof(st1), 0);//是堵塞的,与创建套接字socket的时候设置有关 // 回合中第3步:客户端解析服务器的回复,再做下一步定夺 if (st1.stat == STAT_OK) { printf("注册学生信息成功\n"); } else { printf("注册学生信息失败\n"); } } return 0;}

转载地址:https://blog.csdn.net/zw1996/article/details/114113581 如侵犯您的版权,请留言回复原文章的地址,我们会给您删除此文章,给您带来不便请您谅解!

上一篇:海思项目学习记录 -1、HI3518E的sdk编译部署
下一篇:linux之进程线程信号全解

发表评论

最新留言

不错!
[***.144.177.141]2024年04月18日 12时36分02秒

关于作者

    喝酒易醉,品茶养心,人生如梦,品茶悟道,何以解忧?唯有杜康!
-- 愿君每日到此一游!

推荐文章