首页 > 系统相关 >Linux Socket 摘要(二)(基于TCP的C/S基本实现,相关基础知识,非阻塞select)

Linux Socket 摘要(二)(基于TCP的C/S基本实现,相关基础知识,非阻塞select)

时间:2023-02-11 16:55:18浏览次数:51  
标签:sockaddr set Socket int struct TCP fd Linux addr

PS:要转载请注明出处,本人版权所有。

PS: 这个只是基于《我自己》的理解,

如果和你的原则及想法相冲突,请谅解,勿喷。

前置说明

  本文作为本人csdn blog的主站的备份。(BlogID=039)
  本文发布于 2017-08-31 16:18:21,现用MarkDown+图床做备份更新。blog原图已丢失,使用csdn所存的图进行更新。(BlogID=039)

环境说明

  测试环境:Linux 4.10.0-33-generic #37~16.04.1-Ubuntu SMP Fri Aug 11 14:07:24 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux

前言


  无





Socket摘要2


  1. 关于linux socket通信,要详细了解清楚,不知道要说多少天。所以网上大部分教程也是只介绍了基本的api调用流程。一些其他的问题还没有提及,当然本文作者由于水平有限,估计也只能介绍个流程,并且解决一些简单的未涉及的问题。

  2. TCP的基本要点,三次握手,四次分手,分别代表了开始和结束。下图是我在百度图片上找的一个图,完全找不到原图出自哪里,很伤感。

rep_img

  说明:此图完全清晰可见的描述了一个tcp通信到底做了一些什么。我也不详细说明,改天可以给大家抓包分析分析。

  1. 通过上图我们可以看到,客户端connect后,就可以write和read了,而服务端accept后可以做同样的事情,最后只需要close就能够解决。下面我们简要的来分析一下这个流程。
rep_img
rep_img

  上图是我写的一个服务端程序跑起来后,通过netstat可以看到此进程进入了listen状态。

rep_img
rep_img
rep_img

  上面三个图演示了一个tcp通信的完整过程。第三图1-3是connect,4是write:Hello Server,5是对4的响应,代表收到,6是write:Hello Client,7同理5,8-11对应close,和上文所要展示的流程基本相同。

rep_img

  上图是查看端口,可见tcp的链接状况(图中pid和上文图中pid不对应的原因是非同一个测试)。

  1. 好了,上文BB了那么多,只是要科普一下而已,现在进入正题,首先来看几个定义及结构体。
    结构体1
    typedef unsigned short __kernel_sa_family_t;
    typedef __kernel_sa_family_t	sa_family_t;      
    struct sockaddr {//通用结构体,很多socket相关api都要使用它
               sa_family_t sa_family;
               char        sa_data[14];
    }

结构体2

    typedef uint32_t in_addr_t;
    struct in_addr//ip地址存放结构体
    {
    in_addr_t s_addr;
    };
    
    #define __SOCKADDR_COMMON(sa_prefix) \
    sa_family_t sa_prefix##family

    struct sockaddr_in//ip4 地址结构
    {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;                 //Port number.  
    struct in_addr sin_addr;            // Internet address.  
    
    // Pad to size of `struct sockaddr'
    unsigned char sin_zero[sizeof (struct sockaddr) -
                               __SOCKADDR_COMMON_SIZE -
                               sizeof (in_port_t) -
                               sizeof (struct in_addr)];
    };

