网络编程

TCP/IP和OSI模型

  • OSI模型共有七层
应用层 应用程序:FTP(文件传输)、E-mail、Telnet(网络)
表示层 数据格式定义、数据转换/加密
会话层  
传输层  
网络层  
数据链路层 数据组成可发送、接收的帧
物理层  

 

 

 

 

 

 

 

 

  • TCP/IP协议族有四层
应用层 Telnet、FTP、HTTP(超文本传输协议)、DNS(域名解析)、SMTP(邮件传输)、ARP(地址解析协议)
传输层 TCP(以字节流)、UDP(以数据包传输)
网络层 IP、ICMP、IGMP
网络接口和物理层 以太网、令牌环网、FDDI

 

 

 

 

 

TCP/IP协议

   TCP/IP协议分成两个不同的协议:

  1. 用来检测网络传输中差错的传输控制协议TCP;
  2. 专门负责对不同网络进行互联的互联网协议IP;

一、TCP与UDP

(一)TCP

   1、 TCP协议特点

         TCP协议(传输控制协议)特点:全双工的面向连接的传输层协议,它能提高可靠想通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)。

  2、TCP协议适用情况

  • 适用于对传输质量要求高,以传输大量数据的通信;
  • 在需要可靠数据传输的唱歌,通常适用TCP协议;
  • MSN/QQ等即通信软件的用户登录账户管理相关的功能;

(二)UDP

   1、UDP协议特点

         UDP(用户数据报协议):是不可靠的无连接协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。

   2、UDP协议适用情况

  • 在接收到数据,给出应答较困难的网络中使用UDP(如:无线网络);
  • 适用于广播、组播通信中;

(三)TCP与UDP区别

       共同点:同为传输层协议;

       不同点:TCP - 有连接、可靠;

                      UDP - 无连接、不保证可靠、效率高;

二、socket

  socket特点:

  1.  是一个编程接口;
  2. 是一种特殊的文件描述符;
  3. 可以使用TCP/IP<流式套接字,面向连接、数据可靠>、可以使用UDP<数据报套接字:无连接、不保证数据可靠>;
  4. 使用原因:主要解决不同主机之间数据通信,并且保证数据的高效传输,即通过socket套接字文件。

三、IP地址

 (一)IP地址特点

  1. IP地址是Internet中的主机的标识;
  2. Internet中的主机要与别的机器通信必须具有一个IP地址;
  3. IP地址分为32位(IPV4)、128位(IPV6);
  4. 每个数据包都必须携带目的IP地址和源IP地址,路由依靠此信息为数据包选择路由;

 (二)IP地址解析

  •   IP地址 = 网络地址 + 主机地址
  •   网络地址 = IP地址 & 子网掩码
  •   主机地址 = IP地址 & -子网掩码

   IP地址分类:A(1+3)、B(2+2)、C(3+1)、D、E类,其中D、E类用于广播。

  (三)IP地址的转换

     1、inet_addr()

      将点分形式的IP地址转换为网络IP地址(大端存储的无符号32位整形)。        

        #include

        函数原型: in_addr_t inet_addr(const char *address);//返回转换后的地址

        address是以NULL结尾的点分IPV4字符串。

        返回32位的地址。如果字符串包含不合格法的IP地址,则函数返回-1;

struct in_addr addr;
addr.s_addr = inet_addr(192.168.1.122);

     2、inet_aton()

     #include

     int inet_aton(const char *cp, struct in_addr *inp);

     3、inet_ntoa()

 #include

 char *inet_ntoa(struct in_addr in);

  in是IPV4地址结构,函数返回一指向包含点分IP地址的静态存储区字符指针。如果错误则函数返回NULL。

 (四)字节序转换转换

     网络中传输的数据必须按网络字节序,即大端字节序。

   1、大小端

     大端序:低字节存储在高低序;

     小端序:低字节存储在高地址;

 2、字节序转换函数

  (1)主机字节序到网络字节序

       #include

       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

    (2)网络字节序到主机字节序

          #include

          uint32_t ntohl(uint32_t netlong);

          uint16_t ntohs(uint16_t netshort);

 

