网络编程

 

一.计算机网络

1.网络分类

地理范围:广域网WAN、城域网MAN、局域网LAN、个人区域网PAN。

按拓扑结构:星型拓扑结构、总线型拓扑结构、环形拓扑结构、网状拓扑结构、混合拓扑结构。

按网络使用者分类:公用网、专用网。

局域网(LoxalAreaNetwork,LAN)是指范围在几百米到十几公里内办公楼群或校园内的计算机相互连接所构成的计算机网络。计算机局域网被广泛应用于连接校园、工厂以及机关的个人计算机或工作站,以利于个人计算机或工作站之间共享资源(如打印机)和数据通信。

城域网(MetropolitanAreaNetwork,MAN)所采用的技术基本上与局域网相类似,只是规模上要大一些。城域网既可以覆盖相距不远的几栋办公楼,也可以覆盖一个城市;既可以是私人网,也可以是公用网。城域网既可以支持数据和话音传输,也可以与有线电视相连。城域网一般只包含一到两根电缆,没有交换设备,因而其设计就比较简单。

广域网(WidoAreaNetwork,WAN)通常跨接很大的物理范围,如一个国家。广域网包含很多用来运行用户应用程序的机器集合,我们通常把这些机器叫做主机;把这些主机连接在一起的是通信子网(communicationsubnet)。通信子网的任务是在主机之间传送报文。将计算机网络中的纯通信部分的子网与应用部分的主机分离开来,可以大大简化网络的设计。

网络编程_第1张图片

2.设备分类

交换机:基于MAC识别(就是每个计算机或者手机上都有唯一固定的网卡硬件地址),封装转发数据包的网络设备,简单的说就是讲附近的机器集合起来构成局域网

路由器:又称为选径器---连接不同网段找到网络中数据传输最佳路径(具有更强异种网连接能力)

3.网络体系结构

osi七层体系结构:应用层、表示层、会话层、传输层、网络层、数据链路层、物理层

tip/ip四层体系结构:应用层、传输层、网络(网际)层、网络接口层

 

网络编程_第2张图片

应用层:负责应用程序的网络访问,这里通过端口号来识别各个不同的进程。

传输层:负责端对端之间的通信会话连接和建立(tcp udp)。传输协议的选择根据数据传输方式而定。

网络层:负责将数据帧封装成IP数据报,并运行必要的路由算法。

网络接口层:负责将数据帧转换为二进制流,并进行数据帧的发送和接收

网络编程_第3张图片网络编程_第4张图片

4.网络通讯协议

TCP/IP是英文(Transmission Control Protocol/Internet Protocol)的缩写,意思是“传输控制协议/网际协议”

网络编程_第5张图片应用层还有HTTP,STMP

Telnet协议是TCP/IP协议族中的一员,是Internet远程登陆服务的标准协议。

ftp:(文件传输协议是用于在网络上进行文件传输的一套标准协议,使用客户/服务器模式。

TCP:(传输控制协议) 为应用程序提供可靠的通信连接。适合于一次传输大批数据的情况。并适用于要求

得到相应的应用程序。

UDP:(用户数据包协议)提供了无连接通信,且不对传送包进行可靠的保证。适合于一次传输少量数据。

ICMP:(网络控制消息协议)用于发送报告有关数据包的传送错误的协议。

IGMP:(网络组管理协议)被 IP 主机用来向本地多路广播路由器报告主机组成员的协议。

IP:(网际互联协议)负责在主机和网络之间寻址和路由数据包。

ARP:(地址转换协议)用于获得同一物理网络中的硬件主机地址。

MPLS:(多协议标签交换)很有发展前景的下一代网络协议。

 二.通信流程

网络通信中有两种数据帧格式:以太网帧、802帧-----选择哪种格式由TCP/IP协议簇中的网络层决定。(以太网中大多数的数据帧使用的是Ethernet II格式)

网络编程_第6张图片

1.网络中设备接收数据的处理过程(以太网帧)

网络编程_第7张图片

2.以太网中帧格式分析

 

网络编程_第8张图片

其中的源地址和目的地址是指网卡的硬件地址(也叫MAC 地址),长度是48 位,是在网卡出厂时固化的。

注意网卡芯片(例如DM9000A)收到的数据就是如上所示的一长串数据;其中包括以太网帧头、IP报报头、传输层协议段头、应用层所需数据。

以太网帧中的数据长度规定最小46 字节,最大1500 字节,ARP 和RARP 数据包的长度不够46 字节,要在后面补填充位。最大值1500 称为以太网的最大传输单元(MTU),不同的网络类型有不同的MTU,如果一个数据包从以太网路由到拨号链路上,数据包度大于拨号链路的MTU了,则需要对数据包进行分片fragmentation)。ifconfig 命令的输出中也有“MTU:1500”。注意,MTU 个概念指数据帧中有效载荷的最大长度,不包括帧首部的长度。

3.ARP协议分析

1)逻辑地址与硬件地址

硬件地址(MAC地址):数据链路层和物理层使用的地址。是识别计算机的标识符---姓名(单个局域网中不同计算机之间通信)

