Pipe相关基础知识
Ordinary Pipes(无名管道)
pipe()
函数描述:用于创建一个管道,以实现进程间的通信。定义在#include<unistd.h>
函数原型: int pipe(int fd[2]);
pipe()会建立管道,并将文件描述词由参数 filedes 数组返回。
参数:一个整数数组:fd[0]是管道的读取端,fd[1]是管道的写入端
返回值:
- 成功则返回:0
- 失败返回:-1,错误原因存于 errno 中。
错误代码:
+ EMFILE 进程已用完文件描述词最大量
+ ENFILE 系统已无文件描述词可用。
+ EFAULT 参数 filedes 数组地址不合法。
fcntl()
函数作用:改变已打开的文件性质。定义在#include<fcntl.h>
函数原型 int fcntl(int fd, int cmd, long arg)
参数:
- fd:参数fd代表欲设置的文件描述符。
- cmd:参数cmd代表打算操作的指令。
- F_DUPFD用来查找大于或等于参数arg的最小且仍未使用的文件描述符,并且复制参数fd的文件描述符。执行成功则返回新复制的文件 描述符。新描述符与fd共享同一文件表项,但是新描述符有它自己的一套文件描述符标志,其中FD_CLOEXEC文件描述符标志被清除。
- F_GETFD取得close-on-exec旗标。若此旗标的FD_CLOEXEC位为0,代表在调用exec()相关函数时文件将不会关闭。
- F_SETFD 设置close-on-exec 旗标。该旗标以参数arg 的FD_CLOEXEC位决定。
- F_GETFL 取得文件描述符状态旗标,此旗标为open()的参数flags。
- F_SETFL 设置文件描述符状态旗标,参数arg为新旗标,但只允许O_APPEND、O_NONBLOCK和O_ASYNC位的改变,其他位的改变将不受影响。
- F_GETLK 取得文件锁定的状态。
- F_SETLK 设置文件锁定的状态。此时flcok 结构的l_type 值必须是F_RDLCK、F_WRLCK
- F_UNLCK。如果无法建立锁定,则返回-1,错误代码为EACCES 或EAGAIN。
- F_SETLKW F_SETLK 作用相同,但是无法建立锁定时,此调用会一直等到锁定动作成功为止。若在等待锁定的过程中被信号中断时,会立即返回-1,错误代码为EINTR。
例如:fcntl(pipefd[1], F_SETFL, flags | O_NONBLOCK)
-------将pipe的写入端设置为阻塞模式
write()
函数原型: ssize_t write (int fd, const void * buf, size_t count)
函数作用:将buf所指向的内存块的count个字节写入管道中
参数:
- fd:管道文件描述符
- buf:指向一个内存块的指针
- count:需要写入的数据量
返回值: - 如成功则返回实际写入的字节数。
- 当有错误发生时则返回-1,错误代码存入errno中。
read()
函数原型:ssize_t read(int pipefd[0], void* buf, size_t count)
函数作用:从管道中的数据读取count个字节到buf中
参数:
- pipefd:管道文件描述符
- buf:指向一个内存块的指针
- 请求读取的字节数
返回值: - 成功则返回读取的字节数
- 失败返回-1,原因保存在errno中
close()
函数描述:关闭一个文件。定义在#include<unisted.h>
函数原型:int close(int fd);
参数:fd:要关闭的文件的描述符
返回值:
- 成功返回0
- 失败返回-1.失败原因保存在errno中
Named Pipes(命名管道)
unlink()
函数描述:删除所给参数指定的文件,如果其他进程也打开了此文件,则在所有关于这个文件的文件描述符皆关闭之后才会删除。定义在#include<unisted.h>
函数原型:int unlink(const char* pathname)
参数:pathname:要关闭的文件的绝对路径
返回值:
- 成功则返回0
- 失败则返回-2,错误原因保存在errno中
- 错误代码:
- EROFS 文件存在于只读文件系统内。
- EFAULT 参数pathname 指针超出可存取内存空间。
- ENAMETOOLONG 参数pathname 太长。
- ENOMEM 核心内存不足。
- ELOOP 参数pathname 有过多符号连接问题。
- EIO I/O 存取错误。
- 错误代码:
mkfifo()
函数描述:用于建立一个命名管道
函数原型:int mkfifo(const char * pathname,mode_t mode);
函数说明:mkfifo ()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开 FIFO文件时,O_NONBLOCK非阻塞标志的使用与否会有影响
- 当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会出错返回errno,出错号为ENXIO 。
- 没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。
参数:
- pathname:FIFO文件的绝对路径
- mode:文件的权限
返回值: - 成功则返回0
- 失败则返回-1,原因保存在errno中
同样:
- Named Pipes使用open(),close()打开或关闭FIFO文件
- Named Pipes使用write(),read()写入或读取FIFO文件
最后需要关闭FIFO文件
access()
函数描述:access to作为有权使用什么,即可理解access()函数想表达有某事的权限。函数参数有两个,第一个为文件,第二个参数表示文件的某个权限是否存在.定义在<unisted.h>
中。
函数功能:确定文件或文件夹的访问权限。
`函数原型:int access(const char *filenpath, int mode)
参数:
- filenpath:文件的路径
- mode:要判断模式
- R_OK 只判断是否有读权限;p
- W_OK 只判断是否有写权限
- X_OK 判断是否有执行权限
- F_OK 只判断是否存在
返回值:
- 所指定的权限存在则返回0
- 不存在则返回-1
strcat()
函数原型:char *strcat(char *dest, const char *src)
函数作用:将src所指向的字符串追加到dest所指向的字符串的结尾
参数:
- dest:指向目标数组
- src:指向要追加的字符串,该字符串不会覆盖目标字符串
返回值:函数最终返回一个指向最终的目标字符串dest的指针
Socket
socket相关数据结构
struct sockaddr
{
unsigned short sa_family; //套接字地址族,AF_XXX
char sa_data[14]; //协议地址
}
struct in_addr
{
unsigned long s_addr; //按照网络字节顺序储存IP地址
};
struct sockaddr_in
{
short int sin_family; //协议族, AF_INET for ipv4
unusigned short int sin_port; //端口号
struct in_addr sin_Addr; //ip地址
unsigned char sin_zero[8]; //为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的孔子姐
};
struct ifaddrs
{
struct ifaddrs *ifa_next; /* Next item in list */
char *ifa_name; /* Name of interface */
unsigned int ifa_flags; /* Flags from SIOCGIFFLAGS */
struct sockaddr *ifa_addr; /* Address of interface */
struct sockaddr *ifa_netmask; /* Netmask of interface */
union
{
struct sockaddr *ifu_broadaddr; /* Broadcast address of interface */
struct sockaddr *ifu_dstaddr; /* Point-to-point destination address */
} ifa_ifu;
#define ifa_broadaddr ifa_ifu.ifu_broadaddr
#define ifa_dstaddr ifa_ifu.ifu_dstaddr
void *ifa_data; /* Address-specific data */
};
socket()
函数原型: int socket( int af, int type, int protocol);
定义于<sys/socket.h>
函数作用:创建一个套接口
参数:
- af:地址描述符,仅支持AF_INET格式
- type:指定socket类型。新套接口的类型描述类型:TCP(SOCK_STREAM),UDP(SOCK_DGRAM)
- protocol:指定协议。套接口所用的协议,如不想指定可以用0
返回值: - 成功则返回引用新套接口的描述字
- 失败返回INVALID_SOCKET错误,应用程序可通过WSAGetLastError()获取相应错误代码
- WSANOTINITIALISED:在使用此API之前应首先成功地调用WSAStartup()。
- WSAENETDOWN:套接口实现检测到网络子系统失效。
- WSAEAFNOSUPPORT:不支持指定的地址族。
- WSAEINPROGRESS:一个阻塞的套接口调用正在运行中。
- WSAEMFILE:无可用文件描述字。
- WSAENOBUFS:无可用缓冲区,无法创建套接口。
- WSAEPROTONOSUPPORT:不支持指定的协议。
- WSAEPROTOTYPE:指定的协议不适用于本套接口。
- WSAESOCKTNOSUPPORT:本地址族中不支持该类型套接口。
bind()
函数原型:int bind(int sock, struct sockaddr *addr, socklen_t addrlen);
函数作用:将一个本地地址与一个套接口捆绑。当用socket()创建套接口后,它便存在于一个名字空间中,但并未赋名,bind()函数通过给一个未命名套接口分配一个本地名字来为套接口建立本地捆绑。
参数:
- sock:socket文件描述符
- addr:sockaddr结构体变量指针
- addrlen为addr变量的大小,可用sizeof()计算
返回值: - 成功返回0
- 失败返回-1,可通过WSAGetLastError()获取相应错误代码
getsockname()
函数原型: int getsockname(int sockfd, struct sockaddr* restrict addr, socklen_t* restrict addrlen)
函数作用:用于获取一个套接字的名字。用于一个已捆绑或已连接套接字s,本地地址将被返回。适合用于未调用bind()就调用了connect(),这时唯有getsockname()调用可以获知系统内定的本地地址。
返回值:
- 成功返回0
- 失败返回SOCKET_ERROR,可通过WSAGetLastError()获取相关错误代码
listen()
函数原型:int listen( int sockfd, int backlog);
函数作用:将socket设置为监听状态
参数:
- sockfd:用于表示一个已捆绑未连接套接口的描述字
- backlog:等待连接队列的最大长度
返回值: - 成功返回0
- 失败返回-1,可通过WSAGetLastError()获取相应错误代码
connect()
函数原型:int connect(int sockfd, const struct sockaddr * serv_addr, int addrlen);
函数作用:将参数sockfd
参数:
- s:制定一个未连接的套接口
- serv_addr:指向数据结构sockaddr的指针,其中包括目的端口和IP地址
- addrlen:sockaddr的长度
返回值: - 成功返回0
- 失败返回-1
accept()
函数原型:int accept(int sockfd, struct aockaddr restrict* addr, socklen_t restrict* addrlen)
函数作用:用于从sockfd的等待连接队列中抽取出第一个链接,创建一个与sockfd同类的新的套接口并返回句柄
参数:
- sockfd:套接字描述符,该套接口在listen()后监听链接
- addr:(可选)指针,指向一缓冲区,其中接受为通讯层所知的连接实体的地址
- addlrn:(可选)指针,addr的长度
返回值: - 成功返回新的sock套接字句柄
- 失败返回-1
send()
函数原型:ssize_t send(int sockfd, const void* buf, socklen_T len, int flag)
函数作用:向一个已经连接的socket发送数据
参数:
- sockfd:发送端的套接字描述符
- buf:指向要发送的数据
- len:要发送的数据的长度
- flag:置0即可
返回值: - 成功返回所发送数据的总数
- 失败返回SOCKET_ERROR
recv()
函数原型:ssize_t send(int sockfd, const void* buf, socklen_T len, int flag)
函数作用:从一个已经连接的socket接收数据
setsockopt()
函数原型:int setsockopt(int sockfd, int level, int optname, const void* potval, socklen_t optlen)
函数作用:设置socket选项值
参数:
- sockfd:套接字的描述符
- level:选项定义的层次
- optname:需设置的选项
- optval:指针,指向存放选项值的缓冲区
- optlen:optval缓冲区长度
返回值: - 成功返回0
- 失败返回-1
getifaddrs()
函数原型:int getifaddrs(struct ifaddrs **__ifap)
函数作用:获取本地网络接口信息,将之储存在链表中,链表头结点指针储存于ifap中带回
返回值:
- 成功返回0
- 失败返回-1
freeifaddrs()
函数原型:void freeifaddrs(struct ifaddrs* ifap)
函数作用:释放getifaddrs()所返回的数据结构
socket programming的基本过程:
htonl()
函数原型:uint32_t htonl(uint32_t hostlong)
函数作用:将主机数转换成无符号长整数的网络字节顺序
参数:hostlong:主机字节顺序表达的32位数
返回值:返回一个网络字节顺序的值
INADDR_ANY:指定地址为0.0.0.0的地址,这个地址表示不确定地址。当指定ip地址为通配地址(INADDR_ANY),那么内核将等到套接字已连接(TCP)或已在套接字上发出数据报时才选择一个本地IP地址。
ntohs()
函数原型:uint16_t ntohs(uint16_t netshort);
函数作用:将网络字节顺序转换为主机字节顺序
参数:netshort:网络字节
返回值:返回一个主机字节顺序的值
bzero()
函数原型:extern void bzero(void *s, int n);
函数作用:置字节字符串s的前n个字节为0
参数:
- s:要置零的字符串
- n:要置零前n个字符
返回值:无返回值
hostent
struct hostent
{
char *h_name; //正式主机名
char **h_aliases; //主机别名
int h_addrtype; //主机IP地址类型:IPV4-AF_INET
int h_length; //主机IP地址字节长度,对于IPv4是四字节,即32位
char **h_addr_list; //主机的IP地址列表
};
- h_name:字符指针,指向主机名
- h_aliases:是一个二重指针,指向主机的别名。一个主机可以有多个别名
- h_addrtype: 是int型数据,指定主机使用的ip地址类型
- h_length:ip地址长度
- h_addr_list:是一个二重指针,可以通过下标进行访问,指定主机的不同ip地址
gethostbyname()
函数原型:struct hostent *gethostbyname(const char *name);
函数作用:返回主机名字和地址信息的指针
参数:
- name:指向主机名的指针
返回值: - 成功返回指向hostent的指针
- 返回空指针
inet_ntop()
函数原型:const char *inet_ntop(int af, const void *src, char *dst, socklen_t cnt);
函数作用:将二进制整数转化为点分十进制
inet_ntoa()
函数原型:char *inet_ntoa (struct in_addr);
函数作用:将网络地址转换成点隔的字符串模式
isgist()
函数原型:int isdigit(int c)
函数作用:检查参数是否为十进制数字字符
返回值:若参数c为阿拉伯数字0-9,则返回非0值,否则返回0
解释BBS相关代码:
BBS一共包含三个程序:input,connector,server
input程序:
原理性解释:
input程序是connector和server的输入窗口,主要负责接收键盘输入然后通过Named Pipe传给connector(server)
细节解释:
注意:在使用完Pipe之后需要调用
close()
函数关闭Pipe
connector函数
原理性解释
首先需要终端输入server的ip地址和端口号,然后connector建立一个socket请求连接server。
connector中创建了一个子进程,parent process用于向server发送消息,child process用于接收server发来的消息
由于connector的输入是通过input程序来实现的,所以基本过程是:
- 在input终端下输入信息,然后通过Named Pipe发送给connector的parent process
- parent process接收后通过socket发送给server
- child process单纯用于接收server通过socket发来的信息
细节解释:
server函数
原理性解释:
server主要分成三部分:main,Pipe_data,recv_send_data
main: 因为server和connector的通信是通过socket来完成的所以server需要创建一个socket,调用bind()绑定上服务器的IP地址和端口。然后开始监听,坐等connector来请求连接。
server创建了一个子进程,用于调用Pipe_data()函数,Pipe_data函数的主要功能如下:
- 接收与server进行Named Pipe通信的input程序的输入
- 持续更新max_sn,记录最多有多少connector正在与server连接通信
- 更新sn_stat
- 更新nickname
- 找到被@的对象并将只发给的信息发送到它对应的Ordianry Pipe中
- 广播某个connector发来的信息
- 读取与server通过Named Pipe通信的input发来的信息,要么发给某一个connector,要么广播给全部的connector
server每当与一个connector建立连接,就会创建一个只属于该connector的一个子进程,该子进程调用recv_send_data()。
recv_send_data()主要作用是负责connector与server之间的通信。
下面捋一捋各个变量的含义与作用:
- sn_attri[]:用于记录各个connector的状态和nickname
- server_fd:记录server的socket
- connector[ ]:用于记录connectors的socket
- fd[ ][ ]:fd[0][0]用于传递max_sn;recv_send_data()使用fd[][0]从pipe_data()获取要向connector发送的信息
- fd_stat[ ]:用于pipe_data从main获取connect_sn的stat
- fd_msg[]:用于从recv_send_data()获得connect_sn发来的信息
- fdr:named pipe,用于从input terminal获得stdin_buf
下面是BBS的大致拓扑图:
需要注意的是,每当server有一个新连接时,server就会创建一个新的子进程去调用recv_send_data()函数,然后这个子进程就一直与该连接通过socket来通信。
子进程接收到的msg通过fd_msg[ ]发送到pipe_data()中
pipe_data()通过fd[sn][ ]将要回给connector的消息发送给connector对应的recv_Send_data()进程,再通过socket发回给connector
接下来是消息发送的格式:
connector对应的input terminal的消息发送格式:
#0:代表结束连接
#1:用于改名
@nickname:之发送给某个人
message:广播message
server对应的input terminal的消息发送格式:
@sn#0:将sn对应的connector强行退出连接
@snmessage:将message发给sn对应的connector
message:向所有connector发送message
细节解释:
以上主要是main()函数的细节解释,下面是pipe_data()和recv_send_data()的细节解释:
pipe_data():
在处理由connector发来的消息的模块中,主要是根据消息的格式来处理消息
同理,这个模块主要用于input terminal 通过命名管道发送来的消息,同样是按照格式来处理