• 第13章 TCP编程(1)_socket套接字


    1. socket套接字

    (1)套接字简介

     

      ①socket是一种通讯机制,它包含一整套的调用接口和数据结构的定义,它给应用进程提供了使用如TCP/UDP等网络协议进行网络通讯的手段。

      ②Linux中的网络编程通过socket接口实现。socket是一种特殊的IO,提供对应的文件描述符

      ③一个完整的socket都有一个相关描述(协议、本地地址、本地端口、远程地址和远程端口等)。

      ④每一个套接字有一个本地的唯一socket,由操作系统分配。

    (2)创建socket

    头文件

    #include <sys/socket.h>

    函数

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

    参数

    domain参数:

      ①AF_INET:   IPv4因特网域

      ②AF_INET6:  IPv6因特网域

      ③AF_UNIX:   unix域

      ④AF_UNSPEC: 未指定

    type参数:

       ①SOCK_STREAM 流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议,TCP保证了数据传输的正确性和顺序性。

      ②SOCK_DGRAM数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,并且不保证可靠及无差错。

      ③SOCK_RAW:原始套接字允许对低层协议(如IP或ICMP)直接访问,主要用于新的网络协议实现的测试等。

      ④SOCK_SEQPACKET: 长度固定、有序、可靠的面各连接报文传递。

    protocol参数通常为0,表示按给定的域和套接字类型选择默认协议。

    功能

    创建套接字

    返回值

    成功返回内核中消息队列的标识ID,出错返回-1

    备注

    套接字创建在内核中,若创建成功则返回内核文件描述表中的socket描述符。

    2. 字节序、地址结构和IPv4地址族

    (1)字节序

     

      ①不同体系结构的主机使用不同的字节序存储器来保存多字节整数。字节存储顺序不同,有的系统是高位在前,低位在后,而有的系统是低位在前,高位在后。

      ②字节序分为大端高位存储于低地址,低位存储于高地址小端高位存储于高地址,低位存储于低地址

      ③网络协议使用网络字节序即大端字节序

    (2)字节序转换函数

      ①uint32_t htonl(unit32_t hostlong); //将32位整数从主机字节序转为网络字节序

      ②uint16_t htons(unit16_t hostshort);//将16位整数从主机字节序转为网络字节序③uint32_t ntohl(unit32_t netlong); //将32位整数从网络字节序转为主机字节序  

      ④uint16_t ntohs(unit16_t netshort);//将16位整数从网络字节序转为主机字节序

    (3)通用地址结构

    头文件

    #include <sys/socket.h>

    结构体

    struct sockaddr{

      unsigned short sa_family;  //internet地址族,AF_XXX

      char sa_data[14];          //14字节的协议地址

    }

    备注

    ①sa_data包含了一些远程电脑的地址、端口和套接字的数目,它里面的数据是杂溶在一起的。

    ②sa_family一般来说,IPv4使用AF_INET。

    ③在传递给需要地址结构的函数时,把指向该结构体的指针转换成(struct sockaddr*)传递进去。

    (4)因特网地址结构(专用地址结构)

    头文件

    #include <sys/socket.h>

    结构体

    struct in_addr{   //其中的in指的是internet

      in_addr_t  s_addr   //ipv4地址

    }

    struct sockaddr_in{

      short int  sin_family;    //internet地址族,AF_XXX(主机字节序)

      unsigned short int sin_port; //端口号,16位(网络字节序)

      struct in_addr sin_addr;   //32位的IPv4地址(网络字节序)

      unsigned char sin_zero[8]; //添0(为了格式对齐的填充位)

    }

    备注

    sockaddr和sockaddr_in这两个数据类型是等效的,可以相互转换,通常使用sockaddr_in更为方便。

    (5)IPv4地址族和字符地址间的转换

    头文件

    #include <arp/inet.h>

    函数1

    const char* inet_ntop(int domain, const void* addr,

    char* str, socklen_t size);//网络字节地址转为点分十进制

    返回:成功返回地址字符串指针,出错返回NULL

    函数2

    int inet_pton(int domain, const char* str, void* addr);//点分转网络

    返回:成功返回1,无效格式返回0,出错返回-1

    参数

    domain:Internet地址族,如AF_INET

    addr: internet地址,32位IPv4地址(网络字节序)

    str: 地址字符串(点分十进制)指针

    size: 地址字符串大小

    (6)填写IPv4地址族结构体案例

    struct sockaddr_in sin;      //定义一个sockaddr_in结构体
    char buf[16];
    
    memset(&sin, 0, sizeof(sin));
    sin.sin_family = AF_INET;    //填写Internet地址族
    sin.sin_port = htons((short)3001); //填写端口号(网络字节序)
    
    //填充sin_addr
    if(inet_pton(AF_INET, "192.168.2.1", &sin.sin_addr.s_addr) <=0){
        //错误处理
    }
    
    printf("%s
    ", inet_ntop(AF_INET, &sin.sin_addr.s_addr,buf, sizeof(buf)));

    3. TCP编程模型

    3.1 TCP通讯模型

     

    (1)服务端调用序列

      ①调用socket函数创建套接字

      ②调用bind绑定本地地址和端口

      ③调用listen启动监听

      ④调用access从己连接队列中提取客户连接

      ⑤调用I/O函数(read/write)与客户端通讯

      ⑥调用close关闭套接字

    (2)客户端调用序列

      ①调用socket函数创建套接字

      ②调用connect连接服务器端

      ③调用I/O函数(read/write)与服务器端通讯

      ④调用close关闭套接字

    3.2 相关函数

    (1)套接字与地址绑定

    头文件

    #include <sys/socket.h>

    绑定地址

    int bind(int sockfd, const struct sockaddr* addr, socklen_t len);

    返回:成功返回0,出错返回-1

    特特地址:#define INADDR_ANY  (uint32_t)0x00000000 //响应主机上的任何一个可用的IP地址。

    查找绑定到套接字的地址

    int getsockname(int sockfd, struct sockaddr* addr, socklen_t alenp)

    返回:成功返回0,出错返回-1

    获取对方地址

    int getpeername(int sockfd, struct sockaddr* addr, socklen_t alenp)

    返回:成功返回0,出错返回-1

    (2)建立连接

    头文件

    #include <sys/socket.h>

    服务器端

    ①int listen(int sockfd, int backlog);//backlog为指定进行客户端连接排队的队列长度。

    返回:成功返回0,出错返回-1

    ②int accept(int sockfd, struct sockaddr* addr, socklen_t* len);

    客户端

    int connect(int sockfd, cosnt struct sockaddr* addr, socklen_t len)

    返回:成功返回0,出错返回-1

    【编程实验】获得服务器的时间

    //time_tcp_server.c(服务端)

    #include <netdb.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    #include <time.h>
    #include <signal.h>
    
    /*获取服务器端的当前时间*
    测试:telnet 127.0.0.1 xxxx 
          http://xxx.xxx.xxx.xxx:端口号
    注意:演示时可关闭服务器的防火墙,防止端口被过滤
          #service iptables status     查看防火墙
          #service iptables stop       关闭防火墙
    */
    
    int sockfd;
    int bStop = 0;
    
    void sig_handler(int signo)
    {
        if(signo == SIGINT){
            bStop = 1;
            printf("server close
    ");
    
            exit(1);
        }
    }
    
    //显示客户端信息
    void out_addr(struct sockaddr_in* addr)
    {
        //将端口从网络字节序转换成主机字节序
        int port = ntohs(addr->sin_port);
        //获得IP地址
        char ip[16] ={0};
        //将ip地址从网络字节序转换成点分十分制
        inet_ntop(AF_INET, &addr->sin_addr.s_addr, ip, sizeof(ip));
    
        printf("Client: %s(%d) connected
    ", ip, port);
    }
    
    //服务程序
    void do_service(int fd)
    {
        //获得系统时间
        long t = time(0);
        char* s = ctime(&t);
        size_t size = strlen(s) * sizeof(char);
    
        //将服务器端获得的系统时间写回客户端
        if(write(fd, s, size) != size){
            perror("write error");
        }
    }
    
    int main(int argc, char* argv[])
    {
        if(argc < 2){
            printf("usage: %s port
    ", argv[0]);
        }
    
        //按ctrl-c时中止服务端程序
        if(signal(SIGINT, sig_handler) == SIG_ERR){
            perror("signal sigint error");
            exit(1);
        }
    
        /*步骤1:创建socket(套接字)
         *注:socket创建在内核中,是一个结构体
         *AF_INET:IPv4
         *SOCK_STREAM:tcp协议
         */
        sockfd = socket(AF_INET, SOCK_STREAM, 0);
    
        /*步骤2:将sock和地址(包括ip、port)进行绑定*/
        struct sockaddr_in servAddr; //使用专用地址结构体
        memset(&servAddr, 0, sizeof(servAddr));
        //往地址中填入ip、port和Internet地址族类型
        servAddr.sin_family = AF_INET;//IPv4
        servAddr.sin_port = htons(atoi(argv[1])); //port
        servAddr.sin_addr.s_addr = INADDR_ANY; //任一可用的IP
    
        if(bind(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) <0 ){
            perror("bind error");
            exit(1);
        }
    
        /*步骤3:调用listen函数启动监听
         *       通知系统去接受来自客户端的连接请求
         */
        if(listen(sockfd, 10) < 0){  //队列中最多允许10个连接请求
            perror("listen error");
            exit(1);
        }
    
        /*步骤4:调用accept函数,从请求队列中获取一个连接
         *       并返回新的socket描述符
         * */
        struct sockaddr_in clientAddr;
        socklen_t clientAddr_len = sizeof(clientAddr);
        while(!bStop){
            //如果没有客户端连接,调用此函数后会阻塞,直至获得一个客户端连接
            int fd = accept(sockfd, (struct sockaddr*)&clientAddr, &clientAddr_len);
           
            if(fd < 0){
                perror("accept error");
                continue;
            }
            
            //输出客户端信息
            out_addr(&clientAddr);
    
            /*步骤5:调用IO函数(read/write)和客户端进行双向通信*/
            do_service(fd);
            
            /*步骤6: 关闭fd套接字*/
            close(fd);
        }
    
        close(sockfd);
    
        return 0;
    }
    /*输出结果
     * [root@localhost 13.TCP]# bin/time_tcp_server 8888                         
     * Client: 192.168.32.100(40672) connected
     * Client: 127.0.0.1(40608) connected
     * Client: 192.168.32.100(40674) connected
     */

    //time_tcp_client.c(客户端)

    #include <netdb.h>
    #include <sys/socket.h>
    #include <unistd.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <memory.h>
    
    int main(int argc, char* argv[])
    {
        if(argc < 3){
            printf("usage: %s ip port
    ", argv[0]);
            exit(1);
        }
    
        /*步骤1: 创建socket(套接字)*/
        int sockfd = socket(AF_INET, SOCK_STREAM, 0);
        if(sockfd < 0){
            perror("socket error");
        }
    
        //往servAddr中填入ip、port和地址族类型
        struct sockaddr_in servAddr;
        memset(&servAddr, 0, sizeof(servAddr));
        servAddr.sin_family = AF_INET;
        servAddr.sin_port = htons(atoi(argv[2]));
        //将ip地址转换成网络字节序后填入servAdd中
        inet_pton(AF_INET, argv[1], &servAddr.sin_addr.s_addr);
    
        /*步骤2: 客户端调用connect函数连接到服务器端*/
        if(connect(sockfd, (struct sockaddr*)&servAddr, sizeof(servAddr)) < 0){
            perror("connect error");
            exit(1);
        }
    
        /*步骤3: 调用IO函数(read/write)和服务端进行双向通信*/
        char buffer[1024];
        memset(buffer, 0, sizeof(buffer));
        size_t size;
        if((size = read(sockfd, buffer, sizeof(buffer))) < 0){
            perror("read error");
        }
    
        if(write(STDOUT_FILENO, buffer, size) != size){
            perror("write error");
        }
    
        /*关闭套接字*/
        close(sockfd);
    }
    /*输出结果
     [root@localhost 13.TCP]# bin/time_tcp_client 127.0.0.1 8888
     Fri Mar 17 16:35:57 2017
     */
  • 相关阅读:
    被忽视的调试工具Swagger
    MongoDB操作
    js获取当月第一天和最后一天
    vue中 关于$emit的用法
    map和flatmap的区别
    element 的el-dialog 浮层嵌套,第二次弹出的会被遮住
    el-table加背景色
    java 正则表达式匹配
    Python自动化测试 (七)logging 日志模块
    git安装配置与使用
  • 原文地址:https://www.cnblogs.com/5iedu/p/6650916.html
Copyright © 2020-2023  润新知