结构体3

    #define _K_SS_MAXSIZE   128      //Implementation specific max size 
    #define _K_SS_ALIGNSIZE (__alignof__ (struct sockaddr *))
    //Implementation specific desired alignment 

    typedef unsigned short __kernel_sa_family_t;

    struct __kernel_sockaddr_storage {//此结构体是新内核提出的,可以存储所有协议地址类型
        __kernel_sa_family_t    ss_family;              //address family 
        // Following field(s) are implementation specific 
        char            __data[_K_SS_MAXSIZE - sizeof(unsigned short)];
        // space to achieve desired size, 
    // _SS_MAXSIZE value minus size of ss_family 
    } __attribute__ ((aligned(_K_SS_ALIGNSIZE)));   /* force desired alignment 

  结构体1,2是ip4网络编程常用的一些结构体,结构体3是新内核对于多种协议地址结构提出的一个新的通用的存储结构体。

  1. 关于这些结构体的知识我们就到这里了。现在来讲一讲linux上的socket编程。下面就是cs通信中,各自要使用的api及调用顺序,这也是最基本的socket通信,也是网上流传最广的通信例子。
client:
int socket(int domain, int type, int protocol);
int connect(int sockfd, const struct sockaddr *addr,  socklen_t addrlen);
read/write
close
server:
int socket(int domain, int type, int protocol);
int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
int listen(int sockfd, int backlog);
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);//阻塞io
read/write
close
  1. 非阻塞通信,关键select(注意,这里没有使用更高级的epoll,因为我对这个api也是一个菜鸡)
       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);

       void FD_CLR(int fd, fd_set *set);
       int  FD_ISSET(int fd, fd_set *set);
       void FD_SET(int fd, fd_set *set);
       void FD_ZERO(fd_set *set);

  对于这个api,简单来说,就是监控 所有传入的 文件描述符集合,当相关文件描述集合可读, 可写 ,异常时,select正常返回,否则可能是等待超时,可能是出错。
对于本文,就是监控客户端socket fd,监控服务端socket fd和accept接受的fd。

  1. 其他的都不说了,口水都干了,直接上例子代码,然后喝口水,上个厕所,洗个手,坐等下班
    client.c
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include <errno.h>
#include <string.h>

#include <arpa/inet.h>

#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>
#include <unistd.h>

#define TARGETPORT 6666
#define TARGETIP "127.0.0.1"
int main(int argc, char *argv[])
{

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

domain:
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       AF_IPX              IPX - Novell protocols
       AF_NETLINK          Kernel user interface device     netlink(7)
       AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
       AF_AX25             Amateur radio AX.25 protocol
       AF_ATMPVC           Access to raw ATM PVCs
       AF_APPLETALK        AppleTalk                        ddp(7)
       AF_PACKET           Low level packet interface       packet(7)
       AF_ALG              Interface to kernel crypto API
type:
       SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based
                       byte  streams.  An out-of-band data transmission mecha‐
                       nism may be supported.

       SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages
                       of a fixed maximum length).

       SOCK_SEQPACKET  Provides  a  sequenced,  reliable,  two-way connection-
                       based data transmission path  for  datagrams  of  fixed
                       maximum  length;  a  consumer  is  required  to read an
                       entire packet with each input system call.

       SOCK_RAW        Provides raw network protocol access.

       SOCK_RDM        Provides a reliable datagram layer that does not  guar‐
                       antee ordering.

       SOCK_PACKET     Obsolete  and  should  not be used in new programs; see
                       packet(7).

protocol:
       The protocol specifies a  particular  protocol  to  be  used  with  the
       socket.  Normally only a single protocol exists to support a particular
       socket type within a given protocol family, in which case protocol  can
       be  specified  as  0.   However, it is possible that many protocols may
       exist, in which case a particular protocol must be  specified  in  this
       manner.   The  protocol number to use is specific to the “communication
       domain” in which communication is to take place; see protocols(5).  See
       getprotoent(3) on how to map protocol name strings to protocol numbers.
*/
    int fd;
    if (0 > (fd = socket(AF_INET, SOCK_STREAM, 0)))
    {

        perror("socket create error:");
        return -1;
    }
    /*
    int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);

    typedef unsigned short __kernel_sa_family_t;
    typedef __kernel_sa_family_t	sa_family_t;      
    struct sockaddr {
               sa_family_t sa_family;
               char        sa_data[14];
    }

    typedef uint32_t in_addr_t;
    struct in_addr
    {
    in_addr_t s_addr;
    };

    #define __SOCKADDR_COMMON(sa_prefix) \
    sa_family_t sa_prefix##family

    struct sockaddr_in
    {
    __SOCKADDR_COMMON (sin_);
    in_port_t sin_port;                 /* Port number.  
    struct in_addr sin_addr;            /* Internet address.  
    
    // Pad to size of `struct sockaddr'.  
    unsigned char sin_zero[sizeof (struct sockaddr) -
                               __SOCKADDR_COMMON_SIZE -
                               sizeof (in_port_t) -
                               sizeof (struct in_addr)];
    };
    

    #define _K_SS_MAXSIZE   128      //Implementation specific max size 
    #define _K_SS_ALIGNSIZE (__alignof__ (struct sockaddr *))
    //Implementation specific desired alignment 

    typedef unsigned short __kernel_sa_family_t;

    struct __kernel_sockaddr_storage {
        __kernel_sa_family_t    ss_family;              /* address family 
        /* Following field(s) are implementation specific 
        char            __data[_K_SS_MAXSIZE - sizeof(unsigned short)];
        /* space to achieve desired size, */
    /* _SS_MAXSIZE value minus size of ss_family 
    } __attribute__ ((aligned(_K_SS_ALIGNSIZE)));   /* force desired alignment 

    */

    struct sockaddr_in addr;
    memset(&addr, 0, sizeof(struct sockaddr_in));
    addr.sin_family = AF_INET;
    addr.sin_port = htons(TARGETPORT);
    //addr.sin_addr.s_addr = htonl
    //int inet_pton(int af, const char *src, void *dst);
    inet_pton(AF_INET, TARGETIP, (void *)&(addr.sin_addr.s_addr));

    //int connect(int sockfd, const struct sockaddr *addr,\
        socklen_t addrlen);

    if (0 > connect(fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr)))
    {

        perror("socket connect error:");
        return -1;
    }
    int ret;
    struct timeval timeout;
    char  SendMsg[] = {"Hello Server"};
    char RecBuf[100] = {0};
    fd_set rec_set;
    FD_ZERO(&rec_set);  
    FD_SET(fd, &rec_set);  

    while (1)
    {
        //ssize_t write(int fd, const void *buf, size_t count);
        if ( 0 > write(fd, SendMsg, sizeof(SendMsg)) ){

            printf("write failed\n");
        }

        timeout.tv_sec = 5;   
        timeout.tv_usec = 0;  
        //int select(int nfds, fd_set *readfds, fd_set *writefds,\
            fd_set *exceptfds, struct timeval *timeout);
        ret = select(fd + 1, &rec_set, NULL, NULL, &timeout); 
        //select返回表示检测到可读事件 
        switch(ret){
            case 0:{
                printf("select timeout!\n");
                break;
            }
            case -1:{
                perror("select error:");
                return -1;
                break;
            }
            default:{
                //ssize_t read(int fd, void *buf, size_t count);
                read(fd, RecBuf, sizeof(SendMsg));
                printf("RecBuf:%s\n",RecBuf);
                sleep(1);
            }
        }
    }
    close(fd);
    return 0;
}