逻辑地址(ip地址):网络层及其以上使用的地址。是用来记在详细信息的地址----身份、年龄、家庭地址等(路由网络中不同计算机之间的通信)

2)ARP请求与ARP响应

在同一局域网内,两台计算机通信通过MAC地址通信,在非局域网内通信则通过ip地址获取MAC地址

假设A要获取B的MAC地址,其流程如下:

首先A以广播的形式发送ARP请求,其中就包括了B的ip地址,B收到请求以后,发现自己是A要寻找的主机,B以单播的形式向A返回ARP响应,其中就包括自己的MAC地址。

3)ARp协议结构分析

网络编程_第9张图片

4)ARp工作过程

网络编程_第10张图片

5) 以太网帧在网络中传输---寻路(根据最近的寻找,先找通192的在找下一级。。。直到找到最短)

网络编程_第11张图片

4.ip数据包分析

1)IP数据报结构

网络编程_第12张图片

2)Ip分类

按照IP版本分类:IPv4(13台根服务器)、IPv6(26台根服务器)

IPv4的地址位数为32位(0—42亿),也就是最多有2的32次方的电脑可以连接: 近十年来由于互联网的蓬勃发展,IP位址的需求量愈来愈大,导致IPv4定义的有限地址空间将被耗尽,(给你分配静态/动态地址)地址空间的不足必将妨碍互联网的进一步发展。为了扩大地址空间,拟通过IPv6重新定义地址空间。IPv6采用128位地址空间长度,几乎可以不受限制地提供地址。

按照状态分类:静态IP与动态IP

静态ip:一直持续不变的

动态ip:现使用先分配

按照IP身份分类:公有IP与私有IP

公有IP:指以公网连接Internet上的非保留地址。私有IP:是在本地局域网上的IP。

3)IPv4分类

IPv4地址分为五类,A类保留给政府机构,B类分配给中等规模的公司,C类分配给任何需要的人,D类用于组播,E类用于实验,各类可容纳的地址数目不同。

ip地址分为网络地址和主机地址。网络位是用来确定网络的,就相当于你生活在哪个区域。主机位就是每一台电脑所用的IP地址,就相当于你所在区域有多少人,每个人的固定住所。

A类地址范围:1.0.0.1—126.155.255.254

A类:0xxxxxxx.hhhhhhhh.hhhhhhhh.hhhhhhhh

一个A类网络主机位:2^24-2=16777214;(全为0表示任意 全为1表示广播)

B类地址范围:128.0.0.1—191.255.255.254

B类:10xxxxxx. xxxxxxxx.hhhhhhhh.hhhhhhhh

1个B类网络允许2^16-2=65534个主机IP地址;

C类地址范围:192.0.0.1—223.255.255.254

C类:110xxxxx.xxxxxxxx.xxxxxxxx.hhhhhhhh

一个网络主机位2^8-2=254个;

D类地址范围:224.0.0.1—239.255.255.254

E类地址范围:240.0.0.1—255.255.255.254

点分十进制算法:2345678988(HEX)----转成16进制----8BD0388C---每两位加一个.---8B.D0.38.8C‬‬‬‬‬‬-----每两位转换成十进制------139.208.56.140

ifconfig ens33 IP地址 修改IP地址

4)子网掩码

子网掩码只有一个作用,将某个IP地址划分成网络地址和主机地址两部分。

局域网中某主机的IP地址为202.116.1.12/21,该局域网的子网掩码为()。

A.255.255.255.0    11111111 11111111 11111000 00000000

B.255.255.252.0

C.255.255.248.0

D.255.255.240.0

5.TCP/UDP段分析

网络编程_第13张图片

TCp是传输控制协议,是一个面向连接的协议,具有可靠数据连接的保证。但是传输数据比较慢(三次握手、四次挥手)

1).三次握手(TCP连接)

TCP实现目的主机与源主机进行通信的时候,为了确保连接成功,采用三次握手方式,使得序号/确认号系统能够正常工作达成同步,如果三次握手成功,数据开始传输

第一次握手:客户端发送连接请求包SYN给服务器

第二次握手:服务器收到客户端发来的连接请求包后,服务器给客户端发送两个响应包

                      (1)向客户端发送连接请求的确认包ACK,表明服务器已经收到消息

                      (2)向客户端发送连接建立询问包SYN,询问客户端是否准备好建立连接。

第三次握手:客户端收到服务器发送来的数据以后,向服务器发送连接建立的确认包ACK,建立数据通信

      网络编程_第14张图片

2).四次挥手(TCP关闭)

TCP连接是全双工的,可以同时发送和接受数据,关闭的时候要关闭这两个方向的通道,每个方向必须单独的进行关闭。这个原则就是:当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向上的连接。当一端收到一个FIN后,它必须通知应用层另一端已经终止了那个方向的数据传送。即收到一个FIN 意味着在这一方向上没有数据流动了。

网络编程_第15张图片

第一次挥手:客户端发送FIN给服务器,请求关闭连接。

第二次挥手:服务器收到FIN后发送ACK给客户端,同时关闭自己的Receive通道。客户端收到ACK后,关闭Send通道

第三次挥手:服务器关闭连接,发送FIN给客户端。

