套接字概念(太长不看,看总结摘要)
Socket中文意思是“插座”,在Linux环境下,用于表示进程x间网络通信的特殊文件类型。本质为内核借助缓冲区形成的伪文件。
既然是文件,那么理所当然的,我们可以使用文件描述符引用套接字。Linux系统将其封装成文件的目的是为了统一接口,使得读写套接字和读写文件的操作一致。区别是文件主要应用于本地持久化数据的读写,而套接字多应用于网络进程间数据的传递。
在TCP/IP协议中,“IP地址+TCP或UDP端口号”唯一标识网络通讯中的一个进程。“IP地址+端口号”就对应一个socket。欲建立连接的两个进程各自有一个socket来标识,那么这两个socket组成的socket pair就唯一标识一个连接。因此可以用Socket来描述网络连接的一对一关系。
套接字通信原理如下图所示:
在网络通信中,套接字一定是成对出现的。一端的发送缓冲区对应对端的接收缓冲区。我们使用同一个文件描述符索发送缓冲区和接收缓冲区。
总结摘要
1.Linux系统中,套接字=一种文件,编程时有相应的文件标识符(fd)
2.用于网络进程间(一般是不同电脑的应用)的数据传输
3.IP地址+端口号”就对应一个socket。
4.上面的图片
Socket通信创建流程图
网络字节序
两种字节序
无论机器是小端字节序还是大端字节序
网络上发送数据一般是先发送低地址再发送高地址
网络上接收数据一般也是先低后高
收发数据都是先低后高
当小端字节序机器向大端字节序机器发送数据时,会发生问题:
TCP\IP协议规定,网络数据流采用大端字节序
也就是说,无论主机是小端字节序存储,还是大端字节序存储,在TCP\IP协议中发送主机和接收主机都看成是大端字节序。
小端主机参与接收发送数据需要进行字节转换,而大端主机不用
为使网络程序具有可移植性,使同样的C代码在大端和小端计算机上编译后都能正常运行,可以调用htonx库函数做网络字节序和主机字节序的转换。
总结:
TCP\IP协议是默认规定发收双方是大端主机,所以发收双方需要htonx函数进行字节转换(使得小端主机自己转换,大端主机不变)
转换相关函数 htonx ntohx
eg:回声服务器echo_server.c
项目需求
Socket通信三要素
1)通信的目的地址 (IP地址)
2)使用的端口号 (服务器用不同的端口号区分不同的应用) http 80 smtp 25
3)使用的传输层协议(TCP、UDP)
Socket通信模型
sockaddr数据结构
很多网络编程函数诞生早于IPv4协议,那时候都使用的是sockaddr结构体,为了向前兼容,现在sockaddr退化成了(void *)的作用,传递一个地址给函数,至于这个函数是sockaddr_in还是其他的,由地址族确定,然后函数内部再强制类型转化为所需的地址类型。
sockaddr与sockaddr_in的结构差异:
sockaddr_in结构对于sockaddr的14字节地址数据进行了细分,如上图所示
分为16位端口号、32位IP地址、8字节填充
代码定义
PS:
1.in_addr中存储的ip地址是32位整型的,使用时可能要转换为字符串形式
eg:回音服务器echo_server.c中
其中AF_INET表示使用IPV4协议,INADDR_ANY为0.0.0.0表示本机
贴标签(调用bind函数)的时候需要强制将sockaddr_in强转为sockaddr
bzero函数——清空标签
头文件:#include<string.h>
函数原型:
作用:
在定义sockaddr_in结构体后用来清空里面的内容
代码示例:
struct sockaddr_in server_addr;//代表标签
bzero(&server_addr,sizeof(struct sockaddr_in));
IP地址转换函数 inet_xxxx
其中p代表的是字符串形式的ip地址,n代表网络字节序
这里的size指的是dst的size
代码示例:
#include<stdio.h>
#include<string.h>
#include<arpa/inet.h>
int main()
{
char ip[] = "2.3.4.5";
char server_ip[64];
struct sockaddr_in server_addr;
inet_pton(AF_INET,ip, &server_addr.sin_addr.s_addr);
printf("s_addr : %x\n",server_addr.sin_addr.s_addr);//网络字节序
printf("s_addr from net to host : %x\n",ntohl(server_addr.sin_addr.s_addr));//主机字节序
inet_ntop(AF_INET, &server_addr.sin_addr.s_addr,server_ip,64);
printf("server ip : %s \n",server_ip);
return 0;
}
输出结果:
INADDR_ANY——0.0.0.0 本机
宏定义,真实值为0,表示本机
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
表示服务器监听本机所有IP地址
socket函数——创建邮箱
代码定义:
代码示例:
1.服务器连接前创建socket
2.accpet接收客户端请求形成socket
服务端相关函数
bind函数——贴标签
功能:
服务器端程序用bind函数来绑定一个固定的网络地址和端口号。
代码定义:
代码示例:
sock = socket(AF_INET,SOCK_STREAM,0);
//2.清空标签,写上地址和端口号
bzero(&server_addr,sizeof(server_addr));
server_addr.sin_family = AF_INET;//悬崖边则协议族IPV4
server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址
server_addr.sin_port = htons(SERVER_PORT);//绑定端口号
//将标签铁道收信的信箱上
bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
首先将整个结构体清零,然后设置地址类型为AF_INET,网络地址为INADDR_ANY,这个宏表示本地的任意IP地址,因为服务器可能有多个网卡,每个网卡也可能绑定多个IP地址,这样设置可以在所有的IP地址上监听,直到与某个客户端建立了连接时才确定下来到底用哪个IP地址,端口号为6666。
listen函数——等待来信
代码定义:
accept函数
功能:
三次握手完成后,服务器调用accept()接受连接,如果服务器调用accept()时还没有客户端的连接请求,就阻塞等待直到有客户端连接上来。
代码定义:
产生的是一个客户端socket
代码示例:
struct sockaddr_in client;
socklen_t client_addr_len;
client_addr_len = sizeof(client);
client_sock = accept(sock,(struct sockaddr*)&client,&client_addr_len);
//sock为之前创建的服务端连接前socket
客户端相关函数
connect函数
函数定义:
通用函数
出错处理函数
可以对strerror函数进行进一步封装: