网络基础socket

1. 基本概念

1.1 网络设计模式

  1. B/S: 客户端:浏览器B;服务器:服务器S;
    (1)优势:跨平台;开发成本低。
    (2)劣势:固定的协议(http/https);无法处理大的数据;
  2. C/S: 客户端:桌面应用程序C;服务器:后台服务器S;
    (1)优势:可以处理大量的磁盘数据;
    (2)劣势:跨平台的开发成本高;

1.2 IP和端口

1.2.1 IP地址
  1. IPV4:本质是一个32bit的整型数:即4Byte的int型数据;
    (1)实际用点分10进制字符串描述,分成4份, 每份1字节8bit, 范围0~255;
    (2)最大取值 255.255.255.255;
    (3)IPv4一共可以表示2^32-1个地址;
  2. IPV6:本质是一个128位的整形数;
    (1)实际是一个128位的整形数;xxx:xxx:xxx:xxx:xxx:xxx:xxx:xxx 分成了8分, 每份16位;
    (2)IP可以有多少个 2^128 - 1 。
  3. IP地址的作用:每台主机拥有不同的ip地址,因此通过IP地址就可找到某一台主机。
1.2.2 端口
  1. 通过端口可以将数据发送到主机上的某个进程;
    (1)一台主机上运行了多个进程,若其中一个进程用来交互传输数据,如何将数据传输到该进程?
    (2)如果要进程网络通信, 可以让这个进程绑定一个端口,通过这个端口就可以确定某个进程;
  2. 端口号:unsigned short int型数据(16bit),即取值范围0~65535;

1.3 OSI网络分层模型

1.3.1 七层模型
  1. 应用层:规定数据的传输协议
  2. 表示层:对应用层数据编码和转化,确保一个系统应用层发的消息被另一个系统应用层识别,解决不同系统之间的通信;
  3. 会话层:建立、管理和终止表示层与实体之间的通信会话;建立一个连接(如自动网络寻址等);
  4. 传输层:向高层提供可靠的端到端网络数据流服务,用于端口之间通信;
  5. 网络层:在源和重点之间建立连接,如IPv4和IPv6;
  6. 数据链路层:通过物理网络链路传输数据;规定了0和1的分包形式,确定了网络数据包的形式;
  7. 物理层:将信息编码成高低电位信号传输;
1.3.2 四层模式
  1. 应用层:应用层、表示层、会话层;
  2. 传输层:传输层;
  3. 网络层:网络层;
  4. 网络接口层:数据链路层、物理层;在这里插入图片描述

2. 协议

2.1 协议格式

  1. TCP协议
    在这里插入图片描述
  2. UDP协议
    在这里插入图片描述
  3. IP协议
    在这里插入图片描述
  4. 以太网帧协议在这里插入图片描述

2.2 数据的封装

在这里插入图片描述

3. socket编程

3.1 基本概述

  1. socket实际上是一套通信接口, 应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。
  2. 两个网络应用程序进行通信时,传输层实现端到端的通信,每一个传输层连接有两个端点,称为套接字(socket)。
  3. 套接字通信分两部分:
    (1)服务器端: 被动接受连接的角色, 不会主动发起连接;
    (2)客户端通信: 主动向服务器发起连接。

3.2 字节序

  1. 大端模式(Big-Endian): 网络字节序,内存的低地址位存储数据高位字节, 内存高地址位存储数据的低位字节;
低地址位 ---> 高地址位
0x12 0x34 0x56 0x78
0x11 0x22 0x33 0x44

在这里插入图片描述

  1. 小端模式(Little-Endian): 主机字节序, 内存的低地址位存储数据低位字节, 内存高地址位存储数据的高位字节;
低地址位 ---> 高地址位
0x78 0x56 0x34 0x12
0x44 0x33 0x22 0x11

在这里插入图片描述
3. 大小端转换函数

短整型:一般用于端口,因为端口取值范围2^16
uint16_t htons(uint16_t hostshort);		主机字节序转为网络字节序
uint16_t ntohs(uint16_t netshort);		网络字节序转为主机字节序
	htons: host to net short
