栈存放:局部变量,先入后出,系统自己分配和释放。
函数参数
堆存放:数组
少于200字节的数组
pthread_join等待线程函数结束
pthread_create创建线程函数,线程间同步操作。
select的作用:网络中当存在阻塞时,为了不影响别的程序运行,选择了select
read / recv 阻塞函数,
accept返回值时一个新的socketID;
文件描述符为什么要加1?
因为文件描述符是从0 开始的。 0 1 2。。。。。最大文件描述符是2, 但是实际上
监听的文件描述符是3个
进程的内存结构:
在创建进程时,会给它分配内存。4G
一般情况下分为四个段,代码段,数据段,堆段,栈段
代码段时只读的;数据段一般分为bss段(未初始化的全局变量)
数据段(已初始化的全局变量、静态变量)和常量区
堆段(malloc / new)
栈段(局部变量,参数,保存现场)
1.UDP
打开socket
绑定自己的ip,port
通信recvfrom/set dest addr, sendto-->addr
关闭socket
2. UDP download
server:
打开socket
绑定自己的ip,port
open file
发送文件大小
while(1)
{
ret = read();
if (0 >= ret)
break;
sendto
}
close file
close socket
client:
打开socket
绑定自己的ip,port
create and open file
接收文件大小
while(1)
{
ret = recvfrom();
if (0 >= ret)
break;
write
fileSize -= ret;
if (0 >= fileSize)
break;
}
close file
close socket
内存使用:小栈大堆
栈:先入后出;系统自己分配和释放;用的时候分配,用完自动释放。
栈里存放-》局部变量,参数... ...
例:数组-》栈 -》少于200个字节
堆:
ogm->视频->7M空间
一、IO模型
1.fcntl修改阻塞<->非阻塞
2.多路复用
tcp server
版本1
打开socket; 绑定自己的ip,port; 监听
三次握手
通信
三次握手
通信
三次握手
通信
三次握手
通信
... ...
关闭socket
版本2
打开socket; 绑定自己的ip,port; 监听
while(1)
{
三次握手 accept
通信 recv, (scanf)send
}
关闭socket
版本3
打开socket; 绑定自己的ip,port; 监听
while(1)
{
三次握手 accept
通信 recv, recv, recv ... (scanf)send
}
关闭socket
版本4
打开socket; 绑定自己的ip,port; 监听
while(1)
{
三次握手 accept(socketID)
while(1){
通信 recv(newID) //(scanf)send
}
}
关闭socket
总结:
当前的程序中有多个阻塞描述符,每一个描述符可能会对其它描述符的正常通信造成影响。
可以使用多路复用来解决上述问题,例如select
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>
int select(int n, fd_set *read_fds, fd_set *write_fds, fd_set *except_fds, struct timeval *timeout);
参数
n: 监听的最大文件描述符+1.
read_fds : 读文件描述符集。-- 通俗的讲是使用读相关的函数来操作描述符的。
例: accept, read, recv, scanf ... ...
write_fds : 所有要的写文件文件描述符的集合
except_fds : 其他要向我们通知的文件描述符
timeout : 超时设置.
NULL:一直阻塞,直到有文件描述符就绪或出错
时间值为0:仅仅检测文件描述符集的状态,然后立即返回
时间值不为0:在指定时间内,如果没有事件发生,则超时返回。
定义变量fd_set read_fds;
FD_SET 将fd加入到fd_set
FD_CLR 将fd从fd_set里面清除
FD_ZERO 从fd_set中清除所有的文件描述符
FD_ISSET 判断fd是否在fd_set集合中
void FD_CLR(int fd, fd_set *set);
int FD_ISSET(int fd, fd_set *set);
void FD_SET(int fd, fd_set *set);
void FD_ZERO(fd_set *set);
//清空
FD_ZERO(&read_fds);
//把socketID加入到读描述符集里
FD_SET(socketID, &read_fds);
//设置描述符最大值加1
int maxFds = socketID + 1;
//调用select
select(maxFds, &read_fds, NULL, NULL, NULL);
多路复用版本1
打开socket; 绑定自己的ip,port; 监听
fd_set read_fds;
//清空
FD_ZERO(&read_fds);
//把socketID加入到读描述符集里
FD_SET(socketID, &read_fds);
//设置描述符最大值加1
int maxFds = socketID + 1;
while(1)
{
select(maxFds, &read_fds, NULL, NULL, NULL);
三次握手 accept(socketID)
//while(1){
通信 recv(newID) //(scanf)send
//}
}
关闭socket
总结版本1,问题是:当一个客户端连接服务器后,accept会返回newID.
这时,直接调用recv,这时,如果当前客户端没有发过来数据,那么recv阻塞。
此时,如果第二个客户端想要连接服务器,必须要等待。
多路复用版本2
打开socket; 绑定自己的ip,port; 监听
fd_set read_fds;
//清空
FD_ZERO(&read_fds);
//把socketID加入到读描述符集里
FD_SET(socketID, &read_fds);
//设置描述符最大值加1
int maxFds = socketID;
while(1)
{//1,3,4,5
fd_set tmp = read_fds;
select(maxFds +1, &tmp, NULL, NULL, NULL);
//select正确返回时,判断每一个监听的描述符是否可以进行IO操作
for (i = 0; i < maxFds + 1; i++)
{
if (FD_ISSET -> socketID && socketID == i)//tmp
{
三次握手 accept(socketID)--> newID=16
FD_SET(newID, &read_fds); //16
if (maxFds < newID) 16
{
maxFds = newID;
}
}
if (FD_ISSET -> i)//tmp
通信 recv(i) //(scanf)send
}
}
关闭socket
typedef struct
{
__fd_mask __fds_bits[__FD_SETSIZE / __NFDBITS];
} fd_set;
__fd_mask类型 : long int
__fds_bits数组名
__FD_SETSIZE / __NFDBITS数组的大小: 1024 / (8 * sizeof(long int))
-->
typedef struct
{
long arr[128 / (sizeof(long))];
} fd_set;
fd_set readFds; --> 大小为1024bit
select(maxFds +1, &read_fds, NULL, NULL, NULL);
三次握手 accept(socketID)--> newID=13
FD_SET(newID, &read_fds);
if (maxFds < newID)13
{
maxFds = newID;
}
//2
select(maxFds +1, &read_fds, NULL, NULL, NULL);
if (FD_ISSET -> socketID)
{
三次握手 accept(socketID)--> newID=15
FD_SET(newID, &read_fds); //15
if (maxFds < newID) 15
{
maxFds = newID;
}
}
if (FD_ISSET -> 13)
通信 recv(13) //(scanf)send
//3-> socketID, 13, 15
select(maxFds +1, &read_fds, NULL, NULL, NULL);
if (FD_ISSET -> socketID)
{
三次握手 accept(socketID)--> newID=16
FD_SET(newID, &read_fds); //16
if (maxFds < newID) 16
{
maxFds = newID;
}
}
if (FD_ISSET -> 13)
通信 recv(13) //(scanf)send
if (FD_ISSET -> 15)
通信 recv(15) //(scanf)send
//4 socketID, 13, 15, 16
select(maxFds +1, &read_fds, NULL, NULL, NULL);
//select正确返回时,判断每一个监听的描述符是否可以进行IO操作
if (FD_ISSET -> socketID)
{
三次握手 accept(socketID)--> newID=16
FD_SET(newID, &read_fds); //16
if (maxFds < newID) 16
{
maxFds = newID;
}
}
if (FD_ISSET -> 13)
通信 recv(13) //(scanf)send
if (FD_ISSET -> 15)
通信 recv(15) //(scanf)send
if (FD_ISSET -> 16)
通信 recv(16)
思考:上面的在个if是重复性的动作,想要简化。
方法1:
for (i = 0; i < array.count; i++)
{
if (FD_ISSET -> i)
通信 recv(i) //(scanf)send
}
总结:数组的大小无法确定,不好。
方法2:
for (i = 0; i < maxFds + 1; i++)
{
if (FD_ISSET -> i)
通信 recv(i) //(scanf)send
}
---------------------------------------------
现在综合上面把//4修改一下
select(maxFds +1, &read_fds, NULL, NULL, NULL);
//select正确返回时,判断每一个监听的描述符是否可以进行IO操作
for (i = 0; i < maxFds + 1; i++)
{
if (FD_ISSET -> socketID && socketID == i)
{
三次握手 accept(socketID)--> newID=16
FD_SET(newID, &read_fds); //16
if (maxFds < newID) 16
{
maxFds = newID;
}
}
if (FD_ISSET -> i)
通信 recv(i) //(scanf)send
}