第7章 套接字基础
套接字地址结构
struct sockaddr{···};
struct sockaddr_in{···};
创建网络插口函数socket()
# include<sys/types.h>
# include<sys/socket.h>
int socket(int domain, int type, int protocol); //prototype
描述:协议族domain、协议类型type、协议编号protocol;
返回值:成功返回套接字文件描述符,失败返回-1
例子:
int sock = socket(AF_INET, SOCK_STREAM, 0);
绑定一个地址端口对函数bind()
作用:将一个套接字文件描述符与一个本地地址进行绑定,即指定数据的端口地址和IP地址。
int bind(int sockfd, const struct sockaddr* my_addr, socklen_t addrlen); //prototype
描述:函数将长度为addrlen的my_addr结构与sockfd绑定在一起
sockfd为socket()函数创建的文件描述符;
my_addr为指向sockaddr结构类型的指针,须先设置好my_addr的数据再进行绑定;
addrlen为my_addr结构的长度,一般为sizeof(struct sockaddr)。
返回值: 0成功,-1失败
监听本地端口listen()
服务器模式接受一个连接前,用listen()侦听端口,初始化可连接队列,将多个客户端请求放在等待队列中。
int listen(int sockfd, int backlog); //prototype
描述:
backlog:等待队列的长度
返回值:0成功,-1失败
接受一个网络请求accept()
为阻塞式函数
int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);
描述:通过addr参数获得成功连接客户端的IP、端口和协议族信息;
返回值:成功返回新连接客户端套接字文件描述符,失败返回-1
连接目标网络服务器connect()
客户端在建立套接字之后,不需要进行地址绑定就可以直接连接服务器。
int connect(int sockfd, struct sockaddr* serv_addr, int addrlen);
描述:连接指定参数的服务器,例如IP、端口。
serv_addr包含需要连接的服务器目的端口、IP和协议类型
返回值:0成功,-1失败
写入数据函数write()
当服务器端收到一个客户端的连接后,可以通过套接字描述符进行数据的写入。
int write(int sockfd, datatype* data, int datalen); //prototype
返回值:成功写入的数据长度
int size;
char data[1024];
size = write(sockfd, data, 1024); //将1024长度的data写入sockfd
读取数据函数read()
int read(int sockfd, datatype* buffer, int readlen);
描述:从sockfd中读取readlen长度的数据放入缓存区buffer中
注:使用write()和read()函数时,sockfd为0表示标准输入,1表示标准输出。
关闭套接字函数close() / shutdown()
关闭已经打开的socket连接,释放相关的资源,不能再使用该套接字文件描述符sockfd进行读写操作。
int shutdown(int sockfd, int how);
描述:how表示切断的方式(0,1,2)
返回值:0成功,-1失败
信号
信号将事件发送给相关的进程,相关进程可以对信号进行捕捉和处理。捕捉由系统自动完成,处理通过函数signal()完成。
信号SIGPIPE:例如当服务器已经关闭,而客户端还试图向套接字写入数据时会产生SIGPIPE信号。
信号SIGINT: 终止进程事件产生SIGINT信号,用于终止进程运行。
第8章 服务器和客户端信息的获取
字节序
概念:字节序是由于不同的处理器和操作系统,对大于一个字节的变量在内存中存放的顺序不同产生的。
小端字节序:变量内存地址的起始地址存放低位字节,高位字节顺序存放。
大端字节序:变量内存地址的起始地址存放高位字节,低位字节顺序存放。
字节序转换函数
#include<arpa/inet.h>
uint32_t htonl(uint32_t hostlong); //主机字节序到网络字节序的long转换
uint16_t ntohs(uint16_t netshort); //网络字节序到主机字节序的short转换
字符串IP地址和二进制IP地址的转换
Linux中的inet_xxx()
函数
inet_pton()
和inet_ntop()
函数
int inet_pton(int af, const char* src, void* dst); //将字符串类型IP地址转换成二进制类型
描述:
af为网络类型协议族,IPv4下为AF_INET;
src为需要转换的字符串;
dst指向转换后的结果;
返回值:成功返回正值,失败返回-1,0
const char* inet_ntop(int af, const void* src, char* dst, socket_t cnt); //将二进制IP地址转换成字符串类型
返回值:返回一个指向dst的指针
套接字描述符判定函数issockettype()
判定一个文件描述符是否是套接字描述符
int issockettype(int fd);
IP地址与域名之间的相互转换
DNS域名系统,树形结构,按照区域组成层次性结构。
获取主机信息的函数: gethostbyname() 和 gethostbyaddr()
struct hostinfo* gethostbyname(const char* name); //prototype
struct hostinfo* gethostbyaddr(const void* addr, int len, int type); //type为IP地址类型
不推荐使用,代替函数getnameinfo()
和getaddrinfo()
协议名称处理函数xxxprotoxxx()
获取协议的名称和值
第9章 数据的IO和复用
网络数据的接收和发送有多种方案,例如直接收发、向量收发、消息收发。
IO函数(阻塞型)
直接收发:recv()接收数据
ssize_t recv(int sockfd, void* buf, size_t len, int flags);
描述:从套接字sockfd中接收数据,放到长度为len的缓冲区buf中,flags为接收方式。
返回值:成功时返回接收到的字节数,失败时返回-1
send()发送数据
ssize_t send(int sockfd, const void* buf, size_t len, int flags);
描述:将缓冲区buf中长度为len的数据,通过套接字描述符sockfd发送出去,flags为发送方式。
返回值:成功时返回已发送字节数,失败返回-1
向量收发:readv()接收数据
ssize_t readv(int sockfd, const struct iovec* vector, int count);
描述:可用于接收多个缓冲区数据。从套接字描述符sockfd中读取count块数据放入到缓冲区向量vector中。
writev()发送数据
ssize_t writev(int sockfd, const struct iovec* vector, int count);
描述:向sockfd中写入在vector中的count块数据。
消息收发:recvmsg()接收数据
ssize_t recvmsg(int s, struct msghdr* msg, int flags);
sendmsg()发送数据
ssize_t sendmsg(int s, struct msghdr* msg, int flags);
IO模型
阻塞IO、非阻塞IO模型、IO复用、信号驱动、异步IO
阻塞IO模型: 数据接收时,在数据没有到来之前会一直等待
非阻塞IO模型: 对于每次请求,内核都不会阻塞,会立即返回
IO复用: 在等待的时候加入超时的时间,未到超时时间阻塞,超时后仍没有数据到达则系统返回,不再等待。
信号驱动IO模型: 进程开始时注册信号处理回调函数,进程继续执行,信号发生时即IO数据到来,回调函数用recvmsg()接收数据。
异步IO模型: 与信号驱动类似,区别在于异步IO在数据复制完成再发送信号给回调函数。
select() 和 pselect()函数
用于IO复用,监视多个文件描述符的集合,判断是否有符合条件的事件发生。
int select(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, struct timeval* timeout);
描述:select()函数先对文件描述符进行查询,是否可读、写或者错误操作,当文件描述符满足条件时才进行操作。超时轮询的方式查看文件的读写错误可操作性。
int pselect(int nfds, fd_set* readfds, fd_set* writefds, fd_set* exceptfds, const struct timespec* timeout, const sigset_t* sigmask);
描述:Linux下与select()相似的函数,替换了信号处理方式,当sigmask为NULL时,与select方式一致。
pselect()的timeout为纳秒级分辨率,但是Linux内核调度为毫秒级
poll() 和 ppoll()函数
int poll(struct pollfd* fds, nfds_t nfds, int timeout); //等待某个文件描述符上的某个事件的发生
描述: 监视在fds数组指明的一组文件描述符上发生的动作,当满足条件或者超时即退出。
int ppoll(struct pollfd* fds, nfds_t nfds, const struct timespec* timeout, const sigset_t* sigmask);
描述:timeout采用纳秒级的变量,可在ppoll()函数处理中挂接临时的信号掩码。
非阻塞编程
fcntl(s, F_SETFL, O_NONBLOCK);
描述:使用F_SETFL命令将套接字s设置为非阻塞方式,再进行读写就能马上返回。
第10章 基于UDP协议的接收和发送
UDP框架
缺少了connect(), listen(), accept()函数,因为UDP协议的无连接特性。
套接字类型:数据报套接字SOCK_DGRAM
出现的问题
问题一: UDP报文丢失,导致接收函数recv()/recvfrom()一直被阻塞。
解决办法:对数据报文进行反馈和重发。
问题二: 数据发送和接收的乱序。
解决:加入数据报序号。
问题三: 发送网络接口的绑定–>connect()
第11章 高级套接字
主要内容:UNIX编程、广播和多播概念及例子
UNIX域函数
UNIX域的协议族是在同一台主机上的客户/服务端通信的方法。
两种套接字:字节流套接字和数据报套接字,类似TCP/UDP。
广播
广播地址和端口(接收端角度的广播接收地址和端口)
广播用于一个主机对整个局域网所有主机上的通信。
例子:发现局域网上服务器的IP地址,服务器/客户端自动发现。
多播
多播地址和端口(接收端通过自己的这个地址和端口进行接收)
单播和广播为两个极端,多播对一组特定的主机进行通信。
setsockopt()
和getsockopt()
函数实现,多播的选项为IP层的。
例子:一个多播的服务器端,建立一个数据报套接字,选定多播的IP的端口,直接向多播地址发送数据即可。
客户端只有加入多播组才能接收多播组的数据,将套接字绑定多播地址和端口。
第12章 套接字选项
主要内容:套接字选项、ioctl()
函数有关请求命令、fcntl()
函数有关请求命令。
获取和设置套接字getsocketopt() / setsocketopt()
int getsocketopt(int s, int level, int optname, void* optval, socklen_t* optlen);
int setsocketopt(int s, int level, int optname, void* optval, socklen_t* optlen);
参数level
:
通用套接字为SOL_SOCKET
IP选项为IPPROTO_IP
TCP选项为IPPROTO_TCP
都对应有很多不同的选项optname
使用套接字选项
设置和获取缓冲区大小、获取套接字类型等等···
ioctl()函数
Linux下与内核交互的一种方法,网络程序设计中使用与内核中的网络协议栈进行交互。
int ioctl(int d, int request, ...); //多种参数
fcntl()函数
对套接字和通用描述符进行操作
int fcntl(int fd, int cmd, void arg);
例如对设置套接字为非阻塞IO
第13章 原始套接字
标准套接字:流式套接字、数据报套接字
原始套接字
第14章 服务器模型选择
循环服务器(串行服务器)
简单并发服务器
在服务端,主程序提前构建多个子进程,当客户端请求到来时,系统从进程池中选取一个子进程处理客户端的请求,每个进程处理一个客户端请求。
TCP高级并发服务器模型
按照accept()
分类的多进程和多线程并发服务器
单客户端单进程,统一accept()
主进程统一处理客户端连接,客户端连接到来时才临时fork()进程。
单客户端单线程,统一accept()
单客户端单线程,各线程独自accept(),使用互斥锁
预先分配线程,创建线程池