第四次挥手:客户端收到FIN后发送ACK给服务器,同时关闭自己的Receive通道,进入TIME_WAIT状态。服务器收到ACk后,关闭自己的Send通道。

 

三.TCP编程

1.网络通信--套接字

套接字是内核中存在的一种数据结构,本质就是网络进程的ID

在网络通信中,ip地址只能确定是那一台计算机,端口号确定了该计算机中对接的哪一个进程(一个端口号一次只能分配给一个进程)

2.网络通信--端口

端口号:具有网络功能应用软件的端口号

一台服务器可以有256*256个端口号0----65535    其中0-1023是公用端口号

需要注意:TCP和UDP的端口号是可以同时监听的,两个互不影响

3.基于TCP客户端编程-步骤说明

(1)创建套接字socket

(2)编写服务器信息---ip、端口号、协议类型

(3)连接服务器connect

(4)发送接受消息 write read send rcv

(5)关闭套接字 close

4.基于TCP服务器端编程-步骤说明:

(1)创建套接字socket

(2)编写服务器信息---ip、端口号、协议类型

(3)将服务器与监听套接字进行绑定bind

(4)开启监听listen

(5)阻塞等待连接accept

(6)发送接受消息 write read send rcv

(7)关闭套接字 close

5.API:

(1)int socket(int domain,int type,int protocol)

描述:向系统注册监理一个新的套接字

参数:

damain:协议族 -指定使用何种的地址类型,完整的定义在/usr/include/bits/socket.h 内          

PF_INET/AF_INET  Ipv4 网络协议

PF_INET6/AF_INET6  Ipv6 网络协议

AF_LOCAL: UNIX 域协议

AF_ROUTE:路由套接字(socket)

AF_KEY:密钥套接字(socket)

type:套接字类型

SOCK_STREAM:字节流套接字socket, tcp方式 ;

SOCK_DGRAM:数据报套接字socket,udp方式 ;

SOCK_RAW:原始套接字socket (―般不用);

 Protocol:协议控制 通常为0

对于protocol为0(IPPROTO_IP)的raw socket。用于接收任何的IP数据包。其中的校验和和协议分析由程序自己完成。

返回值:创建成功返回套接字描述符,失败-1

(2)编写服务器信息

通用套接字地址结构

struct sockaddr
{
	uint8_t		sa_len;
	sa_family_t	sa_family;	     /* address family: AF_XXX value */
	char		    sa_data[14];	     /* protocol-specific address */IP地址   端口号
};

IPV4地址结构

struct in_addr 
{
	in_addr_t	 s_addr;		   /* 32-bit IPv4 address */
};					       /* network byet orered */
 
stuct sockaddr_in
{
	uint8_t		sin_len;	    /* length of structure */
	sa_familyt	sin_family;	/* AF_INET */
	in_port_t 	sin_port;	    /* 16-bit TCP or UDP port number */
					        /* network byet orered */
	struct in_addr	sin_addr;	    /* 32-bit IPv4 address */
					        /* network byet orered */
	char		sin_zero[8]	    /* unused */
};

所以需要将IPV4地址结构转换成通用套接字地址结构。包括字节序转换以及地址格式转换

字节序转换

需要将主机(小端)字节序转换成网络(大端)字节序,相关函数:

atoi()     //将字符串数据转换成整数(注意:转换范围不能超,且首字符如果非法,会输出0)

详见:https://blog.csdn.net/dingzj2000/article/details/89277497?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522161136916016780265489012%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=161136916016780265489012&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-1-89277497.first_rank_v2_pc_rank_v29&utm_term=atoi&spm=1018.2226.3001.4187

htonl()     //32位无符号整型的主机字节顺序到网络字节顺序的转换(小端->>大端)

htons()     //16位无符号短整型的主机字节顺序到网络字节顺序的转换  (小端->>大端)

ntohl()     //32位无符号整型的网络字节顺序到主机字节顺序的转换  (大端->>小端)

ntohs()     //16位无符号短整型的网络字节顺序到主机字节顺序的转换  (大端->>小端)

实例:ser_addr.sin_port=htons(8888);//主机字节序转换成网络字节序(端口号转换

地址格式转换

struct in_addr{

in_addr_t  s_addr;

}

(1)int inet_aton(const char *stdaddr,struct in_addr *addrptr):将点分十进制ip转换为struct in_addr

     示例:inet_aton(“192.168.1.1”,&ser_addr.sin_addr);通过传址接收数据

(2)char *inet_ntoa(struct in_addr inaddr):把struct in_addr类型的IP转换为点分十进制格式

(3) in_addr_t inet_addr(const char *straddr);作用:把点分十进制IP转换为in_addr_t(uint32)类型IP

 (3)服务器与套接字绑定

int bind(int sockfd,struct sockaddr*my_addr,int addrlen)

参数:my_addr是指向数据结构sockaddr的指针。数据结构sockaddr中包括了关于你的地址、端口和IP地址的信息。

          Addrlen结构体大小  可以设置成sizeof(struct sockaddr)。

(4)监听

int listen(int sockfd,int backlog)

backlog:监听的最大队列(允许客户端连接的最大上限)

(5)等待连接

