客户机-服务器模型
一个简单的客户机-服务器模型如下图所示。 客户机需要某个数据或某项服务时,向服务器发送请求,服务器对该请求作出响应,向客户机提供它想要的东西。 一个服务器可以同时对多个客户机作出响应。 客户机和服务器都是以进程(不是机器)为单位的,即客户机进程、服务器进程。
基于TCP的网络编程模型
基于TCP协议的客户机-服务器网络模型执行流程如下: 服务器进程先启动:
- 创建socket
- 在socket上绑定指定的IP地址和端口号
- 主动socket变被动socket
- 监听客户机的连接请求,阻塞等待客户机发起并完成连接
客户机进程后启动:
- 创建socket
- 向服务器进程发起连接
经过三次握手,TCP连接建立成功后,进程间就可以收发(读写)数据了。此时服务器进程会新增一个执行流,处理客户的请求,原执行流继续阻塞等待新的客户连接,即并发服务器。 请求完毕后,客户机进程首先发起断开连接。
socket == 套接字 主动socket == 普通的套接字 被动socket == 监听套接字(只存在于服务器进程) 应用程序把socket看作一个文件,使用文件描述符读写它。
套接字地址
套接字地址主要有三个重要信息:地址族(表示哪种套接字)、IP地址、端口号。 不同类型的套接字定义了不同的结构。
IPV4套接字
typedef uint32_t in_addr_t;
struct in_addr
{
in_addr_t s_addr;
};
struct sockaddr_in
{
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
unsigned char sin_zero[8];
};
IPV6套接字
struct sockaddr_in6
{
sa_family_t sin6_family;
in_port_t sin6_port;
uint32_t sin6_flowinfo;
struct in6_addr sin6_addr;
uint32_t sin6_scope_id;
};
本地套接字
struct sockaddr_un {
unsigned short sun_family;
char sun_path[108];
};
通用套接字
typedef unsigned short int sa_family_t;
struct sockaddr{
sa_family_t sa_family;
char sa_data[14];
};
C 语言不支持函数重载,而早期的C 语言也没有void * ,所以系统定义了一个通用的套接字地址,避免了定义多个功能相同的网络编程API。 API中,凡是用到套接字地址的地方,传入的都是通用套接字类型的指针和它的长度。
网络编程API
socket
int socket(int domain, int type, int protocol);
功能:创建socket(主动套接字) 参数:
-
domain :地址族。取值有PF_INET (IPv4 )、PF_INET6 (IPv6 ) 、 PF_LOCAL 等。 -
type :通信的数据格式。取值有SOCK_STREAM (字节流,对应TCP )、SOCK_DGRAM (数据报,对应UDP )和SOCK_RAW (原始套接字,暂不讨论)。 -
protocol:暂不讨论,填0 即可
返回值:一个整型,表示能对socket进行读写的文件描述符。
bind
int bind(int socket, const struct sockaddr *address, socklen_t address_len);
功能:把socket和套接字地址绑定起来。其他进程通过该地址访问该socket。 参数:
socket :socket的文件描述符address :socket通用地址类型的指针address_len :socket通用地址类型的长度
返回值:成功返回0 ;失败返回-1
listen
int listen(int socket, int backlog);
功能:把主动套接字变成被动套接字(监听套接字)。用于服务器端。 参数:
socket :socket的文件描述符backlog :这个参数的大小决定了服务器可以接收的并发数目。
返回值:成功返回0 ,失败返回-1
accept
int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len);
功能:服务器等待和客户机完成建立连接 参数:
socket :socket的文件描述符address :用于获取客户机的套接字地址address_len :用于获取套接字地址的长度
返回值:成功,返回一个非负整型,表示连接套接字的文件描述符;失败返回-1
连接套接字是主动套接字,服务器使用该套接字响应客户机,而监听套接字始终用于监听新的客户连接请求。
connect
int connect(int socket, const struct sockaddr *address, socklen_t address_len);
功能:客户机向服务器发起并完成连接 参数:
socket :socket的文件描述符address :服务器的通用套接字地址address_len :套接字地址长度
返回值:成功返回0 ;失败返回-1
read
ssize_t read(int fildes, void *buf, size_t nbyte);
功能:从文件fildes 里读nbyte 个字节的数据到首地址为buf 的缓冲区上。 参数:
fildes :文件描述符buf :缓冲区首地址nbyte :读取的字节个数
返回值:
- 正常情况下,返回读到的字节数
- 读到
EOF ,返回0 - 其他情况,返回
-1
如果read读到了EOF(end-of-file),这在网络中表示对端发送了FIN 包,即对端调用了close
write
ssize_t write(int fildes, const void *buf, size_t nbyte);
功能:从地址buf 开始,把nbyte 个字节的数据写到文件fildes 。 参数:
fildes :文件描述符buf :缓冲区首地址nbyte :要写入的字节个数
返回值:成功,返回写入的字节数;失败返回-1
close
int close(int fildes);
功能:向对端发送FIN 包(关闭文件) 参数:
fildes :文件描述符
返回值:成功,返回0 ;失败,返回-1
read、write和close可用于对所有文件的操作。
|