server.c

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>

#include <errno.h>
#include <string.h>

#include <arpa/inet.h>

#include <sys/select.h>

/* According to earlier standards */
#include <sys/time.h>
#include <sys/types.h>

#include <sys/select.h>



#define TARGETPORT 6666
#define TARGETIP "127.0.0.1"
typedef struct _MyFdSet
{
    int FdNum;
    int AllFdSet[FD_SETSIZE];
} MyFdSet;

void MyFdSet_INSERT(MyFdSet *set, int fd){

    set->AllFdSet[set->FdNum] = fd; 
    set->FdNum++;    
}

int  MyFdSet_GETMAX(MyFdSet *set){

    int i = 1;
    int max_fd = set->AllFdSet[0];
    for ( ; i < set->FdNum; i ++) { 
        if ( max_fd < set->AllFdSet[i]){
            max_fd = set->AllFdSet[i];
        }
    }
    return max_fd;
}

void MyFdSet_REMOVE(MyFdSet * set,int fd) {

    MyFdSet tmp;
    tmp.FdNum = 0;

    int i = 0;
    for ( ; i < set->FdNum; i++ ){
        if ( fd != set->AllFdSet[i] ){
            tmp.AllFdSet[tmp.FdNum] = set->AllFdSet[i];
            tmp.FdNum++;
        }
    }

    set->FdNum = tmp.FdNum;
    for ( i = 0; i < set->FdNum; i++){
        set->AllFdSet[i] = tmp.AllFdSet[i];
    }
}

