1. 接口
socket( , , )返回一个int端口
- 调用connect read就是client;
- 调用bind,listen,accept就是server
1.2 基于linux的文件操作
linux里面认为socket也是文件的一种,所以调用的是read, write
但是windows里区分socket和文件,所以它用的是recv, send
文件描述符:是系统分配给文件或套接字的整数(int),但是标准输入,标准输出,标准错误是三个输入输出对象,程序一开始就自动分配文件描述符了的就(一般编号就是0,1,2)。文件描述符可以理解为映射号。
1.3 基于windows的实现
windows下适用socket如下条件:
1.头文件:winsock2.h
2.链接库:ws2_32.lib
3.定义WSADATA结构体,
3.初始化:WASstartup()设置winsock版本(可用MAKEWORD函数),出入WSADATA结构体;
WSADATA wsaData;
...
if(WSAStartup(MAKEWORD(2,2), &wsaData)!=0)
ErrorHandling("WSAStartup() error");
...
WSACleanup(void);
接口差异:
(功能差不多,具体有差别)
- close -> closesocket
- write -> send
- read -> recv
2.套接字类型及协议
2.1协议
socket(协议族,套接字类型,
3.地址族与数据序列
一些计算机网络的基础知识。
- 操作系统由端口号区分不同套接字
- 地址信息的表示:
ipv4地址结构体:
地址族;ip地址;端口号;
struct sockaddr_in{
sa_family_t sa_family; // 地址族
uint_16t sin_port; //端口号
struct in_addr sin_addr; // ip结构体
char sin_zero[8];
}
#define uint32_t In_addr_t;
struct in_addr{
In_addr_t s_addr;
}
-
cpu解析数据的方式2种:
- 大端序:数据高字节存放到内存低字节
- 小端序:低字节存放到低字节
intel以小端序存数据。
但是约定网络传输数据以大端序传。
-
转换字节序的函数:htons(把short型数据从host主机字节序转化to为网络字节序n)
-
inet_addr能自动解析ip地址,赋值就行
-
inet_aton 直接返回修改addr_in结构体
-
inet_ntoa 把网络字节序整数转为ip字符串
-
INADDR_ANY 自动获取服务端ip地址
4.基于TCP的服务端
listen(socket1, c) c是等待队列大小
如何实现非阻塞的accpet?
5.TCP的服务/客户端
server给client传数据,可能数据长度会大于缓存窗口嘛?
不会,因为TCP有流量控制功能,窗口控制。
TCP数据传输没有边界:数据传输过程中调用的I/O次数不具有意义
6.UDP服务/客户端
UDP有数据边界:sendto和recvfrom的次数要完全一致才不会丢包。
udp 的client流程:
- 向udp的socket注册目标ip的端口号;
- sendto传数据;
- 删除socket中注册的目标地址信息;
udp的client sock直接sendto就是没连接的udp
若多次udp传输,可以建立连接的udp,向sock注册ip和端口,后面不仅可以sendto,
7. 断开套接字
close和closesocket是完全断开连接。
半关闭:只接不发,或者只发不接
8.域名
DNS:是ip地址和域名相互转换的系统 domain name system
域名相当于虚拟地址,dns把它转化为地址
内置dns服务器-> DNS服务器->根DNS服务器
gethostbyname
- 由域名字获取ip地址
gethostbyaddr
9.套接字多种可选项
getsockopt:获取套接字种类信息
setsockopt:设置套接字属性
SO_REUSEADDR属性
TCP-NODELAY属性
- nagle算法:最大限度的缓冲,收到ACK后就发送;
- 不用nagle就是流水线传输,速度快,大文件可考虑禁用;
- 设TCP_NODELAY为1
10. 多进程服务器
并发服务器实现模型:
- 多进程服务器
- 多路复用服务器
- 多线程服务器
多进程服务器
多进程:“占用内存空间的正在运行的程序”;
进程ID:
-
id>=2
-
ps查看正在运行所有线程
-
fork创建进程 子进程调fork返回0,
-
僵尸进程:
-
wait
- pid_t wait(int *statloc)
- statloc: WIFEXITED 子进程正常终止时返回,WEXISTSTATUS返回子进程的返回值
-
waitpid
- pid_t waitpid(pid_t pid, int *statloc, int options)
- pid为-1则等待任意进程终止
- statloc:同上
- options:传WNOHANG表没终止的子进程在此不阻塞而是返回0退出;
- 返回终止的进程号
信号:signal
- 注册信号
- alarm会产生SIGNALRM信号
- ctrl+c产生SIGINT信号
sigaction:
- 类似signal
- 稳定
- 在不同UNIX系统中完全相同,signal可能不同
socket属于操作系统的资源,不属于进程
11.进程间通信
- 管道pipe
- [0]:出口
- [1]:入口
- 通过fork传递到子进程中
12.I/O复用
accpet/recv是阻塞的,有客户端连接上来才会继续往下执行(如果是多个客户端都链接上来,listen时有监听队列池,可以挨个处理),
select使用
1.设置文件描述符
fd_set结构体,数组,每位存0/1,1表示是监视对象,具体是通过位为单位进行的。
- FD_ZERO:全部位初始化0
- FD_SET:FD_SET(int fd, fd_set* fdset) 向fdset注册fd的信息
- FD_CLR:清除某一位的fd信息
- FD_ISSET:检查某一位是否被监视
2.设置监视范围
int select(int maxfd, fd_set* readset, fd_set* writeset, fd_set* exceptset, const struct timeval* timeout);
在区间范围内监视文件描述符的三种状态:
- 是否存在待读取数据
- 是否可传输无阻塞数据
- 是否异常
3.设置超时
struct timeval{
long tv_sec;
long tv_usec;
}
4.调用select函数查看结果
除了发生变化的位外,fd_set别的位都要归0。
13.其他IO函数
1. send、recv
关于最后的flags位设置:
- MSG_DONTWAIT: 非阻塞IO
- MSG_WAITALL:
- MSG_DONTROUTE:
- MSG_PEEK:
- MSG_OOB
- 发送紧急消息
关于OOB
tcp的oob并不是通过完全不同的通信路径传输数据实现的,而是由tcp里面的紧急模式标识设置传输的。
并不保证数据的迅速传输,但是通过及时处理信号的方式实现对紧急数据的及时处理。
2. readv writev
对数据整合一起传输以及发送
减少write,read次数
14.广播和多播
1.多播
- 基于udp
- 借助路由器,复制UDP数据包传递到多个主机,不向同一片区域发送多个数据包
- 不像tcp建立多个连接传多次,不像udp传多次
- 只发送一次数据,组内客户端都会接收到数据
- 多播组数在IP范围内可任意增加
- 多播组D类IP地址: 224.0.0.0~239.255.255.255
- 不支持多播的路由器,使用隧道技术(Tunneling)
- TTL( time to live)
设置: setsockopt(sock, IPPROTO_IP, IP_MULTICAST_TTL, (void*)&itime_live, sizeof(itime_live));
IPPROTO_IP是协议,黑体是关键字
3.加入多播
结构体,设置成员变量,再通过setsockopt导入设置;
struct ip_mreq join_addr;
join_addr.imr_multiaddr.s_addr = "多播组地址信息“;
join_addr.imr_interface.s_addr = "加入多播组的主机地址信息”;
setsockopt(sock, IPPROTO_IP, IP_ADD_MEMBERSHIP, (void*)& join_addr, sizeof(join_addr));
3.广播
区域网内
也是由setsockopt完成
15. 标准I/O
缓冲带给数据传输带来提升。
- 数据包更高效
- 数据向输出缓冲移动的次数
例如复制文件,利用系统函数read, write比fgets, fputs慢。
标准I/O函数缺点:
- 不容易双向通信;
- 可能频繁调用fflush函数;强迫将缓冲区数据输出
- 要以FILE结构体指针接收文件描述符;
int -> FILE* : fdopen
FILE* -> int : fileno
16.I/O流分离
此前也有过io的流分离,如TCP/IP的半关闭(shutdown(dock, SHUT_WR) )和fdopen对sock的读写分离
优点:
- 分离输入输出降低实现难度;
- 与输入无关的输入操作可以提高速度;
- 区分IO缓冲提高缓冲性能;
fcntl:更改文件描述符属性;
如何实现fdopen后的半关闭? - 复制文件描述符
dup(fd)
dup2(fd, i)
17. epoll
select的速度慢
- 对所有文件描述符都要循环检查;
- 每次调用都要传递监视对象信息;
尤其是第二点更影响速度,第一点可以代码优化掉,第二点传递监视对象进select,就是想操作系统传递监视对象信息,难以优化。
select优点:
- 大部分系统都支持select;
- 在接入量少,需要兼容性时都应考虑select;
epoll
- epoll_create: 创建epoll例程(类似socket描述符)
- epoll_ctl: 向epoll注册监视文件描述符
- 有很多类型事件
- struct epoll_event
- union epoll_data
- epoll_wait: 监听等待
在长连接且频繁发送大小不一的消息时,epoll和IOCP模型性能好。
条件触发-边缘触发 - 边缘触发可以分离接收数据和处理数据的时间点;
- 如果用条件触发方式延迟接收处理数据以求时间一致,会导致epoll_wait的事件累计
- epoll默认条件触发
18.多线程服务器
解决多进程的开销、效率问题。多进程的上下文切换开销大,线程创建更快。
pthread_create() 创建
pthread_join() 接收返回
编译: -lpthread
线程安全:
- linux: _r win: _s
- #define _REENTRANT
- 编译加参数 -D_REENTRANT
线程同步
mutex
- pthread_mutex_t
- pthread_mutex_init
- pthread_mutex_lock;
- pthread_mutex_unlock;
关于临界区的保护范围,大还是小,根据情况判断。
信号量
sem_t
sem_init
sem_destory
sem_post 信号量加1
sem_wait 信号量减1
利用类似引用计数的方式来标记资源的更新与请求。所以信号量本身是不是原子的(是),应该可以用atomic_flag/atomic_bool代替么?
线程销毁
- pthread_join: 让主线程阻塞等待子线程
- pthread_detach: 分离线程