• TCPIP网络编程(一)


    这一系列博客将用于记录学习《TCP/IP网络编程》的笔记。
    先上代码。下面是服务器端的代码

    #include 
    #include 
    #include 
    //提供针对系统调用的封装
    #include 
    //提供用于网络字节序转换的函数
    #include 
    #include 
    
    //错误信息展示
    void error_handling (char *message);
    
    int main(int argc, char *argv[]){
        int serv_sock, clnt_sock;
        struct sockaddr_in  serv_addr, clnt_addr;
        socklen_t clnt_addr_size;
    
        char message[] = "Hello Word!";
    
        if (argc != 2){
            printf ("Usage : %s 
    ", argv[0]);
            exit(1);
        }
    
        serv_sock = socket(PF_INET, SOCK_STREAM, 0);
        if (serv_sock == -1)
            error_handling("Socket() error");
    
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(atoi(argv[1]));
    
        if (bind(serv_sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
            error_handling("bind() error");
    
        if (listen(serv_sock, 5) == -1)
            error_handling("Listen() Error");
    
        clnt_addr_size = sizeof(clnt_addr);
        clnt_sock = accept(serv_sock, (struct sockaddr*)&clnt_addr, &clnt_addr_size);
        if (clnt_sock == -1)
            error_handling("Acceot Error");
    
        write(clnt_sock, message, sizeof(message));
        close(clnt_sock);
        close(serv_sock);
    
        return 0;
    }
    
    void error_handling (char *message){
        fputs(message, stderr);
        fputc('
    ', stderr);
        exit(1);
    }
    
    • 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

    下面是客户端的代码

    #include 
    #include 
    #include 
    #include 
    #include 
    #include 
    
    //错误信息展示
    void error_handling (char *message){
        fputs(message, stderr);
        fputc('
    ', stderr);
        exit(1);
    }
    
    int main(int argc, char* argv[]){
        int sock;
        struct sockaddr_in serv_addr;
        char message[30];
        int str_len;
    
        if (argc != 3){
            printf ("Uasge : %s  ", argv[0]);
            exit(1);
        }
    
        sock = socket(PF_INET, SOCK_STREAM, 0);
        if (sock == -1)
            error_handling("Socket Error");
    
        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = inet_addr(argv[1]);
        serv_addr.sin_port = htons(atoi(argv[2]));
    
        if (connect(sock, (struct sockaddr*)&serv_addr, sizeof(serv_addr)) == -1)
            error_handling("Connect Error");
    
        str_len = read(sock, message, sizeof(message)) - 1;
        if (str_len == -1)
            error_handling("Read Error");
    
        printf("Message Is %s
    ", message);
        close(sock);
    
        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

    创建socket

    对于Linux系统而言,一切皆文件。所以我们一开始的时候就要创建一个_文件_,也就是我们的socket。有了文件,才能对文件进行各种各样千奇百怪的操作才是嘛,所以我们创建一个socket文件

    int socket(int domain, int type, int protocol)
    
    • 1

    socket函数会返回一个_描述符_,也就是一个int整形数字,在Linux下,这个数字就是与系统沟通好的,用来描述当前socket的一个暗号。

    描述socket

    有了这个socket_文件_之后呢,我们需要告诉操作系统,这个文件的一些特征。socket函数大概就类似于告诉系统,我要创建一个mp3文件还是一个txt文件。而接下来的这段代码,就是要告诉系统,这个有多大,它可以接受怎样的内容

        memset(&serv_addr, 0, sizeof(serv_addr));
        serv_addr.sin_family = AF_INET;
        serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
        serv_addr.sin_port = htons(atoi(argv[1]));
    
    • 1
    • 2
    • 3
    • 4

    我们先将文件给清空,然后再一一告诉系统这个文件相关的东西。

    • AF_INETIPv4 网络协议的套接字类型
    • htonl(INADDR_ANY) 这里告诉系统,这个socket监听所有的网卡,就是指定地址为0.0.0.0的地址
    • htons(atoi(argv[1]))这里告诉系统,监听哪一个端口,该端口由我们指定

    其中htonshtons代表了将本地的字符串字节序转化为网络字节序,防止因为本地的大小端存放问题导致出错。

    进行绑定

    普通的文件创建完了之后,就可以丢那里不管,需要的时候直接写入数据就可以,但毕竟socket不是一般的文件,它需要从网络上来获取数据,所以我们还需要一步绑定的操作,来告诉操作系统,我这个socket需要监听来自xxxIP,yyy端口的东西啦。这样当有数据从指定的地址端口来的时候,系统才知道,要送到这个socket这里。
    我们使用bind函数进行绑定

    int bind (int sockfd, struct sockaddr* myaddr, socklen_t addrlen)
    
    • 1

    通过这个函数,我们就可以绑定到操作系统上,从而接收数据。

    开始监听

    绑定完了之后,我们需要让socket去监听丫。bind只是绑定了端口,但是socket并没有对这个端口做什么操作,来了数据也不知道,是吧。
    使用listen来监听我们指定的端口

    int listen(int sockfd, int backlog)
    
    • 1

    通过这个操作,当有东西来了的时候,socket就能够知道,并且能够进行相应的处理。

    受理连接

    对于一个socket而言,它不一定是给指定的连接使用的,它大部分情况下都是能够接收很多很多的连接,那我们怎么去区分这不同的连接,跟不同的连接做交互呢?所以需要一个函数来负责获取这些连接,将每一个连接打上不同的记号,方便我们进行处理。因此,就有了accept函数

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

    很明显,系统也将不同的连接当成了文件,返回了一个个的文件描述符。因此我们能够根据这不同的文件描述符去区分不同的连接。
    注意,当socket没有接收到连接的时候,它会阻塞到这里,直到有连接进来或者出现什么失败为止。

    连接服务器

    对于客户端的代码而言,基本上都和服务器端的代码相同,但是有个不一样的函数出现在这。对于客户端而言,我需要主动的去找服务器,所以客户端的代码需要一个连接的操作

    int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen)
    
    • 1

    通过connect函数,我们能够连接到想要的服务器,跟其进行通信。

    关闭socket

    对于任一打开的操作,基本上都要记得关闭,不然就可能出大问题!!

    int close(int fd)
    
    • 1

    这个函数非常简单,传入我们的socket描述符,系统便能够将这个socket
    相关的东西断开,方便下次使用。

    写入数据

    既然有了网络连接,自然是需要传递数据的。我们把socket当成了文件看待,所以像其中写入数据自然是使用write函数

    ssize_t write(int fd, const void *buf, size_t nbytes)
    
    • 1

    这个函数的返回值有点奇怪,它是一种元数据类型(primitive),这里与C语言的语言特点有关,C并未规定int, long short这些数据类型的大小,而且操作系统也在不断的发生变化,由最初的16位发展到现在的64位,所以如果我们直接使用int类型的话,可能在之后的代码中需要进行不断的修改,并且可能在某一系统上,int的大小类型并不符合我们的要求,所以我们利用typedef进行额外的定义,定义出一系列大小规整的类型。

    读取数据

    读取数据与写入数据很类似,都是很标准的Linux文件操作

    ssize_t read(int fd, void *buf, size_t nbytes)
    
    • 1

    通过这个函数,我们能够读取到我们需要的大小的数据

    总结

    简而言之,创建一个基本的socket还是比较简单的,过程可能有些繁琐。可以归纳为以下几步。

    • 第一步:调用socket创建一个socket
    • 第二步:调用bind函数绑定IP地址和端口号,绑定到操作系统
    • 第三步:调用listen开始转为可接收请求状态
    • 第四步:调用accept函数受理连接请求
      • 客户端需要一个connect函数来连接到服务器
    • 利用writeread进行数据的读写操作
    • 关闭socket
  • 相关阅读:
    LeetCode:移除元素
    【计算机视觉(CV)】基于图像分类网络VGG实现中草药识别(一)
    [数据结构初阶]初识
    函数指针与回调函数
    [附源码]java毕业设计自治小区物业设备维护管理系统
    C++ 关联式容器map+set
    SpringBoot测试实践
    ChessGPT:免费好用的国际象棋对弈AI机器人
    Python函数的参数与返回值
    rabbitMQ:绑定Exchange发送和接收消息(fanout)
  • 原文地址:https://blog.csdn.net/geejkse_seff/article/details/126516687