服务器与客户端

 一、服务器与客户端流程

网络编程_第1张图片

网络编程_第2张图片

二、通信相关函数

(一)socket

#include           
#include

int socket(int domain, int type, int protocol);

 domain是地址族:AF_INET  ---  IPv4 Internet protocols
                                AF_INET6 ---  IPv6 Internet protocols

                                AF_UNIX --- unix协议,本地多进程间通信

  type是套接字类型:SOCK_STREAM --- 流式套接字(TCP)

                                   SOCK_DGRAM --- 数据报套接字(UDP)

                                   SOCK_RAW --- 原始套接字

   protocol --- 通常设为0

(二)bind

    1、地址相关数据结构

     (1)通用地址结构        

struct sockaddr {
               sa_family_t sa_family; //地址族,AF_xxx
               char        sa_data[14]; //14字节协议地址(存储服务器的IP地址和端口号)
           };

     (2)Internet协议地址结构

struct sockaddr_in struct sockaddr_un
{
    u_short sin_family; //地址族,AF_INET,2bytes
    u_short sin_port; 端口,2bytes =htons()
    struct in_addr sin_addr; //IPV4地址,4bytes 见下面结构体
    char sin_zero[8];    //8bytes unused,作为填充
};

   用法:

/*定义一个 struct sockaddr_in类型的变量并清空*/

struct sockaddr_in myaddr;

memset(&myaddr,0,sizeof(myaddr));
/*填充地址信息*/
myaddr.sin_family = PF_INET;
myaddr.sin_port = htons(8888);//端口号
myaddr.sin_addr.s_addr = inet_addr("192.168.1.123");

     (3) IPV4地址结构

struct in_addr
{
    in_addr_t s_addr;
};

  2、bind()

#include        
#include

int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);

   sockfd --- socket()返回的文件描述符

   struct sockaddr --- 上面通用结构体,addr是指向sockaddr_in 结构的指针,包含本机IP地址和端口号

   addrlen --- sockaddr地址结构的长度;sizof(struct sockaddr_in)

  返回值:0、-1(出错)

(三)listen

     功能:启动服务器,启动监听。启动监听之后,套接字由主动变为被动

  #include        
  #include

  int listen(int sockfd, int backlog);

    sockfd --- 监听连接的套接字

    backlog --- 指定了正在等待连续的最大队列长度,它的作用在于处理可能同时出现的几个连接请求。

                       一般设其值为0

    返回值:0或-1

 (四)accept

 #include           
 #include

 int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

  sockfd --- 监听套接字

  addr --- 对方地址

  addrlen --- 地址长度

                    socklen_t addrlen

                    addrlen = sizeof(socklen_t );

                    addrlen这个参数设为&addrlen

  返回值:已建立好连接的套接字(文件描述符)或-1

 (五)connect

#include           
#include

