1. socket
一系列相关API, 客户只需要提供(IP,端口号)即可进行通信
定义
对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。提供应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议根进行交互的接口。
socket 是由 IP 地址和端口结合的,提供向应用层进程传送数据包的机制。
在 Linux 环境下,用于表示进程间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。与管道类似的,Linux 系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是管道主要应用于本地进程间通信,而套接字多应用于网络进程间数据的传递。
每个套接字文件都有:读/写缓存.
客户端:主动向服务器发起连接,
服务端;被动接受连接,一般不主动发起.
2.字节序
1.定义
- 字节序,顾名思义字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序(一个字节的数据当然就无需谈顺序的问题了)。
- 字节序分为大端字节序(Big-Endian) 和小端字节序(Little-Endian)。大端字节序是指一个整数的最高位字节(23 ~ 31 bit)存储在内存的低地址处,低位字节(0 ~ 7 bit)存储在内存的高地址处;小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处
- 字节 ----> 高地址–低地址
- 内存 ----> 低地址–高地址
所以大端模式是从第一位开始存,小端模式是从最后一位开始存,一般计算机采用小段字节序
2.存储和检测
0x0102030405060708;
0x08 | 0x07 |.....|0x01
0x01 | 0x02 |.....|0x08
#include<stdio.h>
union var{
char c[4];
int i;
int l;
};
int main(){
union var data;
data.c[0] = 0x04;
data.c[1] = 0x03;
data.c[2] = 0x02;
data.c[3] = 0x11;
data.c[4] = 0x11;
data.c[5] = 0x02;
data.c[6] = 0x03;
data.c[7] = 0x04;
printf("%x\n", data.i);
printf("%x\n", data.l);
data.i = 5;
printf("%x\n", data.l);
}
3.字节序转换函数
- 发送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)
- 网络字节序固定:大端模式
h -host 主机,主机字节序;
to -转换目标;
n -network 网络,网络字节序;
s -short(unsigned short) 指明类型,2个字节(端口);unsigned short int
l -long(unsigned int) 指明类型,4个字节(IP);unsigned int
#include<arpa/inet.h>
uint16_t htons(unit16_t hostshort);
uint16_t ntohs(unit16_t netshort);
uint32_t htonl(unit32_t hostlong);
uint32_t ntohl(unit32_t netlong);
4.测试
#include <stdio.h>
#include <arpa/inet.h>
int main()
{
unsigned short port = 0x8081;
unsigned short netport = htons(port);
printf("%x\n", netport);
unsigned char ip[4] = {196, 168, 1, 1};
unsigned int ipInt = *(int *)ip;
unsigned int net = htonl(ipInt);
unsigned char *netip = (char *)&net;
printf("%d %d %d %d\n", *netip, *(netip + 1), *(netip + 2), *(netip + 3));
return 0;
}
char c[6] = {1,1,1,1,1,1};
int c = *(int *)a;
printf("%d\n",c);
int d = 10;
char* a = (char *)&d;
3. socket地址
typedef unsigned short int sa_family_t;
#include <bits/socket.h> struct sockaddr
{
sa_family_t sa_family;
char sa_data[14];
};
1.参数详细
- 地址族类型通常与协议族类型对应。常见的协议族(protocol family,也称 domain PF)和对应的地址族入 下所示:
宏 PF_* 和 AF_* 都定义在 bits/socket.h 头文件中,且后者与前者有完全相同的值,所以二者通常混用。
- 不同的协议族的地址值具有不同的含义和长度,如下所示
2.优化,可以存放IPV6
Linux 定义了下面这个新的通用的 socket 地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的。
#include <bits/socket.h>
typedef unsigned short int sa_family_t;
struct sockaddr_storage
{
//地址族类型
sa_family_t sa_family;
//内存对齐使用
unsigned long int __ss_align;
//存储具体IP数据和端口号等.
char __ss_padding[ 128 - sizeof(__ss_align) ];
};
3.专用的socket地址(现在使用)
所有专用 socket 地址(以及 sockaddr_storage)类型的变量在实际使用时都需要转化为通用 socket 地址类型 sockaddr**(强制转化即可)**,因为所有 socket 通信编程接口使用的地址参数类型都是 sockaddr。
sockaddr_in : IPV4
sockaddr_un: unix本地
sockaddr_in6: IPV6
//unix专用
#include <sys/un.h>
struct sockaddr_un
{
sa_family_t sin_family;
char sun_path[108];
};
//定义
#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;
struct in_addr
{ in_addr_t s_addr; };
//IPV4
#include <netinet/in.h>
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)]; }; //保留段
//IPV6
struct sockaddr_in6
{
sa_family_t sin6_family; /* 协议族 */
in_port_t sin6_port; /* 端口. */
uint32_t sin6_flowinfo; /* 对齐 */
struct in6_addr sin6_addr;/* IP地址 */
uint32_t sin6_scope_id; /* IPv6 scope-id */
};
4. IP地址转化
1.旧版
字符串IP - 整数(还需要转化为网络字节序). 主机字节序 -> 网络字节序
#include <arpa/inet.h>
in_addr_t inet_addr(const char *cp);
int inet_aton(const char *cp, struct in_addr *inp);
char *inet_ntoa(struct in_addr in);
2.新版
既可以用IPV4,也可以用IPV6即可
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
af:地址族: AF_INET AF_INET6
src:需要转换的点分十进制的IP字符串
dst:转换后的结果保存在这个里面
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
af:地址族:
AF_INET AF_INET6
src:要转换的ip的整数的地址
dst: 转换成IP地址字符串保存的地方
size:第三个参数的大小(数组的大小)s
返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
3.测试
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
int main()
{
const char *str = "192.168.1.1";
unsigned int num = 0;
int ret = inet_pton(PF_INET, str, &num);
if (ret <= 0)
{
perror("pton\n");
exit(0);
}
printf("num = %d\n", num);
char res[16] = {0};
const char *rec = inet_ntop(PF_INET, &num, res, sizeof(res));
printf("ret = %s\n", rec);
printf("res = %s\n", res);
printf("str = %s\n", str);
return 0;
}
5. TCP通信
1. TCP 和 UDP
- 均为传输层协议
- UDP:用户数据报协议,面向无连接,可以单播,多播,广播,面向数据报,不可靠
- 不关心用户是否接受;
- 无拥塞控制,所以即使网络差也会以恒定速度发送;
- TCP:传输控制协议,面向连接的,可靠的,基于字节流(一端发送,另一端接受),仅支持单播传输
| UDP | TCP |
---|
是否创建连接 | 无连接 | 面向连接 | 是否可靠 | 不可靠 | 可靠的 | 连接的对象个数 | 一对一、一对多、多对一 、多对多 | 一对一 | 传输的方式 | 面向数据报 | 面向字节流 | 首部开销 | 8个字节 | 最少20个字节(头部) | 适用场景 | 实时应用(视频会议,直播) | 可靠性高的应用(文件传输) |
2.通信流程
1.服务端
- ? 创建一个监听的套接字
- 监听文件描述符绑定本地的 IP 和端口号
- 设置监听,即监听的
fd 开始工作 - 阻塞等待,直到有客户端发起连接,接触阻塞,接受客户端的连接. 连接完成可得到客户端的套接字(
fd ); - 通信
- 通信结束,断开连接
2.客户端
- 创建一个套接字
- 连接服务器,指定服务器的
IP 和 端口 - 连接成功,即可进行通信
- 通信结束,断开连接
3.流程图
4.套接字函数
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
ssize_t write(int fd, const void *buf, size_t count);
ssize_t read(int fd, void *buf, size_t count);
课程:https://www.nowcoder.com/study/live/504.如果侵权请及时通知
|