网络编程一:网络体系结构+socket概念+TCP通信过程

一、回顾系统编程中进程的通信方式

1、管道
    无名管道 (只能作用于亲缘间进程通信) ----pipe   write  read 
    有名管道  (系统中任意两个进程)    -----mkfifo
2、信号 
    发送信号 ---kill
    捕捉信号----signal
3、IPC对象 
    消息队列 ----带有数据标识的特殊管道  ftok() msgget msgsnd  msgrcv msgctl
    共享内存 ----双方进程 可以同时 对一片内存进行读写  ftok shmget  shmat shmdt
    信号量 ------不属于通信方式,只是一种互斥的方式 ftok semget  semop  semctl

特点:只能在同一台主机上内部通信,不能跨平台。

二、网络编程(套接字编程)

1、特点 
        既可以在同一台主机上内部通信,还可以在不同主机之间通信。
            自己的ubuntu  ---- 自己的ubuntu
            自己的ubuntu  ---- 同一个局域网内除了自己之外的任意一台主机

        总结: 网络通信的前提是 在同一个局域网内。

2、协议         
        在不同的主机之间通信,双方都必须遵循的同一种规则。

        
阿帕网(arpanet)

使用协议:网络控制协议(NCP) Network Control Program
    
缺点:不能互联不同类型的计算机 和 不同类型的操作系统,同时也没有纠错功能


因特网(Internet)        

由于阿帕网(arpanet)的局限,所以在其基础上扩展 研发出了 因特网(Internet),并且引入了TCP/IP协议。TCP/IP(Transmission Control Protocol/Internet Protocol,传输控制协议/网际协议)是指能够在多个不同网络间实现信息传输的协议簇。TCP/IP协议不仅仅指的是TCP 和IP两个协议,而是指一个由FTP、SMTP、TCP、UDP、IP等协议构成的协议簇, 只是因为在TCP/IP协议中TCP协议和IP协议最具代表性,所以被称为TCP/IP协议。

            TCP/IP协议:传输控制协议/网际协议 。
            
            TCP协议: 用于检测网络中传输差错
            IP协议: 负责不同的网络之间的通信。
            
            也就是说,TCP主要是负责 数据的传输问题,一旦有问题发出信号,要求重新传输,直到数据安全到达为止。IP给每一台主机分配一个IP地址。

三、计算机网络体系结构

1、概念
    指的是 主机内部集成的结构和各层协议的集合。
    每台主机本身就存在一个相同的网络体系结构。
2、作用 
    封装数据  和  解析数据
3、分类 
        ISO/OSI参考模型
        TCP/IP参考模型

四、ISO/OSI参考模型    (七层)

网络编程一:网络体系结构+socket概念+TCP通信过程_第1张图片

1、概念 
    

        OSI从逻辑上,把一个网络系统分为功能上相对独立的7个有序的子系统,这样OSI体系结构就由功能上相对独立的7个层次组成,如图1所示。它们由低到高分别是物理层、数据链路层、网络层、传输层、会话层、表示层和应用层。
    
        
应用层:确定这个数据应该以什么样的API接口 进行处理
表示层:数据的压缩和解压、加密和解密这些工作都是由表示层完成的。
会话层:访问验证,通信之前建立TCP会话
传输层:控制 和 检测 数据传输过程中是否出错,TCP协议 、UDP协议
网络层:主要是IP协议,识别网络中不同的主机 
数据链路层:1、将数据组合成数据块,也就是一帧帧数据(给数据块添加数据头和数据尾)
物理层:承担数据传输的物理媒体(通信的通道),以太网口


2、OSI参考模型 处理数据的效率很低,这个模型 已经被TCP/IP模型所取消

五、TCP/IP协议模型(四层)

网络编程一:网络体系结构+socket概念+TCP通信过程_第2张图片

1、层次 