int accept(int socket,struct sockaddr *addr,int *addrlen);

addr  为结构体指针变量,和bind的结构体是同种类型的,系统会把远程主机的信息(远程主机的地址和端口号信息)保存到这个指针所指的结构体中。

返回值:成功则返回新的socket处理代码new_fd(新文件描述符à通信),失败返回-1

(6)连接服务器

函数原型:int connect (int sockfd,struct sockaddr * serv_addr,int addrlen);

函数参数: socket 为前面socket的返回值. 即 sfd

           addr  是指向数据结构sockaddr的指针。数据结构sockaddr中包括了关于服务器地址、端口和IP地址的信息。

           addrlen表示结构体的长度,为整型指针

返回值:成功则返回0,失败返回-1

(7)发送函数

函数原型:int send(int s,const void * msg,int len,unsigned int falgs);

函数参数:S:本地套接字描述符

msg:指向要发送数据的指针

len:数据长度

flags: —般为  0

函数返回值:成功返回字节数,失败返回-1;

(8)接收函数

函数原型:int recv(int s,const void * msg,int len,unsigned int falgs);

函数参数:S:本地套接字描述符

msg:指向要接收数据的指针

len:数据长度

flags: —般为 0

函数返回值:成功返回字节数,失败返回-1;

(6)示例

服务器:

#include
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 
#include 

#define BACKLOG 100
//通用套芥子结构体类行
typedef struct sockaddr SA;
//ipv4结构体类型
typedef struct sockaddr_in SIN;

int main(int argv,char *argc[])
{

	int socket_id = socket(AF_INET,SOCK_STREAM,0);
	printf("%d\n",socket_id);

	SIN Server;
	Server.sin_family = AF_INET;
	Server.sin_addr.s_addr = inet_addr(argc[1]);
	//inet_aton();
	Server.sin_port = htons(atoi(argc[2]));

	int bind_flag = bind(socket_id,(SA *)&Server,sizeof(SIN));
	if(bind_flag == -1){
		perror("bind");
	}
	SIN client;
	bzero(&client,sizeof(SIN));
	listen(socket_id,BACKLOG);
	int addrlen = sizeof(SIN);
	int accept_id = accept(socket_id,(SA *)&client,&addrlen);//存储在套芥子结构体中,但是接受长度是根据IPV4来的
	
	char buff[128] = {0};
	while(1)
	{
		strcpy(buff,"nihao i am server");
		write(accept_id,buff,strlen(buff));
		memset(buff,0,128);
		int len = read(accept_id,buff,128);
		printf("len =%d\n",len);
		if(len > 0)
		{
			printf("buff = %s\n",buff);
		}
	}

    return 0;
}

客户端:

#include
#include           /* See NOTES */
#include 
#include 
#include 
#include 
#include 
#include 


#define BACKLOG 100
//通用套芥子结构体类行
typedef struct sockaddr SA;
//ipv4结构体类型
typedef struct sockaddr_in SIN;

int main(int argv,char *argc[])
{

	int socket_id = socket(AF_INET,SOCK_STREAM,0);
	printf("%d\n",socket_id);

	SIN Server;
	Server.sin_family = AF_INET;
	Server.sin_addr.s_addr = inet_addr(argc[1]);
	//inet_aton();
	Server.sin_port = htons(atoi(argc[2]));	
	printf("111\n");
	int connect_flag = connect(socket_id,(SA *)&Server,sizeof(SIN));
	printf("connetct_flag = %d\n",connect_flag);
	if(connect_flag == -1)
	{
		perror("connect");
		exit(0);
	}
	char buff[128]={0};
	while(1)
	{
//		send(socket_id,buff,128,0);
		int len = recv(socket_id,buff,128,0);
	//	int len =read(socket_id,buff,128);
		printf("len = %d\n",len);
		if(len > 0)
		{
			printf("rcv_buff = %s\n",buff);
			memset(buff,0,128);
			strcpy(buff,"nihao i am client");
			write(socket_id,buff,strlen(buff));
		}
	}

    return 0;
}

四.UDP编程

1.什么是UDP

UDP协议是又叫用户数据报协议,他是面向非连接的协议,即不予对方建立连接,只是把数据抛送给对方。不需要经历三次握手,因此传输效率很高,适用于通信可靠性不高或者对实时性要求很高的场合。

网络编程_第16张图片

2.​UDP客户端编程思路

1./*建立socket套接字描述符*/

2. /* 编写服务器信息*/

3./*发送数据到服务器端*/

4./*接收服务器端信息*/

5./*关闭*/

3.​UDP服务器编程思路

1./* 服务器端开始建立sock_fd描述符 */

2. /* 编写服务器信息*/

3. /* sockfd描述符与服务器进行绑定 */

4./*接收客户端发送过来的数据*/-------获取客户端的IP端口号等信息

5./*发送数据到客户端*/

6./*关闭*/

4.API

(1)int sendto ( int s , const void * msg, int len, unsigned int flags, const struct sockaddr * to , int tolen ) ;

函数参数:

s:套接字文件描述符

buf:指向要发送数据的指针 (用户空间  信息首地址)

len:数据长度