int connect(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
 sockfd --- socket()返回的文件描述符

 addr --- 服务器端的地址信息

 addrlen --- addr的长度

(六)send

#include
#include

ssize_t send(int sockfd, const void *buf, size_t len, int flags);
 buf --- 发送缓冲区首地址

 len --- 发送的字节数

 flags --- 发送方式

 (七)recv

#include
#include

ssize_t recv(int sockfd, void *buf, size_t len, int flags);

  (八) 服务器和客户端代码示例

      server.c

include */
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include 
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    struct sockaddr_in srvaddr1;
    socklen_t addrlen = sizeof(socklen_t) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("0.0.0.0");//调用本地IP地址

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    /*启动监听*/
    ret = listen(socketfd,0);
    if( ret == -1)
    {
        perror("server->listen->error");
        return -1;
    }

    /*等待服务器的链接请求*/
    connfd = accept(socketfd,(struct sockaddr *)&srvaddr1,&addrlen);
    if( connfd == -1)
    {
        perror("server->accept->error");
        return -1;
    }

    printf("connect success\n");
    while(1)
    {
        printf("%s",inet_ntoa(srvaddr1.sin_addr));//获取客户端的地址
        /*
        ret = read(connfd,buf,sizeof(buf));
        if( ret == -1)
        {
            perror("srvaddr->read->error");
            return -1;
        } 
        fputs(buf,stdout);

        memset(buf,0,sizeof(sizeof(buf)));
        fgets(buf,sizeof(buf),stdin);
        ret = write(connfd,buf,sizeof(buf));
        if( ret == -1)
        {
            perror("server->write->error");
            return -1;
        }
        */
        memset(buf,0,sizeof(buf));
        ret = recv(connfd,buf,sizeof(buf),0);
        if( ret == -1)
        {
            perror("srvaddr->read->error");
            return -1;
        } 
        fputs(buf,stdout);
/*
        memset(buf,0,sizeof(sizeof(buf)));
        fgets(buf,sizeof(buf),stdin);
        ret = send(connfd,buf,sizeof(buf),0);
        if( ret == -1)
        {
            perror("server->write->error");
            return -1;
        }
        */
    }
    return 0;
}


     client.c

#include
#include
#include
#include
#include
#include
#include 
#include 

int main (void)
{
    int ret;
    char buf[100];
    int sockefd;
    struct sockaddr_in cltaddr;
    sockefd = socket(AF_INET,SOCK_STREAM,0);

    /*建立客服端*/
    if( sockefd == -1)
    {
        perror("client->sockefd->error");
        return -1;
    }

    /*请求连接服务器*/
    memset(&cltaddr,0,sizeof(struct sockaddr));
    cltaddr.sin_family = AF_INET;
    cltaddr.sin_port = htons(9696);
    cltaddr.sin_addr.s_addr = inet_addr("0.0.0.0");
    
    ret = connect(sockefd,(const struct sockaddr *)&cltaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("cltaddr->connect->error");
        return -1;
    }

    printf("connect is success\n");
    memset(buf,0,sizeof(buf));
    while(1)
    {
        /*
        fgets(buf,sizeof(buf),stdin);
        ret = write(sockefd,buf,sizeof(buf));
        if( ret == -1)
        {
            perror("cltent->write->error");
            return -1;
        }
        memset(buf,0,sizeof(buf));
        ret = read(sockefd,buf,sizeof(buf));
        fputs(buf,stdout);
        */
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
        ret = send(sockefd,buf,sizeof(buf),0);
        if( ret == -1)
        {
            perror("send");
            return -1;
        }
   /*     memset(buf,0,sizeof(buf));
        ret = recv(sockefd,buf,sizeof(buf),0);
        fputs(buf,stdout);
        */
    }
    return 0;
}


  用进程来实现服务器与客户端,一个进程用于读数据,一个进程用于写数据: 

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ***********************************************************************/
#include */
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include 
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    socklen_t addrlen = sizeof(socklen_t) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    /*启动监听*/
    ret = listen(socketfd,0);
    if( ret == -1)
    {
        perror("server->listen->error");
        return -1;
    }

    /*等待服务器的链接请求*/
    connfd = accept(socketfd,(struct sockaddr *)&srvaddr,&addrlen);
    if( connfd == -1)
    {
        perror("server->accept->error");
        return -1;
    }

    printf("connect success\n");
    while(1)
    {
        memset(buf,0,sizeof(buf));
        pid_t pid1;
        pid1 = fork();
        if( pid1 == -1 )
        {
            perror("fork");
            return -1;
        }
        else if( pid1 == 0 )
        {
            ret = read(connfd,buf,sizeof(buf));
            if( ret == -1)
            {
                perror("srvaddr->read->error");
                return -1;
            } 
            fputs(buf,stdout);
        
        }
        else
        {
            memset(buf,0,sizeof(sizeof(buf)));
            fgets(buf,sizeof(buf),stdin);
            ret = write(connfd,buf,sizeof(buf));
            if( ret == -1)
            {
                perror("server->write->error");
                return -1;
            }
        }
    }
    return 0;
}
/************************************************************************
> 文件名称:client.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时57分49秒
> 描述:
 ************************************************************************/