应用层: Telnet  FTP  HTTP
传输层: TCP UDP 
网际层: IP  ICMP
网络接口层:以太网 

2、每经过一层,会一层层添加头数据,当数据到达对方时,会一层层解析头数据

3、在上面模型中 ,传输层协议: TCP协议 、UDP协议(重点)

六、研究传输层协议

TCP协议(打电话)

1、概念 
    用来检测网络传输中差错的传输控制协议 (TCP,Transmission Control Protocol)。是一种面向连接的传输层协议,它能够提供高可靠性通信(也就是 数据 无误、数据无丢失、数据无失序、数据无重复到达的通信)

2、应用场合 
    1)对传输质量要求高、以及传输大量数据的通信
    2)在需要可靠性数据传输的场合,通常使用TCP协议
    3)QQ等 通信软件的用户登录账号管理 的功能,采用TCP

UDP(写信)

1、概念 
        用户数据报协议(UDP,User Datagram Protocol)。是一种不可靠、没有连接的协议。在数据发送之前,因为不需要进行连接,所以可以进行高效率的数据传输。
            
2、应用场合  
        1)发送小尺寸数据 
        2) 适用于 广播 /组播 通信 
        3)qq等通讯 软件中 点对点 文本通信  以及音视频

七、网络编程(套接字编程、socket编程)中几个重要概念

1、socket

插座、套接字    插座的种类比较多,就像很多个协议一样,必须提前设置好协议

    1)是一个编程的函数接口,建立套接字
    2)不管你是使用TCP协议  还是 用 UDP协议,首先必须使用socket进行确定 
    
        比如: 我们现在使用TCP协议     
            套接字文件描述符 socketfd = socket(TCP协议);
            普通文件 : int fd = open(文件的名字);---》
    3)套接字 是一种 特殊的 文件描述符 

2、IP地址

1、概念 
    用来标识 网络中不同的主机。通信的前提必须要有一个IP地址 

2、分类 
    IPV4地址 ---32位  IPV6地址 --128位

3、如何表示 
    点分十进制  "192.168.14.2"   网络号(网段 192.168.14) +  主机号(2) 

4、数据包中 都必须要包含 目的IP地址、源IP地址。

3、端口号(无符号16位整数)

1、概念 
    标识 同一台主机内不同的应用程序。

                网络
主机A        ---------------- 主机B

qq                                             qq 
微信                                        微信
soul    “想你了,宝”                        soul


192.168.14.2                192.168.14.5 ---必须在同一个局域网

“想你了,宝” + 192.168.14.2 + 192.168.14.5 + 50000


端口号: 
    1)系统占用端口号: 1-1023(用户不能再次使用这些端口号进行通信)
    2) 可用端口号:1024~65535    


端口号错误 ---》连接错误

4、字节序

网络编程一:网络体系结构+socket概念+TCP通信过程_第3张图片

1)、概念 
    一个多字节存储单元的低地址 存储数据的高有效位 还是 低有效位 ,说白了,也就是说 数据 在计算机内存中以什么样的方式存储
    
    
2)分类 
    小端序:数据的低有效位 存储  在 内存的 低地址
    大端序:数据的低有效位 存储  在 内存的 高地址

3)为了避免不同类别的主机之间 在数据交换时由于字节序的不同导致的差错,引入了网络字节序。也就是说统一规定所有的主机通过网络发送数据包时转为 大端序 ,也就是网络字节序。

    网络字节序---》一定是大端序。
    本地字节序---》取决于 主机  x86是小端  ARM是大端
    
总结: 无论当前你的主机是大端 还是 小端,都必须把自身的字节序 转成 网络字节序,即大端序,才能在网络中传输数据。

八、关于网络配置 

window: 192.168.14.2
ubuntu: 192.168.14.3 
开发板:192.168.14.4 

    

在ubuntu中 : 

1、ifconfig --查看IP 获取 网卡名字  


ubuntu12.04      eth0
ubuntu18.04       ens33