flags:—般为 0  阻塞

to:目地机的ip地址和端口号信息(通用套接字地址结构   IPV4进行强转)

tolen:地址长度

函数返回值:成功:发送的字节数 出错:-1

(2).int recvfrom(int s,void *buf,int len,unsigned int flags ,struct sockaddr *from ,int *fromlen);

函数原型:int recvfrom(int s,void *buf,int len,unsigned int flags ,struct sockaddr *from ,int *fromlen);

函数参数:  fromlen:sizeof(struct sockaddr

            addrlen: sizeof(struct sockaddr

s:套接字文件描述符

buf:指向要存储数据的指针 (存放在用户空间哪个位置----传址)  与read函数进行类比

len:数据长度

flags:—般为 0            阻塞等待

from:源主机的ip地址和端口号信息 (传址操作---保存当前连接客户端的信息ip 端口号 协议)

formlen:地址长度          标准地址结构

函数返回值:成功:接收的字节数 出错:-1

5.案例

1).单播

客户端

#include
#include
#include
#include
#include 
#include
#include 

typedef struct sockaddr SA;
typedef struct sockaddr_in SIN;


int main(int argv,char *argc[])
{
	int sockid = socket(AF_INET,SOCK_DGRAM,0);
	
	SIN Server;
	Server.sin_family = AF_INET;
	Server.sin_port = htons(atoi(argc[2]));
	Server.sin_addr.s_addr = inet_addr(argc[1]);
	
	int fromlen = sizeof(SIN);
	char buff[128] = {0};
	SIN client;
	while(1)
	{
		strcpy(buff,"hello i am client");
		int send_byte = sendto(sockid,buff,strlen(buff),0,(SA *)&Server,fromlen);
		printf("send_byte = %d\n",send_byte);
		memset(buff,0,sizeof(buff));
		printf("111\n");
		int rcv_byte = recvfrom(sockid,buff,sizeof(buff),0,(SA *)&client,&fromlen);
		printf("rcv_byte = %d\n",rcv_byte);
		if(rcv_byte != -1)
		{
			printf("IP:%s   rcv:%s\n",inet_ntoa(client.sin_addr),buff);
		}
	}
    return 0;
}

服务器

#include
#include
#include
#include
#include 
#include
#include 

typedef struct sockaddr SA;
typedef struct sockaddr_in SIN;


int main(int argv,char *argc[])
{
	int sockid = socket(AF_INET,SOCK_DGRAM,0);
	
	SIN Server;
	Server.sin_family = AF_INET;
	Server.sin_port = htons(atoi(argc[2]));
	Server.sin_addr.s_addr = inet_addr(argc[1]);
	
	int value = bind(sockid,(SA *)&Server,sizeof(SIN));
	printf("value = %d\n",value);

	int fromlen = sizeof(SIN);
	char buff[128] = {0};
	while(1)
	{
		SIN client;
		memset(buff,0,sizeof(buff));
		printf("buff = %s\n",buff);
		int rcv_byte = recvfrom(sockid,buff,sizeof(buff),0,(SA *)&client,&fromlen);
		//if(rcv_byte != -1)
		{
			printf("IP:%s   rcv:%s\n",inet_ntoa(client.sin_addr),buff);
//			sleep(5);
			memset(buff,0,sizeof(buff));
			strcpy(buff,"hello i am server");
			int send_byte = sendto(sockid,buff,strlen(buff),0,(SA *)&client,fromlen);
			printf("send_byte = %d\n",send_byte);
		}
	}
    return 0;
}

6.UDP广播

1)UDP广播类型

直接广播地址:假设本客户端ip地址是192.168.0.123,则直接广播地址是192.168.0.255,路由器可以通过查找路由表将信息转发

本地广播地址:广播地址 255.255.255.255,不会被路由器转发,会转发到相同网络段的所有主机

2)理解

UDP广播实质是客户端查找局域网内的服务器。客户端发送消息,服务器接收

客户端广播地址:INADDR_BROADCAST:255.255.255.255

服务器接收地址:INADDR_ANY:0.0.0.0

3)示例

客户端

#include 
#include
#include
#include 
#include 
#include
#include 
//通用套接字地址结构
typedef struct sockaddr    SA;
//IPV4套接字地址结构
typedef struct sockaddr_in SIN;

int Socket(int domain,int type,int protocol);
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);
int main(int argc,char * argv[])
{
	//创建监听套接字UDP--SOCK_DGRAM
	int SocketID = Socket(AF_INET,SOCK_DGRAM,0);
	printf("SocketID:%d\n",SocketID);
	int value=1;
	setsockopt(SocketID,SOL_SOCKET,SO_BROADCAST,&value,sizeof(int));
	//编写服务器信息
	SIN Sever;
	Sever.sin_family = AF_INET;
	Sever.sin_port  = htons(8888);                     //端口号填写
	Sever.sin_addr.s_addr = htonl(INADDR_BROADCAST);     //IP地址填写
	while(1)
	{
		char buff[128]={0};
		int fromlen =sizeof(SIN);
		strcpy(buff,"地震了....");
		sendto(SocketID,buff,strlen(buff),0,(SA*)&Sever,fromlen) ;	
		sleep(1);
	}
	return 0;
}
/********************包裹函数************************/
int Socket(int domain,int type,int protocol)
{
	int Socket_ID = socket(domain,type,protocol);
	if(Socket_ID < 0)
	{
		perror("创建监听套接字失败:");
		exit(0);
	}
	return Socket_ID;
}
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen)
{
	int  value= bind(sockfd,my_addr,addrlen);
	if(value < 0 )
	{
		perror("绑定失败:");
		exit(0);
	}
	return value;
}

  服务器

