网络编程原理

基本原理

计算机网络体系结构模式

通信模式

所有的网络通信的实现方式可以分为两种:线路交换和
包交换


所谓线路交换就是指传输时在发送端和接收端之间建立
一个特定的线路连接数据就可以在这条线路上传输
最常用的电话就是采用这种线路交换的技术

计算机网络采用的是包交换的方法所有的计算机使用一
个共同的网络连接 数据的发送端将要传输的数据分割

块而每个块经过适当的处理(数据封装)后形成一个数

包,包中有接收端的地址等必要信息,并且每个包单独
传输


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); 
}

你可能感兴趣的:(编程,server,网络,struct,socket,buffer)