int main(int argc, char *argv[])
    {

        int fd;
        if (0 > (fd = socket(AF_INET, SOCK_STREAM, 0)))
        {

            perror("socket error:");
            return -1;
        }

        struct sockaddr_in addr;
        struct sockaddr_in client_addr;

        memset(&addr, 0, sizeof(struct sockaddr_in));
        addr.sin_family = AF_INET;
        addr.sin_port = htons(TARGETPORT);
        addr.sin_addr.s_addr = htonl(INADDR_ANY);
        //   int bind(int sockfd, const struct sockaddr *addr,\
        socklen_t addrlen);
        if (0 > bind(fd, (const struct sockaddr *)&addr, sizeof(struct sockaddr)))
        {

            perror("bind error:");
            return -1;
        }
        //int listen(int sockfd, int backlog);
        if (0 > listen(fd, 5))
        {

            perror("bind error:");
            return -1;
        }
        fd_set ser_set;
        FD_ZERO(&ser_set);
        FD_SET(fd, &ser_set);
        struct timeval timeout;
        int ret;

        MyFdSet myfdset = {0,{0}};

        MyFdSet_INSERT(&myfdset, fd);
        char RecBuf[100];
        char SendMsg[] = {"Hello Client"}; 

        while (1)
        {
            int n = 0;
            FD_ZERO(&ser_set);
            for ( ; n < myfdset.FdNum; n++){

                FD_SET(myfdset.AllFdSet[n], &ser_set);
                printf("exist fd %d\n",myfdset.AllFdSet[n]);
            }
            timeout.tv_sec = 5;
            timeout.tv_usec = 0;
            //int select(int nfds, fd_set *readfds, fd_set *writefds,\
            fd_set *exceptfds, struct timeval *timeout);
            ret = select( MyFdSet_GETMAX(&myfdset) + 1, &ser_set, NULL, NULL, &timeout);
            switch (ret)
            {
            case 0:
            {
                printf("select timeout!\n");
                break;
            }
            case -1:
            {
                perror("select error:");
                return -1;
                break;
            }
            default:
            {  
               int i = 0;
               for ( ; i < myfdset.FdNum; i ++ ){

                    if (FD_ISSET(myfdset.AllFdSet[i], &ser_set)){

                        if ( fd == myfdset.AllFdSet[i] ){//client connect
                            
                            int c_fd;
                            int client_addr_len = sizeof(struct sockaddr);
                            if ( 0  >  (c_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len)) ){

                                perror("accept error:");
                                break;
                            }

                            printf("client ip:%s,port:%d\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port));
                            MyFdSet_INSERT(&myfdset, c_fd);
                            FD_SET(c_fd, &ser_set);
                            break;
                        }
                        else{//client sent datas in buf
                            
                            ret = read(myfdset.AllFdSet[i], RecBuf, sizeof(SendMsg));
                            if ( 0 > ret ){
                                
                                MyFdSet_REMOVE(&myfdset, myfdset.AllFdSet[i]);
                                close(myfdset.AllFdSet[i]);
                                perror("read error:");
                                break;
                            }
                            else if( ret == 0 ){//client disconnected
                                
                                MyFdSet_REMOVE(&myfdset, myfdset.AllFdSet[i]);
                                close(myfdset.AllFdSet[i]);
                                printf("client disconnected\n");
                                break;
                            }
                            else{

                                printf("RecMsg:%s\n", RecBuf);
                                write(myfdset.AllFdSet[i], SendMsg, sizeof(SendMsg));
                            }
                        }
                    }
               }
               sleep(1);
            }
            }
        }

        int i;
        for ( i = 0; i < myfdset.FdNum; i++ ){//close fd

            close(myfdset.AllFdSet[i]);
        }
        return 0;
    }

  直接gcc client.c -o client gcc server.c -o server 就可以使用了





后记


  无

参考文献




打赏、订阅、收藏、丢香蕉、硬币,请关注公众号(攻城狮的搬砖之路)
qrc_img

PS: 请尊重原创,不喜勿喷。

PS: 要转载请注明出处,本人版权所有。

PS: 有问题请留言,看到后我会第一时间回复。

标签:sockaddr,set,Socket,int,struct,TCP,fd,Linux,addr
From: https://www.cnblogs.com/Iflyinsky/p/17111999.html

相关文章