#include 
#include
#include
#include 
#include 
#include
#include 
//通用套接字地址结构
typedef struct sockaddr    SA;
//IPV4套接字地址结构
typedef struct sockaddr_in SIN;

int Socket(int domain,int type,int protocol);
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);

//服务器端
int main(int argc,char * argv[])
{
	//创建监听套接字UDP--SOCK_DGRAM
	int SocketID = Socket(AF_INET,SOCK_DGRAM,0);
	printf("SocketID:%d\n",SocketID);
	//编写服务器信息
	SIN Sever;
	Sever.sin_family = AF_INET;
	Sever.sin_port  = htons(8888);                   //端口号填写
	Sever.sin_addr.s_addr = htonl(INADDR_ANY);       //IP地址填写1
	//绑定服务器信息
	Bind(SocketID,(SA *)&Sever,sizeof(SIN));
	while(1)
	{
		char buff[128]={0};
		SIN Client;
		bzero(&Client,sizeof(Client));
		int fromlen =sizeof(struct sockaddr);

		int len = recvfrom(SocketID,buff,sizeof(buff),0,(SA*)&Client,&fromlen);
		if(len >0)
		{
			printf("IP%s:%s\n",inet_ntoa(Client.sin_addr),buff);
		}
	}
	return 0;
}
/********************包裹函数************************/
int Socket(int domain,int type,int protocol)
{
	int Socket_ID = socket(domain,type,protocol);
	if(Socket_ID < 0)
	{
		perror("创建监听套接字失败:");
		exit(0);
	}
	return Socket_ID;
}
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen)
{
	int  value= bind(sockfd,my_addr,addrlen);
	if(value < 0 )
	{
		perror("绑定失败:");
		exit(0);
	}
	return value;
}

7.UDP组播

就是讲发送消息的客户端组成一个组,该组播服务器发送消息。

思路:服务器绑定224.0.0.0至239.255.255.255之间的IP地址,将消息发送至该地址sendto

           客户端加入224.0.0.0至239.255.255.255之间的IP地址(本地址存在),通过setsockopt加入多播组,接收消息。

224.0.0.0至239.255.255.255之间的IP地址:

224.0.0.0~224.0.0.255             :局部链路组播地址 个人用

224.0.1.0~238.255.255.255    :用户组播地址  花钱用

239.0.0.0~239.255.255.255    :本地管理组播地址

局部链路组播地址:

         保留给本地网段上的网络协议使用。路由器不转发目标地址为这段地址的数据包

用户组播地址(全球范围地址):

         公司可以使用这段地址在组织之间和通过Internet以组播方式传输数据。

本地管理组播地址(限制范围地址):

使用这些地址的组播被限定在本地或组织内。公司、大学和其他组织使用有限范围地址来进行本地组播。
与Internet相连的边缘路由器不会将这段多播地址帧转发到外网。

服务器:

/*************************************************************************
  > File Name: main.c
  > Author: csy
  > Mail: [email protected] 
  > Created Time: 2021年03月09日 星期二 16时44分00秒
 ************************************************************************/

#include 
#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 


int main(int argc , char *argv[])
{
  int sockfd ;
  struct sockaddr_in client_addr,server_addr;
  socklen_t addrlen = sizeof(struct sockaddr_in);
  char rcvBuff[32] = {0};

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sockfd < 0) {
    perror("socket");
    return -1;
  }

  client_addr.sin_family = AF_INET;  //设置地址家族
    client_addr.sin_port = htons(8000);  //设置端口
    client_addr.sin_addr.s_addr = INADDR_ANY; //设置地址
    bind(sockfd,(struct sockaddr *)&client_addr,addrlen);

  while (1) {
  
    recvfrom(sockfd,rcvBuff,32,0,(struct sockaddr*)&server_addr,&addrlen);
    printf("%s\n",rcvBuff);
    memset(rcvBuff,0,32);
  }


    return 0;
}

客户端:

/*************************************************************************
  > File Name: main.c
  > Author: csy
  > Mail: [email protected] 
  > Created Time: 2021年03月09日 星期二 16时44分00秒
 ************************************************************************/

#include 
#include 
#include 
#include           /* See NOTES */
#include 
#include 
#include 


