C++linux高并发服务器项目实践 day12
socket介绍
socket是网络环境中进程间通信的API,也是可以被命名和寻址的通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用进程将要传输的一段信息写入它所在主机的socket中,该socket通过与网络接口卡(NIC)相连的传输介质将这段信息送到另外一台主机的socket中,使对方能够接收到这段信息。socket是由IP地址和端口结合的,提供向应用层进程传送数据包的机制
socket与管道的区别在于,管道只能实现文件间通信,socket可以实现进程间通信
字节序
字节序就是大于一个字节类型的数据在内存中的存放顺序
字节序分为大端字节序和小端字节序。大端字节序是指一个整数的最高位字节(23~31bit)存储在内存的低地址处,低位地址(0-7bit)存储在内存的高地址处;小端字节序则是指整数的高位字节存储在内存的高地址处,而低位字节则存储在内存的低地址处
查看字节序:
union
{
short value; //2字节
char bytes[sizeof(short)]; //2字节
}test;
test.value = 0x0102;
if((test.bytes[0]== 1)&&(test.bytes[1] == 2)){
printf("大端字节序");
}else if((test.bytes[0]== 2)&&(test.bytes[1] == 1)){
printf("小端字节序");
}else{
printf("未知字节序");
}
字节序转换函数
当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的理解之。解决问题的方法是:发送端总是把要发送的数据转换成大端字节序数据后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换).
网络字节顺序是TCP/IP中规定好的一种数据表示格式,它与具体的CPU类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序采用大端排序方式。
BSD Socket提供了封装好的转换接口,方便程序员使用。有以下函数供使用htons(转换端口)、htonl、ntohs(转换IP)、ntohl。逐词意思如下
h - host
to - to
n - network
l - long
s - short
#include <stdio.h>
#include <arpa/inet.h>
//htons
unsigned short a = 0x0102;
printf("a : %x\n",a);
unsigned short b = htons(a);
printf("b : %x\n",b);
printf("===========================\n");
//htonl 转换IP
char buf[4] = {192,168,1,100};
int num = *(int *)buf;
int sum = htonl(num);
unsigned char *p = (char *)∑
printf("%d %d %d %d\n",*p ,*(p+1),*(p+2),*(p+3));
printf("===========================\n");
//ntohs
unsigned short c = 0x0102;
printf("c : %x\n",c);
unsigned short d = ntohs(c);
printf("d : %x\n",d);
printf("===========================\n");
//ntohl
unsigned char buf2[4] = {1,1,168,192};
int num2 = *(int *)buf2;
int sum2 = ntohl(num2);
unsigned char *p2 = (unsigned char *)&sum2;
printf("%d, %d, %d, %d\n",*p2 ,*(p2+1),*(p2+2),*(p2+3));
socket地址
socket网络编程接口中表示socket地址的是结构体sockaddr,其定义如下:
#include <bits/socket.h>
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
typedef unsigned short int sa_family_t;
sa_family成员是地址组类型(sa_family_t)的变量。地址族类型通常与协议族类型对应。
宏PF_*和AF_*都定义在bits/socket.h头文件中,且后者与前者有完全相同的值,所以二者通常混用。sa_data成员用于存放socket地址值,但是,不同协议族的地址值具有不同的含义和长度。
用于14字节的sa_data根本无法容纳多数协议族的地址值。因此,Linux定义了下面这个新的通用的socket地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是内存对齐的。
#include <bits/socket.h>
struct sockaddr_storage
{
sa_family_t sa_family;
unsigned long int __ss_align;
char __ss_padding[128 - sizeof(__ss_align)];
};
typedef unsigned short int sa_family_t;
现在的sockaddr退化成了(void*)的作用,传递一个地址给函数,由地址族确定函数是什么,然后函数内部在强制类型转化为所需的地址类型
所有专用socket地址(以及sockaddr_storage)类型的变量在实际使用时都需要转化为通用socket地址类型sockaddr(强制转化即可),因为所有socket编程接口使用的地址参数类型都是sockaddr。
IP地址转换(字符串ip-整数,主机、网络字节序的转换)
下面3个函数可用于用点分十进制字符串表示的IPv4地址和用网络字节序整数表示的IPv4地址之间的转换:
#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);
下面这对更新的函数也能完成同样的功能,且他们同时适用于ipv4和ipv6地址
#include<arpa/inet.h>
//p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af,const char *src ,void *dst);
af:地址族:AF_INET AF_INET6
src:需要转换的点分十进制的IP字符串
dst:转换后的结果保存在这个里面
//将网络字节序的整数,转换成点分十进制的IP字符串
const char *inet_ntop(int af,const void *src,char * dst,socklen_t size);
af:地址族:AF_INET AF_INET6
src:要转换的IP的整数的地址
dst:转换成IP地址字符串保存的地方
size:第三个参数的大小
返回值:返回转换后的数据的地址(字符串),和dst是一样的
TCP通信流程
套接字函数
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>//包含了这个头文件,上面两个就可以省略
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
- 返回值:
- 成功:返回文件描述符,操作的就是内核缓冲区
- 失败:-1
int bind(int sockfd,const struct sockaddr *addr,socklen_t addrlen);//socket命名
- 功能:绑定,将fd和本地的IP+端口进行绑定
- 参数:
- sockfd : 通过socket函数得到的文件描述符
- addr :需要绑定的socket地址,这个地址封装了ip和端口号的信息
- addrlen : 第二个参数结构体占的大小
int listen (int sockfd,int backlog);/proc/sys/net/core/somaxconn,可以使用cat命令查看该目录
- 功能:监听这个socket上的连接
- 参数:
- sockfd : 通过socket()函数得到的文件描述符
- backlog : 未连接和已连接的连接数的上限,超过时会丢失
int accept(int sockfd,struct sockaddr *addr ,socklen_t *addrlen);
- 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接
- 参数:
- sockfd : 用于监听的文件描述符
- addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)
- addrlen : 指定第二个参数的对应的内存大小
- 返回值:
- 成功 : 用于通信的文件描述符
- 失败 : -1
int connect(int sockfd,const struct sockaddr * addr,socklen_t addrlen);
- 功能 : 客户端连接服务器
- 参数 :
- sockfd : 用于通信的文件描述符
- addr : 客户端要连接的服务器的地址信息
- addrlen : 第二个参数对应的内存大小
- 返回值: 成功 0,失败 -1
ssize_t write(int fd,const void *buf,size_t count);
ssize_t read(int fd,void * buf ,size_t count);
TCP三次握手
默认已经学过了
TCP滑动窗口
滑动窗口协议是用来改善吞吐量的一种技术,即容许发送方再接收任何应答之前传送附加的包。接收方告诉发送方在某一时刻能发送多少包(称为窗口尺寸)
TCP中采用滑动窗口来进行传输控制,滑动窗口的大小意味着接收方还有多大的缓冲区可以用于接收数据。发送方可以通过滑动窗口的大小来确定应该发送多少字节的数据。当滑动窗口为0时,发送方一般不能再发送数据报
滑动窗口是TCP中实现诸如ACK确认、流量控制、拥塞控制的承载结构。
可以把窗口理解为缓冲区的大小
滑动窗口的大小会随着发送数据和接收数据而变化
通信的双方都有发送缓冲区和接收数据的缓冲区
MSS:Maximum Segment Size(一条数据的最大数据量)
win:滑动窗口
TCP四次挥手
默认已经学过了