#include
#include
#include
#include
#include
#include
#include 
#include 

int main (void)
{
    int ret;
    char buf[100];
    int sockefd;
    struct sockaddr_in cltaddr;
    sockefd = socket(AF_INET,SOCK_STREAM,0);

    /*建立客服端*/
    if( sockefd == -1)
    {
        perror("client->sockefd->error");
        return -1;
    }

    /*请求连接服务器*/
    memset(&cltaddr,0,sizeof(struct sockaddr));
    cltaddr.sin_family = AF_INET;
    cltaddr.sin_port = htons(9696);
    cltaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    
    ret = connect(sockefd,(const struct sockaddr *)&cltaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("cltaddr->connect->error");
        return -1;
    }

    printf("connect is success\n");
    memset(buf,0,sizeof(buf));
    while(1)
    {
        memset(buf,0,sizeof(buf));  
        pid_t pid1;
        pid1 = fork();
        if( pid1 == -1 )
        {
            perror("fork");
            return -1;
        }
        else if( pid1 == 0 )
        {
            fgets(buf,sizeof(buf),stdin);
            ret = write(sockefd,buf,sizeof(buf));
            if( ret == -1)
            {
                perror("cltent->write->error");
                return -1;
            }
        }
        else
        {
            memset(buf,0,sizeof(buf));
            ret = read(sockefd,buf,sizeof(buf));
            fputs(buf,stdout);
        }
    }
    return 0;
}

UDP

  UDP不需要建立连接,UDP服务器和客户端流程如下:

网络编程_第3张图片 

一、UDP的发送接收函数 

  (一)sendto()

#include
#include

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);

  sockfd --- 套接字文件描述符

  buf --- 缓冲区的首地址

  len --- 发送数据的大小

  flags  --- 发送方式,默认设为0

  addrlen --- 源地址空间大小

  返回值 --- 成功:发送数据的字节数

                   失败: -1

   (二) recvfrom() 

#include
#include

ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);

二、UDP实现服务器和客户端 

  server.c

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include 
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    socklen_t addrlen = sizeof(struct sockaddr) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }
    
    while(1)
    {
        memset(buf,0,sizeof(buf));
        ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr *)&srvaddr,&addrlen);
        if( ret == -1 )
        {
            perror("recvfrom");
            return -1;
        }
        fputs(buf,stdout);
    }
    return 0;
}

    client.c 

9/************************************************************************
> 文件名称:client.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时57分49秒
> 描述:
 ************************************************************************/
#include
#include
#include
#include
#include
#include
#include 

int main (void)
{
    int ret;
    char buf[100];
    int sockefd;
    struct sockaddr_in cltaddr;
    sockefd = socket(AF_INET,SOCK_DGRAM,0);
    socklen_t addrlen = sizeof(struct sockaddr);

    /*建立客服端*/
    if( sockefd == -1)
    {
        perror("client->sockefd->error");
        return -1;
    }

    /*请求连接服务器*/
    memset(&cltaddr,0,sizeof(struct sockaddr));
    cltaddr.sin_family = AF_INET;
    cltaddr.sin_port = htons(9696);
    cltaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
    while(1)
    {
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
        ret = sendto(sockefd,buf,sizeof(buf),0,(struct sockaddr *)&cltaddr,addrlen);
        if( ret == -1 )
        {
            perror("sendto");
            return -1;
        }
    }
    
}

三、网络信息检索函数 

    1、getsockopt()/setsockopt()

#include          /* See NOTES */
#include

