网络编程
套接字编程
套接字地址结构:
struct sockaddr定义了一种通用的套接字地址
#include<linux/socket.h> struct sockaddr { unsigned short sa_family;//地址类型,AF_xxx char sa_data[14];//14字节的协议地址 };
参数:
- sa_family:套接字的协议族类型。对应TCP/IP协议该值为AF_INET。
- sa_data:存储具体的协议地址。
说明:一般在编程中并不对该结构体进行操作,而是使用另一个与他等价的数据结构:sockaddr_in
sockaddr_in:TCP/IP协议族的地址格式(每种协议族都有自己的协议地址格式):
#include<netinet/in.h> struct sockaddr_in { unsigned short sin_family;//地址类型 unsigned short int sin_port;//端口号 struct in_addr sin_addr;//IP地址 unsigned char sin_zero[8];//填充字节,一般赋值0 }; sin_family:对于TCP/IP协议进行的网络编程,该值只能是AF_INET struct in_addr { unsigned long s_addr; }
- 说明:sockaddr的长度为16字节,sockaddr_in的长度也为16字节。通常在编写基于TCP/IP协议的网络程序时,使用sockaddr_in来设置地址,然后通过前置转换转换成sockaddr类型。
设置地址信息的实例代码:
struct sockaddr_in sock; sock.sin_family=AF_INET; sock.sin_port=htons(80);//设置端口号80 sock.sin_addr.s_addr=inet_addr("202.205.3.195");//设置地址 memset(sock.sin_zero,0,sizeof(sock.sin_zero));//将数组清0
创建套接字
socket:创建套接字
#include<sys/types.h> #include<sys/socket.h> int socket(int domain,int type,int protocol);
参数:
domain:用于指定创建套接字所使用的协议族,他们在头文件linux/socket.h中定义,常用的协议族如下:
domain 含义 AF_UNIX 创建只在本机内进行通信的套接字 AF_INET 使用IPv4 TCP/IP协议 AF_INEF6 使用IPv6 TCP/IP协议 - type;指定套接字的类型:
type 含义 SOCK_STREAM 创建TCP流套接字 SOCK_DGRAM 创建UDP数据报套接字 SOCK_RAW 创建原始套接字 protocol:通常设置为0,表示通过参数domain指定的协议族和参数type指定的套接字类型来确定使用的协议。当创建原始套接字时。系统无法唯一的确定协议,此时就要用该参数指定所使用的协议
-
执行情况 返回值 成功 一个新创建的套接字 失败 -1,错误代码存errno 实例:
创建一个TCP套接字:
int sock_fd; sock_fd=socket(AF_INET,SOCK_STREAM,0); if(sock_fd<0) { perror("socket"); exit(1); }
- 创建UDP协议的套接字
int sock_fd; sock_fd=socket(AF_INET,SOCK_DGRAM,0); if(sock_fd<0) { perror("socket"); exit(1); }
建立连接
connect:用来在一个指定的套接字上创建一个链接
#include<sys/types.h> #include<sys/socket.h> int connect(int sockfd,const struct sockaddr *serv_addr,socklen_t addrlen);
参数:
sockfd:由函数socket创建的套接字。
- 如果套接字的类型是SOCK_STREAM,则connect函数用于向服务器发送连接请求,服务器的IP地址和端口号由参数serv_addr指定。
- 若果套接字的类型是SOCK_DGRAM,则connect函数并不建立真正的连接,他只是告诉内核与该套接字通信的目的地址(由第二个参数指定),只有该目的地址发来的数据才会被socket接受,对他来说调用connect的好处是不必在每次发送和接收数据是都指定目的地址。
- 说明:通常一个面向连接的套接字(TCP套接字)只能调用一次connect函数,对于无连接的套接字(UDP套接字)则可以多次调用connect函数以改变与目的地址的绑定。将参数serv_addr中的sa_family设置为AF_UNSPEC可以取消绑定。
- serv_addr:一个地址结构
addrlen:是参数serv_addr的长度
返回
执行情况 | 返回值 |
---|---|
成功 | 返回0 |
失败 | 返回-1,错误代码在errno |
用法:
struct sockaddr_in serv_addr;
memset(&serv_addr,0,sizeof(struct sockaddr_in));//将serv_addr的各个字段请0
serv_addr.sin_family=AF_INET;
serv_addr.sin_port=htons(80);
if(inet_aton("172.17.242.131",&serv_addr.sin_addr)<0)
{
perror("inet_aton");
exit(1);
}
//使用sock_fd套接字连接到serv_addr指定,假设sock_fd已经定义
if(connect(sock_fd,(struct sockaddr*)&serv_addr,sizeof(struct sockaddr_in))<0)
{
perror("connect");
exit(1);
}
绑定套接字
bind:将一个套接字和某个端口绑定在一起(一般只有服务器端的程序调用)
#include<sys/types.h> #include<sys/socket.h> int bind(int sockfd,struct sockaddr *my_addr,socklen_t addrlen);
说明:socket函数只是创建了一个套接字,这个套接字将工作在哪个端口上,程序并没有指定。在客户机/服务器模型中,服务器端的IP地址和端口号是固定的,因此在服务器端的程序中,使用bind函数
参数:
my_addr:制定了sockfd将绑定到的本地地址,可以将参数my_addr的sin_addr设置为INADDR_ANY而不是确定的一个IP地址,这样套接字就可以绑定任何网络接口了。
- 对于只有一个IP地址的计算机,INADDR_ANY对应的就是他的IP地址。
- 对于多宿主主机(拥有多块网卡),INADDR_ANY表示本服务器程序将处理来自所有网络接口上相应端口的连接请求
-
执行情况 返回值 成功 返回0 失败 返回-1,,错误代码在errno 用法:
struct sockaddr_in serv_addr; memset(&serv_addr,0,sizeof(struct sockaddr_in)); serv_addr.sin_family=AF_INET; serv_addr.sin_port=htons(80); serv_addr.sin_addr.s_addr=hton1(INADDR_ANY); if(bind(sock_fd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr_in))<0) { perror("bing"); exit(1); }
在套接字上监听
listen:把套接字转化为被动监听
#include<sys/socket.h> int listen(int a,int backlog);
说明:
- 由socket创建的套接字是主动套接字,这种套接字可以用来主动请求连接到某个服务器(通过connect)但是作为服务器端的程序,通常在某个端口上监听来自客户端的连接请求。在服务器端,一般是先调用socket创建一个主动套接字,然后调用bind将该套接字绑定到某个端口上,再用listen将该套接字转化为监听套接字,等待来自客户端的连接请求。
- 一般多个客户端连接到一个服务器上,服务器向这些客户端提供某种服务。服务器端设置一个连接队列,记录已经建立的连接。
listen只是将套接字设置为倾听模式以等待连接请求,它并不能接受连接请求,真正接受连接请求的是accept
参数:
s:创建的套接字
backlog:制定了该连接队列的最大长度。如果连接队列已经达到最大,之后的连接请求将被服务器拒绝
-
执行情况 返回值 成功 返回0 失败 返回-1,错误代码在errno 实例:
#define LISTEN_NUM 12//定义连接请求队列长度 if(listen(sock_fd,LISTEN_NM)<0) { perror("listen"); exit(1); }
接受连接
accept:接受一个连接请求
#include<sys/types.h> #include<sys/socket.h> int accept(int s,struct sockaddr *addr,socklen_t *addrlen);
参数:
s:socket创建,经bind绑定到本地端口,通过listen转化而来的监听套接字
- addr:保存发起连接请求的主机的地址和端口
addrlen:addr所指向的结构体的大小
-
执行情况 返回值 成功 新的代表客户端的套接字 失败 返回-1,错误代码在errno 说明:
只能对面向连接的套接字使用accept函数,执行成功则创建一个新的套接字,并为这个新的分配一个套接字描述符,并返回这个描述符。
- 如果s指定的套接字被设置为阻塞方式(Linux下的默认方式),且连接请求对列为空,则accept江北阻塞直到有连接请求到达为止。
若果s指定的套接字设置为费阻塞方式(fcntl),则如果队列为空,accept立即返回-1,errno设置为EADIAN。
(阻塞方式下的)实例:
int client_fd; int client_len; struct sockaddr_in client_addr; client_len=sizeof(struct sockaddr_in); client_fd=accept(sock_fd,(struct sockaddr *)&client_addr,&client_len); if(conn_fd<0) { perror("accept"); exit(1); }
TCP套接字的数据传输
发送数据:send
#include<sys/types.h> #include<sys/socket.h> ssize_t send(int s,const void *msg,size_t len,int flags);
说明:
send只能对处于连接状态的套接字使用
参数:
s:已经建立好连接的套接字描述符,即函数的返回值。
- msg:指向存放待发送数据的缓冲区
- len:待发送数据的长度
- flags:控制选项,设置为0或下面的选项
flags 含义 MSG_OOB 在指定的套接字上发送带外数据,该类型的套接字必须支持带外数据(如SOCK_STREAM) MSG_DONTROUTE 通过最直接的路径发送数据,而忽略下层协议的库有设置 -
执行情况 返回 发送的数据太长不能发送 出现错误,errno设置为EMSGSIZE 发送的数据长度大于该套接字的缓冲区剩余空间大小 send会阻塞 如果该套接字设置为非阻塞,立即返回-1,errno设置为EAGAIN 执行成功 返回实际发送的字节数 执行成功只是说明数据写入套接字的缓冲区,并不表示数据已经成功通过网络发送到目的地
套接字为阻塞下的实例:
#define BUFFERSIZE 1500 char send_buf[BUFFERSIZE]; ... if(send(conn_fd,send_buf,len,0)<0)//len为待发送的字节数 { perror("send"); exit(1); }
接收数据:recv
#include<sys/types.h> #include<sys/socket.h> ssize_t recv(int s,void *buf,size_t len,int flags);
函数recv从参数s所指定的套接字描述符(必须是面向连接的)上接收数据并保存到参数buf所制定的缓冲区,参数len为缓冲区的长度。
flags:控制选项,一般为0或下面的:
flags 含义 MSG_OOB 请求接受带外数据 MSG_PEEK 只查看数据而不读出 MSG_WAITALL 只在接收缓冲区满时才返回 -
执行情况 说明 如果一个数据包太长以至于缓冲不能完全放下 剩余的数据将可能被丢弃(根据接收数据的套接字类型而定) 指定的套接字上无数据到达 recv将被阻塞 指定的套接字上无数据到达且指定的套接字被设置为非阻塞方式 立即返回-1,errno设置为EAGAIN recv接收到数据就返回 并不会等待接收到参数len指定的长度的数据才返回 执行成功 返回接收到的数据字节数 执行失败 返回-1,错误代码errno中 套接字为阻塞方式下该函数的实例:
char recv_buf[BUFFERSIZE]; ... if(recv(conn_fd,recv_buf,sizeof(recv_buf),0)<0) { perror("recv"); exit(1); }
UDP套接字的数据传输
发送数据:sendto
#include<sys/types.h> #include<sys/socket.h> ssize_t sendto(int s,const void *msg,size_t len,int flags,const struct sockaddr *to,socklen_t tolen);
说明:sendto不需要套接字处于连接状态。因为是无连接的套接字,在使用sento时要指定数据的目的地址。
参数:
msg:指向待发送数据的缓存区
- len:指定了待发送数据的长度
- flags:控制选项与send中的一样
- to:指定目的地址
tolen:目的地址的长度
-
执行情况 含义 成功 返回实际发送数据的字节数 失败 返回-1,错误代码errno中 实例;
char send_buf[BUFFERSIZE]; struct sockaddr_in dest_addr; //设置目的地址 memset(&dest_addr,0,sizeof(struct sockaddr_in)); dest_addr.sin_family=AF_INET; dest_addr.sin_port=htons(DEST_PORT); if(inet_aton("172.17.242.131",&dest_addr.sin_addr)<0) { perror("inet_aton"); exit(1); } if(sendto(sock_fd,send_buf,len,0,(struct sockaddr *)&dest_addr,sizeof(struct sockaddr_in))<0) { perror("sendto"); exit(1); }
接收数据:recvfrom
#include<sys/types.h> #include<sys/socket.h> ssize_t recvfrom(int s,void *buf,size_t len,int flags,struct sockaddr *from,socklen_t *fromlen);
说明:没有限制,也可以用于无连接的套接字
参数:
buf:指向接受缓存区
- len:制定了缓冲区的大小
- flags:控制选项与recv一致
- from:如果为非空,且该套接字不是面向连接的,则函数返回时,from中将保存数据的源地址
fromlen:调用recvfrom前为参数from的长度,调用后保存from的实际大小
-
执行情况 含义 成功 返回实际接收到数据的字节数 失败 返回-1,错误代码在errno 套接字为阻塞方式下该函数的实例:
char recv_buf[BUFFERSIZE]; struct sockaddr_in src_addr; int src_len; src_len=sizeof(struct sockaddr_in); if(recvfrom(sock_fd,recv_buf,sizeof(recv_buf),0,(struct sockaddr *)&src_addr,&src_len)<0) { perror("again_recvfrom"); exit(1); }
关闭套接字
close:
作用:关闭一个套接字描述符
#include<unistd.h> int close(int fd);//fd:套接字描述符
-
执行情况 返回值 成功 返回0 失败 返回-1错误代码在errno
shutdown:
- 作用:关闭一个套接字描述符
#include<sys/socket.h> int shutdown(int s,int how);
比close功能更强大,它允许对套接字进行单项关闭或全部禁止
参数:
s:待关闭的套接字描述符
howto:关闭的方式
howto 含义 SHUT_RD 将连接上的读通道关闭,此后进程将不能接受任何数据,接收缓冲区中还没有被读的数据也被丢失,但仍然可以在该套接字上发送数据 SHUT_WR 将连接上的写通道关闭,此后进程在不能发送任何数据,发送缓冲区中还没有被发送的数据也将被丢失,但仍然可以写 SHUT_RDWR 读写通道都关闭 -
执行情况 返回值 成功 返回0 失败 返回-1,错误代码errno中
主要系统调用函数
字节顺序转换函数
大端模式:(网络字节顺序)
高字节放在低地址处,低字节在高地址处:
eg: 0x04030201
0x1000 0x04 0x1001 0x03 0x1002 0x02 0x1003 0x01 小端模式:
- 低字节在低地址处,高字节在高地址处:
- eg:0x04030201
0x1000 | 0x01 |
---|---|
0x1001 | 0x02 |
0x1002 | 0x03 |
0x1003 | 0x04 |
* 在网络上传输数据时,由于数据传输的两端可能对应不同的硬件平台,采用的存储字节顺序也可能不一致,因此TCP/IP协议规定在网络上必须采用网络字节顺序(大端模式)。通过对大小端模式存储原理的分析,对于char型数据,由于其只占一个字节,所以不存在这个问题,这也是我们一般把数据缓存区定义为char型的原因之一。对IP地址,端口号等非char型数据,必须在数据发送到网络上之前将其转换成大端模式,在接收到数据后再将其转换成符合接收端主机的存储模式。
字节转换由四种:
#include<netinet/in.h> uint32_t htonl(uint32_t hostlong); uint16_t htons(uint16_t hostshort); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);
- 含义:
- htonl:host to network long:将主机unsigned int型转换成网络字节顺序
- htons:host to network short:将主机unsigned short型数据转换成网络字节顺序
inet系列函数
- 前提:通常我们习惯于使用字符串形式的网络地址(如”172.17.242.131“),然而在网络上进行数据通信时,需要使用的是二进制形式且为网络字节顺序的IP地址。eg:172.17.242.131对应的二进制顺序为0x83f211ac
- 头文件;
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
函数:
inet_aton:
int inet_aton(const char *cp,struct in_addr *inp);
将cp所指向的字符串形式的IP地址转换为二进制的网络字节顺序的IP地址,转换后的结构存于参数inp所指向的空间
-
执行情况 返回值 执行成功 返回非0 参数无效 返回0 inet_aton:
in_addr_t inet_addr(const char *cp);
将cp所指向的字符串形式的网络地址转换为二进制的网络字节顺序的IP地址。
-
执行情况 含义 成功 将转换后的结果返回 参数无效 INADDR_NONE(一般该值为-1) 说明:对有效地址”255.255.255.255“它也返回-1,使得用户可能将这个当成无效的非法地址。
inet_network:
in_addr_t inet_network(const char *cp);
将参数cp所指向的字符串形式的网络地址转换为主机字节顺序形式的二进制IP地址
-
执行情况 含义 执行成功 返回转换后的结果 参数无效 返回-1 inet_ntoa
char *inet_ntoa(struct in_addr in);
将值为in的网络字节顺序形式的二进制IP地址转换成以”.“分割的字符串形式。
-
执行情况 含义 执行成功 返回结果字符串的指针 参数无效 返回NULL inet_makeaddr:
struct in_addr inet_makeaddr(int net,int host);
将网络号为参数net,主机号为参数host的两个地址组合成一个网络地址。
eg:net取0xac11(172.17.0.0 主机字节顺序形式),host取0xf283(0.0.242.131,主机字节顺序),则组合后的地址为:172.17.242.131,并表示为网络字节顺序形式0x83f211ac.
inet_inaof
in_addr_t inet_inaof(struct in_addr in);
该函数从in中提取出主机地址
-
执行情况 返回值 执行成功 返回主机字节顺序形式的主机地址 eg:172.17.242.131,属于B类地址,则主机地址为0.0.242.131 按主机字节顺序形式输出为:0xf283
inet_netof
in_addr_t inet_netof(struct in_addr in);
该函数从in中提取出来网络地址
-
执行情况 返回值 执行成功 返回主机字节顺序形式的网络地址
代码实例:
#include<stdlib.h>
#include<stdio.h>
#include<string.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
int main(void)
{
char buffer[32];
int ret=0;
int host=0;
int network=0;
unsigned int address=0;
struct in_addr in;
in.s_addr=0;
/*输入一个以"."分割的字符串形式的IP地址*/
printf("please input your ip address:");
fgets(buffer,31,stdin);
buffer[31]='\0';
/*实例使用inet_aton()函数*/
if((ret=inet_aton(buffer,&in))==0)
printf("inet_aton:\tinvalid address\n");
else
printf("inet_aton:\t0x%x\n",in.s_addr);
/*实例使用inet_addr()函数*/
if((address=inet_addr(buffer))==INADDR_NONE)
printf("inet_addr:\tinvalid address\n");
else
printf("inet_addr:\t0x%x\n",address);
/*实例使用inet_network函数*/
if((address=inet_network(buffer))==-1)
printf("inet_network:\tinvalid address\n");
else
printf("inet_work:\t0x%x\n",address);
/*实例使用inet_ntoa函数*/
if(inet_ntoa(in)==NULL)
printf("inet_ntoa:\tinvalid address\n");
else
printf("inet_ntoa:\t%s\n",inet_ntoa(in));
/*实例使用inet_lnaof()和inet_netof()函数*/
host=inet_lnaof(in);
network=inet_netof(in);
printf("inet_inaof:\t0x%x\n",host);
printf("inet_netof:\t0x%x\n",network);
in=inet_makeaddr(network,host);
printf("inet_makeaddr:0x%x\n",in.s_addr);
return 0;
}
![2018-08-09 16-13-12 的屏幕截图](/home/lala/图片/2018-08-09 16-13-12 的屏幕截图.png)
getsockopt()和setsockopt()
作用:
- getsockopt:获取套接字的属性
- setsockopt:设置套接字的属性
#include<sys/types.h> #include<sys/socket.h> int getsockopt(int s,int level,int optname,void *optval,socklen_t *optlen); int setsockopt(int s,int level,int optname,const void *optval,socklen_t optlen);
参数:
- s:套接字
- level:进行套接字选项操作的层次
level | 含义 |
---|---|
SOL_SOCKET | 通用套接字(用它来进行与特定协议不相关的操作) |
IPPROTO_IP | IP层套接字 |
IPPROTO_TCP | TCP层套接字 |
* optname:套接字选项的名称
getsockopt | 参数 | 含义 |
---|---|---|
optval | 存放获得的套接字选项 | |
optlen | 调前:optval指向的空间大小d调后:optval所保存结果的实际大小 |
setsockopt | 参数 | 含义 |
---|---|---|
optval | 待设置的套接字选项的值 | |
optlen | 该选项的长度 |
* SOL_SOCKET选项:
SO_KEEPALIVE
- 如果没有设置这个选项,那么即使TCP链接已经很长时间没有数据传输时,系统也不会检测这个连接是否有效。对于服务器进程,如果某一客户端非正常断开连接,这服务器进化出呢个将一直被阻塞等待。因此,服务器端程序需要这个选项,如果某个客户端一段时间没有反应则关闭该连接
SO_RCVLOWAT 和 SO_SNDLOWAT
- 每个套接字还有一个接收低水位标记和一个发送低水位标记。他们由select函数使用,这两个套接字选项允许我们修改这两个低水位标记。
- 接收低水位标记是让select返回“可读”时,套接字接收缓冲区中所需的数据量。对于TCP,UDP和SCTP套接字,其默认值为1。发送低水位标记是让select返回“可写”时套接字发送缓冲区中所需的可用空间。对于TCP套接字,其默认值通常为2048。UDP也使用发送低水位标记,然而由于UDP套接字的发送缓冲区中可用空间的字节数从不改变(意味UDP并不为由应用进程传递给它的数据报保留副本),只要一个UDP套接字的发送缓冲区大小大于该套接字的低水位标记,该UDP套接字就总是可写。我们记得UDP并没有发送缓冲区,而只有发送缓冲区大小这个属性。
SO_RCVTIMEO 和 SO_SNDTIMEO
- 这两个选项允许我们给套接字的接收和发送设置一个超时值。注意,访问他们的getsockopt和setsockopt函数的参数是指向timeval结构的指针,与select所用参数相同。这可让我们用秒数和微妙数来规定超时。我们通过设置其值为0s和0μs来禁止超时。默认情况下着两个超时都是禁止的
- 接收超时影响5个输入函数:read,readv,recv,recvfrom和recvmsg。
- 发送超时影响5个输出函数:write,writev,send,sendto和sendmsg。
- 具体时间有下面这个结构指定:
struct timeval { long tv_sec;//秒数 long tv_usec;//微秒数 };//超过时间为这两个之和
- 如果超时:则认为接受或者发送数据失败
SO_BINDTODEVICE
- 将套接字绑定到特定的网络接口如”eth0“,以后只有该网络接口上的数据才会被套接字处理。如果将该选项值设置为空字符串或选项长度设为0将取消绑定。
SO_DEBUG
- 只能对TCP套接字使用,设置了之后系统保存TCP发送和接受的所有数据的相关信息
SO_REUSEADDR
- 如果一个socket绑定了一个端口,该socket正常关闭或程序异常退出后的一段时间内,该端口仍然维持原来的状态,其他程序无法绑定该端口,如果设置了该选项即可避免这个问题。
int option_value=1; int length=sizeof(option_value); setsockopt(sock_fd,SOL_SOCKET,SO_REUSERDDR,&option_value,length);
SO_TYPE
- 用于获取套接的类型(只能被函数getsockopt获取套接字的类型,不能用setsockopt修改套接字的类型)
SO_ACCEPTCONN
- 用来检测套接字是否处于监听状态(只能用getsockopt来获取监听状态)
- 0:非监听状态
- 1:正在监听
- 用来检测套接字是否处于监听状态(只能用getsockopt来获取监听状态)
SO_DONTROUTE:
- 这只该选项表示:在发送IP数据包时不能使用路由表来寻找路由
SO_BROADCAST:
用来决定套接字是否能够在网络上广播数据。实际应用中要在网络上广播数据必须硬件支持广播(如以太网支持广播)并且使用的是SOCK_DGRAM套接字。系统默认不支持广播,如果希望SOCK_DGRAM支持广播;
int option_value=1; setsockopt(sock_id,SOL_SOCKET,SO_BROADCAST,&option_value,sizeof(int));
SO_SNDBUF和SO_RCVBUF
- 用于设置套接字的发送和接收缓冲区的大小
- 对于TCP类型的套接字,缓冲区太小会影响TCP的流量控制;
- 对于UDP类型的套接字,如果套接字的数据缓冲区蛮厚则及后续数据将被丢弃
- 用于设置套接字的发送和接收缓冲区的大小
SO_ERROR
- 获取套接字的内部错误变量errno,当套接字上发生了异步错误时,系统将设置套接字的so_error/
- 异步错误:错误的发生和错误被发现的时间不一致,通常在目的主机非正常关闭时发生这种错误。
- 只能是被函数getsockopt用来获取so_error,调用完后so_error的值将自动被重新初始化.
多路复用select
在客户端服务器模型中,服务器端需要同时处理多个用户端的链接要求
实现多路复用最简单的方式是:非阻塞方式套接字
服务器不断查询各个套接字的状态,如果有数据到达则读出数据,如果没有则查看下一套接字
另一种方法:向系统登录希望坚实的套接字,然后阻塞
当套接字有事件发生时(有数据到达等),系统通知服务器进程告知哪个套接字上发生了什么事件,服务器进程查询对应套接字并进行处理。套接字上没有事件发生时,服务器进程不会去查询套接字的状态(select)
#include<sys/types.h> #include<sys/time.h> #include<sys/select.h> #include<unistd.h> int select(int n,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
参数;
- n:需要监视文件描述符数
- readfds:制定需要监视的可读文件描述符集合,当这个集合中的一个描述符上有数据到达时,系统将通知调用select函数的程序
- writefds:指定需要监视的可写文件描述符集合,当这个集合中的某个描述符可以发送数据时,程序将受到通知。
- exceptfds:指定需要监视的异常文件描述符集合,当集合中的一个有异常时,程序将受到通知
- timeout:制定了阻塞的时间,如果在这段时间内坚实的文件描述符上都没有事件发生,则函数select()返回0
struct timeval { long tv_sec;//seconds long tv_usec;//微秒 }
-
timeout 含义 NULL 则函数select将一直被阻塞,直到某个文件描述符上发生了事件 0 此时相当于非阻塞方式,select查询完文件描述符集合的状态后立即返回 某一时间值 在这个时间内如果没有事件发生,函数将返回; 有时间发生,程序接到通知。 - 注:文件描述符:普通文件描述或套接字描述符
- 系统为文件描述符提供了一系列宏:
FD_CLR(int fd,fd_set *set);//将文件描述符fd从文件描述符集合set中删除 FD_ISSET(int fd,fd_set *set);//测试fd是否在set中 FD_SET(int fd,fd_set *set);//在文件描述符集合中增加文件描述符fd FD_ZERO(fd_set *set);//将文件描述符集合set清空
- 如果select设定的要监视的文件描述符集合中由描述符发生了事件,则select将返回发生事件的文件描述符的个数
- select实例:
#include<sys/time.h> #include<stdlib.h> #include<stdio.h> #include<sys/types.h> #include<unistd.h> #include<time.h> void display_time(const char *string) { int seconds; seconds=time((time_t*)NULL); printf("%s,%d\n",string,seconds); } int main(void) { fd_set readfds; struct timeval timeout; int ret; /*监视文件描述符0是否有数据输入,文件描述符0表示标准输入,即键盘输入*/ FD_ZERO(&readfds);//开始使用一个描述符集合前一般要将其清空 FD_SET(0,&readfds); /*设置阻塞时间为10秒*/ timeout.tv_sec=10; timeout.tv_usec=0; while(1) { display_time("berore select"); ret=select(1,&readfds,NULL,NULL,&timeout); display_time("after select"); switch(ret) { case 0: { printf("No data in ten seconds\n"); exit(0); break; } case -1: { perror("select"); exit(1); break; } default: { getchar();//将数据读入,否则标准输入上将一直:为读就绪 printf("Data is available now\n"); } } } }