基本套接字函数(8个)
socket()
#include<sys/types.h>
#include<sys/socket.h>
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,即默认协议。
socketpair()
#include<sys/types.h>
#include<sys/socket.h>
int socketpair(int family,int type,int protocol,int fd_array[2]);
//建立一对套接字描述符而不是文件描述符。成功则返回2,失败则返回-1。
family只能为AF_UNIX或AF_LOCAL。
这里两个套接字描述符是双向的,而管道是单向的。
bind()
#include<sys/types.h>
#include<sys/socket.h>
int bind(int fd,struct sockaddr* addr,int addrlen);
//成功则返回0,失败则返回-1.
fd是socket()返回的套接字描述符。
addr是struct sockaddr结构体的指针。
addrlen为sizeof(struct sockaddr)
。
#include<netinet/in.h>
#include<sys/socket.h>
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);
}
listen()
#include <sys/socket.h>
int listen( int sockfd, int qlen);
//sockfd只能是SOCK_STREAM,SOCK_SEQPACKET类型
//qlen为请求队列长度,通常为20.
//错误返回-1
至此,服务器端调用顺序为socket()
,bind()
,listen()
。
connect()
#include<sys/types.h>
#include<sys/socket.h>
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(),即端口不需要指定。
accept()
#include<sys/types.h>
#include<sys/socket.h>
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);
}
read()&write()
#include<unistd.h>
int read(int sockfd,char*buf,int len);
int write(int sockfd,char*buf,int len);
高级套接字函数
send()&sendto()&recv()&recvfrom()
#include<sys/types.h>
#include<sys/socket.h>
//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。
sendmsg()&recvmsg()
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/uio.h>
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可以规格化数据以及发送被中断了的数据。
readv()&writev()
#include<sys/types.h>
#include<sys/uio.h>
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.
close()&shutdown()
#include<unistd.h>
int close(int fd);
#include<sys/socket.h>
int shutdown(int fd,int how);
//how
//0:停止读
//1:停止写
//2:停止读写
//成功则返回0,失败则返回-1.
gethostname()
#include<sys/types.h>
#include<sys/socket.h>
int gethostname(char*hostname,size_t len);
//成功时返回 0,失败时返回 -1,并设置errno。
getpeername()
#include<sys/types.h>
#include<sys/socket.h>
int getpeername(int sockfd,struct sockaddr *addr, socklen_t *addrlen);
//获得与指定套接字连接的对等进程名。
getsockname()
#include<sys/types.h>
#include<sys/socket.h>
int getsockname(int sockfd,struct sockaddr *addr, socklen_t *addrlen);
//返回与套接字连接的本地进程的地址和进程元素。
getsockopt()&setsockopt()
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— 选项名称
- SO_KEEPALIVE,v 设置该选项后,一段时间(通常 2 小时)内没有数据交换时, TCP 协议将自动发送探测数据包,检查网络连接.
- SO_RCVBUF 和 SO_SNDBUF,v 发送和接收数据缓冲区的大小(在连接建立以前设置).
- SO_RCVTIMEO 和 SO_SNDTIMEO,v 发送和接收超时,当指定时间内数据没有成功接收或发送,发送和接收函数将返回.
- SO_REUSEADDR,v 一般一个端口释放后会等待两分钟才能被再次使用,该选项可让端口释放后立即就可以被再次使用,这通常在重启监听服务器时可避免 bind 函数出错v 允许在同一端口上启动同一服务器的多个实例,只要每个实例捆绑一个不同的本地 IP 地址即可.
fcntl()
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的所有者 |
ioctl()
功能:控制输入输出
-
req -执行的操作类型
-
第三个参数-总是指针类型,存储操作返回的数据或操作所需的数据
返回值: 0 -成功, -1 -失败
man 2 ioctl_list
req | 参数 | 说明 |
---|---|---|
SIOCATMARK | int* | 检测是否到达带外标记 |
FIONBIO | int* | 非阻塞模式 |
FIOASYNC | int* | 异步输入/输出标志 |
SIOCSPGRP | int* | 设置/获取目标进程或进程组 |
/SIOCGPGRP | ||
FIONREAD | int* | 缓冲区中有多少字节数据可读 |
多路复用
通过一种机制,可以监视多个描述符,一旦某个描述符就绪(一般是读就绪或者写就绪),能够通知程序进行相应的读写操作。
select()
int select(
int maxfd,
fd_set *rdset,
fd_set *wrest,
fd_set *exset,
struct timeval *timeout);
返回值:
- 有描述符就绪则返回就绪的描述符个数;超时时间内没有描述符就绪返回 0 ;执行失败返回-1 。
参数:
-
maxfd -集合中所有描述符的最大值 +1
-
rdset -需要测试是否可读的描述符集合(包括处于listen 状态的 socket 接收到连接请求)
-
wrset -需要测试是否可写的描述符集合(包括以非阻塞方式调用 connect 是否成功)
-
exset -需要测试是否异常的描述符集合(包括接收带外数据的 socket 有带外数据到达)
-
timeout -指定测试超时的时间 .若将 NULL 以形参传入,即不传入时间结构,就是将
select 置于阻塞状态,一定等到监视文件描述符集合中某个文件描述符发生变化为止;
若将时间值设为__0 秒 0 毫秒___,就变成一个纯粹的非阻塞函数,不管文件描述符是否有变化,都立刻返回继续执行,文件无变化返回 0 ,有变化返回一个正值.timeout 的值__大于 0__,则该时间内阻塞。
功能:
检查多个文件描述符( socket 描述符)是否就绪,当某一个描述符就绪(可读、可写或发生异
常)时函数返回,可以实现输入输出多路复用.
select()把一些文件描述符集合在一起,如果某个文件描述符的状态发生变化,比如进入“写就绪”或者“读就绪”状态,函数 select 会立即返回,并且通知进程读取或写入数据;如果没有 I/O 到达,进程将进入阻塞,直到函数 select 超时退出为止。
FD_SET()
void FD_SET(int fd,fd_set* fdset);
将一个描述符添加到描述符集合
FD_CLR()
void FD_CLR(int fd,fd_set* fdset);
将一个描述符从描述符集合中清除
FD_ZERO()
void FD_ZERO(fd_set* fdset)
清空描述符集合
FD_ISSET()
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 *);
*/
IPC
Linux进程间通信( InterProcess Communication ,IPC )信主要方式:
-
管道及命名管道:管道可用于具有亲缘关系进程间的通信,命名管道允许无亲缘关系进程间的通信;
-
消息队列:消息的链接表,克服了信号灯承载信息量少,管道只能承载无格式字节流及缓冲区大小受限等缺点;
-
共享内存:使得多个进程可以访问同一块内存空间,速度最快,常与信号量结合实现进程间同步及互斥;
-
信号灯(或信号量):主要作为进程间及同一进程不同线程之间的同步手段;
-
套接字:因特网套接字主要面向不同主机进程间通信, UNIX 域套接字面向同一主机上进程间通信
OOB
带外数据 OOB ,即 Out-Of-Band data ,用于快速传递数据信息,让这些信息在正常的网
络数据前到达目的地.也被称为快速数据( expedited data ),拥有比一般数据高的优先级;
IO模型
Linux 系统四种主要 I/O 模型:
- 阻塞式 I/O
套接字的默认模型, I/O 无法完成时进入阻塞(即睡眠); 优点:编程简单;进程阻塞期间不占用 CPU 时间,不影响其他进程的工作效率;
缺点:进程可能长时间睡眠,在此期间无法执行别的任务,自身效率不高;
- 非阻塞式 I/O
I/O 无法完成时进程不进入睡眠状态,而是立即返回一个错误; 优点:进程可以执行后续代码,提高自身工作效率;
缺点:进程一直运行,占用大量 CPU 时间检测 I/O 操作是
否完成,影响其他进程运行效率;
- 多路复用 I/O
将多个 I/O 通道合成一组,通过 select() 函数来监视一组 I/O 通道的状态; 任何一个通道就绪,进程被激活,进行下一步处理,否则一直阻塞在 select() 函数(也可以设置一段超时时间);
- 信号驱动 I/O
当描述符可以进行 I/O 操作时,操作系统内核发出一个 SIGIO 信号(表 4-1 ),通知用户进程启动一个 I/O 操作;
其余时间,用户进程不阻塞,可以执行其他操作;
通常只在 UDP 协议下使用, TCP 套接字基本不使用。