int getsockopt(int sockfd, int level, int optname,void *optval, socklen_t *optlen);//获取一个套接口选项
int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);//设置一个套接口选项

  sockfd --- 套接字

  level --- 指定控制套接字的层次,可以取下列三种值:

              SOL_SOCKET:通用套接字选项

               IPPROTO_IP:IP选项

               IPPROTO_TCP:TCP选项

  optname --- 指定控制的方式(选项名称)

  optval --- 获得或者是设置套接字选项。根据选项名称的数据类型进行转换

  optlen --- 选项值的最大长度

广播

  • 如果同时发给局域网中的所有主机,称为广播; 

  • 只有用户数据报(UDP协议)套接字才能广播;

 一、广播地址

  1. 以192.168.1.0(255.255.255.0网段为例,最大的主机地址192.168.1.255代表该网段的广播地址;
  2. 发送到该地址的数据包被所有的主机接收;
  3. 255.255.255.255在所有网段中都代表广播地址;

二、广播接收

  1. 绑定IP地址(广播IP或0.0.0.0)和端口号;
  2. 绑定的端口号必须和发送方指定的端口相同;

三、广播代码实现

     server.c

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include 
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    socklen_t addrlen = sizeof(struct sockaddr) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("192.168.2.255");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }
    
    while(1)
    {
        memset(buf,0,sizeof(buf));
        ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr *)&srvaddr,&addrlen);
        if( ret == -1 )
        {
            perror("recvfrom");
            return -1;
        }
        fputs(buf,stdout);
    }
    return 0;
}

   client.c


/************************************************************************
> 文件名称:client.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时57分49秒
> 描述:
 ************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include 
 #include 
#include 
int main (void)
{
    int ret;
    char buf[100];
    int sockefd;
    struct sockaddr_in cltaddr;
    sockefd = socket(AF_INET,SOCK_DGRAM,0);
    socklen_t addrlen = sizeof(struct sockaddr);
   // socklen_t addrlen;

    /*建立客服端*/
    if( sockefd == -1)
    {
        perror("client->sockefd->error");
        return -1;
    }
    printf("11\n");
    /*请求连接服务器*/
    int opt = 1;
    ret = setsockopt(sockefd,SOL_SOCKET,SO_BROADCAST,&opt,sizeof(opt));
    printf("b\n");
    if( ret == -1 )
    {
        perror("setsockopt");
        return -1;
    }
    printf("c\n");
    memset(&cltaddr,0,sizeof(struct sockaddr_in));
    printf("d\n");
    cltaddr.sin_family = AF_INET;
    cltaddr.sin_port = htons(9696);
    cltaddr.sin_addr.s_addr = inet_addr("192.168.2.22");
    while(1)
    {
        memset(buf,0,sizeof(buf));
        fgets(buf,sizeof(buf),stdin);
       ret = sendto(sockefd,buf,sizeof(buf),0,(struct sockaddr *)&cltaddr,addrlen);
        if( ret == -1 )
        {
            perror("sendto");
            return -1;
        }
    }
    
}

组播 

  • 组播(又称为多播)是一种折中的方式,只有加入某个多播组的主机,才能收到数据;
  • 多播方式既可以发给多个主机,又能避免像广播那样带来过多的负载;
  • D类地址(组播地址);
  • 不分网络地址和主机地址,第一个字节的前4位固定为1110,组播地址:224.0.0.1 - 239.255.255.255;
  • 绑定IP地址(加入组的组IP或0.0.0.0)和端口;
  • 绑定的端口必须和发送方指定的端口相同;

  一、加入组播 

struct ip_mreq
{
    struct in_addr imr_multiaddr;
    struct in_addr imr_interface;
};

 /*加入组播*/
