一、基于TCP传输层的编程模型 TCP是面向连接的,安全可靠的。 三次握手 服务器端编程模型 1、创建一个用于网络通讯的设备 通讯端点 socket(2) #include <sys/types.h> #include <sys/socket.h> int socket(int domain, int type, int protocol); 功能:创建一个用于通讯的端点 参数: domain: AF_INET:应用于IPV4地址家族的 AF_INET6:应用于IPV6地址家族的 type: SOCK_STREAM:可靠的、基于连接的 双向的、队列式的 TCP SOCK_DGRAM:支持数据包 不可靠的 无连接的 UDP protocol: 0 返回值: -1 错误 errno被设置 返回一个新的文件描述符 2、将这个通讯端点和本机的ip地址、端口号做绑定 bind(2) int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 功能:绑定名字到地址。创建socket以后,socket中有地址空间但是没有具体地址放在这个地址空间。需要将具体的地址和socket的地址空间绑定。 参数: sockfd:已经创建好的socket,但是这个socked没有具体的地址 addr:指定了具体的地址,将这个地址绑定到sockfd中 addrlen:指定了addr的大小、字节数 返回值: 0 成功 -1 错误 errno被设置 地址家族的通用结构 struct sockaddr{ sa_family_t sa_family; char sa_data[14]; }; ipv4和ipv6 man in.h #include <netinet/in.h> in_port_t uint16_t in_addr_t uint32_t sa_family_t <sys/socket.h> struct in_addr{ in_addr_t s_addr; }; ipv4的具体地址 struct sockaddr_in{ sa_family_t sin_family; //AF_INET. in_port_t sin_port; //Port number. struct in_addr sin_addr; //IP address. }; INADDR_ANY IPv4 local host address 这是一个宏,宏的本质是一个整数。代表了本机的所有的地址。 端口号 0~65535 但是5000以下最好不要用。因为已经被国际组织使用了。 sin_port 采用网络字节序 需要将本机字节序的数字转换为网络字节序 系统提供了函数。处理本机字节序和网络字节序的问题 #include <arpa/inet.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort); h host n net s short l long to ip地址 字符串格式 二进制格式 互相转换 inet_pton(3) #include <arpa/inet.h> int inet_pton(int af, const char *src, void *dst); 功能:从字符串格式转换为二进制格式 IPV4 IPV6 参数: af: AF_INET or AF_INET6 src:字符串格式的ip地址 dst:存储了网络地址结构的信息 返回值: 1 成功 0 src无效 -1 错误 errno被设置 struct in_addr #include <arpa/inet.h> const char *inet_ntop(int af, const void *src, char *dst, socklen_t size); 功能:二进制到字符串的转化 参数: af: AF_INET or AF_INET6 src: struct in_addr dst:用来存储字符串的空间 size:指定了空间的有效字节数 返回值: NULL 错误 errno被设置 非空 返回dst的地址,字符串的首地址。 ip地址 127.0.0.1 本地地址 环回地址 测试机器的网络设备工作是否正常。 setsockopt(2) 3、在这个通讯端点上监听客户端连接的到来,如果有连接的到来,将到达的连接存放在缓冲区中(队列的数据结构) listen(2) int listen(int sockfd, int backlog); 功能:在socket监听连接。将sockfd标记为被动连接。接收即将到来的客户端请求。 参数: sockfd:指定了被监听的socket backlog:指定了未决连接的最大数。 返回值: 0 成功 -1 错误 errno被设置 4、从这个缓冲区队列中取出一个客户端连接,返回一个连接描述符用于和客户端的通讯。(这个连接描述符称为conn_fd) accept(2) int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen); 功能:在socket上接收一个连接 参数: sockfd:指定了监听的socket addr:在这个地址空间里填充了客户端的地址和端口号 addrlen:空间里指定了addr的长度。如果addr为NULL,那么也要设置为NULL 返回值: -1 错误 errno被设置 返回一个非负的整数,就是连接描述符 5、使用conn_fd和客户端通讯 (1)获取客户端的请求 read(2) (2)处理客户端请求 (3)响应客户端 write(2) 6、关闭conn_fd,终止和客户端的通讯 close(conn_fd); 客户端的编程模型 1、创建一个用于通讯的设备(通讯端点) socket(2) 2、使用这个端点连接到服务器(IP地址和端口号) connect(2) int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen); 功能:在socket上发起一个连接 参数: sockfd:指定socket,将这个socket连接到addr的地址空间 addr:指定具体的地址空间,要连接到的地址空间。server addrlen:指定了addr的长度 返回值: 0 成功 -1 错误 errno被设置 3、向服务器发送消息 4、等待服务器端的响应消息 5、处理服务器的响应消息 6、关闭设备,结束本次通讯。 举例说明 编写基于TCP的通讯模型 服务器端代码 参见 server.c 客户端代码参见 client.c 172.30.3.93 二、并发服务器的实现 三种方法实现服务器的并发 多进程 多线程 多路复用 使用多进程实现服务器的并发功能 什么时候?什么地方?子进程才登场。 fork(2) 子进程负责的任务 子进程负责和客户端的通讯 关闭s_fd。 信息处理 关闭和客户端的连接 父进程负责的任务 父进程负责从未决连接队列中取出一个连接,创建和客户端通讯的文件描述符 创建子进程 关闭和客户端的连接 负责对子进程收尸。非阻塞收尸