短整型:一般用于端口,因为端口取值范围2^16
uint32_t htonl(uint32_t hostlong);		主机字节序转为网络字节序
uint32_t ntohl(uint32_t netlong);		网络字节序转为主机字节序
	htons: host to net long

3.3 IP地址转换

即主机点分十进制与网络字节序整型数之间的转换

按照地址族协议(IPv4/IPv6)af,将主机点分十进制ip地址src转为网络字节序整型数,并存的dst地址;
int inet_pton(int af, const char *src, void *dst);
成功值:成功1,失败-1,地址无效0
按照地址族协议,将src处的网络字节序整型转为主机字节序点分十进制存到size大小的dst地址处
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
返回值:成功dst,失败NULL

3.3 sockaddr结构体

  1. 该结构体用来存储端口和IP地址
    在这里插入图片描述
  2. sockaddr结构体
struct sockaddr {
	sa_family_t sa_family; // 地址族协议, ipv4, ipv6
	char   sa_data[14];
}
  1. sockaddr_in结构体
typedef unsigned short  uint16_t;
typedef unsigned int   uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
typedef unsigned short int sa_family_t;
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
struct in_addr
{
  in_addr_t s_addr;
}; 
struct sockaddr_in
{
  sa_family_t sin_family; /* 地址族协议 */
  in_port_t sin_port;     /* 端口号 */
  struct in_addr sin_addr;   /* IP地址 */
  /* 填充字节 */
  unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE -
       sizeof (in_port_t) - sizeof (struct in_addr)];
}; 

4. TCP通信流程

4.1 概述

  1. TCP通信协议:面向连接的、安全的、流式传输协议;
  2. UDP通信协议:面向无连接、不安全、报式传输协议;

注意:安全是指数据不会丢失,而非数据加密。

4.2 通信流程

4.2.1 服务器端

1.

  1. 套接字socket()创建一个用于监听的套接字;
    (1)监听:实时监测是否有客户端连接
    (2)套接字:socket,返回一个文件描述符fd
在地址族协议domain,使用type类型的ptotpcol协议,创建一个套接字,返回fd
int socket(int domain, int type, int protocol);
domain:
	AF_INET: ipv4
	AF_INET6: ipv6
	AF_UNIX, AF_LOCAL: 进行本地套接字通信(进程间通信)
type: 通信过程中使用的协议
	SOCK_STREAM: 流式协议
	SOCK_DGRAM: 报式协议
protocol: 一般写0
	SOCK_STREAM: 流式协议默认使用使用: tcp
	SOCK_DGRAM: 报式协议默认使用使用: udp
返回值:成功fd,失败-1,errno;
  1. 绑定:将监听文件描述符fd与本地IP和端口绑定;
    (1)IP和端口 == 服务器地址信息;
    (2)客户端连接服务器时使用的就是该IP和端口;
将大小为addrlen的存放IP和端口的sockaddr结构体与监听文件描述符sockfd绑定。
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
	sockfd:socket()得到的返回值
	addr: 需要将IP和Port初始化到这个结构体中
	addrlen: addr占的内存大小
  1. 监听:设置监听, 监听的fd开始工作;
设置sockfd开始监听,
int listen(int sockfd, int backlog);
	backlog: 已经连接成功, 但是还没有被处理的连接
	指定的数值不能大于 /proc/sys/net/core/somaxconn 中存储的数据, 默认为128
  1. 阻塞等待:当有客户端发起连接, 解除阻塞, 接受客户端的连接, 会得到一个用户通信的套接字(fd);
当无客户端连接时,该函数阻塞;当有客户端连接时,获取来自客户端addrlen大小的addr结构体中的信息,并解除阻塞,并返回通信sockfd;
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
	sockfd: 用于监听的文件描述符(套接字);
	addr: 传出参数, 记录了连接成功的客户端的IP和端口信息;
	addrlen: addr对应的内存大小;
	返回值:成功:通信的文件描述符;失败-1;