2、配置 
    ifconfig  eth0 192.168.14.3

3、ubuntu与window进行ping连接 
    ping 192.168.14.2(window)

gec@ubuntu:/mnt/hgfs/gz2166/00C习题/2021.10.28笔试题/043广东华中科技大学工业
��术研究院$ ping 192.168.14.2
PING 192.168.14.2 (192.168.14.2) 56(84) bytes of data.
64 bytes from 192.168.14.2: icmp_req=1 ttl=128 time=0.299 ms
64 bytes from 192.168.14.2: icmp_req=2 ttl=128 time=0.240 ms
64 bytes from 192.168.14.2: icmp_req=3 ttl=128 time=0.195 ms

九、TCP通信过程

网络编程一:网络体系结构+socket概念+TCP通信过程_第4张图片

客户端通信: 


1、买手机(建立套接字)

    #include          /* See NOTES */
    #include

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

函数作用:建立套接字

参数: 
    domain:你要选择哪一种地址族 
            PF_INET/AF_INET ------Ipv4 网络协议
            PF_INET6/AF_INET6----- Ipv6 网络协议
    type:  你要选择哪一种 协议(TCP  UDP)
            SOCK_STREAM --流式套接字 TCP 
            SOCK_DGRAM -数据报套接字  UDP 
    protocol:一般设置成 0
    
返回值: 
        成功返回  套接字文件描述符 
        失败  -1
        
2、绑定自己的电话号码(绑定自己的IP地址 和端口号)        
        
    #include          /* See NOTES */
    #include

    int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    
参数:    
        sockfd:套接字文件描述符
        addr:自己的IP地址和端口号 
        addrlen:结构体的大小 
        
返回值 :
        成功则返回 0, 
        失败返回-1, 错误原因存于 errno 中
        
struct sockaddr  --旧的结构体
{
    unsigned short sa_family; /*地址族*/
    char sa_data[14];/*14字节的协议地址,包含该socket的IP地址和端口号。*/
};    

//IPV4结构体      
struct sockaddr_in 
{
    short int sin_family; /*地址族  IPV4   IPV6*/
    unsigned short int sin_port; /*端口号*/
    struct in_addr sin_addr; /*IP地址*/
    unsigned char sin_zero[8]; /*填充0 以保持与struct sockaddr同样大小*/
};
struct in_addr { 
    in_addr_t s_addr; /*in_addr_t为 32位的unsigned int,该无符号整数采用大端字节序。*/
};    
    
    
    
扩展函数: 

1、将本地IP地址转为网络IP地址    
in_addr_t inet_addr(const char *cp);

参数: 
    cp:本地IP地址
返回值: 返回网络IP地址

    
    
3、开始打电话(发起连接)    
    
    #include

    int connect(int socket, const struct sockaddr *address,socklen_t address_len);
    
参数:    
        sockfd:套接字文件描述符    
        address:对方的IP地址和端口号 
        address_len:结构体的大小 

4、聊天

    #include

    ssize_t  send(int  socket,  const  void  *buffer,  size_t  length, int flags);

参数:    
        sockfd:套接字文件描述符
        buffer:你要发送的数据 
        length:你要发送的数据大小  strlen
        flags: 一般为0 
返回值: 
        成功返回 发送的数据字节数 
        失败返回 -1
        
5、关闭 
    close(socketfd);        
    
    
服务器 :


3、设置铃声(监听)

    #include

    int listen(int socket, int backlog);

参数:    
        sockfd:套接字文件描述符    
        backlog:同时支持 最多的客户端连接个数 
        
4、坐等电话(阻塞接收客户端的连接)

#include

    int accept(int socket, struct sockaddr * address,socklen_t * address_len);

参数:    
        sockfd:套接字文件描述符
        address:存储已经连接上来的客户端的IP地址和端口号 ,如果不关系客户端的IP地址和端口号,可以设置成NULL
        address_len:结构体的大小 