int main(int argc , char *argv[])
{
  int sockfd ;
  char rcvBuff[32] = {0};
  struct sockaddr_in client_addr,server_addr;
  socklen_t addrlen = sizeof(struct sockaddr_in);  

  sockfd = socket(AF_INET, SOCK_DGRAM, 0);
  if (sockfd < 0) {
    perror("socket");
    return -1;
  }

  client_addr.sin_family = AF_INET;
  client_addr.sin_port = htons(8000);
  client_addr.sin_addr.s_addr = INADDR_ANY;
  bind(sockfd,(struct sockaddr*)&client_addr,addrlen);

  struct ip_mreq mreq = {0};
  mreq.imr_multiaddr.s_addr = inet_addr("224.0.0.100");
  mreq.imr_interface.s_addr = INADDR_ANY;
  setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

  while (1) {
  
    recvfrom(sockfd,rcvBuff,32,0,(struct sockaddr*)&server_addr,&addrlen);
    printf("%s\n",rcvBuff);
    memset(rcvBuff,0,32);
  }


    return 0;
}

套接字如何加入多播组:

level

optname

optval

IPPROTO_IP

IP_ADD_MEMBERSHIP   //加入多播组

IP_DROP_MEMBERSHIP  //离开多播组

struct ip_mreq          

struct in_addr imn_multiaddr;      (组播Ip)

/*加入或者退出的广播组IP地址*/ 

         struct in_addr imr_interface;    (本地ip)

  /*加入或者退出的网络接口IP地址*/

};

 

         注: 1.如果自己加入了群组,自己发送的数据包自己也会接收到,因为自己也加入到了群组。

                 2.如果仅仅只是发送消息,可以不加入多播组,只需要直接把消息投递到多播组就行了

 

五.网络编程服务器模型

1.回射服务器

网络编程_第17张图片

2.迭代服务器

主要机制是服务器向客户端发送完数据就关闭客户端,然后等待新的客户端连接,这样做就是减少资源的浪费(比如请求时间)

网络编程_第18张图片

/*************************************************************************
	> File Name: 0211.c
	> Author: 信盈达-刘工
	> Mail: [email protected] 
	> Created Time: 2020年02月11日 星期二 14时11分48秒
 ************************************************************************/
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 
#include 

int Socket(int domain,int type,int protocol);
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);
int Listen(int sockfd,int backlog);
int Accept(int socket,struct sockaddr *addr,int *addrlen);

//通用套接字结构
typedef struct sockaddr SA;
//IPV4套接字地址结构
typedef struct sockaddr_in SIN;



int main(int argc,char *argv[])
{
	struct tm * time_struct;
	time_t  number ;
	char buff[512]={0};
	pthread_t ID;
	//1、	创建监听套接字:socket
	int Socket_id =  Socket(AF_INET,SOCK_STREAM,0);
	//2、	编写服务器地址信息(服务器:IP  端口号  协议)
	SIN seraddr;
	//对该变量进行清空操作 memset bzero
	bzero(&seraddr,sizeof(SIN));           //清空
	seraddr.sin_family = AF_INET;          //协议
	seraddr.sin_port=htons(atoi(argv[2]));  //端口号
	seraddr.sin_addr.s_addr= inet_addr(argv[1]);  //IP地址
	//3、	服务器信息与监听套接字绑定:bind  编写为IPV4   填入通用
	Bind(Socket_id,(SA*)(&seraddr),sizeof(SA));
	//4、	开始监听:listen
    Listen( Socket_id,100);
	while(1)
	{
		//等待客户端连接:accept
		SIN cliaddr;
		int addrlen = sizeof(SA);
		int clifd =  Accept(Socket_id,(SA*)(&cliaddr),&addrlen);
        //获取时间
		number = time(NULL);
		time_struct = gmtime(&number); 
		sprintf(buff,"%d-%d-%d %d:%d:%d\n",(time_struct->tm_year + 1900),time_struct->tm_mon ,time_struct->tm_mday,time_struct->tm_hour,time_struct->tm_min, time_struct->tm_sec);
		//发送时间信息
		write(clifd,buff,strlen(buff));
		memset(buff,0,512);
		close(clifd);
	}
	//7、	关闭监听套接字:close
	close(Socket_id);

	return 0;
}
//包裹函数
int Socket(int domain,int type,int protocol)
{
	int value = socket( domain,type, protocol);
	if(value == -1)
	{
		perror("Socket创建错误:");
		exit(0);
	}
	printf("创建监听套接字成功\n");
	return value;
}
//包裹函数
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen)
{
	int value = bind( sockfd, my_addr,addrlen);
	if(value == -1)
	{
		perror("绑定失败:");
		exit(0);
	}
	printf("绑定成功\n");
	return value;
}
//包裹函数
int Listen(int sockfd,int backlog)
{
	int value =listen( sockfd, backlog);
	if(value == -1)
	{
		perror("监听失败:");
		exit(0);
	}
	printf("监听成功\n");
	return value;
}
//等待链接函数  本函数---是阻塞函数  未有客户端链接,处于阻塞状态
int Accept(int socket,struct sockaddr *addr,int *addrlen)
{
	int value = accept( socket,addr,addrlen);
	if(value == -1)
	{
		perror("等待链接失败\n");
		exit(0);
	}
	return value;
}

3.并发服务器

多个客户端连接服务器,没来一个客户端就分配一个进程,达到服务器内多个进程并发。

需要注意:对于多线程来讲,newID会随着线程的创建发生变化

 

网络编程_第19张图片