struct ip_mreq mreq;
memset(&mreq,0,sizeof(mreq));
mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.1");
mreq.imr_interface.s_addr = htonl(INADDR_ANY);
setsockopt(socketfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

二、组播代码实现 

  server.c

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ************************************************************************/
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include 
int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    socklen_t addrlen = sizeof(struct sockaddr) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_DGRAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }

    /*加入组播*/
    struct ip_mreq mreq;
    memset(&mreq,0,sizeof(mreq));
    mreq.imr_multiaddr.s_addr = inet_addr("224.10.10.1");
    mreq.imr_interface.s_addr = htonl(INADDR_ANY);
    setsockopt(socketfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq));

    
    /*设置服务器的IP地址和段口号*/

    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("0.0.0.0");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    while(1)
    {
        memset(buf,0,sizeof(buf));
        ret = recvfrom(socketfd,buf,sizeof(buf),0,(struct sockaddr *)&srvaddr,&addrlen);
        if( ret == -1 )
        {
            perror("recvfrom");
            return -1;
        }
        fputs(buf,stdout);
    }
    return 0;
}

  client.c 

#include 
#include 
#include 

#include 
#include 
#include 

int main()
{
	int ret;
	int sockfd;
	char buf[256];
	struct sockaddr_in srvaddr;

	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if (sockfd == -1) {
		perror("socket");
		return -1;
	}
	memset(&srvaddr, 0, sizeof(struct sockaddr_in));
	srvaddr.sin_family = AF_INET;
	srvaddr.sin_port = htons(9999);
	srvaddr.sin_addr.s_addr = inet_addr("224.10.10.1");

	while(1) {
		fgets(buf, sizeof(buf), stdin);
		ret = sendto(sockfd, buf, sizeof(buf), 0, (const struct sockaddr *)&srvaddr, sizeof(struct sockaddr));
		if (ret == -1) {
			perror("sendto");
			return -1;
		}
		printf("ret = %d\n", ret);
	}
	

	return 0;
}

I/O模型

   在UNIIX/Linux下主要有4中I/O模型:阻塞I//O、非阻塞I/O、I/O多路复用、信号驱动IO(一种异步通信模型)。

一、阻塞I/O

1、特点:最常用、最简单、效率低。

2、以read函数为例进行解释

    进程调用read函数从套接字上读取数据,当套接字的接收缓冲区中还没数据可读,函数read将发生阻塞,它一直阻塞下去,等待套接字的接收缓冲区有数据可读。当缓冲区内接收到数据,于是内核便去唤醒该进程,通过read访问这些数据。

int main()
{
    char buf[128];
    memset(&buf,0,sizeof(buf));
    while(1)
    {
        read(0,buf,sizeof(buf));
        printf("%s\n",buf);
    }
    return 0;
}

二、非阻塞I/O

  1、特点

  •  可防止进程阻塞在I/O操作上,需要轮询;
  • 当I/O操作不能马上完成,立刻返回一个错误;

  2、以fcntl为例

#include
#include

 int fcntl(int fd, int cmd, ... /* arg */ );

#include 
#include 
#include 
#include 
int main()
{
	int ret;
	char buf[128];
	int flag = fcntl(0,F_GETFL,0);
	flag |= O_NONBLOCK;
	fcntl(0,F_SETFL,flag);
	while(1)
	{
		memset(buf,0,sizeof(buf));
		ret = read(0,buf,sizeof(buf));
		printf("buf = %s\n",buf);
		sleep(1);
	}
	return 0;
}

三、I/O多路复用

       允许同时对多个I/O进行控制,主要有三种机制:select()、poll()、epoll_ctl()。

    select()、poll()、epoll_ctl()的思路:

  1. 将所有要处理的文件描述符存储到一张表中;
  2. 检测表当中有没有已经就绪的文件描述符,如果有,返回就绪的文件描述符;
  3. 轮询所有就绪的文件描述符;
while(1)
{
    if(listenfd)
        //说明是新的客户端发起了请求;
    if(confd)
        //说明是已经连接的客户端发送了数据请求;
    if(/*普通文件*/)
        //说明普通文件的数据;
    if(0)
        //标准输入;
}

(一)select

    select机制思路及步骤:

  1. 根据描述符处理事件不同,创建不同的表;例如将所有文件描述符添加到一张读的表中;
  2. 检测表当中是否有就绪的文件描述符,如果有返回状态,同时返回就绪的文件描述符;
  3. 轮询

         (1)找到那些文件描述符;

         (2)处理数据;

