Linux网络编程
2020-09-28 15:55:14 3 举报
AI智能生成
Linux网络编程
作者其他创作
大纲/内容
Socket编程
套接字概念
socket本身有“插座”的意思,再Linux环境下,用于表示进程间网络通信的特殊文件类型。
本质为内核借助缓冲区形成的伪文件
本质为内核借助缓冲区形成的伪文件
再网络通信中,套接字一定是成对出现的,
一个文件描述符,对应两个缓冲区
一个文件描述符,对应两个缓冲区
预备知识
网络字节序
网络数据流应采用大端字节序
(即低地址高字节)
(即低地址高字节)
网络字节序和主机字节序的转换
#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);
uint32_t htonl(uint32_t hostlong);
uint16_t htons(uint16_t hostshort);
uint32_t ntohl(uint32_t netlong);
uint16_t ntohs(uint16_t netshort);
IP地址转换函数
#include <arpa/inet.h>
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
可重入函数
int inet_pton(int af, const char *src, void *dst);
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);
可重入函数
点分十进制-->网络字节序 inet_pton();
网络字节序-->点分十进制 inet_ntop();
sockaddr数据结构
sudo grep -r "struct sockaddr_in {" /usr
可查看struct sockaddr in 结构体定义
可查看struct sockaddr in 结构体定义
需要强制类型转换
struct sockaddr_in servaddr;
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
bind(listen_fd, (struct sockaddr *)&servaddr, sizeof(servaddr));
网络套接字函数
socket模型创建流程图
socke函数
建立套接字
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
domain:
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
AF_INET 这是大多数用来产生socket的协议,使用TCP或UDP来传输,用IPv4的地址
AF_INET6 与上面类似,不过是来用IPv6的地址
AF_UNIX 本地协议,使用在Unix和Linux系统上,一般都是当客户端和服务器在同一台及其上的时候使用
type:
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
SOCK_STREAM 这个协议是按照顺序的、可靠的、数据完整的基于字节流的连接。这是一个使用最多的socket类型,这个socket是使用TCP来进行传输。
SOCK_DGRAM 这个协议是无连接的、固定长度的传输调用。该协议是不可靠的,使用UDP来进行它的连接。
SOCK_SEQPACKET该协议是双线路的、可靠的连接,发送固定长度的数据包进行传输。必须把这个包完整的接受才能进行读取。
SOCK_RAW socket类型提供单一的网络访问,这个socket类型使用ICMP公共协议。(ping、traceroute使用该协议)
SOCK_RDM 这个类型是很少使用的,在大部分的操作系统上没有实现,它是提供给数据链路层使用,不保证数据包的顺序
protocol:
传0 表示使用默认协议。
传0 表示使用默认协议。
返回值
成功:返回指向新创建的socket的文件描述符
失败:返回-1,设置errno
bind函数
绑定IP、端口
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
#include <sys/socket.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockfd:
socket文件描述符
socket文件描述符
addr:
构造出IP地址加端口号
构造出IP地址加端口号
struct sockaddr_in servaddr;
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
网络地址为INADDR_ANY,这个宏表示本地的任意IP地址
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
servaddr.sin_port = htons(6666);
网络地址为INADDR_ANY,这个宏表示本地的任意IP地址
addrlen:
sizeof(addr)长度
sizeof(addr)长度
返回值:
成功返回0,失败返回-1, 设置errno
成功返回0,失败返回-1, 设置errno
listen函数
指定最大同时发起连接数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int listen(int sockfd, int backlog);
#include <sys/socket.h>
int listen(int sockfd, int backlog);
查看系统默认backlog
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
cat /proc/sys/net/ipv4/tcp_max_syn_backlog
sockfd:
socket文件描述符
backlog:
排队建立3次握手队列和刚刚建立3次握手队列的链接数和
socket文件描述符
backlog:
排队建立3次握手队列和刚刚建立3次握手队列的链接数和
成功返回0,失败-1
accept函数
阻塞等待客户端发起连接请求
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
sockdf:
socket文件描述符
socket文件描述符
addr:
传出参数,返回链接客户端地址信息,含IP地址和端口号
传出参数,返回链接客户端地址信息,含IP地址和端口号
addrlen:
传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
返回值:
成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
成功返回一个新的socket文件描述符,用于和客户端通信,失败返回-1,设置errno
connect函数
发起连接
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
sockdf:
socket文件描述符
socket文件描述符
addr:
传入参数,指定服务器端地址信息,含IP地址和端口号
传入参数,指定服务器端地址信息,含IP地址和端口号
addrlen:
传入参数,传入sizeof(addr)大小
传入参数,传入sizeof(addr)大小
返回值:
成功返回0,失败返回-1,设置errno
成功返回0,失败返回-1,设置errno
客户端需要调用connect()连接服务器,connect和bind的参数形式一致,
区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。
区别在于bind的参数是自己的地址,而connect的参数是对方的地址。connect()成功返回0,出错返回-1。
C/S模型下
server
socket()-->bind()-->listen()-->accept()-->( )-->close
client
socket()-->bind()-->connect()-->( )-->close()
客户端可以“隐式绑定”
出错处理函数
wrap.c
将原本函数加上出错处理代码包装成新的函数
eg:read 返回值:
1. > 0 实际读到的字节数 buf=1024 1. == buf 1024 2. < buf 56;
2. = 0 数据读完(读到文件、管道、socket 末尾--对端关闭)
3. -1 异常
1. errno == EINTR 被信号中断 重启/quit
2. errno == EAGAIN (EWOULDBLOCK) 非阻塞方式读,并且没有数据
3. 其他值 出现错误。--perror exit。
readn\readline
1. > 0 实际读到的字节数 buf=1024 1. == buf 1024 2. < buf 56;
2. = 0 数据读完(读到文件、管道、socket 末尾--对端关闭)
3. -1 异常
1. errno == EINTR 被信号中断 重启/quit
2. errno == EAGAIN (EWOULDBLOCK) 非阻塞方式读,并且没有数据
3. 其他值 出现错误。--perror exit。
readn\readline
高并发服务器
多进程并发服务器
需要考虑以下几点:
1.父进程最大文件描述符个数(父进程需要close关闭accept返回的新文件描述符)
2.系统内创建进程个数(与内存大小相关)
3.进程创建过多是否降低整体服务器性能(进程调度)
1.父进程最大文件描述符个数(父进程需要close关闭accept返回的新文件描述符)
2.系统内创建进程个数(与内存大小相关)
3.进程创建过多是否降低整体服务器性能(进程调度)
fork、signal/sigcation
多线程并发服务器
需要考虑以下几点:
1.调整进程内最大文件描述符上限
2.线程如有共享数据,考虑线程同步
3.服务于客户端线程退出时,退出处理(退出值,分离态)
4.系统负载,随着链接客户端增加,导致其他线程不能及时得到CPU
1.调整进程内最大文件描述符上限
2.线程如有共享数据,考虑线程同步
3.服务于客户端线程退出时,退出处理(退出值,分离态)
4.系统负载,随着链接客户端增加,导致其他线程不能及时得到CPU
线程分离detach
修改线程属性
修改线程属性
多路I/O转接服务器
(多任务IO服务器)
(多任务IO服务器)
主要思想
不再由应用程序自己监视客户端连接,
取而代之由内核应用程序监视文件
取而代之由内核应用程序监视文件
select
1.select能监听的文件描述符个数受限于FD_SETSIZE,一般为1024,
单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
单纯改变进程打开的文件描述符个数并不能改变select监听文件个数
2.解决1024以下客户端时使用select是很合适的,但如果连接客户端过多,select
采用的是轮询模型,会大大降低服务器相应效率,不应再用select
采用的是轮询模型,会大大降低服务器相应效率,不应再用select
#include <sys/select.h>
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,
fd_set *exceptfds, struct timeval *timeout);
nfds: 监控的文件描述符集里最大文件描述符加1,因为此参数会告诉内核检测前多少个文件描述符的状态
readfds: 监控有读数据到达文件描述符集合,传入传出参数
writefds: 监控写数据到达文件描述符集合,传入传出参数
exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout: 定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
readfds: 监控有读数据到达文件描述符集合,传入传出参数
writefds: 监控写数据到达文件描述符集合,传入传出参数
exceptfds: 监控异常发生达文件描述符集合,如带外数据到达异常,传入传出参数
timeout: 定时阻塞监控时间,3种情况
1.NULL,永远等下去
2.设置timeval,等待固定时间
3.设置timeval里时间均为0,检查描述字后立即返回,轮询
struct timeval {
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
long tv_sec; /* seconds */
long tv_usec; /* microseconds */
};
void FD_CLR(int fd, fd_set *set); //把文件描述符集合里fd清0
int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
int FD_ISSET(int fd, fd_set *set); //测试文件描述符集合里fd是否置1
void FD_SET(int fd, fd_set *set); //把文件描述符集合里fd位置1
void FD_ZERO(fd_set *set); //把文件描述符集合里所有位清0
select函数原型分析
select分析
select2
poll
select的升级:不用自行维护文件描述符集合
#include <poll.h>
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
int poll(struct pollfd *fds, nfds_t nfds, int timeout);
struct pollfd {
int fd; /* 文件描述符 */
short events; /* 监控的事件 */
short revents; /* 监控事件中满足条件返回的事件 */
};
int fd; /* 文件描述符 */
short events; /* 监控的事件 */
short revents; /* 监控事件中满足条件返回的事件 */
};
POLLIN 普通或带外优先数据可读,即POLLRDNORM | POLLRDBAND
POLLOUT 普通或带外数据可写
POLLERR 发生错误
nfds 监控数组中有多少文件描述符需要被监控
timeout 毫秒级等待
-1:阻塞等,#define INFTIM -1 Linux中没有定义此宏
0:立即返回,不阻塞进程
>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
-1:阻塞等,#define INFTIM -1 Linux中没有定义此宏
0:立即返回,不阻塞进程
>0:等待指定毫秒数,如当前系统时间精度不够毫秒,向上取值
poll函数
epoll
是select/poll的增强版本,
它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,
原因:1.复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合
2.获取事件时,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行
它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率,
原因:1.复用文件描述符集合来传递结果而不用迫使开发者每次等待事件之前都必须重新准备要被侦听的文件描述符集合
2.获取事件时,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行
epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边沿触发(Edge Triggered),
这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。
可以使用cat命令查看一个进程可以打开的socket描述符上限。
cat /proc/sys/fs/file-max
如有需要,可以通过修改配置文件的方式修改该上限值。
sudo vi /etc/security/limits.conf
在文件尾部写入以下配置,soft软限制,hard硬限制。如下图所示。
* soft nofile 65536
* hard nofile 100000
cat /proc/sys/fs/file-max
如有需要,可以通过修改配置文件的方式修改该上限值。
sudo vi /etc/security/limits.conf
在文件尾部写入以下配置,soft软限制,hard硬限制。如下图所示。
* soft nofile 65536
* hard nofile 100000
API
1. 创建一个epoll句柄,参数size用来告诉内核监听的文件描述符的个数,跟内存大小有关。
#include <sys/epoll.h>
int epoll_create(int size) size:监听数目
#include <sys/epoll.h>
int epoll_create(int size) size:监听数目
2. 控制某个epoll监控的文件描述符上的事件:注册、修改、删除。
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
#include <sys/epoll.h>
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event)
epfd: 为epoll_creat的句柄
op: 表示动作,用3个宏来表示:
EPOLL_CTL_ADD (注册新的fd到epfd),
EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
EPOLL_CTL_DEL (从epfd删除一个fd);
EPOLL_CTL_ADD (注册新的fd到epfd),
EPOLL_CTL_MOD (修改已经注册的fd的监听事件),
EPOLL_CTL_DEL (从epfd删除一个fd);
event: 告诉内核需要监听的事件
struct epoll_event {
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
__uint32_t events; /* Epoll events */
epoll_data_t data; /* User data variable */
};
typedef union epoll_data {
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
void *ptr;
int fd;
uint32_t u32;
uint64_t u64;
} epoll_data_t;
EPOLLIN : 表示对应的文件描述符可以读(包括对端SOCKET正常关闭)
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLERR: 表示对应的文件描述符发生错误
EPOLLOUT: 表示对应的文件描述符可以写
EPOLLERR: 表示对应的文件描述符发生错误
3. 等待所监控文件描述符上有事件的产生,类似于select()调用。
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
#include <sys/epoll.h>
int epoll_wait(int epfd, struct epoll_event *events, int maxevents, int timeout)
events: 用来存内核得到事件的集合,
maxevents: 告之内核这个events有多大,
这个maxevents的值不能大于创建epoll_create()时的size
这个maxevents的值不能大于创建epoll_create()时的size
timeout: 是超时时间
-1: 阻塞
0: 立即返回,非阻塞
>0: 指定毫秒
-1: 阻塞
0: 立即返回,非阻塞
>0: 指定毫秒
返回值: 成功返回有多少文件描述符就绪,时间到时返回0,出错返回-1
事件模型
epoll ET
边沿触发。 event = EPOLLIN | EPOLLET
边沿触发。 event = EPOLLIN | EPOLLET
epoll LT
水平触发(缺省的处理方式)
水平触发(缺省的处理方式)
epoll 非阻塞IO
边沿触发 while(read()) fcntl(O_NONBLOCK);
边沿触发 while(read()) fcntl(O_NONBLOCK);
epoll反应堆模型
libevent核心思想实现
libevent--跨平台 精炼--epoll
核心思想
1. epoll --- 服务器 --- 监听 --- fd ----可读 ---- epoll返回 ---- read --- 小写转大写 --- write ---- epoll继续监听
2. epoll 反应堆模型:
("滑动窗口")
1) epoll --- 服务器 --- 监听 --- cfd ---- 可读 ---- epoll返回 ---- read -- cfd从树上摘下 --- 设置监听cfd写事件, 操作
--- 小写转大写 -- 等待epoll_wait 返回 --- 回写客户端 -- cfd从树上摘下 ----- 设置监听cfd读事件, 操作 -- epoll继续监听
2) evt[i].events = EPOLLIN, evt[I].data.fd == cfd *ptr struct {int fd, void (*func)(void *arg), void *arg}
("滑动窗口")
1) epoll --- 服务器 --- 监听 --- cfd ---- 可读 ---- epoll返回 ---- read -- cfd从树上摘下 --- 设置监听cfd写事件, 操作
--- 小写转大写 -- 等待epoll_wait 返回 --- 回写客户端 -- cfd从树上摘下 ----- 设置监听cfd读事件, 操作 -- epoll继续监听
2) evt[i].events = EPOLLIN, evt[I].data.fd == cfd *ptr struct {int fd, void (*func)(void *arg), void *arg}
struct myevent_s {
int fd; //要监听的文件描述符
int events; //对应的监听事件
void *arg; //泛型参数
void (*call_back)(int fd, int events, void *arg); //回调函数
int status; //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
char buf[BUFLEN];
int len;
long last_active; //记录每次加入红黑树 g_efd 的时间值,
};
int fd; //要监听的文件描述符
int events; //对应的监听事件
void *arg; //泛型参数
void (*call_back)(int fd, int events, void *arg); //回调函数
int status; //是否在监听:1->在红黑树上(监听), 0->不在(不监听)
char buf[BUFLEN];
int len;
long last_active; //记录每次加入红黑树 g_efd 的时间值,
};
服务器监听,如果客户端在指定的时间内无数据收发,则服务器将其从红黑树上剔除
线程池
1. 预先创建阻塞于accept多线程,使用互斥锁上锁保护accept
2. 预先创建多线程,由主线程调用accept
2. 预先创建多线程,由主线程调用accept
线程池思路分析
socket IPC
1. Pipe fifo 实现最简单
2. mmap 非血缘关系进程间
3. 信号 开销小
4. domain 稳定性最好
2. mmap 非血缘关系进程间
3. 信号 开销小
4. domain 稳定性最好
本地套接字domain
只是将应用层数据从一个进程拷贝到另一个进程
更有效率,无需金国网络协议栈、打包拆包等
更有效率,无需金国网络协议栈、打包拆包等
IPC机制本质上是可靠的通讯,
网络协议栈是不可靠的通讯设计
网络协议栈是不可靠的通讯设计
与网络socket的不同
结构体sockaddr_un表示
地址是一个socket类型的文件在文件系统 中的路径
这个文件由bind电泳创建,若文件存在,则bind()错误返回所以在bind前先unlink
地址是一个socket类型的文件在文件系统 中的路径
这个文件由bind电泳创建,若文件存在,则bind()错误返回所以在bind前先unlink
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* Address family */ 地址结构类型
__be16 sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP地址
};
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */ 地址结构类型
char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路径)
};
__kernel_sa_family_t sin_family; /* Address family */ 地址结构类型
__be16 sin_port; /* Port number */ 端口号
struct in_addr sin_addr; /* Internet address */ IP地址
};
struct sockaddr_un {
__kernel_sa_family_t sun_family; /* AF_UNIX */ 地址结构类型
char sun_path[UNIX_PATH_MAX]; /* pathname */ socket文件名(含路径)
};
将UNIX Domain socket绑定到一个地址。
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#define offsetof(type, member) ((int)&((type *)0)->MEMBER)
size = offsetof(struct sockaddr_un, sun_path) + strlen(un.sun_path);
#define offsetof(type, member) ((int)&((type *)0)->MEMBER)
len长度
网络基础
协议的概念
协议
“规则”
是数据传输和数据解释的规则
典型协议
传输层
常见协议有TCP/UDP协议
常见协议有TCP/UDP协议
TCP传输控制协议(Transmission Control Protocol)是一种面向连接的、可靠的、基于字节流的传输层通信协议。
UDP用户数据报协议(User Datagram Protocol)是OSI参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务。
应用层
常见的协议有HTTP协议,FTP协议
常见的协议有HTTP协议,FTP协议
HTTP超文本传输协议(Hyper Text Transfer Protocol)是互联网上应用最为广泛的一种网络协议
FTP文件传输协议(File Transfer Protocol)
网络层
常见协议有IP协议、ICMP协议、IGMP协议
常见协议有IP协议、ICMP协议、IGMP协议
IP协议是因特网互联协议(Internet Protocol
ICMP协议是Internet控制报文协议(Internet Control Message Protocol)它是TCP/IP协议族的一个子协议,用于在IP主机、路由器之间传递控制消息
IGMP协议是 Internet 组管理协议(Internet Group Management Protocol),是因特网协议家族中的一个组播协议。该协议运行在主机和组播路由器之间
网络接口层
常见协议有ARP协议、RARP协议
常见协议有ARP协议、RARP协议
ARP协议是正向地址解析协议(Address Resolution Protocol),通过已知的IP,寻找对应主机的MAC地址。
RARP是反向地址转换协议,通过MAC地址确定IP地址
网络应用程序设计模式
C/S(客户机/服务器)
优点
较高的数据传输效率
协议选用较为灵活
缺点
对用户安全构成威胁
开发工作量大,调试困难
B/S(浏览器/服务器)
优点
开发量较小
移植性好,不受平台限制
缺点
网络应用支持受限
数据缓存不尽人意
协议选择不灵活--http
分层模型
OSI七层模型
物理层
比特
数模转换/模数转换
数据链路层
帧
错误检测和纠正
网络层
数据报
传输层
段
传输协议和端口号
会话层
系统之间发起会话或者接受会话请求
表示层
可确保两系统之间应用层可以实现信息交互
应用层
位用户的应用程序提供网络服务
TCP/IP四层模型
应用层
Telnet、FTP、e-mail等
传输层
TCP、UDP
网络层
IP、ICMP、IGMP
链路层
设备驱动程序及接口卡
模型图
协议格式
数据包封装
数据--应用层--传输层--网络层--链路层
以太网帧格式
MTU:以太网的最大传输单元--1500
ARP数据报格式
ARP缓存表
arp -a
IP段格式
首部长度和数据长度都是可变的,但总是4字节的整数倍
最小20字节,最大60字节
TTL生存时间,经过一跳就减1,为0自动丢弃
UDP数据包格式
TCP数据报格式
NAT映射
私有IP转共有IP
打洞机制
TCP协议
三次握手四次握手
TCP不是不会丢包,是丢包后会重传
三个相关概念
MTU
最大传输单元 受协议限制 以太网1500 IP 65535
mss
表示一个数据包携带数据的上限数
半关闭
滑动窗口(TCP流量控制)
TCP状态转换
2MSL
报文最大生存时间
2MSL等待状态
端口复用
使用setsockopt()设置socket描述符的选项SO_REUSEADDR为1,
表示允许创建端口号相同但IP地址不同的多个socket描述符
表示允许创建端口号相同但IP地址不同的多个socket描述符
在server代码的socket()和bind()调用之间插入如下代码:
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
int opt = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
半关闭
当TCP链接中A发送FIN请求关闭,B端回应ACK后(A端进入FIN_WAIT_2状态),
B没有立即发送FIN给A时,A方处在半链接状态,此时A可以接收B发送的数据,但是A已不能再向B发送数据。
B没有立即发送FIN给A时,A方处在半链接状态,此时A可以接收B发送的数据,但是A已不能再向B发送数据。
#include <sys/socket.h>
int shutdown(int sockfd, int how);
int shutdown(int sockfd, int how);
sockfd: 需要关闭的socket的描述符
how: 允许为shutdown操作选择以下几种方式:
SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。
SHUT_RDWR(2): 关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
SHUT_RD(0): 关闭sockfd上的读功能,此选项将不允许sockfd进行读操作。
该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被无声的丢弃掉。
SHUT_WR(1): 关闭sockfd的写功能,此选项将不允许sockfd进行写操作。进程不能在对此套接字发出写操作。
SHUT_RDWR(2): 关闭sockfd的读写功能。相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。
与close区别:
注意:
1. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
2. 在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其它的进程将无法进行通信。但,如果一个进程close(sfd)将不会影响到其它进程。
1. 如果有多个进程共享一个套接字,close每被调用一次,计数减1,直到计数为0时,也就是所用进程都调用了close,套接字将被释放。
2. 在多进程中如果一个进程调用了shutdown(sfd, SHUT_RDWR)后,其它的进程将无法进行通信。但,如果一个进程close(sfd)将不会影响到其它进程。
TCP异常断开
心跳检测机制
使用场景
在TCP网络通信中,经常会出现客户端和服务器之间的非正常断开,需要实时检测查询链接状态
实现机制
Heart-Beat线程
这个是最常用的简单方法。在接收和发送数据时个人设计一个守护进程(线程),
定时发送Heart-Beat包,客户端/服务器收到该小包后,立刻返回相应的包即可检测对方是否实时在线
这个是最常用的简单方法。在接收和发送数据时个人设计一个守护进程(线程),
定时发送Heart-Beat包,客户端/服务器收到该小包后,立刻返回相应的包即可检测对方是否实时在线
乒乓包
可以携带简单的数据
设置TCP属性
SO_KEEPALIVE 保持连接检测对方主机是否崩溃,避免(服务器)永远阻塞于TCP连接的输入
保持存活探测分节
对方接受一切正常:以期望的ACK相应
对方已崩溃且已重新启动:以RST响应
套接口的待处理错误被置为ECONNRESET,套接 口本身则被关闭
套接口的待处理错误被置为ECONNRESET,套接 口本身则被关闭
对方无任何响应:
源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。
套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。(主机不可达)
源自berkeley的TCP发送另外8个探测分节,相隔75秒一个,试图得到一个响应。在发出第一个探测分节11分钟 15秒后若仍无响应就放弃。
套接口的待处理错误被置为ETIMEOUT,套接口本身则被关闭。(主机不可达)
int keepIdle = 1000; /*开始首次KeepAlive探测前的TCP空闭时间 */
int keepInterval = 10; /* 两次KeepAlive探测间的时间间隔 */
int keepCount = 10; /* 判定断开前的KeepAlive探测次数*/
Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
int keepInterval = 10; /* 两次KeepAlive探测间的时间间隔 */
int keepCount = 10; /* 判定断开前的KeepAlive探测次数*/
Setsockopt(listenfd, SOL_TCP, TCP_KEEPIDLE, (void *)&keepIdle, sizeof(keepIdle));
Setsockopt(listenfd, SOL_TCP,TCP_KEEPINTVL, (void *)&keepInterval, sizeof(keepInterval));
Setsockopt(listenfd,SOL_TCP, TCP_KEEPCNT, (void *)&keepCount, sizeof(keepCount));
UDP协议
发短信--无需建立、维护连接
因此开销较小,数据传输速度快、实时性强
无连接的不可靠报文传递
再应用层添加辅助校验协议来弥补UDP的不足
可能出现的问题
UDP无滑动窗口机制,会出现缓冲区被填满后,再接受数据时丢包的现象
解决方案:1.服务器应用层设计流量控制,控制发送数据速度
2.借助setsockopt函数改变接受缓冲区大小
2.借助setsockopt函数改变接受缓冲区大小
#include <sys/socket.h>
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
int n = 220x1024
setsockopt(sockfd, SOL_SOCKET, SO_RCVBUF, &n, sizeof(n));
220*1024在不断实验中发现大小最佳
UDP处理模型
C/S模型-UDP(直接传数据)
TCP与UDP比较
TCP
面向连接的可靠数据包传递 ---完全弥补
优点
稳定
1.数据稳定 --- 丢包回传(回执机制)(丢包率97‰)
2.速率稳定
3.流量稳定 --- 滑动窗口
缺点
效率低、速度慢
使用场景
大文件、重要文件传输
UDP
无连接的不可靠报文传递 ---完全不弥补
缺点
不稳定
数据、速率、流量
优点
效率高、速度快
使用场景
对实时性要求比较高,视频会议、视频电话、广播、飞秋
腾讯
TCP --- TCP + UDP --- UDP+应用层自定义协议弥补UDP的丢包
广播
IP:192.168.42.255(广播)
IP:192.168.42.1(网关)
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
给sockfd开放广播权限
给sockfd开放广播权限
组播(多播)
永久/临时组播组
官方分配---永久组播组
ip地址不变
组内成员可变化、数量任意
224.0.0.0~224.0.0.255 为预留的组播地址(永久组地址),地址224.0.0.0保留不做分配,其它地址供路由协议使用;
224.0.1.0~224.0.1.255 是公用组播地址,可以用于Internet;欲使用需申请。
224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。
224.0.1.0~224.0.1.255 是公用组播地址,可以用于Internet;欲使用需申请。
224.0.2.0~238.255.255.255 为用户可用的组播地址(临时组地址),全网范围内有效;
239.0.0.0~239.255.255.255 为本地管理组播地址,仅在特定的本地范围内有效。
ip ad查看网卡编号
if_nametoindex 命令可以根据网卡名,获取网卡序号
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
server:获取组播权限。
setsockopt(sockfd, IPPROTO_IP, IP_MULTICAST_IF, &group, sizeof(group));
client:将本客户端加入组播。
setsockopt(confd, IPPROTO_IP, IP_ADD_MEMBERSHIP, &group, sizeof(group));
分屏软件实现思路
1.屏幕截图模块(24帧 几MB) -- 2.截取帧数(8-12帧) -- 3.压缩图片M->K -- 4.压缩数据包 -- 5.传递(多播) -- 6.解压缩(算法) -- 7.成像
setsockopt
int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);
作用总结
1.端口复用
2.设置缓冲区大小
3.开放广播权限
4.开放组播权限
5.加入组播组
0 条评论
下一页