linux之网络通信

一、网络基础

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 communication

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

       int socket(int domain, int type, int protocol);
domain;表示网络域,就是网络范围,ipv4还是ipv6有相关宏设置AF_INET、AF_INET6
type;通信协议的类型,tcp,udp...也有相关的宏来设置
protocol  一般传0表示无特例,默认协议

在这里插入图片描述

在这里插入图片描述

1.2、bind

类似于文件IO中的fcntl函数给打开的文件描述符操作其相关属性,而bind就是给打开的网络文件描述符绑定自己的IP地址
注意;这个函数IP地址可兼容IPV4\IPV6两种

NAME
       bind - bind a name to a socket

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

       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 socket

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

       int listen(int sockfd, int backlog);
成功返回0 ,失败返回-1并设置erron
backlog 表示监听最大的排队数,也就是一起最多可以监听多少个客户端

1. 4、connect;连接

NAME
       connect - initiate a connection on a socket

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

       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 <sys/types.h>
       #include <sys/socket.h>

       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 descriptor

SYNOPSIS
       #include <unistd.h>

       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 long
htonl : h(host) to n (net) l (long ) :
用来将网络字节顺序转换为主机字节顺序,转换的变量为 unsigned long
ntohl:  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地址
返回值:执行成功返回非零数值,失败返回 0

in_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 <arpa/inet.h>
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 <stdio.h>
#include <stdlib.h>
#include <arpa/inet.h>
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 <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>        
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#include "netw.h"
#include <pthread.h>

//使用子线程完成对客户端的通信操作
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 <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <string.h>
#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;
}
  • 2
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值