网络编程
1. 概念
1.1 局域网
局域网:局域网将一定区域的各种计算机、外部设备和数据连接起来形成计算机通信的私有网络
广域网:又称广域网、外网、公网。是连接不同地区局域网或城域网计算机通信的远程公共网络
1.2 IP
本质是一个整形数,用于表示计算机在网络中的地址。IP协议版本有两个:IPV4和IPV6
-
IPV4
-
使用一个32位的整形数表示一个IP地址,4个字节,int型
-
也可以使用点分十进制字符串描述这个IP地址:192.168.10.27
-
分成四份,每份1字节,8bit(char),最大值255
- 0.0.0.0是最小的ip地址
- 255.255.255.255是最大的IP地址
-
4. 按照IPV4协议计算,可以使用的IP地址有2的32次方个
-
IPV6
-
使用一个128位的整形数描述一个IP地址,16个字节
-
也可以使用一个字符串描述这个IP地址:2001:0db8:3c4d:0015:0000:0000:1a2b
-
分成了8份,每份2字节,每一部分以16进制的方式表示
-
按照协议IPV6计算,可以使用的IP地址有2的128次方个
-
1.3 端口
作用:定位到主机上的某一个进程,通过这个端口进程就可以接受对应的网络数据
端口也是一个整形数unsigned short,一个16位整型数,有效端口取值范围:0~65535
注意:一个进程不需要网络通信的话就不需要绑定端口,如果需要网络通信那么就需要绑定,一个端口对应一个进程,多个进程不能同时使用一个端口
1.4 OSI/ISO 模型
OSI,即开放式系统互联,一般都叫OSI参考模型。是ISO(国际标准化组织)在1985年研究的网络互联模型
2. 网络协议
TCP UDP IP 以太网
3. socket 编程
socket 对于程序员来说是一套网络通信接口,使用这套接口可以完成网络通信,网络通信主要分为两部分:客户端和服务器端。
需要了解三个概念:IP、端口、通信数据
3.1 字节序
概念:大于一个字节类型的数据在内存中的存放顺序,也就是说对于单字符来说是没有字节序问题,字符串是单字符的集合,因此字符串也没有字节序问题。
计算机通常采用的字节存储机制主要有两种:Big-endian大端存储和little-endian小端存储
-
小端->主机字节序
数据的低位字节存储到内存的低地址位,数据的高位字节存储到内存的高地址位
PC机默认使用小端存储
-
大端->网络字节序
数据的低位字节存储到内存的高地址位,数据的高位字节存储到内存的低地址位
套接字通过过程中操作的数据都是大端存储,包括:接收/发送数据、IP地址、端口
4个字节的32 bit值以下面的次序传输:首先是0~7bit,其次8~15bit,然后16~23bit,最后是24~31bit
0x12 34 56 78
小端:78 56 34 12
大端:12 34 56 78
3.2 大小端转换函数
#include <arpa/inet.h>
/*
u:unsigned
16:16位
h:host,主机字节序
n:net,网络字节序
s:short
l:int
*/
uint16_t htons(uint16_t hostshort);
uint32_t htonl(uint32_t hostlong);
uint16_t ntohs(uint16_t netshort);
uint32_t ntohl(uint32_t netlong);
3.3 IP地址转换
-
字符串类型的IP地址转换为大端整型数
int inet_pton(int af , const char *src, void *dst ); /* af:地址族协议 AF_INET4:ipv4格式的ip地址 AF_INET6:ipv6格式的ip地址 src:传入参数,对应要转换的点分十进制的ip地址:192.168.10.27 dst:传出参数,函数调用完成,转换得到的大端整形IP被写入到这块内存中 返回值:成功1,失败0或-1 */
-
大端整型数转换为小端的点分十进制的IP地址
#include <arpa/inet.h> const char *inet_ntop(int af,const void *src,char *dst,socklen_t size); /* src:传入参数,指向大端整形数的地址 dst:传出参数,指向小端点分十进制的IP地址字符串 size:dst的内存大小 返回值:成功返回a non-null pointer to dst 失败返回NULL with errno set to indicate the error */
-
只能用于IPV4大小端转换的函数(一般不用)
//IP->大端整形 in_addr_t inet_addr(const char *cp); // 大端整形 -> 点分十进制ip char *inet_ntoa(struct in_addr in);
4. TCP 通信流程
TCP报文长度
TCP是一个面向连接的,安全的,流式传输协议,这个协议是传输层协议
- 面向连接:是一个双线连接,通过三次握手完成,断开连接需要通过四次挥手
- 安全:tcp通信过程中,会对发送的每一数据包都会进行校验,如果发现数据丢失会自动重传
- 流式传输:发送端和接收端处理数据的速度,数据的量都可以不一致
4.1 服务器端通信流程
int lfd = socket(int domain, int type, int protocol); //创建用于监听的socket,lfd是一个文件描述符
bind(); //将得到的监听的文件描述符和本地的IP端口进行绑定
listen(); //设置监听(成功之后开始监听,监听的是客户端的连接)
int cfd = accept(); //等待并接受客户端的连接请求,建立新的连接,会得到一个新的文件描述符(通信的)
read(); /recv();//通信读写默认都是阻塞的
write();/send();
close();//断开连接,关闭套接字 **调用一次挥手两次**
4.2 客户端通信流程
在单线程的情况下客户端通信的文件描述符有一个,没有监听的文件描述符
int cfd = socket();//创建一个通信的套接字
connect(); //连接服务器,需要知道服务器绑定的IP和端口
// 接收数据
read();/recv();
//发送数据
write();/send();
close(); //断开连接,关闭文件描述符(套接字)
4.3 socket函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
/*
domain 用于指定一个通信域;这将选择将用于通信的协议族。
TCP/IP协议来说,AF_INET\AF_INET6\AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE
type 指定套接字的类型
protocol 通常设置为 0,表示为给定的通信域和套接字类型选择默认协议。
当对同一域和套接字类型支持多个协议时,可以使用 protocol 参数选择一个
特定协议。在 AF_INET 通信域中,套接字类型为SOCK_STREAM 的默认协议是
传输控制协议(Transmission Control Protocol,TCP 协议)。
在 AF_INET 通信域中,套接字类型为 SOCK_DGRAM 的默认协议时 UDP。
*/
*注意*:并不是上面的type和protocol可以随意组合的,如SOCK_STREAM不可以跟IPPROTO_UDP组合。当protocol为0时,会自动选择type类型对应的默认协议。
4.4 bind函数
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
//bind()函数用于将一个 IP 地址或端口号与一个套接字进行绑定(将套接字与地址进行关联)。
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
/*sa_data 是一个 char 类型数组,一共 14 个字节,在这 14 个字节中就包括了 IP 地址、端口
号等信息*/
typedef uint32_t in_addr_t;
struct in_addr{
in_addr_t s_addr;
};
struct sockaddr_in {
sa_family_t sin_family; /* 协议族 AF_INET*/
in_port_t sin_port; /* 端口号 大端*/
struct in_addr sin_addr; /* IP 地址 大端*/
unsigned char sin_zero[8];
};
/*
sin_port,sin_addr,会组成sa_data,然后传递给bind
*/
//`````````````````````````使用示例```````````````````````````````````````````
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);//打开套接字
if (0 > socket_fd) {
perror("socket error");
exit(-1);
}
struct sockaddr_in socket_addr;
memset(&socket_addr, 0x0, sizeof(socket_addr)); //清零
//填充变量
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket_addr.sin_port = htons(5555);
bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(socket_addr));
·····
close(socket_fd);