返回值: 
        成功返回  已经连接上来的新的客户端的套接字文件描述符 
        失败返回  -1

十、扩展函数

#include
#include
#include

in_addr_t inet_addr(const char *cp); 

函数作用: 
        将本地IP地址转为网络IP地址

参数: 
    cp:本地IP地址
    
返回值:返回网络IP地址 ,注意是 in_addr_t
    
char *inet_ntoa(struct in_addr in); //n 网络   a 本地

函数作用: 
        将网络IP地址 转为本地IP地址    
        
参数:
        in:网络IP地址  注意是 struct in_addr 类型 
返回值:
        本地IP地址
        
        
#include

uint16_t htons(uint16_t hostshort); //h表示本地 n表示网络  s表示 unsigned short int 16

函数作用:将本地端口号转为网络端口号

参数: 
        hostshort 本地端口号
返回值: 
        返回网络端口号

uint16_t ntohs(uint16_t netshort);

函数作用:将网络端口号转为本地端口号

参数: 
        netshort 网络端口号
返回值: 
        返回本地端口号

十一、代码例程

1、01TCP客户端.c

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

#define  OWNADDR  "192.168.14.3"  //我自己电脑的ip地址
#define  OWNPORT  10000 //我自己电脑的该程序的端口号

#define  SERVERADDR  "192.168.14.3"  //对方的  服务器的IP地址
#define  SERVERPORT  20000  //对方的  服务器的端口号 

int main()
{
	//1、买手机(建立套接字)
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if(socketfd == -1)
	{
		printf("没钱了....,失败\n");
		return -1;
	}
	//2、绑定自己的电话号码(绑定自己的IP地址 和端口号)
	//定义一个IPV4结构体变量,初始化自己的IP地址和端口号
	struct sockaddr_in ownAddr;
	ownAddr.sin_family = AF_INET;/*地址族  IPV4*/
	ownAddr.sin_port = htons(OWNPORT); //htons 将本地端口号转为网络端口号
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR); //将本地IP地址转为网络IP地址
	
	bind(socketfd, (struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//3、开始打电话(发起连接)
	struct sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;/*地址族  IPV4*/
	serverAddr.sin_port = htons(SERVERPORT); //htons 将本地端口号转为网络端口号
	serverAddr.sin_addr.s_addr = inet_addr(SERVERADDR); //将本地IP地址转为网络IP地址

	connect(socketfd,(struct sockaddr *)&serverAddr,sizeof(struct sockaddr_in));
	
	
	//4、聊天
	while(1)
	{
		printf("data:");
		char buf[1024]={0};
		scanf("%s",buf);
		//发送数据
		send(socketfd, buf, strlen(buf), 0);
	}
	//5、关闭 
	close(socketfd);
	
	return 0;
}



2、02TCP服务器.c

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

#define  OWNADDR  "192.168.14.3"  //我自己电脑的ip地址
#define  OWNPORT  20000 //我自己电脑的该程序的端口号

int main()
{
	//1、买手机(建立套接字)
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if(socketfd == -1)
	{
		printf("没钱了....,失败\n");
		return -1;
	}
	//因为服务器立马退出之后,端口号不会及时释放
	//此时如果服务器又马上运行,那么端口号会被占用,导致服务器分配端口号失败,连接失败
	//所以设置端口号可以复用
	int optval = 1;
	setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
	
	//2、绑定自己的电话号码(绑定自己的IP地址 和端口号)
	//定义一个IPV4结构体变量,初始化自己的IP地址和端口号
	struct sockaddr_in ownAddr;
	ownAddr.sin_family = AF_INET;/*地址族  IPV4*/
	ownAddr.sin_port = htons(OWNPORT); //htons 将本地端口号转为网络端口号
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR); //将本地IP地址转为网络IP地址
	
	bind(socketfd, (struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//3、设置铃声(监听) listen
	listen(socketfd,5);
	
	//4、坐等电话(阻塞接收客户端的连接)
	printf("等待客户端连接.......\n");
	//定义一个IPV4结构体变量,存储连接上来的客户端的IP地址 和 端口号 
	struct sockaddr_in clientAddr;
	//如果你要想要获取对方的IP地址和端口号,第三个参数必须把结构体的大小传递进去
	int len = sizeof(struct sockaddr_in);
	
	int newClientFd = accept(socketfd,(struct sockaddr*)&clientAddr,&len);
	if(newClientFd != -1)
	{
		printf("有客户端连接上来了............\n");
		//打印连接上来的客户端的IP地址和端口号,将网络字节序转为 本地字节序
		printf("连接上来的客户端IP:%s 端口号:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
	}
	while(1)
	{
		char buf[1024]={0};
		//重点重点重点:recv 一定是使用 已经连接上来的新客户端的套接字文件描述符 newClientFd
		int ret = recv(newClientFd,buf,sizeof(buf),0);
		if(ret == 0)//代表客户端退出
		{
			printf("客户端退出.....\n");
			break;
		}
		printf("recv:%s\n",buf);
	}
	//5、关闭 
	close(socketfd);
	close(newClientFd);
	
	return 0;
}

十二、端口号复用

    #include  
    #include  
    int setsockopt(int s, int level, int optname, const void * optval, , socklen_toptlen);

函数作用:设置端口号可以复用


参数: 
        s:套接字文件描述符
        level 代表欲设置的网络层, 一般设成 SOL_SOCKET 以存取 socket 层
        optname:SO_REUSEADDR 端口号复用 
        optval 代表欲设置的值, 比如给它一个1 
        optlen 则为 optval 的长度.

//所以设置端口号可以复用,这两条语句放在 绑定bind 之前
    int optval = 1;
    setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));

十三、代码例程2

1、03TCP客户端+多线程.c

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

#define  OWNADDR  "192.168.14.3"  //我自己电脑的ip地址
#define  OWNPORT  10000 //我自己电脑的该程序的端口号

#define  SERVERADDR  "192.168.14.3"  //对方的  服务器的IP地址
#define  SERVERPORT  20000  //对方的  服务器的端口号 

void * start_routine(void *arg)
{
	int socketfd = *((int*)arg);//强转类型 并且解引用 得到socketfd
	
	//接收服务器的数据
	while(1)
	{
		char buf[1024]={0};
		int ret = recv(socketfd,buf,sizeof(buf),0);
		if(ret == 0)
			break;
		
		printf("recv:%s\n",buf);
	}
	
	pthread_exit(NULL);
}

int main()
{
	//1、买手机(建立套接字)
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if(socketfd == -1)
	{
		printf("没钱了....,失败\n");
		return -1;
	}
	//2、绑定自己的电话号码(绑定自己的IP地址 和端口号)
	//定义一个IPV4结构体变量,初始化自己的IP地址和端口号
	struct sockaddr_in ownAddr;
	ownAddr.sin_family = AF_INET;/*地址族  IPV4*/
	ownAddr.sin_port = htons(OWNPORT); //htons 将本地端口号转为网络端口号
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR); //将本地IP地址转为网络IP地址
	
	bind(socketfd, (struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//3、开始打电话(发起连接)
	struct sockaddr_in serverAddr;
	serverAddr.sin_family = AF_INET;/*地址族  IPV4*/
	serverAddr.sin_port = htons(SERVERPORT); //htons 将本地端口号转为网络端口号
	serverAddr.sin_addr.s_addr = inet_addr(SERVERADDR); //将本地IP地址转为网络IP地址

	connect(socketfd,(struct sockaddr *)&serverAddr,sizeof(struct sockaddr_in));
	
	//创建一条线程,接收服务器的数据
	pthread_t thread;
	pthread_create(&thread,NULL,start_routine,&socketfd);

	
	//4、聊天
	while(1)
	{
		printf("data:");
		char buf[1024]={0};
		scanf("%s",buf);
		//发送数据
		send(socketfd, buf, strlen(buf), 0);
	}
	//5、关闭 
	close(socketfd);
	
	//接合线程 --阻塞等待子线程退出,回收资源
	pthread_join(thread,NULL);
	
	return 0;
}



2、04TCP服务器+多线程.c

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

#define  OWNADDR  "192.168.14.3"  //我自己电脑的ip地址
#define  OWNPORT  20000 //我自己电脑的该程序的端口号


void * start_routine(void *arg)
{
	int newClientFdfd = *((int*)arg);//强转类型 并且解引用 得到socketfd
	
	//给客户端发送数据
	while(1)
	{
		printf("data:");
		char buf[1024]={0};
		scanf("%s",buf);
		//发送数据
		send(newClientFdfd, buf, strlen(buf), 0);
	}
	
	pthread_exit(NULL);
}

int main()
{
	//1、买手机(建立套接字)
	int socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if(socketfd == -1)
	{
		printf("没钱了....,失败\n");
		return -1;
	}
	//因为服务器立马退出之后,端口号不会及时释放
	//此时如果服务器又马上运行,那么端口号会被占用,导致服务器分配端口号失败,连接失败
	//所以设置端口号可以复用
	int optval = 1;
	setsockopt(socketfd, SOL_SOCKET, SO_REUSEADDR,&optval, sizeof(optval));
	
	//2、绑定自己的电话号码(绑定自己的IP地址 和端口号)
	//定义一个IPV4结构体变量,初始化自己的IP地址和端口号
	struct sockaddr_in ownAddr;
	ownAddr.sin_family = AF_INET;/*地址族  IPV4*/
	ownAddr.sin_port = htons(OWNPORT); //htons 将本地端口号转为网络端口号
	ownAddr.sin_addr.s_addr = inet_addr(OWNADDR); //将本地IP地址转为网络IP地址
	
	bind(socketfd, (struct sockaddr *)&ownAddr,sizeof(struct sockaddr_in));
	
	//3、设置铃声(监听) listen
	listen(socketfd,5);
	
	//4、坐等电话(阻塞接收客户端的连接)
	printf("等待客户端连接.......\n");
	//定义一个IPV4结构体变量,存储连接上来的客户端的IP地址 和 端口号 
	struct sockaddr_in clientAddr;
	//如果你要想要获取对方的IP地址和端口号,第三个参数必须把结构体的大小传递进去
	int len = sizeof(struct sockaddr_in);
	
	int newClientFd = accept(socketfd,(struct sockaddr*)&clientAddr,&len);
	if(newClientFd != -1)
	{
		printf("有客户端连接上来了............\n");
		//打印连接上来的客户端的IP地址和端口号,将网络字节序转为 本地字节序
		printf("连接上来的客户端IP:%s 端口号:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));
	}
	//创建一条线程,给客户端发送数据
	//注意 使用的是 连接上来的客户端的新的套接字文件描述符newClientFd
	pthread_t thread;
	pthread_create(&thread,NULL,start_routine,&newClientFd);
	
	while(1)
	{
		char buf[1024]={0};
		//重点重点重点:recv 一定是使用 已经连接上来的新客户端的套接字文件描述符 newClientFd
		int ret = recv(newClientFd,buf,sizeof(buf),0);
		if(ret == 0)//代表客户端退出
		{
			printf("客户端退出.....\n");
			break;
		}
		printf("recv:%s\n",buf);
	}
	//5、关闭 
	close(socketfd);
	close(newClientFd);
	
	//接合线程 --阻塞等待子线程退出,回收资源
	pthread_join(thread,NULL);
	
	return 0;
}

你可能感兴趣的:(Linux网络编程,linux)