• 服务器搭建(TCP套接字)-基础版(服务端)


    在这里插入图片描述

    一、socket

    在这里插入图片描述

    1.1、vim man查看socket

    :!man socket
    
    • 1

    在这里插入图片描述
    在这里插入图片描述

    1.2、 依赖的头文件

     #include      
     #include 
    
    • 1
    • 2

    1.3、原型

    int socket(int domain, int type, int protocol);
    
    • 1
    domain说明
    AF_INETIPV4协议
    AF_INET6IPV6协议
    AF_LOCALUnix域协议
    type说明
    SOCK_STREAM字节流套接字(TCP/SCTP)
    SOCK_DGRAM数据报套接字(UDP)
    SOCK_RAM原始套接字 (IPv4/IPv6)
    SOCK_SEQPACKET有序分组套接字(SCTP)
    protocol说明
    IPPROTO_CPTCP传输协议
    IPPROTO_UDPUDP传输协议
    IPPROTO_SCTPSCTP传输协议

    socket函数在成功时会返回一个小的非负整数值,它与文件描述符类似,就称它为套接字描述符。只需要指定协议族(IPV4、IPV6或Unix)和套接字类型即可

    • 代码实现
    socket_fd=socket(AF_INET,SOCK_STREAM,0);
    
    • 1

    二、bind

    在这里插入图片描述

    2.1、 原型

    int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
    
    • 1
    • sockfd
      该参数为成功创建socket返回的套接字描述符

    • addr
      该参数为通用的套接字地址结构,套接字地址结构总是以引用形式来传递
      IPv4套接字地址结构
      IPv4套接字地址结构通常也称为“网际套接字地址结构”,它以sockaddr_in命名,定义在头文件中。

    struct sockaddr_in {
        short sin_family;        // 地址家族,一般为AF_INET
        unsigned short sin_port; // 端口号
        struct in_addr sin_addr; // IPv4地址
        char sin_zero[8];        // 填充字节,通常为0
    };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    sockaddr_in结构体定义了套接字的IPv4地址信息。它具有以下字段:

    sin_family:指定地址家族,通常为AF_INET表示IPv4地址族。
    sin_port:表示端口号,使用无符号短整数(16位)表示,需要以网络字节顺序(大端序)进行表示。
    sin_addr:struct in_addr类型的结构体,用于表示IPv4地址。
    sin_zero:用于填充字段,通常为0,以确保结构体的大小与sockaddr一致。

    struct in_addr {
        unsigned long s_addr; // IPv4地址,以网络字节顺序(大端序)表示
    };
    
    • 1
    • 2
    • 3
    #define PORT 8596
    struct sockaddr_in local_addr;
    
     //set socket address
      local_addr.sin_family=AF_INET;
      local_addr.sin_port=htons(PORT);
      local_addr.sin_addr.s_addr=INADDR_ANY;
      bzero(&(local_addr.sin_zero),8);
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    htons:
    头文件

    #include 
    
    • 1

    原型

    uint32_t htonl(uint32_t hostlong);
    uint16_t htons(uint16_t hostshort);
    uint32_t ntohl(uint32_t netlong);
    uint16_t ntohs(uint16_t netshort);
    
    • 1
    • 2
    • 3
    • 4

    在这些函数的名字当中,h代表host,n代表network,s代表short,l代表long。

    • addrlen
      socklen_t 是一个在网络编程中使用的类型,用于表示套接字地址结构的长度

    2.2、代码

    #define PORT 8596
    struct sockaddr_in local_addr;
    //set socket address
      local_addr.sin_family=AF_INET;
      local_addr.sin_port=htons(PORT);
      local_addr.sin_addr.s_addr=INADDR_ANY;
      bzero(&(local_addr.sin_zero),8);
     //bind socket
     ret=bind(socket_fd, (struct sockaddr *)&local_addr,sizeof(struct sockaddr_in));
     if(ret == -1){
        perror("bind socket error");
        exit(1);
     }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    三、listen

    在这里插入图片描述

    3.1、原型

    int listen(int sockfd, int backlog);
    
    • 1
    • sockfd
      该参数为成功创建socket返回的套接字描述符
    • backlog
        在网络编程中使用的参数,用于指定监听套接字(listening socket)上挂起连接请求的队列的最大长度。
        在 TCP/IP 网络编程中,当服务器应用程序使用 listen 函数将套接字设置为监听模式时,内核会创建一个连接队列来存储客户端发起的连接请求。
        backlog 参数用于指定这个连接队列的最大长度,即可以挂起等待被接受的连接请求数量。具体来说,当有新的连接请求到达监听套接字时,如果连接队列未满,内核会将该连接请求添加到队列中,并发送一个 SYN+ACK 响应给客户端。如果连接队列已满,则客户端的连接请求可能会被忽略或拒绝。
        只影响处于未接受状态的连接请求,在已经接受的连接请求上没有影响。一旦服务器应用程序调用 accept 函数接受了一个连接请求,该连接就会从队列中移除。

    3.2、代码

    ret=listen(socket_fd, backlog);
    if(ret ==-1){
       perror("listen error");
       exit(1);
      }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    四、accept

    在这里插入图片描述

    4.1、原型

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

      accept 是一个在网络编程中使用的函数,用于接受客户端的连接请求并创建与客户端连接的套接字。
      accept 函数通常在服务器端使用,它从监听套接字(listening socket)的连接队列中取出一个连接请求,并创建一个新的套接字来与客户端进行通信。这个新创建的套接字可以用于与客户端进行数据交换。

    入参:

    • sockfd:整型参数,表示监听套接字的文件描述符
    • addr:指向 struct sockaddr 类型的指针,用于存储连接的客户端地址信息。可以传入 NULL,表示不获取客户端地址信息。
    • addrlen:指向 socklen_t 类型的指针,用于传入客户端地址结构的长度,并在函数调用后被修改为实际的客户端地址结构长度。

    返回值:

    • 如果成功接受连接请求,accept 返回一个新的套接字的文件描述符,该套接字用于与客户端进行通信。
    • 如果发生错误,accept 返回 -1,并设置相应的错误码,可以通过 errno 全局变量获取具体的错误信息。

    4.2、代码

    struct sockaddr_in remote_addr;
    int accept_fd=-1;
    
    socklen_t addrlen = sizeof(remote_addr);
    accept_fd=accept(socket_fd,( struct sockaddr *)&remote_addr, &addrlen);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    五、recv

    在这里插入图片描述

    5.1、原型

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

    recv 是一个在网络编程中使用的函数,用于接收数据从已连接的套接字或接收数据报套接字中。

    需要注意的是,recv 函数是一个阻塞调用,即在没有数据到达之前,它会一直等待。如果需要非阻塞的操作,可以使用非阻塞 I/O 或多线程/多进程的方式处理接收操作。

    入参:

    • sockfd:整型参数,表示要接收数据的套接字的文件描述符。
    • buf:指向接收数据缓冲区的指针,用于存储接收到的数据。
    • len:无符号整型参数,表示接收数据缓冲区的长度。
    • flags:整型参数,用于指定接收操作的可选标志。常见的标志包括 0(无特殊标志)、MSG_DONTWAIT(非阻塞模式接收)和 MSG_WAITALL(阻塞模式接收,直到接收到指定长度的数据)等。

    返回值:

    • 如果成功接收数据,recv 返回接收到的字节数。
    • 如果连接关闭(对于流套接字)或接收超时(对于数据报套接字),recv 返回 0。
    • 如果发生错误,recv 返回 -1,并设置相应的错误码,可以通过 errno 全局变量获取具体的错误信息。

    5.2、代码

    #define MESSAGE_SIZE 1024
    int accept_fd=-1;
    char in_buf[MESSAGE_SIZE]={0,};
    
    memset(in_buf,0,MESSAGE_SIZE);
    //read data
    ret =recv(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
    if(ret ==0){
        break;
    }
    printf("receive data:%s\n",in_buf);
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    六、send

    在这里插入图片描述

    6.1、原型

    ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    
    • 1

    send 是一个在网络编程中使用的函数,用于发送数据到已连接的套接字或发送数据报套接字中。

    入参:

    • sockfd:整型参数,表示要发送数据的套接字的文件描述符。
    • buf:指向要发送数据的缓冲区的指针。
    • len:无符号整型参数,表示要发送数据的长度。
    • flags:整型参数,用于指定发送操作的可选标志。常见的标志包括 0(无特殊标志)和 MSG_DONTWAIT(非阻塞模式发送)等。

    返回值

    • 如果成功发送数据,send 返回发送的字节数。
    • 如果连接关闭(对于流套接字)或发送超时(对于数据报套接字),send 返回 0。
    • 如果发生错误,send 返回 -1,并设置相应的错误码,可以通过 errno 全局变量获取具体的错误信息。

    6.2、代码

    #define MESSAGE_SIZE 1024
    int accept_fd=-1;
    char in_buf[MESSAGE_SIZE]={0,};
    
    send(accept_fd, (void *)in_buf, MESSAGE_SIZE, 0);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    七、完整代码

    #include 
    //socket
    #include 
    #include 
    //close
    #include 
    //exit
    #include 
    //perror
    #include 
    //memset
    #include 
    //htons
    #include 
    
    #define PORT 8596
    #define MESSAGE_SIZE 1024
    
    int main(){
    
      int ret=-1;
      int socket_fd=-1;
      int accept_fd=-1;
      int backlog=10;
      int flag=1;
    
      char in_buf[MESSAGE_SIZE]={0,};
      struct sockaddr_in local_addr,remote_addr;
    
      //create socket
      socket_fd=socket(AF_INET,SOCK_STREAM,0);
      if(socket_fd == -1){
        perror("create socket error");
        exit(1);
      }
     //set option of socket
      ret = setsockopt(socket_fd, SOL_SOCKET, SO_REUSEADDR, &flag, sizeof(flag));
      if ( ret == -1 ){
        perror("setsockopt error");
      }
     //set socket address
      local_addr.sin_family=AF_INET;
      local_addr.sin_port=htons(PORT);
      local_addr.sin_addr.s_addr=INADDR_ANY;
      bzero(&(local_addr.sin_zero),8);
     //bind socket
     ret=bind(socket_fd, (struct sockaddr *)&local_addr,sizeof(struct sockaddr_in));
     if(ret == -1){
        perror("bind socket error");
        exit(1);
     }
    
      ret=listen(socket_fd, backlog);
      if(ret ==-1){
       perror("listen error");
       exit(1);
     }
      //loop to accept client
      for(;;){
       socklen_t addrlen = sizeof(remote_addr);
       accept_fd=accept(socket_fd,( struct sockaddr *)&remote_addr, &addrlen);
       for(;;){
         memset(in_buf,0,MESSAGE_SIZE);
         //read data
         ret =recv(accept_fd, (void*)in_buf, MESSAGE_SIZE, 0);
         if(ret ==0){
            break;
         }
         printf("receive data:%s\n",in_buf);
         send(accept_fd, (void *)in_buf, MESSAGE_SIZE, 0);
       }
       printf("close client connection......");
       close(accept_fd);
      }
      printf("quit server....");
      close(socket_fd);
      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

    八、编译

    gcc baseServer.cpp -o server
    
    • 1

    在这里插入图片描述
    gcc 默认不会自动链接 C++ 标准库,因此需要手动添加链接选项 -lstdc++ 来告知编译器链接 C++ 标准库。

    gcc baseServer.cpp -o server -lstdc++
    
    • 1

    或者直接使用g++编译。

    8.1、gcc和g++的区别

    gcc 和 g++ 在功能上有一些区别,主要体现在它们的默认行为和对源代码的处理方式上。

    1、默认语言:gcc 默认将源代码视为 C 语言源代码进行处理,而 g++ 默认将源代码视为 C++ 语言源代码进行处理。这意味着如果您使用 gcc 编译 C++ 代码,可能会导致一些 C++ 特定的功能和语法无法正常工作。

    2、链接库:g++ 在链接时会自动包含 C++ 标准库(例如 libstdc++),而 gcc 则不会。这是因为 C++ 标准库包含了许多与 C++ 相关的功能和数据结构,而 C 标准库(例如 libc)则只包含了与 C 相关的功能。

    3、标准支持:g++ 支持更多的 C++ 标准,例如 C++11、C++14、C++17 等。而 gcc 的 C 标准支持(例如 C89、C99)更全面,但对于最新的 C++ 标准可能支持较少。

    4、编译选项:gcc 和 g++ 有一些针对特定语言的编译选项。例如,g++ 具有额外的选项来支持 C++ 异常处理、RTTI(运行时类型信息)等特性。

    虽然 gcc 和 g++ 在默认行为和一些功能上有所区别,但它们都是 GNU 编译器套件的一部分,共享许多相同的代码和基础设施。实际上,g++ 实际上是一个对 gcc 进行了配置和扩展的别名。

    在大多数情况下,如果您要编译 C 代码,可以使用 gcc。如果您要编译 C++ 代码,可以使用 g++,这样可以确保默认行为和所需的语言支持正确。 然而,您也可以使用 gcc 编译 C++ 代码,或者使用 g++ 编译 C 代码,只需根据需要适当调整编译选项和参数。

  • 相关阅读:
    Java 使用Reactive Redis
    深度学习系列60: 大模型文本理解和生成概述
    递增删除图象
    【古谷彻】算法模板(更新ing···)
    什么是代理IP池?如何判断IP池优劣?
    UVa1311/LA2666 Servers
    LeetCode 1004.最大连续1的个数
    泰语同声翻译一天多少钱呢
    CSS主题切换方案说明
    电子器件系列55:lm339比较器
  • 原文地址:https://blog.csdn.net/u011557841/article/details/132872648