#include
#include
#include

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout);
 nfds --- 最大文件描述符+1

 readfds --- 所有要读的文件描述符的集合(读文件件描述表)

 writefds --- 所有要写的文件描述符的集合(写文件件描述表)

 exceptfds --- 其他要向我们通知的文件描述(错误处理文件描述符表)

 timeout --- 超时设置:NULL:一直阻塞,直到有文件描述符就绪或出错

                                           0:仅仅检测文件描述符集的状态,然后立即返回

                                    不为0:在指定时间内,如果没有事件发生,则超时返回

 返回值:成功 --- 准备就绪的文件描述符的个数

               失败 --- -1

               超时返回 ---- 0

 设置文件描述符的几个宏:

  1. void FD_CLR(int fd, fd_set *set);   //将fd从集合set当中清除
  2. int  FD_ISSET(int fd, fd_set *set);  //判断fd是否在集合set中
  3. void FD_SET(int fd, fd_set *set);   //将fd添加到集合set中
  4. void FD_ZERO(fd_set *set);          //清空集合set

  select实现并发服务器:

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ************************************************************************/
#include */
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include
int select_func(int connfd)
{
    char buf[100];
    int ret;
    
        memset(buf,0,sizeof(buf));
        ret = read(connfd,buf,sizeof(buf));
        if( ret == -1)
        {
            perror("srvaddr->read->error");
            return -1;
        } 
        if( ret == 0 )
        {
            return 1;
        
        }

        fputs(buf,stdout);

       

    return 0;
}

int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    int addrlen = sizeof(struct sockaddr_in ) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    /*启动监听*/
    ret = listen(socketfd,1024);
    if( ret == -1)
    {
        perror("server->listen->error");
        return -1;
    }
    
    int fd;
    int nfds;
    /* 创建一个集合,用来存储所要读处理的所有的文件描述符*/
    fd_set readfds;
    fd_set rfds;
    /* 添加要读处理的文件描述符 */
    FD_SET(socketfd,&readfds);
    nfds = socketfd + 1;

    while(1)
    {
        printf("==========\n");
            rfds=readfds;
        /* 检测集合当中是否有继续的文件描述符 */
        ret = select(nfds,&rfds,NULL,NULL,NULL);
        /* 轮循 */
        for(fd = 0;fd < nfds;fd++)
        {
            /* 判断,寻找就绪的文件描述符 */
            if( FD_ISSET(fd,&rfds) )
            {
                /* 如果是监听套接字socketfd,则建立连接 */
                printf("---------------\n");
                if( fd == socketfd )
                {
                    printf("++++++++++++++++\n");
                    memset(&srvaddr,0,sizeof(srvaddr));
                    connfd = accept(socketfd,(struct sockaddr *)&srvaddr,&addrlen);
                    if( -1 == connfd )
                    {
                        perror("accept");
                        return -1;
                    }
                    printf("connnect success connfd = %d\n",connfd);

                    FD_SET(connfd,&readfds);
                    if( connfd >= nfds )
                    {
                        nfds = connfd + 1;
                    }
                }
                else
                {
                    ret = select_func(fd);
                    if( ret == 1 )
                    {
                        FD_CLR(fd,&readfds);
                        close(fd);
                    }
                }
            }
        }
    }
    close(socketfd);
    return 0;
}

(二)poll

   poll机制思路及步骤:

  1. 创建一个集合文件描述符及其所要处理的事件;
  2. 检测集合当中是否有事件发生(是否有文件描述符准备就绪);如果有,返回有的状态,同时返回就绪的文件描述符及事件;
  3. 轮询;

          (1)找到发生的事件;

          (2)处理数据;

功能:检测是否有文件描述和事件处于就绪状态,有返回,没有一直阻塞。 

