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

来源:互联网 发布:淘宝卖家如何加入村淘 编辑:程序博客网 时间:2024/05/21 04:20

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

#PS:这个只是 《 我自己 》理解,如果和你的

#原则相冲突,请谅解,勿喷

测试环境:

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

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

2 TCP的基本要点,三次握手,四次分手,分别代表了开始和结束。下图是我在百度图片上找的一个图,完全找不到原图出自哪里,很伤感。
这里写图片描述
说明:此图完全清晰可见的描述了一个tcp通信到底做了一些什么。我也不详细说明,改天可以给大家抓包分析分析。

3 通过上图我们可以看到,客户端connect后,就可以write和read了,而服务端accept后可以做同样的事情,最后只需要close就能够解决。下面我们简要的来分析一下这个流程。
这里写图片描述
这里写图片描述
上图是我写的一个服务端程序跑起来后,通过netstat可以看到此进程进入了listen状态。
这里写图片描述
这里写图片描述
这里写图片描述
上面三个图演示了一个tcp通信的完整过程。第三图1-3是connect,4是write:Hello Server,5是对4的响应,代表收到,6是write:Hello Client,7同理5,8-11对应close,和上文所要展示的流程基本相同。
这里写图片描述
上图是查看端口,可见tcp的链接状况(图中pid和上文图中pid不对应的原因是非同一个测试)。

4 好了,上文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是新内核对于多种协议地址结构提出的一个新的通用的存储结构体。

5 关于这些结构体的知识我们就到这里了。现在来讲一讲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/writecloseserver: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);//阻塞ioread/writeclose

6 非阻塞通信,关键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。

7 其他的都不说了,口水都干了,直接上例子代码,然后喝口水,上个厕所,洗个手,坐等下班
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 APItype:       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 就可以使用了

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

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

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

阅读全文
0 0
原创粉丝点击