基本原理
计算机网络体系结构模式
通信模式
所有的网络通信的实现方式可以分为两种:线路交换和
包交换
所谓线路交换就是指传输时在发送端和接收端之间建立
一个特定的线路连接数据就可以在这条线路上传输
最常用的电话就是采用这种线路交换的技术
计算机网络采用的是包交换的方法所有的计算机使用一
个共同的网络连接 数据的发送端将要传输的数据分割
成
块而每个块经过适当的处理(数据封装)后形成一个数
据
包,包中有接收端的地址等必要信息,并且每个包单独
传输
OSI标准
系统互联标准(Open System Interconnection)
定义的是一种七层通信协议标准
物理层(Physical)
数据链接层(Data Link)
网络层(Network)
传输层(Transport)
会话层(Session)
表示层(Presentation)
应用层(Application)
应用层
应用层是网络的最高层 也就是最接近用户的一层 应用
层里包含了构筑在各种通信协议上的网络应用软件 可
以
实现与用户直接交互的功能 例如电子邮件和文件传输
程
序
表示层
表示层完成被传输数据的表示和解释工作 它包含数据
转
换和数据加密以及数据压缩等 它的主要功能为:
为用户提供执行会话层服务原语的手段
提供描述复杂数据结构的方法
管理当前所需的数据结构集
完成数据的内部格式与外部格式间的转换
会话层
会话层使用运输层协议提供的可靠的端到端通信服务
并
增加一些用户所需要的附加功能和建立不同机器上的用
户之间的数据交换
传输层
它是OSI网络体系结构中最核心的一层 它把实际使用的
通信子网与高层应用分开 提供发送端和接收端之间的
高
可靠低成本的数据传输 TCP和UDP协议都属于这一层
网络层
网络层主要对主机和网络之间的交互进行定义 它又被
称
为通信子网层 定义了在网络中传输的基本数据单元以
及
目的寻址和选路的概念 IP协议属于这一层
数据链接层
数据链接层对下层传来的数据进行打包封装 将上层的
数
据分割成帧 它还完成流量控制和差错处理的工作
物理层
物理层是OSI的最底层 它规定传输媒体本身及与其相关
联的机械和电气接口 这些接口和传输媒体必须保证发
送
和接受信号的一致性
TCP/IP协议
TCP/IP协议是一组在网络中提供可靠数据传输和无连接
数据服务的协议 其中提供可据传输的协议称为传输控
制
协议TCP 而提供无连接数据包服务的协议叫做网际协议
IP
TCP/IP协议的体系结构包含五层
硬件层
网络接口层
网络层
传输层
应用层
应用层
应用层包括网络应用程序和网络进程 是与用户交互的
界
面 它为用户提供所需要的各种服务 包括远程登录 文
件
传输和电子邮件等 它的作用相当于OSI 中的应用层及
表
示层和会话层
传输层
相当于OSI 中的传输层 它为应用程序提供通信服务 这
种通信又叫端对端通信 它有三个主要协议:
传输控制协议(TCP)
用户数据包协议(UDP)
互联网控制消息协议 ICMP)
TCP 协议以建立连接高可靠性的消息传输为目的它负责
把大量的用户数据按一定的长度组成多个数据包进行发
送并在接收到数据包之后按分解顺序重组和恢复用户数
据 它是一种面向连接的可靠的双向通信的数据流
UDP 协议提供无连接数据包传输服务 它把用户数据分
解
为多个数据包后发送给接收方 它具有执行代码小以及
系
统开销小和处理速度快等优点
ICMP协议主要用于端主机和网关以及互联网管理中心等
地消息通信以达到控制管理网络运行的目的ICMP协议能
发送出错消息给发送数据包的端主机 还有限制流量的
功
能
网络层
相当于OSI的网络层 使用的协议是IP协议 它是用来处
理
机器之间的通信问题的它接收传输层请求 传输某个具
有
目的地址信息的分组 该层把分组封装到IP数据包中 填
入数据包的头部包头)使用路由算法来选择是直接把数
据包发送到目标主机还是发给路由器 然后把数据包交
给
下面的网络接口层中的对应网络接口模块
网络接口层
相当于 OSI 中的数据链接层和物理层 它负责接收 IP
数据包和把数据包通过选定的网络发送出去
套接口编程基础
学习网络编程无论使用哪种语言 哪种操作系统 不可避
免地要遇到socket这个名词 它就是所说的套接口
套接口也就是网络进程的ID 网络通信归根到底就是进
程
间的通信
在网络中每一个节点都有一个网络地址 也就是IP地址
两个进程通信时 首先要确定各自所在的网络节点的网
络
地址 但是网络地址只能确定进程所在的计算机 由于一
台计算机上可能同时有多个网络进程 所以仅凭网络地
址
还不能确定到底是网络中的哪个进程 因此套接口中还
需
要其他的信息 也就是端口号(port) 在一台计算机中一
个端口号一次只能分配给一个进程 在一台计算机中 端
口号和进程之间是一一对应的关系 所以使用端口号
和网络地址的组合就能唯一地确定整个网络中的一个网
络进程
把网络应用程序中所用到的网络地址和端口号信息放在
一个结构体中 就是套接口地址结构 套接口函数都需要
一个指向套接口地址结构的指针作为参数 来传递地址
信
息
套接口根据所使用的协议的不同可以分为很多类主要介
绍两种 TCP套接口(流式套接口) UDP套接口(数据包套
接口 无连接套接口)
TCP是一种面向连接的可靠的双向通信的数据流 它使用
的是三段式握手方式(重传 肯定 确认)传输数据 接收
端
在接收数据之后要发出一个肯定确认 发送端必须接到
接
收端的肯定信号 否则就要将数据重发 而且它还保证数
据的顺序 它有自己的错误控制机制所以是无错误传递
UDP提供读数据的直接访问权 传输时无连接不执行端对
端的可靠性检查 没有验证机制 因此是一种不太可靠的
协议 但是它的开销非常低
当传输可靠性要求不是特别高的时候就可以使用UDP协
议
但是使用UD 套接口时最好在应用程序中有一定的弥补
措
施以确认数据是否正确传输
Linux中套接口的数据结构
#include<sys/socket.h>
struct sockaddr
{
unit8_t sa_len;
sa_family_t sa_family;
char sa_data[14];
}
字节排序函数
计算机在内存中的数据存储方式有两种:一种是小端字
节序 另一种是大端字节序
网络字节序使用的是大端字节序
假如某个给定系统所采用的字节序为主机字节序 它可
能
是小端字节序 也可能是大端字节序 在网络协议中处理
多字节数据时采用的都是网络字节序 而不是主机字节
序
需要把主机字节序和网络字节序相对应 就要用到提供
主
机字节序和网络字节序之间相互转换功能的函数
#include <netinet/in.h>
uint16_t htons(uint16_t hostvalue)
uint32_t htonl(uint32_t hostvalue)
返回的是网络字节序
#include <netinet/in.h>
uint16_t ntohs(uint16_t netvalue)
uint32_t ntohl(uint32_t netvalue)
返回的是主机字节序
h 代表host
n 代表network
s 代表short
l 代表long
使用htons和ntohs转换端口号
使用htonl和ntohl 转换IP地址
htonl()把32位值从主机字节序转换成网络字节序
htons()把16位值从主机字节序转换成网络字节序
ntohl()把32位值从网络字节序转换成主机字节序
ntohs()把16位值从网络字节序转换成主机字节序
字节操纵函数
#include <strings.h>
void bzero(void *dest,size_t nbytes)
bzero 函数将指定的起始地址设置为0
地址转换函数
TCP/IP网络上使用地址即使用以“.”隔开的四个十进制
的
数表示 但是在套接口地址中则是使用32位的网络字节
序
的二进制数值 要实现两者之间的转换 就要用到以下三
个函数
in_addr_t inet_addr(const char *straddr)
成功时返回32位二进制的网络字节序地址
出错返回INADDR_NONE
int inet_aton(const char *straddr,struct in_addr
*addrp)
返回1表示转换成功
返回0则是转换不成功
char *inet_ntoa(struct in_addr inaddr)
成功返回地址
转换失败返回NULL
inet_aton是将地址转换成网络字节序的32位二进制值
输入的数放在参数straddr中
返回结果的整数放在参数addrp中
inet_ntoa的功能正好相反它将32位二进制值地址转换
成
地址
如果有struct_sockaddr_in类型的变量sin想将IP地址
162.105.12.145存储到其中可以使用函数inet_addr()
sin.sin_addr.s_addr=inet_addr(“162.105.12.145”)
函数inet_addr()返回的地址已经是按照网络字节序
的
32位二进制地址
又如有一个数据结构struct in_addr要按照ASCII格式
打
印可以使用函数inet_ntoa()
printf(“%s”,inet_ntoa(sin.sin_addr))
连接函数
在编写客户和服务器程序时当分配端口后要建立连接这
一过程可能用到的函数如下
创建套接口函数socket 它的功能是生成一个套接口描
述
字也称为套接字
#include <sys/socket.h>
int socket(int family,int type,int protocol)
成功返回值为非负描述字
失败返回负值
参数family指明协议族
AF_LOCAL UNIX协议族
AF_ROUTE 路由套接口
AF_INET IPv4协议
AF_INET6 IPv6协议
AF_KEY 密钥套接口
参数type指明字节流类型
SOCK_STREAM TCP套接口
SOCK_DGRAM UDP套接口
SOCK_PACKET 支持数据链路访问
SOCK_RAM 原始套接口
而参数protocol一般为0
绑定套接口函数bind 是为套接口分配一个本地协议地
址
也就是本地IP地址与本地套接口的组合 调用函数bind
可以指定一个端口号 一个IP地址 或者
两者都不指定
#include <sys/socket.h>
int bind(int sockfd,const struct sockaddr
*myaddr,socklen_t addrlen)
参数sockfd为套接字
参数 myaddr是一个指向特定协议地址结构的指针
addrlen是地址结构的长度
套接口中设定port为0内核指定端口号
设置sin_addr为INADDR_ANY内核指定IP地址
struct sockaddr_in serveraddr
serveraddr.port=0
serveraddr.sin_addr.s_addr=htonl(INADDR_ANY)
当IP地址使用统配地址INADDR_ANY 如果端口号设置为0
则表示内核选择地址和端口
如果端口号非零 则为内核选择地址进程指定端口
当IP地址指定为本地IP地址时 如果端口号设置为0则表
示进程选择地址 内核指定端口
如果端口号设置为非零则表示进程选择地址和端口
若是不指定IP地址 将缺省为统配地址INADDR_ANY其值
为零
若是不指定端口则端口号缺省为0
成功返回值为0
失败则返回-1
如果希望使客户端和服务器连接就要调用connect()函
数
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd,const struct sockaddr
*serv_addr,int addrlen)
参数sockfd是系统调用socket()返回的套接口描述
serv_addr是目的端口和IP地址的套接口
参数addrlen是目的套接口的长度
连接成功则返回0
错误则返回-1
服务器不知道自己要与谁连接 因此它也不会主动地要
求
与某个进程连接 它只是监听是否有其他客户进程要与
它
连接 然后响应这个连接请求并对它作出处理
#include <sys/socket.h>
int listen(int sockfd,int backlog)
参数sockfd为套接字
参数backlog则规定内核为此套接口排队的最大选择个
数
成功返回为0
失败则返回-1
函数accept的功能是从已完成连接队列头返回下一个已
完成连接,如果已完成连接队列
为空时,则进程睡眠,这个函数由服务器调用。定义如
下。
#include <sys/socket.h>
int accept(int sockfd,struct sockaddr
*cliaddr,socklen_t *addrlen)
参数cliaddr返回客户进程的协议地址
参数addrlen为返回客户进程的协议长度
参数sockfd称为监听套接口描述字它由函数 socket生
成
如果不需要客户的协议地址,可将第二个和第三个参数
设
置为空指针
成功返回非负 则返回值用来标识新建立的连接
出错返回-1
accept 的函数返回值称为已连接套接口描述字
它们的区别在于 监听套接口描述字只有一个而且一直
存
在 每一个连接都有一个已连接套接口描述字当连接断
开
时就关闭该描述字
send()和recv()这两个函数用于面向连接的socket上进
行数据传输
size_t send(int sock_fd, const void *msg, int
len, int flags)
sock_fd是你想用来传输数据的socket描述符
msg是一个指向要发送数据的指针
len是以字节为单位的数据的长度
flags一般情况下置为0
成功返回实际反送的数据的字节数
失败返回-1
int recv(int sock_fd,void *buf,int len,unsigned
int flags)
sock_fd是接受数据的socket描述符
buf 是存放接收数据的缓冲区
len是缓冲的长度
flags也被置为0
成功返回实际上接收的字节数
返回-1
sendto()和recvfrom()用于在无连接的数据报socket方
式下进行数据传输
由于本地socket并没有与远端机器建立连接,所以在发
送数据时应指明目的地址
int sendto(int sockfd, const void *msg,int
len,unsigned int flags,const struct sockaddr
*to,
int tolen)
to表示目地机的IP地址和端口号信息
而tolen常常被赋值为sizeof(struct sockaddr)
sendto函数也返回实际发送的数据字节长度
错误时返回-1
recvfrom()函数原型为
int recvfrom(int sockfd,void *buf,int
len,unsigned int flags,struct sockaddr *from,int
*fromlen)
from是一个struct sockaddr类型的变量该变量保存源
机
的IP地址及端口号
fromlen常置为sizeof(struct sockaddr)
当recvfrom()返回时fromlen包含实际存入from中的数
据
字节数
Recvfrom()函数返回接收到的字节数
错误时返回-1
一个服务器进程中系统调用的顺序通常如下:
socket()
bind()
listen()
accept()
TCP协议建立连接时使用三段握手 TWH方式:
客户端先用connect()向服务器发出一个要求连接的信
号
SYN1
服务器进程接收到这个信号后发回应答信号 ack1同时
这
也是一个要求回答的信号SYN2
客户端收到信号ack1和SYN2后 再次应答ack2
服务器收到应答信号ack2一次连接才算建立完成
函数close()的功能是关闭套接口
#include <unistd.h>
int close(int sockfd)
参数sockfd 是关闭的套接口描述字
成功则返回0
否则返回-1
当对一个套接口调用close()时 关闭该套接口描述字
并
停止连接 以后这个套接口不能再使用也不能再执行任
何读写操作 但关闭时已经排队准备发送的数据仍会被
发出
如果想在任何关闭套接口上有多一点控制 还可以使用
函
数shutdown() 允许将某一方向的通信或者双向通信关
闭
int shutdown(int sockfd,int how)
参数sockfd是想要关闭的套接口文件描述字
参数how的值是下面中的一个
0:不允许再接收
1:不允许再发送
2:不允许再接收和发送
shutdown()不是关闭套接口 而是关闭该套接口的通信
功
能 该套接口仍是打开的
IP地址转换
用gethostbyname()函数来实现名字地址到数字地址之
间
的转换工作
#include <netdb.h>
struct hostent *gethostbyname(const char
*hostname)
成功返回一个指向结构hostent 的指针
调用失败返回一个空指针
Hostent 的数据结构
Struct hostent
{
char *h_name
char **h_aliases
int h_addrtype
int h_length
char **h_addr_list
}
#define h_addr_list[0]
h_name主机的规范名字
h_aliases主机的别名列表指向一个二维数组
h_addrtype返回主机的地址类型
h_length返回地址长度
h_addr_list主机的一组网络地址列表
h_addr h_addr_list[0]主机的第一个网络地址
socket 系统调用都将错误
#include<netdb.h>
HOST_NOT_FOUND找不到主机
TRY_AGAIN重试
NO_RECOVERY不可修复性错误
NO_DATA指定的名字有效但没有记录
gethostbyaddr()将一个数字地址转换成一个名字地址
或
者主机名
#include <netdb.h>
struct hostent *gethostbyaddr(const char
*addr,size_t len,int family)
addr是一个指向含有地址结构(in_addr或in6_addr)的
指
针
len是此结构的大小 如果是IPv4为4 而IPv6则为16
参数family为协议地址族
成功返回一个指向结构hostent 的指针
调用失败返回一个空指针
得到当前主机的名字的函数
函数uname()的作用是返回当前主机的名字经常把它和
函
数gethostbyname一起使用来确定本地主机的IP地址
#include <sys/utsname.h>
int uname(struct utsname *name)
成功返回一个非负值
失败返回-1
结构utsname 的定义形式如下。
#define UTS_NAMESIZE 16
#define UTS_NODESIZE 256
struct utsname
{
char sysname[UTS_NAMESIZE]
char nodename[UTS_NODESIZE]
char release[UTS_NAMESIZE]
char version[UTS_NAMESIZE]
char machine[UTS_NAMESIZE]
}
TCP套接口编程例子:
client:
#include <sys/types.h>
#include <sys/socket.h>
#include <netdb.h>
#include <unistd.h>
#include <unp.h>
#define MAXSIZE 80
void str_cli(FILE *,int)
int main(int argc,char **argv)
{
int sockfd,ret,len;
struct sockaddr_in ser_addr;
char *myname;
struct hostent *sh;
struct in_addr **addrs;
if(argc!=2){
printf(“parameters not match.”);
exit(0);
}
/*判断参数是否匹配 */
if((sh=gethostbyname(argv[1]))==NULL)
{
printf(“error when gethostbyname”);
exit(0);
}
/* 根据服务器名获得详细信息*/
addrs=(struct in_addr **)sh->h_addr_list;
for(;*addrs!=NULL;addrs++){
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
printf(“error in socket”);
exit(1);
}
/* 创建套接口*/
ser_addr.sin_family=AF_INET;
ser_addr.sin_port=sh->h_port;
memcpy(ser_addr,sin_addr,*addrs,sizeof(struct
in_addr));
bzero(&(ser_addr.sin_zero),8);
ret=connect(sockfd,(SA)&ser_addr,sizeof(struct
sockaddr));
if(ret==0)
break;
/* 连接成功则跳出循环*/
else
{
printf(“error connecting”);
close(sockfd);
}
}
/*尝试与服务器的各个地址连接,知道连接上其中一个
为止 */
if(*addrs==NULL)
{
printf(“can’t get connected with server”);
exit(0);
}
/*连接不成功则报错并退出 */
str_cli(stdin,sockfd);
/*数据传输操作 */
close(sockfd);
exit(0);
}
void str_cli(FILE *fp,int sockfd)
{
char sends[MAXSIZE],recvs[MAXSIZE];
int n=0;
while(fgets(sends,MAXSIZE,fp)!=NULL)
{
send(sockfd,sends,strlen(sends),0);
if((n=recv(sockfd,recvs,MAXSIZE,0))==0){
printf(“error receiving data”);
exit(1);
}
recvs[n]=0;
fputs(recvs,stdout);
}
}
server:
/*服务器端代码*/
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <unp.h>
#define MAXSIZE=80
#define MYPORT 3490
#define BACKLOG 10
#define BUFSIZE 100
main ( )
{
int sockfd,new_fd,numbytes,ret;
struct sockaddr_in my_addr;
struct sockaddr_in their_addr;
int sin_size;
char *buf;
sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0){
printf(“error in socket”);
exit(1);
}
/*创建监听套接口 */
my_addr.sin_family=AF_INET;
my_addr.sin_port=htons(MYPORT);
my_addr.sin_addr.s_addr=htonl(INADDR_ANY);
bzero(&(my_addr.sin_zero),8);
ret=bind(sockfd,(struct sockaddr
*)&my_addr,sizeof(struct sockaddr));
if(ret<0)
{
printf(“error in binding”);
exit(1);
}
/*绑定监听套接口 */
ret=listen (sockfd,BACKLOG);
if(ret<0)
{
printf(“error in listening”);
exit(1);
}
/* 监听连接请求*/
while(true)
{
sin_size=sizeof(struct sockaddr_in);
con_fd=accept(sockfd,(SA*)&their_addr,&
s
in_size);
if(con_fd<0)
{
printf(“error in accept”);
exit(1);
}
/*创建新的连接 */
if((pid=fork())==0)
{
close(sockfd);
str_ser(con_fd);
/*接收并处理数据 */
close(con_fd);
exit(0);
}
/*子进程代码 */
else
close(con_fd);
/*父进程代码 */
}
close(sockfd);
exit(0);
}
void str_ser(int sockfd)
{
char recvs[MAXSIZE];
int n=0;
while(true)
{
if((n=recv(sockfd,recvs,MAXSIZE,0))==0)
return;
/*对方关连接,返回主程序 */
send(sockfd,recvs,n,0);
}
}
sockaddr_in sockaddr in_addr
struct sockaddr {
unsigned short sa_family;
char sa_data[14];
};
struct sockaddr_in {
short int sin_family;
unsigned short int sin_port;
struct in_addr sin_addr; //
unsigned char sin_zero[8];
};
struct in_addr {
unsigned long s_addr;
};
使用socket, listen, bind等函数填值的时候使用
sockaddr_in结构,而作为函数的参数传入的时候转换
成
sockaddr
UDP:
server:socket-->bing-->sendto -->recvfrom
client:socket-->sendto -->recvfrom
LINUX网络编程(fork、select)
1.fork:每accept到一个socket之后,开启一个子进程
来负责收发处理工作。
int main(int argc, char*argv[])
{
int fdServer = -1;
int fdClient = -1;
int nStatus = -1;
pid_t pid = 0;
int nSockAddrLen = sizeof(struct sockaddr_in);
struct sockaddr_in addrServer;
struct sockaddr_in addrSocket;
bzero(&addrServer, sizeof(struct sockaddr_in));
bzero(&addrSocket, sizeof(struct sockaddr_in));
if(-1 == (fdServer = socket(AF_INET,
SOCK_STREAM, 0))){
perror(strerror(errno));
exit(-1);
}
int nReuseAddr = 1;
SetOption(fdServer, SOL_SOCKET, SO_REUSEADDR,
&nReuseAddr, sizeof(int));
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(SERVER_PORT);
addrServer.sin_addr.s_addr = INADDR_ANY;
if (-1 == bind(fdServer, (struct sockaddr
*)&addrServer, sizeof(struct sockaddr)))
{
perror(strerror(errno));
exit(-1);
}
if (-1 == listen(fdServer, 128))
{
perror(strerror(errno));
exit(-1);
}
while (true)
{
if ((fdClient = accept(fdServer, (struct
sockaddr *)&addrSocket,
(socklen_t*)&nSockAddrLen)) < 0)
{
perror(strerror(errno));
exit(-1);
}
printf("client address:%s\t port:%d\r\n",
inet_ntoa(addrSocket.sin_addr),
ntohs(addrSocket.sin_port));
if ((pid = fork()) < 0)
{
perror(strerror(errno));
exit(-1);
}
else if (0 == pid) /* clild */
{
while (true)
{
try
{
string strValue;
if(0 < SocketRead(fdClient, strValue))
{
SocketWrite(fdClient, strValue.c_str(),
strValue.length());
}
else
{
close(fdClient);
}
// 要有延时,不然CPU使用率很高
usleep(10);
}
catch(const SocketException& e)
{
cerr << e.what() << endl;
close(fdClient);
break;
}
}
}
else /* parent */
{
close(fdClient);
}
}
return 0;
}
1.select:监控文件描述符事件。
int main(int argc, char*argv[])
{
int fdServer = -1;
int fdClient = -1;
int fdMax = -1;
int nRead = 0;
char buf[NUM_BUFFER];
int nSockAddrLen = sizeof(struct sockaddr_in);
struct sockaddr_in addrServer;
struct sockaddr_in addrSocket;
bzero(&addrServer, sizeof(struct sockaddr_in));
bzero(&addrSocket, sizeof(struct sockaddr_in));
int fdBuf[NUM_MON];
memset(fdBuf, -1, sizeof(fdBuf));
// 被监控的描述符集合
fd_set fsMon;
// 事件的描述符集合
fd_set fsRead;
FD_ZERO(&fsMon);
FD_ZERO(&fsRead);
if(-1 == (fdServer = socket(AF_INET,
SOCK_STREAM, 0)))
{
perror(strerror(errno));
exit(-1);
}
int nReuseAddr = 1;
SetOption(fdServer, SOL_SOCKET, SO_REUSEADDR,
&nReuseAddr, sizeof(int));
addrServer.sin_family = AF_INET;
addrServer.sin_port = htons(SERVER_PORT);
addrServer.sin_addr.s_addr = INADDR_ANY;
if (-1 == bind(fdServer, (struct sockaddr
*)&addrServer, sizeof(struct sockaddr)))
{
perror(strerror(errno));
exit(-1);
}
if (-1 == listen(fdServer, 128))
{
perror(strerror(errno));
exit(-1);
}
// 服务端是被监控对象之一
FD_SET(fdServer, &fsMon);
fdMax = fdServer;
while (true)
{
fsRead = fsMon;
switch(select(fdMax + 1, &fsRead, NULL, NULL,
NULL))
{
case -1:
perror(strerror(errno));
exit(-1);
case 0:
perror("timeout");
continue;
default:
break;
}
// 是否为fdServer的可读事件
if (FD_ISSET(fdServer, &fsRead))
{
if (0 > (fdClient = accept(fdServer, (struct
sockaddr *)&addrSocket,
(socklen_t*)&nSockAddrLen)))
{
perror(strerror(errno));
exit(-1);
}
// socket加入fdBuf, fdBuf中是保存的所有
socket的集合
for (int i = 0; i < NUM_MON; ++i)
{
if(-1 == fdBuf[i])
{
fdBuf[i] = fdClient;
break;
}
}
// 把socket加入监控集合
FD_SET(fdClient, &fsMon) ;
fdMax = max(fdMax, fdClient);
continue;
}
for (int i = 0; i < NUM_MON; ++i)
{
if (-1 == fdBuf[i]) continue;
if (!FD_ISSET(fdBuf[i], &fsMon))
continue;
// 缓冲区中,是否有数据可读
ioctl(fdBuf[i], FIONREAD, &nRead);
if (0 >= nRead)
{
// 非可读
FD_CLR(fdBuf[i], &fsMon);
continue;
}
read(fdBuf[i], buf, nRead) ;
write(fdBuf[i], buf, nRead) ;
}
}
}
定义函数
int select(int n,fd_set * readfds,fd_set *
writefds,fd_set * exceptfds,struct timeval *
timeout)
函数说明
select()用来等待文件描述词状态的改变。
参数n代表最大的文件描述词加1,参数readfds、writefds 和
exceptfds 称为描述词组,是用来回传该描述词的读,
写或例外的状况。底下的宏提供了处理这三种描述词组
的方式:
FD_CLR(inr fd,fd_set* set);用来清除描述词组set
中相关fd 的位
FD_ISSET(int fd,fd_set *set);用来测试描述词组
set中相关fd 的位是否为真
FD_SET(int fd,fd_set*set);用来设置描述词组set
中相关fd的位
FD_ZERO(fd_set *set); 用来清除描述词组set的全
部位参数
timeout为结构timeval,用来设置select()的等待时间
,其结构定义如下
struct timeval
{
time_t tv_sec;
time_t tv_usec;
};
返回值
如果参数timeout设为NULL则表示select()没有
timeout
范例
FD_ZERO(&readset)
FD_SET(fd,&readset)
select(fd+1,&readset,NULL,NULL,NULL)
if(FD_ISSET(fd,readset){……}
#include <sys/time.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <assert.h>
int main ()
{
int keyboard;
int ret,i;
char c;
fd_set readfd;
struct timeval timeout;
keyboard = open("/dev/tty",O_RDONLY |
O_NONBLOCK);
assert(keyboard>0);
while(1)
{
timeout.tv_sec=1;
timeout.tv_usec=0;
FD_ZERO(&readfd);
FD_SET(keyboard,&readfd);
ret=select(keyboard+1,&readfd,NULL,NULL,&timeout
);
if(FD_ISSET(keyboard,&readfd))
{
i=read(keyboard,&c,1);
if('\n'==c)
continue;
printf("hehethe input is %c\n",c);
if ('q'==c)
break;
}
}
}
利用socket套接字和select实现异步聊天
就是通讯任意一方可以任意发送消息,有消息来到时会
收到系统提示去接收消息。
先建立好套接字,然后绑定,转化为监听套接字,接受
连接。
这里要用到select函数。使用步骤如下:
1、设置一个集合变量,用来存放所有要判断的句柄
(file descriptors:即我们建立的每个socket、用
open打开的每个文件等)
2、把需要判断的句柄加入到集合里
3、设置判断时间
4、开始等待,即select
5、如果在设定的时间内有任何句柄状态变化了就马上
返回,并把句柄设置到集合里
服务端代码:
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <sys/socket.h>
#include <sys/wait.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/time.h>
#include <sys/types.h>
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, new_fd;
socklen_t len;
struct sockaddr_in my_addr, their_addr;
unsigned int myport, lisnum;
char buf[MAXBUF + 1];
fd_set rfds;
struct timeval tv;
int retval, maxfd = -1;
if (argv[1])
myport = atoi(argv[1]);
else
myport = 7838;
if (argv[2])
lisnum = atoi(argv[2]);
else
lisnum = 2;
if ((sockfd = socket(AF_INET, SOCK_STREAM,
0)) == -1)
{
perror("socket");
exit(1);
}
bzero(&my_addr, sizeof(my_addr));
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(myport);
if (argv[3])
my_addr.sin_addr.s_addr=
inet_addr(argv[3]);
else
my_addr.sin_addr.s_addr = INADDR_ANY;
if (bind(sockfd, (struct sockaddr *)
&my_addr, sizeof(struct sockaddr))
== -1)
{
perror("bind");
exit(1);
}
if (listen(sockfd, lisnum) == -1)
{
perror("listen");
exit(1);
}
while (1)
{
printf
("\n----等待新的连接到来开始新一轮聊
天……\n");
len = sizeof(struct sockaddr);
if ((new_fd =
accept(sockfd, (struct sockaddr *)
&their_addr, &len)) == -1)
{
perror("accept");
exit(errno);
}
else
printf("server: got connection from
%s, port %d, socket %d\n",
inet_ntoa(their_addr.sin_addr
),
ntohs(their_addr.sin_port),
new_fd);
/* 开始处理每个新连接上的数据收发 */
printf
("\n准备就绪,可以开始聊天了……直接输
入消息回车即可发信息给对方\n");
while (1)
{
/* 把集合清空 */
FD_ZERO(&rfds);
/* 把标准输入句柄0加入到集合中 */
FD_SET(0, &rfds);
maxfd = 0;
/* 把当前连接句柄new_fd加入到集合中
*/
FD_SET(new_fd, &rfds);
if (new_fd > maxfd)
maxfd = new_fd;
/* 设置最大等待时间 */
tv.tv_sec = 1;
tv.tv_usec = 0;
/* 开始等待 */
retval = select(maxfd + 1, &rfds,
NULL, NULL, &tv);
if (retval == -1)
{
printf("将退出,select出错!
%s", strerror(errno));
break;
}
else if (retval == 0)
{
continue;
}
else
{
if (FD_ISSET(0, &rfds))
{
/* 用户按键了,则读取用户输
入的内容发送出去 */
bzero(buf, MAXBUF + 1);
fgets(buf, MAXBUF, stdin);
if (!strncasecmp(buf,"quit",
4))
{
printf("自己请求终止聊天
!\n");
break;
}
len = send(new_fd, buf,
strlen(buf) - 1, 0);
if (len > 0)
printf
("消息:%s\t发送成功
,共发送了%d个字节
!\n",
buf, len);
else {
printf
("消息'%s'发送失败!
错误代码是%d,错误信
息是'%s'\n",
buf, errno,
strerror(errno));
break;
}
}
if (FD_ISSET(new_fd, &rfds))
{
/* 当前连接的socket上有消息
到来则接收对方发过来的消息并
显示 */
bzero(buf, MAXBUF + 1);
/* 接收客户端的消息 */
len = recv(new_fd, buf,
MAXBUF, 0);
if (len > 0)
printf
("接收消息成功
:'%s',共%d个字节的
数据\n",
buf, len);
else {
if (len < 0)
printf
("消息接收失败!
错误代码是%d,错
误信息是'%s'\n",
errno,
strerror(errno)
);
else
printf("对方退出了,
聊天终止\n");
break;
}
}
}
}
close(new_fd);
/* 处理每个新连接上的数据收发结束 */
printf("还要和其它连接聊天吗?(no->退出
)");
fflush(stdout);
bzero(buf, MAXBUF + 1);
fgets(buf, MAXBUF, stdin);
if (!strncasecmp(buf, "no", 2))
{
printf("终止聊天!\n");
break;
}
}
close(sockfd);
return 0;
}
客服端:
#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <sys/time.h>
#include <sys/types.h>
#define MAXBUF 1024
int main(int argc, char **argv)
{
int sockfd, len;
struct sockaddr_in dest;
char buffer[MAXBUF + 1];
fd_set rfds;
struct timeval tv;
int retval, maxfd = -1;
if (argc != 3)
{
printf("参数格式错误!正确用法如下:\n\t\t%s
IP地址 端口\n\t比如:\t%s 127.0.0.1 80\n此程
序用来从某个 IP 地址的服务器某个端口接收最多
MAXBUF 个字节的消息",
argv[0], argv[0]);
exit(0);
}
/* 创建一个 socket 用于 tcp 通信 */
if ((sockfd = socket(AF_INET, SOCK_STREAM,
0)) < 0)
{
perror("Socket");
exit(errno);
}
bzero(&dest, sizeof(dest));
dest.sin_family = AF_INET;
dest.sin_port = htons(atoi(argv[2]));
if (inet_aton(argv[1], (struct in_addr *)
&dest.sin_addr.s_addr) == 0)
{
perror(argv[1]);
exit(errno);
}
if (connect(sockfd, (struct sockaddr *)
&dest, sizeof(dest)) != 0)
{
perror("Connect ");
exit(errno);
}
printf
("\n准备就绪,可以开始聊天了……直接输入消
息回车即可发信息给对方\n");
while (1)
{
/* 把集合清空 */
FD_ZERO(&rfds);
/* 把标准输入句柄0加入到集合中 */
FD_SET(0, &rfds);
maxfd = 0;
/* 把当前连接句柄sockfd加入到集合中 */
FD_SET(sockfd, &rfds);
if (sockfd > maxfd)
maxfd = sockfd;
/* 设置最大等待时间 */
tv.tv_sec = 1;
tv.tv_usec = 0;
/* 开始等待 */
retval = select(maxfd + 1, &rfds,
NULL,NULL, &tv);
if (retval == -1)
{
printf("将退出,select出错! %s",
strerror(errno));
break;
}
else if (retval == 0)
{
/* printf
("没有任何消息到来,用户也没有按
键,继续等待……\n"); */
continue;
}
else
{
if (FD_ISSET(sockfd, &rfds))
{
/* 连接的socket上有消息到来则接
收对方发过来的消息并显示 */
bzero(buffer, MAXBUF + 1);
/* 接收对方发过来的消息,最多接
收 MAXBUF 个字节 */
len = recv(sockfd, buffer,
MAXBUF, 0);
if (len > 0)
printf
("接收消息成功:'%s',共
%d个字节的数据\n",
buffer, len);
else
{
if (len < 0)
printf
("消息接收失败!错误
代码是%d,错误信息
是'%s'\n",
errno,
strerror(errno));
else
printf("对方退出了,聊天
终止!\n");
break;
}
}
if (FD_ISSET(0, &rfds))
{
/* 用户按键了,则读取用户输入的
内容发送出去 */
bzero(buffer, MAXBUF + 1);
fgets(buffer, MAXBUF, stdin);
if (!strncasecmp(buffer, "quit",
4))
{
printf("自己请求终止聊天
!\n");
break;
}
/* 发消息给服务器 */
len = send(sockfd, buffer,
strlen(buffer) - 1, 0);
if (len < 0)
{
printf
("消息'%s'发送失败!错误代
码是%d,错误信息是'%s'\n",
buffer, errno,
strerror(errno));
break;
}
else
printf
("消息:%s\t发送成功,共
发送了%d个字节!\n",
buffer, len);
}
}
}
/* 关闭连接 */
close(sockfd);
return 0;
}
fork:
服务:
#include<stdio.h>
#include<strings.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<arpa/inet.h>
#define PORT 1234
#define BACKLOG 1
#define MAXDATASIZE 100
void main()
{
int listenfd, connectfd;
struct sockaddr_in server;
struct sockaddr_in client;
socklen_t addrlen;
char buf[MAXDATASIZE];
pid_t pid;
if((listenfd = socket(AF_INET, SOCK_STREAM, 0))
== -1) {
perror("socket() error.");
exit(1);
}
int opt = SO_REUSEADDR;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR,
&opt, sizeof(opt));
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr.s_addr = htonl(INADDR_ANY);
if(bind(listenfd, (struct sockaddr *)&server,
sizeof(server)) == -1)
{
perror("bind() error.");
exit(1);
}
if(listen(listenfd, BACKLOG) == -1)
{
perror("listen() error.");
exit(1);
}
addrlen = sizeof(client);
while(1)
{
if((connectfd = accept(listenfd, (struct
sockaddr *)&client, &addrlen )) == -1)
{
perror("accept() error.");
exit(1);
}
if((pid = fork()) >0)
{
while(1)
{
close(listenfd);
recv(connectfd, buf, MAXDATASIZE,0);
printf("the client says: %s\n",buf);
}
}
else if(pid == 0)
{
while(1)
{
close(listenfd);
fgets(buf, MAXDATASIZE, stdin);
send(connectfd, buf, MAXDATASIZE, 0);
}
}
else
return 1;
}
close(connectfd);
close(listenfd);
}
客户:
#include<stdio.h>
#include<unistd.h>
#include<strings.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<netinet/in.h>
#include<netdb.h>
#define PORT 1234 /*listen port*/
#define MAXDATASIZE 100
int main ( int argc, char *argv[])
{
int sockfd, num;
pid_t pid;
char buf[MAXDATASIZE];
struct hostent *he;
struct sockaddr_in server;
if(argc!=2)
{
printf("usage %s<ip address>\n",argv[0]);
exit(1);
}
if((he = gethostbyname(argv[1])) == NULL)
{
printf("gethostbyname error\n");
exit(1);
}
if((sockfd = socket(AF_INET, SOCK_STREAM, 0))
== -1)
{
printf("socket() error \n");
exit(1);
}
bzero(&server, sizeof(server));
server.sin_family = AF_INET;
server.sin_port = htons(PORT);
server.sin_addr = *((struct in_addr
*)he->h_addr);
if(connect(sockfd, (struct sockaddr *)&server,
sizeof(server)) == -1)
{
printf("connetc() error\n");
exit(1);
}
if((pid = fork()) >0)
{
while(1)
{
fgets(buf, MAXDATASIZE, stdin);
send(sockfd, buf, MAXDATASIZE, 0);
}
}
else if(pid == 0)
{ while(1)
{
recv(sockfd, buf, MAXDATASIZE, 0);
printf("server message: %s",buf);
}
}
else
return 1;
close(sockfd);
}