#include

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

  fds --- 事件和文件描述符的集合

  nfds --- 最大文件描述符+1

  timeout --- 超时时间(ms)

  struct pollfd {
               int   fd;              /* 文件描述符 */
               short events;     /* 需要的事件*/
               short revents;    /* 返回的事件 */
           };

 poll()机制实现并发服务器:

/************************************************************************
> 文件名称:server.c
> 创建者:wl
> 邮箱:[email protected]
> 时间:2017年09月19日 星期二 14时15分50秒
> 描述:
 ***********************************************************************nclude */
#include
#include
#include
#include
#include
#include
#include
#include
#include 
#include
#include
int poll_func(int connfd)
{
    char buf[100];
    int ret;
    
    memset(buf,0,sizeof(buf));
    ret = read(connfd,buf,sizeof(buf));
    if( ret == -1)
    {
        perror("srvaddr->read->error");
        return -1;
    } 
    if( ret == 0 )
    {
        return 1;
    }
    fputs(buf,stdout);
    return 0;
}

int main (void)
{
    int socketfd;
    int ret;
    char buf[100];
    int connfd;
    struct sockaddr_in srvaddr;
    int addrlen = sizeof(struct sockaddr_in ) ;
    
    /*创建socket套接字*/
    socketfd = socket(AF_INET,SOCK_STREAM,0);
    if( socketfd == -1)
    {
        perror("server->socket->error");
        return -1;
    }
    
    /*设置服务器的IP地址和段口号*/
    memset(&srvaddr,0,sizeof(struct sockaddr));
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(9696);
    srvaddr.sin_addr.s_addr = inet_addr("127.0.0.1");

    ret = bind(socketfd,(const struct sockaddr *)&srvaddr,sizeof(struct sockaddr));
    if( ret == -1)
    {
        perror("server->bind->error");
        return -1;
    }

    /*启动监听*/
    ret = listen(socketfd,1024);
    if( ret == -1)
    {
        perror("server->listen->error");
        return -1;
    }
    /* 创建集合,并且将集合当中的每一个数组元素的fd成员赋值为-1;*/
    int nfds;
    int fd;
    int i,j;
    struct pollfd fds[1024];
    for(i=0;i<1024;i++)
    {
        fds[i].fd = -1;
    }
    /* 添加所需要处理的事件和文件描述符 */
    fds[0].fd = socketfd;
    fds[0].events = POLLIN;
    nfds = socketfd + 1;
    while(1)
    {
        /* 检测集合当中是否有继续的文件描述符 */
        ret = poll(fds,nfds,5000);
        if( ret == -1)
        {
            perror("poll");
            return -1;
        }
        else if( ret == 0 )
        {
            printf("timeout\n");
            continue;
        }

        /* 轮循 */
        for( i=0;i<1024;i++)
        {
            /* 判断是什么事件  */
            if( POLLIN == fds[i].events)
            {
                /* 判断,寻找就绪的文件描述符 */
                if( fds[i].fd != -1 )
                {
                    fd = fds[i].fd;
                    /* 如果是监听套接字listenfd,则建立连接 */
                    if( fd == socketfd )
                    {
                        memset(&srvaddr,0,sizeof(srvaddr));
                        connfd = accept(socketfd,(struct sockaddr *)&srvaddr,&addrlen);
                        if( connfd == -1 )
                        {
                            perror("accept");
                            return -1;
                        }
                        printf("connent success connfd = %d\n",connfd);
                        for( j=0;j<1024;j++)
                        {
                            if( fds[j].fd != -1 )
                            {
                                continue;
                            }
                            fds[j].fd = connfd;
                            fds[j].events = POLLIN;

                            if( nfds <= connfd )
                            {
                                nfds = connfd + 1;
                            }
                        }
                    }
                    else
                    {
                        ret = poll_func(fd);
                        if( ret == -1 )
                        {
                            fds[i].fd = -1;
                        }
                    }
                }
            }
        }
    }

    close(socketfd);
    return 0;
}

(三) epoll_ctl

 #include

int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);

你可能感兴趣的:(笔记,linux,网络编程)