zoukankan      html  css  js  c++  java
  • 实现基于TCP的服务端/客户端

    服务端套接字创建过程

    第一步:调用socket函数创建套接字

    //成功时返回文件表述符,失败时返回-1
    int socket(int __domain, int __type, int __protocol)
    
    • domain:套接字使用的协议族(Protocol Family)信息;
    • type:套接字数据传输类型信息;
    • protocol:计算机间通信中使用的协议信息;

    协议族(Protocol Family)

     Name         Purpose                                    Man page
     AF_UNIX      Local communication                        unix(7)
     AF_LOCAL     Synonym for AF_UNIX
     AF_INET      IPv4 Internet protocols                    ip(7)
     AF_IPX       IPX - Novell protocols
     AF_INET6     IPv6 Internet protocols                    ipv6(7)
     AF_PACKET    Low-level packet interface                 packet(7)
    

    套接字类型(Type):是指套接字的传输方式

    • 面向连接的套接字(SOCK_STREAM)
      • 传输过程中数据不会消失
      • 按序传输数据
      • 传输的数据不存在数据边界(Boundary):调用了三次write传递了100字节,接受者仅一次read接收了全部
    • 面向消息的套接字(SOCK_DGRAM)
      • 强调快速传输而非传输顺序
      • 传输的数据可能丢失也可能损毁
      • 传输的数据有数据边界
      • 限制每次传输的数据大小

    协议的最终选择

    /* Standard well-defined IP protocols.  */
    enum
      {
        IPPROTO_IP = 0,	   /* Dummy protocol for TCP.  */
    #define IPPROTO_IP		IPPROTO_IP
        IPPROTO_ICMP = 1,	   /* Internet Control Message Protocol.  */
    #define IPPROTO_ICMP		IPPROTO_ICMP
        IPPROTO_IGMP = 2,	   /* Internet Group Management Protocol. */
    #define IPPROTO_IGMP		IPPROTO_IGMP
        IPPROTO_IPIP = 4,	   /* IPIP tunnels (older KA9Q tunnels use 94).  */
    #define IPPROTO_IPIP		IPPROTO_IPIP
        IPPROTO_TCP = 6,	   /* Transmission Control Protocol.  */
    #define IPPROTO_TCP		IPPROTO_TCP
        IPPROTO_EGP = 8,	   /* Exterior Gateway Protocol.  */
    #define IPPROTO_EGP		IPPROTO_EGP
        IPPROTO_PUP = 12,	   /* PUP protocol.  */
    #define IPPROTO_PUP		IPPROTO_PUP
        IPPROTO_UDP = 17,	   /* User Datagram Protocol.  */
    #define IPPROTO_UDP		IPPROTO_UDP
        IPPROTO_IDP = 22,	   /* XNS IDP protocol.  */
    #define IPPROTO_IDP		IPPROTO_IDP
        IPPROTO_TP = 29,	   /* SO Transport Protocol Class 4.  */
    #define IPPROTO_TP		IPPROTO_TP
        IPPROTO_DCCP = 33,	   /* Datagram Congestion Control Protocol.  */
    #define IPPROTO_DCCP		IPPROTO_DCCP
        IPPROTO_IPV6 = 41,     /* IPv6 header.  */
    #define IPPROTO_IPV6		IPPROTO_IPV6
        IPPROTO_RSVP = 46,	   /* Reservation Protocol.  */
    #define IPPROTO_RSVP		IPPROTO_RSVP
        IPPROTO_GRE = 47,	   /* General Routing Encapsulation.  */
    #define IPPROTO_GRE		IPPROTO_GRE
        IPPROTO_ESP = 50,      /* encapsulating security payload.  */
    #define IPPROTO_ESP		IPPROTO_ESP
        IPPROTO_AH = 51,       /* authentication header.  */
    #define IPPROTO_AH		IPPROTO_AH
        IPPROTO_MTP = 92,	   /* Multicast Transport Protocol.  */
    #define IPPROTO_MTP		IPPROTO_MTP
        IPPROTO_BEETPH = 94,   /* IP option pseudo header for BEET.  */
    #define IPPROTO_BEETPH		IPPROTO_BEETPH
        IPPROTO_ENCAP = 98,	   /* Encapsulation Header.  */
    #define IPPROTO_ENCAP		IPPROTO_ENCAP
        IPPROTO_PIM = 103,	   /* Protocol Independent Multicast.  */
    #define IPPROTO_PIM		IPPROTO_PIM
        IPPROTO_COMP = 108,	   /* Compression Header Protocol.  */
    #define IPPROTO_COMP		IPPROTO_COMP
        IPPROTO_SCTP = 132,	   /* Stream Control Transmission Protocol.  */
    #define IPPROTO_SCTP		IPPROTO_SCTP
        IPPROTO_UDPLITE = 136, /* UDP-Lite protocol.  */
    #define IPPROTO_UDPLITE		IPPROTO_UDPLITE
        IPPROTO_MPLS = 137,    /* MPLS in IP.  */
    #define IPPROTO_MPLS		IPPROTO_MPLS
        IPPROTO_RAW = 255,	   /* Raw IP packets.  */
    #define IPPROTO_RAW		IPPROTO_RAW
        IPPROTO_MAX
      };
    

    第二步:调用bind函数分配IP地址和端口号

    //成功时返回0,失败时返回-1
    int bind(int __fd, const struct sockaddr *__addr, socklen_t __len)
    
    • __fd:要分配地址信息(IP地址和端口号)的套接字文件表述符。
    • __addr:存有地址信息的结构体变量地址值
    • __len:第二个结构体变量的长度

    第三步:调用listen函数转为可接收请求状态

    //成功时返回0,失败时返回-1
    int listen(int __fd, int __n)
    
    • __fd:希望进入等待连接请求状态的套接字文件描述符,传递的描述符套接字参数成为服务器端套接字(监听套接字)。
    • __n:连接请求等待队列的长度。

    第四步:调用accept函数受理连接请求

    //成功时返回0,失败时返回-1
    int accept(int __fd, struct sockaddr *__restrict__ __addr, socklen_t *__restrict__ __addr_len)
    
    • __fd:服务器套接字的文件描述符。
    • __addr:保存发起连接请求的客户端地址信息地变量地址值,调用函数后向传递来的地址变量参数填充客户端的地址信息。
    • __addr_len:第二个参数__addr结构体的长度,但是存有长度的变量地址。函数调用完后,该变量即被填入客户端地址长度。

    客户端创建套接字过程

    第一步:调用socket函数创建套接字

    //成功时返回文件表述符,失败时返回-1
    int socket(int __domain, int __type, int __protocol)
    

    第二步:调用socket函数向服务器端发送连接请求

    //成功时返回0,失败时返回-1
    int connect(int __fd, const struct sockaddr *__addr, socklen_t __len)
    
    • __fd:客户端套接字文件表述符。
    • __addr:保存目标服务器端地址信息的变量地址值。
    • __len:以字节为单位传递已传递给第二个结构体参数__addr地址变量长度。

    客户端调用connect函数后服务端接收连接请求(记录到等待队列)或发生断网等异常情况而中断连接请求才会返回(完成函数调用)。客户端的IP地址和端口号在调用connect函数时由内核自动分配,无需调用标记的bind函数进行分配。
    在这里插入图片描述
    总体流程就是:服务器端创建套接字后联系调用bind、listen函数进入等待状态,客户端通过调用connect函数发起连接请求,需要注意的是,客户端只能等到服务器端调用listen函数后才能调用connect函数。同时要清楚,客户端调用connect前,服务器端可能先调用了accept函数。当然,此时服务器端在调用accept函数时进入了阻塞状态,直到客户端调用connect函数为止。

    TCP套接字中的I/O缓冲

    在这里插入图片描述

    • I/O缓冲在每个TCP套接字中单独存在;
    • I/O缓冲在创建套接字时自动生成;
    • 即使关闭套接字也会继续传递输出缓冲中遗留的数据;
    • 关闭套接字将丢失输入缓冲中的数据;

    编写

    echosrv.c

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main(int argc, char** argv) {
        // 1. 创建套接字
        int listenfd;
                              //协议族    套接字类型    协议类型
        if ((listenfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
           perror("socket");
        }
    
        // 2. 分配套接字地址
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof servaddr);
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(6666);
        servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        // servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
        // inet_aton("127.0.0.1", &servaddr.sin_addr);
    
        int on = 1;
        // 确保time_wait状态下同一端口仍可使用
        if (setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof on) < 0)
        {
            perror("setsockopt");
        }
    
        // 3. 绑定套接字地址
        if (bind(listenfd, (struct sockaddr*) &servaddr, sizeof servaddr) < 0) {
            perror("bind");
        }
        // 4. 等待连接请求状态
        if (listen(listenfd, SOMAXCONN) < 0) {
            perror("listen");
        }
        // 5. 允许连接
        struct sockaddr_in peeraddr;
        socklen_t peerlen = sizeof peeraddr;
        int connfd;
        if ((connfd = accept(listenfd, (struct sockaddr *) &peeraddr, &peerlen)) < 0) {
            perror("accept");
        }
    
        printf("id = %s, ", inet_ntoa(peeraddr.sin_addr));
        printf("port = %d
    ", ntohs(peeraddr.sin_port));
    
        // 6. 数据交换
        char recvbuf[1024];
        while (1)
        {
            memset(recvbuf, 0, sizeof recvbuf);
            int ret = read(connfd, recvbuf, sizeof recvbuf);
            if (ret == 0)
            {
                printf("client close
    ");
                break;
            } else if (ret == -1)
            {
                perror("read");
            }
            fputs(recvbuf, stdout);
            write(connfd, recvbuf, ret);
        }
    
        // 7. 断开连接
        close(connfd);
        close(listenfd);
        return 0;
    }
    

    echocli.c

    #include <stdio.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/types.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    
    int main()
    {
        // 1. 创建套接字
        int sockfd;
        if ((sockfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) < 0) {
            perror("socket");
        }
    
        // 2. 分配套接字地址
        struct sockaddr_in servaddr;
        memset(&servaddr, 0, sizeof servaddr);
        servaddr.sin_family = AF_INET;
        servaddr.sin_port = htons(6666);
        // servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
         servaddr.sin_addr.s_addr = inet_addr("127.0.0.1");
        // inet_aton("127.0.0.1", &servaddr.sin_addr);
    
        // 3. 请求链接
        if (connect(sockfd, (struct sockaddr *) &servaddr, sizeof servaddr) < 0) {
            perror("connect");
        }
    
        // 4. 数据交换
        char recvbuf[1024] = {0};
        char sendbuf[1024] = {0};
        while (fgets(sendbuf, sizeof sendbuf, stdin) != NULL)   // 键盘输入获取
        {
    //        memset(recvbuf, 0, sizeof recvbuf);
    //        memset(sendbuf, 0, sizeof sendbuf);
            write(sockfd, sendbuf, sizeof sendbuf); // 写入服务器
            int ret = read(sockfd, recvbuf, sizeof recvbuf);    // 服务器读取
            if (ret == 0)
            {
                printf("server close
    ");
                break;
            } else if (ret == -1)
            {
                perror("read");
            }
            fputs(recvbuf, stdout); // 服务器返回数据输出
    
            // 清空
            memset(recvbuf, 0, sizeof recvbuf);
            memset(sendbuf, 0, sizeof sendbuf);
        }
    
        // 5. 断开连接
        close(sockfd);
    
        return 0;
    }
    
  • 相关阅读:
    day6_redis模块和pipeline
    day6_hashlib模块
    18 MySQL数据导入导出方法与工具介绍之二
    【Vijos1264】神秘的咒语
    【Vijos1180】选课
    【vijos1234】口袋的天空
    【vijos1790】拓扑编号
    【WC2008】【BZOJ1271】秦腾与教学评估(二分,前缀和,奇偶性乱搞)
    【Baltic2003】【BZOJ1370】Gang团伙(并查集,拆点)
    【基础】二分算法学习笔记
  • 原文地址:https://www.cnblogs.com/chengmf/p/15102050.html
Copyright © 2011-2022 走看看