#include
#include
int socket(int family,int type,int protocol);
//返回套接字描述符,失败则返回-1.
//该函数实际为socket数据结构分配空间
int fd;
fd=socket(AF_INET,SOCK_STREAM,0);
family指定协议簇:
AF_INET,ipv4
AF_UNIX,UNIX 进程通信协议
AF_ISO,iso协议簇
AF_INET6,ipv6
type:表示套接字类型:
SOCK_STREAM,双向连续且可信赖虚电路服务,tcp。
SOCK_DGRAM,不连续不可信赖数据报服务,udp。
SOCK_SEQPACKET,连续且可信赖,有序分组套接字。
SOCK_RAW,原始套接字。
SOCK_RDM,能可靠交付信息的数据报套接字。
protocol:指定协议。通常为0,即默认协议。
#include
#include
int socketpair(int family,int type,int protocol,int fd_array[2]);
//建立一对套接字描述符而不是文件描述符。成功则返回2,失败则返回-1。
family只能为AF_UNIX或AF_LOCAL。
这里两个套接字描述符是双向的,而管道是单向的。
#include
#include
int bind(int fd,struct sockaddr* addr,int addrlen);
//成功则返回0,失败则返回-1.
fd是socket()返回的套接字描述符。
addr是struct sockaddr结构体的指针。
addrlen为sizeof(struct sockaddr)
。
#include
#include
struct sockaddr{
unsigned short sa_family; //AF_XXXX
char sa_data[14]; //address
};
struct sockaddr_in{
short sin_family; //AF_XXXX
unsigned short sin_port;//using htons().
struct in_addr sin_addr;//using
unsigned char sin_zero[8]; //填充以与sockaddr对齐
};//解决了struct sockaddr的缺陷,分开地址和端口。
//调用bind(),accept()等函数时需把struct sockaddr_in*转换为struct sockaddr*
struct in_addr{
in_addr_t s_addr; //32位的IPv4地址(typedef uint32_t in_addr_t;)
};
要转换为网络字节顺序(NBO)的地址和端口类型有两种,short(2字节)和long(4字节)。
sin_family 没有发送到网络上,它们可以是本机字节顺序。
htons()–“Host to Network Short”
htonl()–“Host to Network Long”
ntohs()–“Network to Host Short”
ntohl()–“Network to Host Long”
而__ip地址这样转为NBO__
myAddr.sin_addr.s_addr=inet_addr("192.168.0.0")
若要转为点格式(ascii):printf("%s\n",inet_ntoa(myAddr.sin_addr))
注意:每次调用 inet_ntoa(),它就将覆盖上次调用时所得的IP地址,即静态指针。
以上转换函数头文件arpa/inet.h
,winsock2.h
.
注意,inet_addr()返回的地址已经是网络字节格式,所以你无需再调用 函数htonl()。
//sockaddr_in和bind用法
struct sockaddr_in myAddr;
bzero(&myAddr,sizeof(myAddr));//推荐
memset(&myAddr,0,sizeof(myAddr));
myAddr.sin_family=AF_INET;
myAddr.sin_port=htons(16);//0x0010-->0x1000,实际应用应大于1024.0端口为自己选择端口。
if(myAddr.sin_addr.s_addr=htonl(INADDR_ANY)<0) //0x00000000,任意网络设备接口,服务器常用。
{
perror("inet_addr error.");exit(1);
}
/*或
if (inet_aton(argv[1], (struct in_addr *)&myAddr.sin_addr.s_addr) == 0) {
perror(argv[1]);
exit(errno);
}
*/
if(bind(myAddr,(struct sockaddr*)&saddr,sizeof(saddr))<0)
{
perror("bind error");
exit(1);
}
#include
int listen( int sockfd, int qlen);
//sockfd只能是SOCK_STREAM,SOCK_SEQPACKET类型
//qlen为请求队列长度,通常为20.
//错误返回-1
至此,服务器端调用顺序为socket()
,bind()
,listen()
。
#include
#include
int connect(int sockfd,struct sockaddr *addr,int addrlen);
//客户端函数,用来连接服务器。
//错误时返回-1。
if(connect(mySock,(struct sockaddr*)&serveAddr,sizeof(serveAddr))<0)
{
perror("connect error");
exit(1);
};
//客户端不需要bind(),即端口不需要指定。
#include
#include
int accept(int sockfd, struct sockaddr*addr, int* addrlen);
//成功则返回新的socket 处理代码, 失败返回-1, 错误原因存于errno 中
clientSock=accept(serveSock,(struct sockaddr *)&clientAddr,&sizeof(struct sockaddr_in));
if clientSock<0
{
perror("client accept error.");
exit(1);
}
#include
int read(int sockfd,char*buf,int len);
int write(int sockfd,char*buf,int len);
#include
#include
//typedef int ssize_t;(signed size_t)
ssize_t send(int sockfd,const void* msg,size_t len,int flags);
ssize_t sendto(int sockfd,const void* msg,size_t len,int flags,struct sockaddr *dest_addr,int addrlen);
ssize_t recv(int sockfd,const void* msg,size_t len,int flags);
ssize_t recvfrom(int sockfd,const void* msg,size_t len,int flags,struct sockaddr *src_addr,int addrlen);
//falgs为0时,功能同write()和read().
//错误的时候返回-1,并设置 errno。
#include
#include
#include
struct iovec{
caddr_t iov_base;//buf起始地址,typedef void* caddr_t.
int iov_len; //buf长度
};
struct msghdr{
caddr_t msg_name; //目的套接字地址
int msg_namelen;
struct iovec* msg_iov;//包含msg
int msg_iovlen;
caddr_t msg_accrights;//访问权限
int msg_addrightslen;
}
int sendmsg(int sockfd,struct msghdr* msgp,int flags);
int recvmsg(int sockfd,struct msghdr* msgp,int flags);
sendmsg可以规格化数据以及发送被中断了的数据。
#include
#include
ssize_t readv(int fd,const struct iovec *iov,int iovcnt);
ssize_t writev(int fd,const struct iovec *iov,int iovcnt);
//一次读写多个非连续缓存,又称散布读(scatter read)和聚集写(gather write).
//成功则返回读写字节数,失败则返回-1.
#include
int close(int fd);
#include
int shutdown(int fd,int how);
//how
//0:停止读
//1:停止写
//2:停止读写
//成功则返回0,失败则返回-1.
#include
#include
int gethostname(char*hostname,size_t len);
//成功时返回 0,失败时返回 -1,并设置errno。
#include
#include
int getpeername(int sockfd,struct sockaddr *addr, socklen_t *addrlen);
//获得与指定套接字连接的对等进程名。
#include
#include
int getsockname(int sockfd,struct sockaddr *addr, socklen_t *addrlen);
//返回与套接字连接的本地进程的地址和进程元素。
int getsockopt(int sockfd, int level, int optname, void *optval,sock_len_t*optlen);
int setsockopt(int sockfd, int level, int optname, void *optval, sock_len_t optlen);
level -选项级别
SOL_SOCKET — 通用 socket 选项
IPPROTO_TCP—TCP 选项
IPPROTO_IP—IP 选项
optname— 选项名称
int fcntl(int fd,int cmd,…);
功能:改变套接字属性
设置 socket 为阻塞 / 非阻塞模式
设置允许 / 不允许接收异步 I/O 信号
设置 / 获取 socket 的所有者
返回值: ≥0 -成功, -1 -失败
cmd | 参数 | ret | 说明 |
---|---|---|---|
F_GETFL | 0 | 描述符标志 | 获得描述符标志 |
F_SETFL | O_NONBLOCK | 成功0,否则-1 | 设置socket为非阻塞方式 |
O_ASYNC | 成功0,否则-1 | 设置允许SIGIO异步通知 | |
F_GETOWN | int* | 成功0,否则-1 | 获得socket的所有者 |
F_SETOWN | int | 成功0,否则-1 | 设置socket的所有者 |
功能:控制输入输出
req -执行的操作类型
第三个参数-总是指针类型,存储操作返回的数据或操作所需的数据
返回值: 0 -成功, -1 -失败
man 2 ioctl_list
req | 参数 | 说明 |
---|---|---|
SIOCATMARK | int* | 检测是否到达带外标记 |
FIONBIO | int* | 非阻塞模式 |
FIOASYNC | int* | 异步输入/输出标志 |
SIOCSPGRP | int* | 设置/获取目标进程或进程组 |
/SIOCGPGRP | ||
FIONREAD | int* | 缓冲区中有多少字节数据可读 |
通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
int select(
int maxfd,
fd_set *rdset,
fd_set *wrest,
fd_set *exset,
struct timeval *timeout);
返回值:
参数:
maxfd -集合中所有描述符的最大值 +1
rdset -需要测试是否可读的描述符集合(包括处于listen 状态的 socket 接收到连接请求)
wrset -需要测试是否可写的描述符集合(包括以非阻塞方式调用 connect 是否成功)
exset -需要测试是否异常的描述符集合(包括接收带外数据的 socket 有带外数据到达)
timeout -指定测试超时的时间 .若将 NULL 以形参传入,即不传入时间结构,就是将
select 置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
若将时间值设为__0 秒 0 毫秒___,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回 0 ,有变化返回一个正值.timeout 的值__大于 0__,则该时间内阻塞。
功能:
检查多个文件描述符( socket 描述符)是否就绪,当某一个描述符就绪(可读、可写或发生异
常)时函数返回,可以实现输入输出多路复用.
select()把一些文件描述符集合在一起,如果某个文件描述符的状态发生变化,比如进入“写就绪”或者“读就绪”状态,函数 select 会立即返回,并且通知进程读取或写入数据;如果没有 I/O 到达,进程将进入阻塞,直到函数 select 超时退出为止。
void FD_SET(int fd,fd_set* fdset);
将一个描述符添加到描述符集合
void FD_CLR(int fd,fd_set* fdset);
将一个描述符从描述符集合中清除
void FD_ZERO(fd_set* fdset)
清空描述符集合
int FD_ISSET(int fd,fd_set* fdset)
检查描述符是否在集合中,如果在集合中返回非 0 值,否则返回 0.
在设置描述符集合前应该先调用 FD_ZERO 将集合清空,每次调用 select 函数前应该重新设置读、写和错
误 3 个集合;三个集合中的描述符可以交叉.
同一时刻只能处理一个客户端请求
同一时刻可以处理多个客户端请求
一般是UDP循环,TCP并发。
要有一段程序供该进程运行;
进程专用的系统堆栈空间;
进程控制块 PCB , Linux 下为 task_struct 结构;
有独立的存储空间
守护进程特点:
通常在系统初始化时被启动;
生存期为系统执行时间;
一直等待事件发生并处理事件;
可以利用其他进程完成各种请求;
一般不和终端发生联系
信号来源:
通过系统调用,比如 kill :int kill(int pid, int sig);
通过 kill 命令,比如 kill -9 pid 发送 SIGKILL 信号;
特定键盘字符,比如 ctrl+c 产生 SIGINT 信号;
硬件故障,比如 SIGFPE 为浮点算术错信号;
某些软件产生,比如加急数据到达的 SIGURG 信号。
int sigaction(int signum,
const struct sigaction *act,
struct sigaction *oldact);
/*
功能:绑定信号和处理信号的函数指针(通过一个结
构体实现);
函数参数 :
signum— 指定需要捕获的信号, SIGKILL 和 SIGSTOP
不能指定;
act— 指定处理捕获信号的动作;
oldact— 存储旧的动作
*/
struct sigaction {
void (*sa_handler)(int); // 函数指针
sigset_t sa_mask; // 屏蔽的信号集
int sa_flags; // 标志
void (*sa_restorer)(void); // 已废弃
}
/*
sa_handler 指定信号的动作:
使用默认动作时设置为 SIG_DFL ;
忽略信号时设置为 SIG_IGN ;
使用用户指定的处理函数时设置为相应处理函数。
*/
void (*signal(int signum,void (*handler)(int)))(int);
/*
参数:
signum— 指定需要捕获的信号 ,SIGKILL 和 SIGSTOP 不能
指定
handler— 指定信号处理函数
返回值:void (*)(int);若成功返回原来的信号处理配置,如出错则为
SIG_ERR
如果使用下面的typedef,则可使其简单一些:
typedef void Sigfunc(int);
然后,可将signal函数原型写成:
Sigfunc *signal(int,Sigfunc *);
*/
Linux进程间通信( InterProcess Communication ,IPC )信主要方式:
管道及命名管道:管道可用于具有亲缘关系进程间的通信,命名管道允许无亲缘关系进程间的通信;
消息队列:消息的链接表,克服了信号灯承载信息量少,管道只能承载无格式字节流及缓冲区大小受限等缺点;
共享内存:使得多个进程可以访问同一块内存空间,速度最快,常与信号量结合实现进程间同步及互斥;
信号灯(或信号量):主要作为进程间及同一进程不同线程之间的同步手段;
套接字:因特网套接字主要面向不同主机进程间通信, UNIX 域套接字面向同一主机上进程间通信
带外数据 OOB ,即 Out-Of-Band data ,用于快速传递数据信息,让这些信息在正常的网
络数据前到达目的地.也被称为快速数据( expedited data ),拥有比一般数据高的优先级;
Linux 系统四种主要 I/O 模型:
套接字的默认模型, I/O 无法完成时进入阻塞(即睡眠); 优点:编程简单;进程阻塞期间不占用 CPU 时间,不影响其他进程的工作效率;
缺点:进程可能长时间睡眠,在此期间无法执行别的任务,自身效率不高;
I/O 无法完成时进程不进入睡眠状态,而是立即返回一个错误; 优点:进程可以执行后续代码,提高自身工作效率;
缺点:进程一直运行,占用大量 CPU 时间检测 I/O 操作是
否完成,影响其他进程运行效率;
将多个 I/O 通道合成一组,通过 select() 函数来监视一组 I/O 通道的状态; 任何一个通道就绪,进程被激活,进行下一步处理,否则一直阻塞在 select() 函数(也可以设置一段超时时间);
当描述符可以进行 I/O 操作时,操作系统内核发出一个 SIGIO 信号(表 4-1 ),通知用户进程启动一个 I/O 操作;
其余时间,用户进程不阻塞,可以执行其他操作;
通常只在 UDP 协议下使用, TCP 套接字基本不使用。