文章目录
- 九、完整的udp server代码
- 十、完整的udp client代码
- 十一、对比TCP字节流服务和UDP数据包服务
- 十二、一个单线程的HTTP服务器
- 十三、libevent的使用
一、什么是Socket
- Socket可以看成是用户进程与内核网络协议栈的编程接口
- Socket不仅可以用于本机的进程通信,还可以用于网络上不同主机的进程间通信
二、套接字地址结构
- IPv4地址结构,以 “sockaddr_in”命名,定义在头文件<netinet/in.h>中
- sin_len:整个sockaddr_in结构体的长度
- sin_family:指定地址家族,IPv4中是AF_INET
- sin_port:端口
- sin_addr:IPv4的地址
- sin_zero:一般设为0
填信息的时候一律用sockaddr_in
,传入API的时候用sockaddr
- 通用地址结构
- 头文件:<sys/socket.h>
- sin_len:整个sockaddr_in结构体的长度
- sin_family:指定地址家族,IPv4中是AF_INET,IPv6中是AF_INET6
- sa_data:由sin_family决定大小
三、套接字函数使用
- 字节序转换函数
- h表示host
- n表示network
- l表示long
- s表示short
- 地址转换函数
四、套接字类型
- 流式套接字(SOCK_STREAM)
- 提供面向连接的、可靠的数据传输服务,数据无差错,无重复发送,且按序接收(TCP)
- 数据报式套接字(SOCK_DGRAM)
- 提供无连接服务,数据可能丢失或重复,可能顺序混乱(UDP)
- 原始套接字(SOCK_RAW)
五、socket编程
1、服务器端函数
- 创建套接字
- 原型:int socket(int domain, int type, int protocol)
- domain:指定通信协议族,IPv4(AF_INET),IPv6(AF_INET6)
- type:指定socket类型,流式套接字(SOCK_STREAM)、数据报式套接字(SOCK_DGRAM)、原始套接字(SOCK_RAW)
- protocol:协议类型,一般写成0
- 返回值:一般称之为套接字描述字(类似于文件描述符),成功返回非负整数,失败返回-1
- 绑定函数(数据从进程到内核,服务器)
- 功能:绑定一个本地地址到套接字
- 原型:int bind(int sockfd, const struct sockaddr addr, socklen_t addrlen)*
- sockfd:socket函数返回的套接字
- addr:要绑定的套接字
- addrlen:套接字地址长度,IPv4为16,IPv6为24
- 返回值:成功返回0,失败返回-1
- 监听函数(服务器)
- 功能:将套接字用于监听进入的连接
- 原型:int listen(int sockfd, int backlog)
- sockfd:socket函数返回的套接字
- backlog:规定内核为此套接字排队的最大连接数
- 返回值:成功返回0,失败返回-1
listen只是把套接字从主动变为被动,并限制链接数;剩下的问题就是accept的,它会检测的;
listen意思是监听:但是它不是一直在监听,accept才是;
listen函数不会阻塞,它只是相当于把socket的属性更改为被动连接,可以接收其他进程的连接。listen侦听的过程并不是一直阻塞,直到有客户端请求连接才会返回,它只是设置好socket的属性之后就会返回。监听的过程实质由操作系统完成。但是accept会阻塞(也可以设置为非阻塞),如果listen的套接字对应的连接请求队列为空(没有客户端连接请求),accept会一直阻塞等待
- 连接函数(服务器)
- 功能:从已完成连接队列返回第一个连接,如果已完成连接队列为空,则阻塞
- 原型:int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen)
- sockfd:服务器套接字
- addr:将返回对方的套接字地址
- addrlen:返回对方的套接字地址长度
- 返回值:成功返回非负描述字conn,失败返回-1
2、客户端函数
- connect函数
- 返回值
- 0:成功
- -1:失败
3、其他函数
- 地址转换函数
- int inet_aton(const char* cp, struct in_addr* inp);//点分十进制的ip地址转换成网络字节序的ip地址
- in_addr_t inet_addr(const char* cp);//点分十进制的ip地址转换成32位整数
- char* inet_ntoa(struct in_addr in);//32位整数转换成点分十进制的ip地址
- 新型网路地址转化函数inet_pton和inet_ntop(to point)
- 头文件:#include <arpe/inet.h>
- int inet_pton(int family, const char *strptr, void *addrptr) //将点分十进制的ip地址转化为用于网络传输的数值格式
- 返回值:若成功则为1,若输入不是有效的表达式则为0,若出错则为-1
- const char * inet_ntop(int family, const void *addrptr, char *strptr, size_t len); //将数值格式转化为点分十进制的ip地址格式
- 返回值:若成功则为指向结构的指针,若出错则为NULL
- readn函数
- *ssize_t readn(int fd, void vptr, size_t n)
- 功能:从描述字中读取n个字节为止或读到EOF为止
- 返回值:
1.大于0,代表成功读取的字节数
2.等于0,代表读取到了EOF,一般是对方关闭了socket的写端或者直接close
3.小于0,出现错误。
- writen函数
- 原型:*ssize_t writen(int fd, const void vptr, size_t n)
- 功能:往描述字中写入n个字节
- 返回值
小于0:错误
大于0:成功写入n个字节
- readline函数
- 原型:ssize_t readline(int fd, void *vptr, size_t maxlen)
- 功能:从描述字中一个字节一个字节地读取放到缓冲区vptr,直到读到最大长度maxlen,或读到换行符,或读完数据
- 返回值
大于0:返回的是成功读取到的字节数
等于0:没有数据读取
小于0:错误
- read函数
- 原型:ssize_t write(int fd, const void*buf,size_t nbytes);
- 功能:将buf中的nbytes字节内容写入文件描述符fd.成功时返回写的字节数.失败时返回-1. 并设置errno变量
- 返回值
大于0,表示写了部分或者是全部的数据
小于0,此时出现了错误
- write函数
- 原型:*ssize_t read(int fd,void buf,size_t nbyte)
- 功能:read函数是负责从fd中读取内容
- 返回值
大于0,返回的是实际所读的字节数
小于0,表示出现了错误
socket套接字是全双工的
六、完整的tcp server代码
七、完整的tcp client代码
八、完整的多进程tcp client代码
抓包命令: tcpdump -i <网卡> -nt -S '(src dst) or (src dst)'
流式服务和粘包问题
HTTP协议解决粘包问题: 在每个数据报头写入当前报文的长度,接收端会根据该长度控制或分割自己接收的数据包。
TCP状态转移总图
TIME_WAIT状态的意义: 主动关闭的一方 收到对方的FIN,自己回复ACK后,并没有直接进入CLOSED状态,而是进入TIME_WAIT状态一段时间(大约是报文最长生存时间的2倍)。
- 若先关闭的一方回复ACK后,直接进入CLOSED状态,假如ACK丢失了,后关闭的一方会重发FIN,此时先关闭的一方无法接收,四次挥手无法完成。(若后关闭的一方不重发FIN,则说明成功收到ACK)
- 进入TIME_WAIT,若后关闭的一方没收到ACK,会再次发送FIN,进入TIME_WAIT的一方重发ACK。
为什么TIME_WAIT状态大约是报文最长生存时间的2倍?
假设网络拥塞,数据包在网络中传送了很长时间,若发送ACK后没有TIME_WAIT状态直接关闭连接,而这时又收到了迟到的数据包就无法处理。
TIME_WAIT状态大约是报文最长生存时间的2倍,是为了保证能正确收到所有能被送达的数据包。
若先关闭的一方处于TIME_WAIT状态,则程序无法重新启动
九、完整的udp server代码
udp编程流程
十、完整的udp client代码
十一、对比TCP字节流服务和UDP数据包服务
TCP服务中将收取的字节改为1
UDP服务中将收取的字节改为1
tcp可以把多次send的数据放到缓冲区然后发送,是因为tcp发送的数据一定是发送给指定的主机。而udp不能合并报文,是因为每个udp数据报去往的主机不一定相同。
对于tcp而言,客户端发送一个报文hello
到了tcp缓冲区,虽然进程一次recv
只能取一个字节,但只要tcp缓冲区不空,就会不断执行while循环,不断 从缓冲区中取数据。
对于udp而言,一次recvfrom
直接从数据报中取数据 ,只能取一个字节,由于udp服务没有数据缓冲区,所以一个报文的数据没收完就丢失了。
面试题:如果server和client建立连接后,server的网线断了,然后断电重启联网,重启进程后,server和client分别处于什么状态?
断网断电后,server无法发出任何的报文通知client,所以client认为连接就是正常的,除非client发出报文,才会发现连接异常。服务器重启后,和原来客户端的连接已经丢失,处于监听状态
十二、一个单线程的HTTP服务器
十三、libevent的使用
libevent就是一个封装好select、poll、epoll等功能的库
使用libevent写的tcp服务器