注意:返回值sockfd用于通信,参数sockfd用于监听,二者不相同。
  1. 通信:接收数据/发送数据;
  2. 断开:通信结束,断开连接;
#include <sys/types.h>        
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
	//1.套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("sockfd");
		exit(0);
	}
	
	//2.绑定
		//2.1建立sockaddr结构体
	struct sockaddr_in addr = 
	{
		.sin_family = AF_INET,
		.sin_port = htons(9000),
	};
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
		//2.2将sockaddr与sockfd绑定
	int ret = bind(sockfd, (struct sockaddr *)&addr, sizeof(addr));
	if(ret == -1)
	{
		perror("bind");
		exit(0);
	}
	
	//3.监听
	ret = listen(sockfd, 100);
	if(ret == -1)
	{
		perror("listen");
		exit(0);
	}
	
	//4.等待/通信套接字
	struct sockaddr_in acceptAddr;
	int len = sizeof(acceptAddr);
	int connfd = accept(sockfd, (struct sockaddr *)&acceptAddr, &len);
	if(connfd == -1)
	{
		perror("accept");
		exit(0);
	}
	
	//5.传输
	char buf[1024] = {0};
	while(1)
	{
		//读操作
		read(connfd, buf, sizeof(buf));
		printf("%s\n", buf);
		//写操作
		write(connfd, buf, strlen(buf));
	}
	
	//6.关闭
	close(sockfd);
	close(connfd);
	
	return 0;
}
4.2.2 客户端
  1. 套接字socket()创建一个用于通信的套接字,得到一个文件描述符。
  2. 通信
通过操作内存缓冲区的文件操作符sockfd,连接信息为addr的服务器;
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:成功0,失败-1

(2)读写方式:write()和read()函数操作sockfd;

#include <sys/types.h>          /* See NOTES */
#include <sys/socket.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <string.h>
#include <arpa/inet.h>
int main()
{
	//1.套接字
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("sockfd");
		exit(0);
	}
	
	//2.通信请求
	struct sockaddr_in addr = 
	{
		.sin_family = AF_INET,
		.sin_port = htons(9000),
	};
	inet_pton(AF_INET, "127.0.0.1", &addr.sin_addr.s_addr);
	int ret = connect(sockfd, (struct sockaddr *)&addr, sizeof(addr));
	if(ret == -1)
	{
		perror("connect");
		exit(0);
	}
	
	//3.传输
	char buf[1024] = {0};
	int i = 0;
	while(1)
	{
		//写操作
		scanf("%s", buf);
		write(sockfd, buf, strlen(buf));
		//读操作
		read(sockfd, buf, sizeof(buf));
		printf("%s\n", buf);	
		sleep(1);
	}
	
	//4.关闭
	close(sockfd);
	return 0;
}

4.2.3 sockfd套接字

在这里插入图片描述

  1. sockfd与管道的fd类似,二者皆不是指向一个磁盘文件,而是指向了一块可以读写的内核缓冲区;
  2. 客户端通过socket()函数生成了一个cfd,当连接服务器时,客户端通过write()函数向cfd指向的内核缓冲区写入连接的请求数据;
  3. 服务器通过socket()函数生成了一个监听的LFD,监听时实际就是通过accept()函数一直读取LFD指向的内核缓冲区是否有客户端发来的连接请求;
  4. 当accept()函数获取到客户端发来的连接请求时,便创立一个用于通信的FD1;
  5. 客户端与服务器就可以通过write()和read()函数分别读写cfd和FD1,实现了通信。
4.2.4 通信细节
  1. 套接字通信默认是阻塞的,原因是函数中的文件描述符导致的;
    (1)服务器端:accept()函数、read()函数、write()函数都操作了文件描述符fd(包括监听和通信);
    (2)客户端:read()函数、write()函数;
  2. 通信过程中如何确定客户端已断开连接?
    答:通过read()的返回值:再连接状态下,当内核缓冲区无数据时,read()默认时阻塞的。
    (1)返回0:read()解除阻塞,客户端断开连接;
    (2)返回-1:读取失败;
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值