网络编程实战 1

  • Ethernet frame
  • IP packet
  • TCP segment
  • Application message

三个层次

  • 充分理解TCP/IP网络模型和协议
  • 结合对协议的理解, 增强对异常情况的处理能力
  • 可以写出支持大规模高并发的网络处理程序

TCP/IP 四层模型

img

MAC层的传输单位是帧(frame), IP层的传输单位是包(packet), TCP层的传输单位是段(segment), HTTP的传输单位是消息或报文(message).

TCP --> stream socket, 字节流套接字, SOCK_STREAM

UDP --> datagram socket, 数据包套接字, SOCK_DGRAM

OSI网络分层模型

OSI (开放式系统互联通信参考模型, open system interconnection reference model),

img

TCP/IP是一个纯软件的栈, 没有网络应有的网卡, 光缆等设备的位置. 而OSI填补了这一层次的缺失, 使得理论层面描述的网络更为完整.

socket是什么

img

客户端发起连接请求之前, 需要服务端初始化. 服务端首先初始化socket, 然后执行bind函数, 将服务能力绑定在约定好的地址和端口, 接着执行listen操作, 监听特定端口, 然后在accept函数处阻塞, 等待客户端连接请求的到来.

三次握手完成之后, 建立连接, 数据的传输就不再是单向的而是双向的.

通用套接字地址格式

/* POSIX.1g 规范规定了地址族为2字节的值.  */
typedef unsigned short int sa_family_t;
/* 描述通用套接字地址  */
struct sockaddr {
    sa_family_t sa_family;  /* 地址族.  16-bit*/
    char sa_data[14];       /* 具体的地址值 112-bit */
}; 

sa_family指定为AF_LOCAL表示本地地址, AF_INET表示使用IPv4, AF_INET6表示使用IPv6. AF_表示的是Address Family, PF_表示的是Protocal Family, 常用AF_XXX初始化socket地址, PF_XXX初始化socket.

IPv4套接字格式地址

/* IPV4套接字地址,32bit值.  */
typedef uint32_t in_addr_t;
struct in_addr {
	in_addr_t s_addr;
};
/* 描述IPV4的套接字地址格式  */
struct sockaddr_in
{
    sa_family_t sin_family;       /* 16-bit, IPv4为AF_INET */
    in_port_t sin_port;          /* 端口口  16-bit*/
    struct in_addr sin_addr;    /* Internet address. 32-bit */
    
    /* 这里仅仅用作占位符,不做实际用处  */
    unsigned char sin_zero[8];
};

服务端准备连接的过程

创建套接字

int socket(int domain, int type, int protocol)

domainPF_INET, PF_INET6, PF_LOCAL. type 对于TCP为SOCK_STREAM, UDP为SOCK_DGRAM.

绑定套接字和套接字地址

bind(int fd, sockaddr *addr, socklen_t len)

sockaddr *可以看作是一个void *类型的参数

struct sockaddr_in name;
bind(sock, (struct sockaddr *)&name, sizeof(name))

对于IPv4地址, 可按照如下方式设置通配地址:

struct sockaddr_in name;
name.sin_addr.s_addr = htonl(INADDR_ANY);  // IPv4通配地址

让服务器处于可监听的状态

int listen(int sockfd, int backlog);

listen函数可以将"主动"的套接字转换为"被动"套接字, 告诉操作系统内核该套接字是用于等待用户请求的. 参数backlog为未完成连接队列的大小.

成功应答

int accept(int listen_sock_fd, struct sockaddr *cli_addr, socklen_t *addrlen)

其中参数cli_addraddrlen是函数的返回值, cli_addr是客户端的地址, addrlen指明了地址的大小, 函数返回的是一个新的套接字, 代表了与客户端的连接.

Q: 为什么需要有监听套接字已连接套接字?

A: 这是考虑到网络程序需要具有一定的并发性, 监听套接字需要一直存在才能够服务多个客户端. 而一个客户端和服务端连接成功后, 就会生成一个已连接套接字, 使用已连接套接字和客户进行通信处理. 如果对该客户的服务已经完成, 那么将释放这一个客户的连接.

客户端发起连接的过程

客户端建立套接字的方法和服务端大致相同. 不同之处是客户端需要调用connect向服务端发起请求.

int connect(int sockfd, const struct sockaddr *servaddr, socklen_t addrlen)

sockfd是连接套接字, servaddraddrlen代表指向套接字地址结构的指针和该结构的大小. 套接字地址结构需要有服务器的IP地址和端口号.

对于TCP套接字, 调用connect函数将会引发TCP的3次握手过程. 函数会在建立连接成功或出错时返回.

以上就是阻塞式网络编程模型的服务端与客户端.

image-20220104211720004

思考题

数据流从应用程序发送端一直到应用程序接收端, 总共经历多少次拷贝?

image-20220104210154993

7次以上(中间还需要经过网络设备拷贝数据). 应用程序将数据送到发送缓冲区时, 调用send或是write方法, 如果缓存中没有空间, 系统调用就会失败或被阻塞. 这个动作是显式拷贝. 之后, 数据将按照TCP/IP的分层进行拷贝, 这些拷贝对我们而言是透明的.

原文地址:https://www.cnblogs.com/tedukuri/p/15768776.html