• c++ 之 socket udp与tcp client server实现


    服务器开发系列



    前言

    socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用打开open –> 读写write/read –> 关闭close模式来操作。Socket就是该模式的一个实现,socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).
    说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。


    一、socket是什么?

    socket(套接字)是一个抽象层,应用程序可以通过它发送或接收数据,可对其进行像对文件一样的打开、读写和关闭等操作。套接字允许应用程序将I/O插入到网络中,并与网络中的其他应用程序进行通信。网络套接字是IP地址与端口的组合

    socket 是一种IPC方法,它允许位于同一主机(计算机)或使用网络连接起来的不同主机上的应用程序之间交换数据。

    套接字描述符
    其实就是一个整数,我们最熟悉的句柄是0、1、2三个,0是标准输入,1是标准输出,2是标准错误输出。0、1、2是整数表示的,对应的FILE *结构的表示就是stdin、stdout、stderr
    在这里插入图片描述
    对于每个程序系统都有一张单独的表。精确地讲,系统为每个运行的进程维护一张单独的文件描述符表。当进程打开一个文件时,系统把一个指向此文件内部数据结构的指针写入文件描述符表,并把该表的索引值返回给调用者 。应用程序只需记住这个描述符,并在以后操作该文件时使用它。操作系统把该描述符作为索引访问进程描述符表,通过指针找到保存该文件所有的信息的数据结构。

    套接字API里有个函数socket,它就是用来创建一个套接字。套接字设计的总体思路是,单个系统调用就可以创建任何套接字,因为套接字是相当笼统的。一旦套接字创建后,应用程序还需要调用其他函数来指定具体细节。
    在这里插入图片描述

    文件描述符:
    在linux系统中打开文件就会获得文件描述符,它是个很小的正整数。每个进程在PCB(Process Control Block)中保存着一份文件描述符表,文件描述符就是这个表的索引,每个表项都有一个指向已打开文件的指针。

    文件指针:
    C语言中使用文件指针做为I/O的句柄。文件指针指向进程用户区中的一个被称为FILE结构的数据结构。FILE结构包括一个缓冲区和一个文件描述符。而文件描述符是文件描述符表的一个索引,因此从某种意义上说文件指针就是句柄的句柄。

    二、socket函数解析

    socket常用函数:
    参数具体含义可以在linux环境下man func

    //tcp and udp
    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)
    
    //only tcp
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen)
    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
    
    int shutdown(int sockfd,int howto); 
    int close(int fd)
    
    //tcp
    ssize_t send(int sockfd, const void *buff, size_t nbytes, int flags);
    ssize_t recv(int sockfd, void *buff, size_t nbytes, int flags);
    
    //udp
    int sendto (socket s , const void * msg, int len, unsigned int flags,const struct sockaddr * to , int tolen );
    int recvfrom(int sockfd,void *buf,int len,unsigned int lags,struct sockaddr *from,int *fromlen);  
    
    //tcp and udp
    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);
    
    //select
    int select(int maxfdp, fd_set* readfds, fd_set* writefds, fd_set* errorfds, struct timeval* timeout);
    
    //epoll
    int epoll_create(int size);
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31

    socket()函数

    int  socket(int protofamily, int type, int protocol);   //返回sockfd
    
    protofamily:即协议域,又称为协议族(family)。常用的协议族有,AF_INET(IPV4)AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,Unix域socket)、AF_ROUTE等等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了要用ipv4地址(32位的)与端口号(16位的)的组合、AF_UNIX决定了要用一个绝对路径名作为地址。
    type:指定socket类型。常用的socket类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。
    protocol:故名思意,就是指定协议。常用的协议有,IPPROTO_TCP、IPPTOTO_UDP、IPPROTO_SCTP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议
    
    • 1
    • 2
    • 3
    • 4
    • 5

    bind()函数

    int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    sockfd:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。
    
    addr:一个const struct sockaddr *指针,指向要绑定给sockfd的协议地址。
    ipv4对应的是:
    struct sockaddr_in {
        sa_family_t    sin_family; /* address family: AF_INET */
        in_port_t      sin_port;   /* port in network byte order */
        struct in_addr sin_addr;   /* internet address */
    };
    struct in_addr {
        uint32_t       s_addr;     /* address in network byte order */
    };
    
    Unix域对应的是: 
    #define UNIX_PATH_MAX    108
    struct sockaddr_un { 
        sa_family_t sun_family;               /* AF_UNIX */ 
        char        sun_path[UNIX_PATH_MAX];  /* pathname */ 
    };
    
    addrlen:对应的是地址的长度。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    listen()、connect()、accept()函数

    int listen(int sockfd, int backlog);
    
    listen函数的第一个参数即为要监听的socket描述字
    第二个参数为相应socket可以排队的最大连接个数。socket()函数创建的socket默认是一个主动类型的,listen函数将socket变为被动类型的,等待客户的连接请求。
    
    int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
    
    connect函数的第一个参数即为客户端的socket描述字
    第二参数为服务器的socket地址
    第三个参数为socket地址的长度。客户端通过调用connect函数来建立与TCP服务器的连接。
    
    int accept(int sockfd,struct sockaddr *addr,socklen_t *addrlen);
    
    sockfd:套接口描述字,该套接口在listen()后监听连接。
    addr:传出参数,返回链接客户端地址信息,含IP地址和端口号
    addrlen:传入传出参数(值-结果),传入sizeof(addr)大小,函数返回时返回真正接收到地址结构体的大小
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    shutdown()、close()函数

    int shutdown(int sockfd, int howto);   
    
    sockfd是需要关闭的socket的描述符。
    how允许为shutdown操作选择以下几种方式:
    SHUT_RD:关闭连接的读端。也就是该套接字不再接受数据,任何当前在套接字接受缓冲区的数据将被丢弃。进程将不能对该套接字发出任何读操作。对TCP套接字该调用之后接受到的任何数据将被确认然后无声的丢弃掉。
    SHUT_WR:关闭连接的写端,进程不能在对此套接字发出写操作
    SHUT_RDWR:相当于调用shutdown两次:首先是以SHUT_RD,然后以SHUT_WR。使用close中止一个连接,但它只是减少描述符的参考数,并不直接关闭连接,只有当描述符的参考数为0时才关闭连接。
    shutdown可直接关闭描述符,不考虑描述符的参考数,可选择中止一个方向的连接。
    
    int close(int sockfd);   
    调用close()函数来释放该socket,从而停止在该socket上的任何数据操作
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    select()函数

    int select(int maxfd,fd_set *rdset,fd_set *wrset,fd_set *exset,struct timeval *timeout);
    
    maxfd:  需要监视的最大的文件描述符的值+1 ;
    rdset:  需要检测的可读文件描述符的集合;
    wrset:  需要检测的可写文件描述符的集合;
    exset:  需要检测的异常文件描述符的集合,不包括错误;
    struct timeval: 用于描述一段时间长度,如果在这个时间内,需要监视的描述符没有事件发生则函数返回,返回值为0
    
    fd_set的对象
    FD_ZERO(fd_set *fdset) //将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
    FD_SET(fd_set *fdset) //用于在文件描述符集合中增加一个新的文件描述符。
    FD_CLR(fd_set *fdset) //用于在文件描述符集合中删除一个文件描述符。
    FD_ISSET(int fd,fd_set *fdset) //用于测试指定的文件描述符是否在该集合中。
    
    struct timeval{
         __time_t tv_sec;        /* Seconds. */
         __suseconds_t tv_usec;  /* Microseconds. */
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    epoll()函数

    int epoll_create(int size)
    
    创建一个epoll的句柄。自从linux2.6.8之后,size参数是被忽略的
    
    int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
    第一个参数是epoll_create()的返回值。
    第二个参数表示动作,用三个宏来表示:
    EPOLL_CTL_ADD:注册新的fd到epfd中;
    EPOLL_CTL_MOD:修改已经注册的fd的监听事件;
    EPOLL_CTL_DEL:从epfd中删除一个fd
    第三个参数是需要监听的fd。
    第四个参数是告诉内核需要监听什么事,struct epoll_event结构如下:
    typedef union epoll_data {
        void *ptr;
        int fd;
        __uint32_t u32;
        __uint64_t u64;
    } epoll_data_t;
    struct epoll_event {
        __uint32_t events; /* Epoll events */
        epoll_data_t data; /* User data variable */
    };
    events可以是以下几个宏的集合:
    EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);
    EPOLLOUT:表示对应的文件描述符可以写;
    EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);
    EPOLLERR:表示对应的文件描述符发生错误;
    EPOLLHUP:表示对应的文件描述符被挂断;
    EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的。
    EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里
    
    int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);
    参数events是分配好的epoll_event结构体数组,epoll将会把发生的事件赋值到events数组中(events不可以是空指针,内核只负责把数据复制到这个events数组中,不会去帮助我们在用户态中分配内存)。
    maxevents告之内核这个events有多大,这个 maxevents的值不能大于创建epoll_create()时的size,
    参数timeout是超时时间(毫秒,0会立即返回,-1将不确定,也有说法说是永久阻塞)。如果函数调用成功,返回对应I/O上已准备好的文件描述符数目,如返回0表示已超时。
    
    Epoll的2种工作方式-水平触发(LT)和边缘触发(ET)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    getsockopt()、setsockopt() 函数

    int getsockopt(int sockfd, int level, int optname, void *optval, socklen_t*optlen);
    sockfd:标识一个套接口的描述字。
    level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
    optname:需设置的选项。
    optval:指针,指向存放选项待设置的新值的缓冲区。
    optlen:optval缓冲区长度。
    
    int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);
    sockfd:标识一个套接口的描述字。
    level:选项定义的层次;支持SOL_SOCKET、IPPROTO_TCP、IPPROTO_IP和IPPROTO_IPV6。
    optname:需设置的选项。
    optval:指针,指向存放选项待设置的新值的缓冲区。
    optlen:optval缓冲区长度。
    
    其中optname:
    SO_DEBUG,打开或关闭调试信息。
    当option_value不等于0时,打开调试信息,否则,关闭调试信息。它实际所做的工作是在sock->sk->sk_flag中置 SOCK_DBG(10)位,或清SOCK_DBG位。
    SO_REUSEADDR,打开或关闭地址复用功能。
    当option_value不等于0时,打开,否则,关闭。它实际所做的工作是置sock->sk->sk_reuse为10。
    SO_DONTROUTE,打开或关闭路由查找功能。
    当option_value不等于0时,打开,否则,关闭。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_LOCALROUTE位。
    SO_BROADCAST,允许或禁止发送广播数据。
    当option_value不等于0时,允许,否则,禁止。它实际所做的工作是在sock->sk->sk_flag中置或清SOCK_BROADCAST位。
    SO_SNDBUF,设置发送缓冲区的大小。
    发送缓冲区的大小是有上下限的,其上限为256 * (sizeof(struct sk_buff) + 256),下限为2048字节。该操作将sock->sk->sk_sndbuf设置为val * 2,之所以要乘以2,是防止大数据量的发送,突然导致缓冲区溢出。最后,该操作完成后,因为对发送缓冲的大小 作了改变,要检查sleep队列,如果有进程正在等待写,将它们唤醒。
    SO_RCVBUF,设置接收缓冲区的大小。
    接收缓冲区大小的上下限分别是:256 * (sizeof(struct sk_buff) + 256)256字节。该操作将sock->sk->sk_rcvbuf设置为val * 2。
    SO_KEEPALIVE,套接字保活。
    如果协议是TCP,并且当前的套接字状态不是侦听(listen)或关闭(close),那么,当option_value不是零时,启用TCP保活定时 器,否则关闭保活定时器。对于所有协议,该操
    作都会根据option_value置或清 sock->sk->sk_flag中的 SOCK_KEEPOPEN位。
    SO_OOBINLINE,紧急数据放入普通数据流。
    该操作根据option_value的值置或清sock->sk->sk_flag中的SOCK_URGINLINE位。
    SO_NO_CHECK,打开或关闭校验和。
    该操作根据option_value的值,设置sock->sk->sk_no_check。
    SO_PRIORITY,设置在套接字发送的所有包的协议定义优先权。Linux通过这一值来排列网络队列。
    这个值在06之间(包括06),由option_value指定。赋给sock->sk->sk_priority。
    SO_LINGER,如果选择此选项, close或 shutdown将等到所有套接字里排队的消息成功发送或到达延迟时间后>才会返回. 否则, 调用将立即返回。
    该选项的参数(option_value)是一个linger结构:
    struct linger {
        int   l_onoff;   
        int   l_linger;  
    };
    如果linger.l_onoff值为0(关闭),则清 sock->sk->sk_flag中的SOCK_LINGER位;否则,置该位,并赋sk->sk_lingertime值为 linger.l_linger。
    
    SO_PASSCRED,允许或禁止SCM_CREDENTIALS 控制消息的接收。
    该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_PASSCRED位。
    
    SO_TIMESTAMP,打开或关闭数据报中的时间戳接收。
    该选项根据option_value的值,清或置sock->sk->sk_flag中的SOCK_RCVTSTAMP位,如果打开,则还需设sock->sk->sk_flag中的SOCK_TIMESTAMP位,同时,将全局变量
    netstamp_needed加1。
    
    SO_RCVLOWAT,设置接收数据前的缓冲区内的最小字节数。
    在Linux中,缓冲区内的最小字节数是固定的,为1。即将sock->sk->sk_rcvlowat固定赋值为1。
    
    SO_RCVTIMEO,设置接收超时时间。
    该选项最终将接收超时时间赋给sock->sk->sk_rcvtimeo。
    
    SO_SNDTIMEO,设置发送超时时间。
    该选项最终将发送超时时间赋给sock->sk->sk_sndtimeo。
    
    SO_BINDTODEVICE,将套接字绑定到一个特定的设备上。
    该选项最终将设备赋给sock->sk->sk_bound_dev_if。
    
    SO_ATTACH_FILTER和SO_DETACH_FILTER。
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64

    三、UDP Client 与Server

    在这里插入图片描述

    1.client

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MAX_READ_LINE 1024
    
    int udpclient(int port) {
        int ret = 0;
        const char *server_ip_addr = "127.0.0.1";
        int server_ip_port = port;
     
        //要发送给server的数据
        const char *send_message = "quit";
    
        //用于存储接收到的数据
        char buff[MAX_READ_LINE] = {0};
    
        int socket_fd = -1;
        int recv_len = -1;
        do{
            //创建client端的socket套接口
            socket_fd = socket(AF_INET, SOCK_DGRAM, 0);
            if (socket_fd < 0) {
                fprintf(stderr, "socket error %s errno: %d\n", strerror(errno), errno);
                ret = -1;
                break;
            }
        
            //初始化sockaddr_in结构体
            struct sockaddr_in u_sockaddr;
            memset(&u_sockaddr, 0, sizeof(struct sockaddr_in));
            u_sockaddr.sin_family = AF_INET;
            u_sockaddr.sin_port = htons(server_ip_port);
            inet_pton(AF_INET, server_ip_addr, &u_sockaddr.sin_addr);
    
            socklen_t server_len=sizeof(u_sockaddr);
    
            //向server发送数据
            if(sendto(socket_fd, send_message, strlen(send_message), 0, (struct sockaddr *) &u_sockaddr, sizeof(struct sockaddr_in)) < 0){
                fprintf(stderr, "send message error: %s errno : %d", strerror(errno), errno);
                ret = -1;
                break;
            }
            fprintf(stdout, "client----> udp client send msg to server: %s\n", send_message); 
    
            //取server发送数据
            recv_len = recvfrom(socket_fd, buff,MAX_READ_LINE, 0, (struct sockaddr *)&u_sockaddr, &server_len);
    
            buff[recv_len] = '\0';
            fprintf(stdout, "client----> udp client recv msg from server: %s\n", buff); 
        }while(false);
    
     
        //关闭套接字
        if (socket_fd >= 0){
            close(socket_fd);
        }
        socket_fd = -1;
     
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66

    2.server

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #define MAX_READ_LINE 1024
    
    int udpserver(int port) {
        
        int ret = 0;
        int recv_len = -1;
        int server_ip_port = port;
     
        //用于存储接收到的数据
        char buff[MAX_READ_LINE] = {0};
     
        //初始化sockaddr_in结构体
        struct sockaddr_in u_sockaddr;
        memset(&u_sockaddr, 0, sizeof(u_sockaddr));      //bzero(&u_sockaddr,sizeof(u_sockaddr));
        u_sockaddr.sin_family = AF_INET;
        u_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        u_sockaddr.sin_port = htons(server_ip_port);
    
        socklen_t socklen = sizeof(u_sockaddr);
        //创建server端的socket套接字
        int server_fd = -1;
        do{
            server_fd = socket(AF_INET, SOCK_DGRAM, 0);
            if (server_fd < 0) {
                fprintf(stderr, "socket error %s errno: %d\n", strerror(errno), errno);
                ret = -1;
                break;
            }
        
            //绑定
            ret = bind(server_fd,(struct sockaddr *) &u_sockaddr,sizeof(u_sockaddr));
            if (ret < 0) {
                fprintf(stderr, "bind socket error %s errno: %d\n", strerror(errno), errno);
                ret = -1;
                break;
            }
        
            //接收来自客户端的链接请求
            for(;;) {
                //读取客户端数据到buff中
                recv_len = recvfrom(server_fd, buff, MAX_READ_LINE, 0, (struct sockaddr *)&u_sockaddr, &socklen);
                if (recv_len < 0) {
                    fprintf(stderr, "recv error %s errno: %d\n", strerror(errno), errno);
                    continue;
                }
        
                buff[recv_len] = '\0';
                //将接收到的数据标准输出
                fprintf(stdout, "server----> udp server recv msg from client: %s\n", buff);
    
                //将数据发送到客户端
                sendto(server_fd, buff,MAX_READ_LINE, 0, (struct sockaddr *)&u_sockaddr, socklen);
                fprintf(stdout, "server----> udp server send msg to client: %s\n", buff);
    
                //解决tcp服务不退出的问题
                if(strcmp(buff,"quit") == 0){
                    fprintf(stdout, "server----> udp server will quit\n");
                    break;
                }
            }
        }while(false);
     
        //关闭套接字
        if (server_fd >= 0){
            close(server_fd);
        }
        server_fd = -1;
     
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79

    四、TCP Client 与Server

    在这里插入图片描述

    1.client

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    #define MAX_READ_LINE 1024
    int tcpclient(int port) {
        int ret = 0;
        const char *server_ip_addr = "127.0.0.1";
        int server_ip_port = port;
     
        //要发送给server的数据
        const char *send_message = "quit";
    
        //用于存储接收到的数据
        char buff[MAX_READ_LINE] = {0};
    
        int socket_fd = -1;
        int recv_len = -1;
        do{
            //创建client端的socket套接口
            socket_fd = socket(AF_INET, SOCK_STREAM, 0);
            if (socket_fd < 0) {
                fprintf(stderr, "socket error %s errno: %d\n", strerror(errno), errno);
                ret = -1;
                break;
            }
        
            //初始化sockaddr_in结构体
            struct sockaddr_in t_sockaddr;
            memset(&t_sockaddr, 0, sizeof(struct sockaddr_in));
            t_sockaddr.sin_family = AF_INET;
            t_sockaddr.sin_port = htons(server_ip_port);
            inet_pton(AF_INET, server_ip_addr, &t_sockaddr.sin_addr);
        
            //连接
            if((connect(socket_fd, (struct sockaddr*)&t_sockaddr, sizeof(struct sockaddr))) < 0 ) {
                fprintf(stderr, "connect error %s errno: %d\n", strerror(errno), errno);
                ret = -1;
                break;
            }
        
            //向server发送数据
            if((send(socket_fd, send_message, strlen(send_message), 0)) < 0) {
                fprintf(stderr, "send message error: %s errno : %d", strerror(errno), errno);
                ret = -1;
                break;
            }
            fprintf(stdout, "client----> tcp client send msg to server: %s\n", send_message); 
    
            //取server发送数据
            recv_len = recv(socket_fd,buff,MAX_READ_LINE,0);
            buff[recv_len] = '\0';
            fprintf(stdout, "client----> tcp client recv msg from server: %s\n", buff); 
        }while(false);
     
        //关闭套接字
        if (socket_fd >= 0){
            close(socket_fd);
        }
        socket_fd = -1;
     
        return ret;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68

    2.server

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
     
    #define MAX_READ_LINE 1024
     
    int tcpserver(int port) {
        int recv_len = -1;
        int conn_fd = -1;
        int ret = 0;
     
        int server_ip_port = port;
     
        //用于存储接收到的数据
        char buff[MAX_READ_LINE] = {0};
     
        //初始化sockaddr_in结构体
        struct sockaddr_in t_sockaddr;
        memset(&t_sockaddr, 0, sizeof(t_sockaddr));
        t_sockaddr.sin_family = AF_INET;
        t_sockaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        t_sockaddr.sin_port = htons(server_ip_port);
     
        //创建server端的socket套接字
        int listen_fd = -1;
        do{
            listen_fd = socket(AF_INET, SOCK_STREAM, 0);
            if (listen_fd < 0) {
                fprintf(stderr, "socket error %s errno: %d\n", strerror(errno), errno);
                ret = -1;
                break;
            }
        
            //绑定
            ret = bind(listen_fd,(struct sockaddr *) &t_sockaddr,sizeof(t_sockaddr));
            if (ret < 0) {
                fprintf(stderr, "bind socket error %s errno: %d\n", strerror(errno), errno);
                ret = -1;
                break;
            }
        
            //监听
            ret = listen(listen_fd, 1024);
            if (ret < 0) {
                fprintf(stderr, "listen error %s errno: %d\n", strerror(errno), errno);
                ret = -1;            
                break;
            }
        
            //接收来自客户端的链接请求
            for(;;) {
                conn_fd = accept(listen_fd, (struct sockaddr*)NULL, NULL);
                if(conn_fd < 0) {
                    fprintf(stderr, "accpet socket error: %s errno :%d\n", strerror(errno), errno);
                    continue;
                }
        
                //读取数据到buff中
                recv_len = recv(conn_fd, buff, MAX_READ_LINE, 0);
                if (recv_len < 0) {
                    fprintf(stderr, "recv error %s errno: %d\n", strerror(errno), errno);
                    continue;
                }
        
                buff[recv_len] = '\0';
                //将接收到的数据标准输出
                fprintf(stdout, "server----> tcp server recv msg from client: %s\n", buff);
    
                //将接收到的数据发送到客户端
                send(conn_fd,(void*)buff,MAX_READ_LINE,0);
                fprintf(stdout, "server----> tcp server send msg to client: %s\n", buff);
    
                close(conn_fd);
                conn_fd = -1;
    
                //解决tcp服务不退出的问题
                if(strcmp(buff,"quit") == 0){
                    fprintf(stdout, "server----> tcp server will quit\n");
                    break;
                }
            }
        }while(false);
        buff[0] = '\0';
        //关闭套接字
        if (listen_fd >= 0){
            close(listen_fd);
        }
        listen_fd = -1;
     
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97

    总结

    通过上面的例子,应该对普通socket模型有了基本的理解,不过对与tcp udp编程来说,就是个皮毛。

  • 相关阅读:
    基于SkyEye运行Qt:著名应用程序开发框架
    Arch/ Manjaro 个人常用命令行
    20221127-1Spring_day01(资料来自黑马程序)
    数据结构-顺序表
    jquery使用infinitescroll无线滚动+自定义翻页
    打通“”任督二脉“”的大模型:基础大模型的进展意味着什么?变革的底层逻辑是什么?
    mongodb
    阻燃窗帘的清洁方法和保养方法-江南爱窗帘十大品牌
    智慧城市运营中心建设方案(SCOC)智慧城市的心脏
    2023-09-22力扣每日一题
  • 原文地址:https://blog.csdn.net/weixin_44834554/article/details/127809950