#include 
#include
#include
#include 
#include 
#include
#include 
//通用套接字地址结构
typedef struct sockaddr    SA;
//IPV4套接字地址结构
typedef struct sockaddr_in SIN;
#define Backlog 100
int Socket(int domain,int type,int protocol);
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen);
int Listen(int s,int backlog);
int Accept(int s,struct sockaddr * addr,int * addrlen);


int main(int argc,char * argv[])
{
	//创建监听套接字
	int SocketID = Socket(AF_INET,SOCK_STREAM,0);
	printf("SocketID:%d\n",SocketID);
	//编写服务器信息
	SIN Sever;
	Sever.sin_family = AF_INET;
	Sever.sin_port  = htons(atoi(argv[2]));         //端口号填写
	Sever.sin_addr.s_addr = inet_addr(argv[1]);     //IP地址填写
	//绑定服务器信息
	Bind(SocketID,(SA *)&Sever,sizeof(SIN));
	//监听
	Listen(SocketID,Backlog);
	//等待连接
	SIN Client;
	bzero(&Client,sizeof(SIN));                     //清零操作
	int addrlen =sizeof(SIN);
	while(1)
	{
		int NewID = Accept(SocketID,(SA*)&Client,&addrlen);
		pid_t pid = fork();
		if(pid > 0)
		{
		}
		else if(pid == 0)
		{
			while(1){
				char buff[128]={0};
				read(NewID,buff,sizeof(buff));
				printf("%s\n",buff);
				write(NewID,buff,strlen(buff));
			}
		}
	}
	return 0;
}
/********************包裹函数************************/
int Socket(int domain,int type,int protocol)
{
	int Socket_ID = socket(domain,type,protocol);
	if(Socket_ID < 0)
	{
		perror("创建监听套接字失败:");
		exit(0);
	}
	return Socket_ID;
}
int Bind(int sockfd,struct sockaddr * my_addr,int addrlen)
{
	int  value= bind(sockfd,my_addr,addrlen);
	if(value < 0 )
	{
		perror("绑定失败:");
		exit(0);
	}
	return value;
}

int Listen(int s,int backlog)
{
	int  value= listen(s,backlog);
	if(value < 0 )
	{
		perror("监听失败:");
		exit(0);
	}
	return value;
}
int Accept(int s,struct sockaddr * addr,int * addrlen)
{
	int New_ID = accept(s,addr,addrlen);
	if(New_ID < 0 )
	{
		perror("等待连接失败:");
		exit(0);
	}
	return New_ID;
}

六.NAT映射

NAT:网络地址转换。(转换IP  或 转换端口号

NAT映射是一种访问广域网的技术,用于将私有ip转换为公网ip,用于抵御外部网络攻击。

转换端口号:举例来说,在某个网络的防火墙上有1个公网地址,200.201.30.41,定义了1个nat,分别是200.201.30.41——>192.168.0.0/16
       则内网中,所有192.168.0.0网段的用户上网时显示的公网ip都是200.201.30.41,那么如何区分是来自同一局域网内的哪个主机的ip呢。通过转换端口号

比如192.168.0.111 端口号是888     192.168.0.222 端口号是777 

当两个ip访问外网的时候,本地端口号的888和777会映射在该公网下不同的端口号,在200.201.30.41重新映射两个端口号808(对应888) 707(对应777)。

这样的话就相当于是同一台电脑(公网)不同的端口(实际就是不同的电脑)访问

 

网络编程_第20张图片

1.NAT映射优点

①极大的节省了合法的IP地址。

②能够处理地址重复情况,避免了地址的重新编号,增加了编址的灵活性。

③隐藏了内部网络地址,增强了安全性。

④可以使多个使用TCP负载特性的服务器之间实现基本的数据包负载均衡。

2.NAT映射缺点

①由于NAT要在边界路由器上进行地址的转换,增大了传输的延迟。

②由于NAT改动了IP地址,失去了跟踪端到端IP流量的能力。当出现恶意流量时,会使故障排除和流量跟踪变的更加棘手。

③不支持一些特定的应用程序。如早期版本的MSN。

④增大了资源开销。处理NAT进程增加了CPU的负荷,并需要更多内存来存储NAT表项。

七.内网穿透 

相当于拥有一个云服务器,告诉通信双方的ip和端口号

1.注册账号

本阶段使用飞鸽穿透产品: https://www.fgnwct.com/ 

2.开通隧道

网络编程_第21张图片

3.下载客户端

网络编程_第22张图片

4.本阶段采用Linux(x86)32

5.移植npc至ubuntu16.04系统下

网络编程_第23张图片

网络编程_第24张图片

6.运行结果

网络编程_第25张图片

 

八.注意

1、套接字连接失败服务器器端遇到问题?

服务器和客户端连接期间,套接字被强制关闭,需要等待一段时间服务器才能重新绑定IP。

解决方法:

int reuse=1;

setsockopt(s,SOL_SOCKET ,SO_REUSEADDR,(const char*)& reuse,sizeof(int));//允许套接字重用,主要用于服务器套接字,放在bind之前。

2、防火墙关闭:sudo ufw disable

你可能感兴趣的:(Linux系统编程,网络通信,网络)