《TCP/IP网络编程书籍》知识点整理
-
理解网络编程和套接字
-
套接字类型与协议设置
-
地址族与数据序列
- 网络字节序是大端,本地是小端,大端是把最后的字节放前面,小端是最后的字节放后面
- h代表host,n代表networt。这些是大小端转换的函数,short4B,long8B。
- unsigned short tons(unsigned short)
- unsigned short ntohs(unsigned short)
- unsigned long htonl(unsigned long)
- unsigned long ntohl(unsigned long)
- p代表presentation,n代表network。这些函数是字符串(点分10进制)转网络字节序地址(8B)的。
- int inet_pton(int af, const char *src, void *dst)
- 返回1成功,返回0是ip地址格式不对,返回-1时af不对。
- af一般是宏确定,ipv4的是AF_INET
- 传入参数时src,字符串类型(点分10进制)
- 穿出参数是dst,一般复制给sockaddr_in中的sin_addr.s_addr上
- const char *inet_ntop(int af, const void *src, char *dst, socklen_t size)
- 成功返回字符串指针,若出错则为NULL
- src是传入参数,是网络字节序2进制的,一般是sockaddr_in中的sin_addr.s_addr
- dst是传出参数,是字符串(点分10进制)
-
基于TCP(1) 这章是告诉你怎么实现socket
- TCP不存在数据边界,可以用一个回应来接收好多条传输
- 注意TCP连接是有奇数个socket,一个监视(socket)加上一对会话或者几对会话(accept)。
- int socket(int domain, int type, int protocol)
- domain 判断是什么协议 ipv4
- type 用什么流 stream 一般是tcp
- protocol 默认用0
- int bind(int socket, const struct sockaddr *address, socklen_t address_len)
- socket 标志符
- address 传入参数,里面有ip地址端口号协议等
- address_len 顾名思义address的大小
- int listen(int socket, int backlog)
- socket 标志符
- backlog 最大连接数
- int accept(int socket, struct sockaddr *restrict address, socklen_t *restrict address_len)
- socket 标志符
- address 传出参数,里面有客户端的ip地址端口号协议等
- address_len 顾名思义address的大小
- read()/write()
- int close(int fildes)
-
基于TCP(2) 这章是实践,做一个回声服务端和客户端。
-
基于UDP
-
存在数据边界
-
1 2
int socket(PF_INET, SOCK_DGRAM, 0); int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
- 创建
-
1
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);//发送
- sockfd用socket创建出来的数
- buf要发送的缓存数据
- len要发送的长度
- flags可选参数, 默认0
- dest_addr有目的地址的结构体
- addrlen结构体长度
-
1
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);//接收
- sockfd用socket创建出来的数
- buf要发送的缓存数据
- len要发送的长度
- flags可选参数, 默认0
- dest_addr有目的地址的结构体
- addrlen结构体长度
-
-
断开套接字
- int shutdown(int sock, int howto) 半关闭函数
- sock文件描述符
- howto 关闭什么
- SHUT_RD 断开输入
- SHUT_WR 断开输出
- SHUT_RDWR 断开IO流
- 相当于第一次调用RD,第二次调用WR。
- 这个函数不管什么模式,都会向客户端传送EOF,也就是结束标志
- 存在的意义就是让客户端知道要断开了,但还可以收到消息。
- int shutdown(int sock, int howto) 半关闭函数
-
域名及网络地址
- nelookup 可以用域名查看ip地址
- struct hostent _ gethostbyname(const char _ hostname)
- struct hostent
- char * h_name 官方域名
- char ** h_aliases 其他的许多域名(所以是二级指针)
- int h_addrtype 地址类型ipv4,ipv6
- int h_length ipv4 4字节,ipv6 6字节
- char ** h_addr_list 许多对应的ip地址(所以是二级指针)
- 这里面的ip地址需要转换才能看懂
- inet_pton()
- 失败返回NULL
- struct hostent
- struct hosting _ gethostbyaddr(const char _ addr, sickle_t len ,int family)
- addr 结构体指针,里面有ip地址
- 里面的ip地址要转换成网络字节序
- inet_ntop()
- len addr的字节数
- family ipv4是AF_INET,ipv6是AF_INET6
- addr 结构体指针,里面有ip地址
-
socket的多种可选项,其中getsockopt是看选项的,setsockopt是设置的。
- 可以改变缓冲区大小,但也是大概改,并不一定就是那个数,因为操作系统会二次调整一下
- 可以在time-wait时, 是端口复用, 但端口还是在time-wait的, 以便下次还可以同一个端口再次通信。
- time-wait在服务端先挥手时会出现,就是4次挥手的倒数2次之后,倒数1次之前。
- 因为你最后的那个ACK数据包,你虽然发出去了,但你也不知道他收没收到,所以你怎么判断,你要等一段时间,看看客户端是否再次给你发了一个FIN包,如果没有,那么成功,如果有那就是失败,你需要再发一个包才能断开。所以你要等一段时间去判断。
- time-wait会都存在于客户端与服务端,只要谁先请求断开,他就会time-wait,但一般不关心客户端的time-wait,因为客户端再次连接时,会随机一个端口连接,就对于用户透明了。
-
多进程服务端,显而易见,服务端要能同事服务好多客户端,所以多进程有用
- fork()创建子进程,父进程返回进程号(PID),子进程返回0。
- 子进程继承父亲的所有东西,包括堆、栈等。
- 僵尸进程的产生是由于,如果要回收子进程,要先结束父进程,所以中途子进程结束,父进程没有结束的间隙,子进程没用了,就叫僵尸进程
- wait()阻塞,等待子进程结束
- Waited()不阻塞,轮训子进程结束
- 信号,因为上面两种方式不合适,所以要用信号,我理解就是中断。
- alarm()函数能够定时,到时间后能引起信号。
- Sigaction()比signal()函数更加稳定,并且在不同的Unix系统中,功能都一样。
- 可以传递给Sigaction()函数指针,第一个参数是信号类型。
- SIGALRM 到时间了alarm()
- SIGINT 输入ctrl+C
- SIGCHLD 子进程终止
- 子进程和父进程都有套接字描述符时,子进程和父进程要把服务端的两个描述符,一共四个描述符都close()掉,才能关闭那个socket
- 根据黑马linux网络编程又知道了一个观点
- 子进程会继承父进程的文件描述符, 但之后不会再通信了,应该就是两个文件描述符了
- shutdown()是关闭服务端的客户端通信套接字, close是关闭文件描述符
- 所以如果子进程和父进程要关闭socket,要调用close4次
- 根据黑马linux网络编程又知道了一个观点
-
进程间通信–管道
- int pipe(int filedes[2])
- 成功返回0,失败返回-1
- filedes[0]是文件描述符出口,filedes[1]是文件描述符入口
- 读取的时候没有数据的话,是阻塞的
- 读取完成后,数据就被取走了
- 管道是半双工,可以用两个管道实现全双工。
- 半双工,同一时间只能单向通信
- 全双工,同一时间可以双向通讯
- 单工,只能单向通讯
- int pipe(int filedes[2])
-
I/O复用
- int select(int maxfd, fdset readset, fd_set _ writes, fd_set _ exceptset, const struct timeval _ timeout)
- maxed最大的检测数量,一般会多1,因为fd是从0开始
- readset检测待读取数据位
- writes检测可传输无阻塞数据位
- exceptset检测发生异常位
- timeout超时的设置位
- sec秒 seconds
- usec毫秒 microseconds
- 超时返回0,失败返回-1,其他数字就是剩下的文件描述符总数。
- int select(int maxfd, fdset readset, fd_set _ writes, fd_set _ exceptset, const struct timeval _ timeout)
-
多种I/O函数
-
1
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
- sockfd 套接字描述符
- buf 缓存
- len 待传输字节数
- flags 标注位
- 一般用0, 就和write()一样
-
1
ssize_t recv(int sockfd, void *buf, size_t len, int flags)
- sockfd 套接字描述符
- buf 缓存
- len 最大传输字节数
- flags 标注位
- 一般用0, 就和write()一样
- 用msg_peek的话是从管道中不拿走, peek一下
-
-
多播与广播
-
套接字与标准I/O
-
1 2 3
FILE *fdopen(int fd, const char *mode) FILE *fopen(const char *pathname, const char *mode) //也可以用pathname字符串来输入 //从fd文件描述符转成FILE * 指针
-
1 2
int fileno(FILE *stream) //从FILE * 指针转成fd文件描述符
-
1 2 3 4 5 6 7
fgets(),fputs()函数有缓存 char *fgets(char *s, int size, FILE *stream) //读取 int fputs(const char *s, FILE *stream) //输出字符串 int fflush(FILE *stream) //刷新缓存, 把数据发送出去.
-
-
关于I/O流分离的其他内容
- 分离流可以让代码更好看,逻辑上更清晰
- 一般读用一个FILE指针, 写用一个FILE指针
- 如果这两个指针指向的都是一个文件描述符, 那么关闭( close() )其中一个FILE, 文件描述符也关了
- 所以解决这个问题, 要复制( dup() )一个文件描述符, 用2个FILE指针指向2个文件描述符,
- 这样关闭其中写FILE指针, 读FILE指针也可以继续读
-
优于select的epoll
-
select是条件触发
-
epoll默认是条件触发,可以在epoll_ctl()更改event来变成边缘触发
- event.events = EPOLLIN | EPOLLET
- EPOLLIN是传入参数, 有需要读的情况的数值
- 或运算EPOLLET后, 就变成了边缘触发
- ET(Edge Triggered)边缘触发
- LT(Level Triggered)水平触发
-
epoll函数
1 2 3 4 5 6 7 8 9 10
int epoll_create(int size) //创建红黑树, 返回红黑树根结点 int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event) //op是操作: EPOLL_CTL_ADD, EPOLL_CTL_ADEL, EPOLL_CTL_MOD //fd: 操作的文件描述符 //event: 要加入的事件 int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout) //events传出参数 //maxevents最大穿出数量 //timeout 毫秒为单位, -1一直阻塞, 0不阻塞
-
epoll标准函数过程
- 创建红黑树
- 用epoll_ctl添加 创建的accept()句柄
- 用epoll_wait返回的结果
- 根据fd判断是accept()还是接收客户端要发送
-
epoll反应堆过程
- 创建红黑树
- 创建拥有同样多时间的结构体,或者函数,这些函数或结构体要放在data.*ptr上, 以便于返回后根据ptr找到函数或结构体
- 用epoll_ctl添加 ptr是accept()函数的 事件
- 封装的accept()的函数, 中能添加处理ptr赋值的事件
- 用epoll_wait返回的结果调用ptr函数
-
-
多线程服务器的实现
- 创建线程
- 线程同步
- 信号量
- 互斥量
-
制作http服务器端
-
请求消息(Request Message)
1 2 3 4 5 6 7
GET /indext.html HTTP/1.1 User-Agent: Mozilla/5.0 Accept: image/git, image/jpeg Accept-Language: zh-cn, zh; Accept-Encoding:gzip <消息体>只有POST方法才存在
- 请求行
- 消息头
- 空行
- 消息体
-
响应消息(Response Message)
1 2 3 4 5 6 7
HTTP/1.1 200 OK Server: SimpleWebServer Content-type: text/html;charset=gb2312 Content-length: 2048 <html> </html>
- 状态行
- 消息头
- 空行
- 消息体
-
-
总结(进阶内容)
- UNIX环境高级编程(第3版)
- TCP/IP详解 (卷1~卷3)
- 推荐卷1, 讲解的是TCP/IP协议