zoukankan      html  css  js  c++  java
  • socket套接字编程(1)——基本函数

    TCP交互流程:

    服务器:1. 创建socket;2. 绑定socket和端口号;3. 监听端口号;4. 接收来自客户端的连接请求;5. 从socket中读取字符;6. 关闭socket。

    客户端:1. 创建socket;2. 连接指定计算机的端口;3. 向socket中写入信息;4. 关闭socket。

    创建socket:

    socket函数

    int socket (int __family, int __type, int __protocol);

    __family是协议域,也称协议族。常见的有AF_INET(ipv4)。

    __type指定socket类型。SOCK_STREAM即TCP协议,SOCK_DGRAM即UDP协议。

    __protocol指定协议。

    该函数返回的socket描述字存在于协议族空间中,但是并没有一个具体的地址。如果想要给它赋予一个地址,就必须调用bind()函数,否则系统就在调用connect()和listen()时自动随机分配一个端口。

    这里注意:type和protocol并不能随意组合。当protocol为0时,会自动选择type类型对应的默认协议。

    创建socket的样例代码如下:

        //创建TCP套接字
        //AF_INET:网络连接,ipv4
        //SOCK_STREAM:TCP连接
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd<0) {
            std::cout<<"create socket error!"<<std::endl;
            return 0;
        }
        std::cout<<"create socket: "<<fd<<std::endl;

    绑定socket和端口号:

    bind函数

    int bind (int, const struct sockaddr *__my_addr, socklen_t __addrlen);

    第一个参数是socket描述字。(我不理解为啥这儿没有参数名)

    __my_addr是指向要绑定给该socket的协议地址。这个地址结构根据socket创建时的地址协议族(family)的不同而不同。

    __addrlen对应的是地址的长度。

    如果该函数执行成功,就返回0,否则为SOCKET_ERROR。

        //命名套接字
        struct sockaddr_in myaddr;
        memset((void *)&myaddr, 0, sizeof(myaddr));
        //关于htonl和htons,参考以下网页:ntohs, ntohl, htons,htonl的比较和详解
        //https://blog.csdn.net/haoxiaodao/article/details/73162663
        myaddr.sin_family = AF_INET;
        myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        myaddr.sin_port = htons(6666);
        if (bind(fd, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) {
            std::cout<<"name socket error!"<<std::endl;
            return 0;
        }
        std::cout<<"name socket"<<std::endl;

    监听端口号:

    作为一个服务器,在调用socket()和bind()之后就会调用listen()来监听这个socket。为了能够在套接字上接受进入的连接,服务器程序必须创建一个队列来保存未处理的请求。

    listen函数

    int listen (int, int __n);

    第一个参数即socket描述子

    __n为队列的大小。

        //创建监听队列
        if (listen(fd, 5) < 0) {
            std::cout<<"listen failed"<<std::endl;
            return 0;
        }

    接收来自客户端的连接请求:

    当TCP服务器监听到了连接请求之后,就会调用accept()函数接收请求,这样连接就建立好了。

    accept函数

    int accept (int, struct sockaddr *__peer, socklen_t *);

    第一个是socket描述子,第二个是用来接收的客户端地址,第三个是地址的大小。注意第三个是指针类型,所以要事先构造好大小的变量,然后传地址进去。

    另外,《后台开发核心技术与应用实践》中的例子,第二个和第三个都传的NULL。我的理解是,如果不需要接收这两个量,就可以传一个空值进去。

    accept函数会返回一个新的socket描述子,这个新的描述子代表了服务端和客户端的连接。后面可以用于读取数据以及关闭连接。

       //等待并接受连接
        const int MAXBUF = 4096;
        char buff[MAXBUF];
        struct sockaddr_in client_addr;
        int client_addr_len = sizeof(client_addr);
        int client_fd;
        while (1) {
            client_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len);
            if (client_fd < 0) {
                std::cout<<"connect error"<<std::endl;
                continue;
            }
            //接收数据
            //关闭套接字
        }

    从socket中读取字符:

    服务器与客户端建立好连接之后,就可以调用网络I/O进行读写操作了。网络I/O操作有下面几组:

    read()/write()

    recv()/send()

    readv()/writev()

    recvmsg()/sendmsg()

    recvfrom()/sendto()

    具体的区别待补充,这里只举一个例子。

    _ssize_t read (int __fd, void *__buf, size_t __nbyte);

    __fd是刚才获得的服务器与客户端建立连接的socket描述子

    __buf是缓冲区指针

    __nbyte是缓冲区大小

            //接收数据
            int nbytes = read(client_fd, buff, MAXBUF);
            std::cout<<"get infomation: "<<buff<<std::endl;

    关闭socket:

    完成读写操作就要关闭相应的socket描述子,可以类比与文件完成读写操作之后也要关闭一样。

    close函数

    int     close (int __fildes);

    只有一个参数,就是socket描述子。

    close会把该socket标记为关闭,然后立即返回到调用进程。该描述子不能再由调用进程使用,即,不能再作为read或write的第一个参数。

    但是,这里需要注意的是,close操作只是使相应socket描述子的引用-1,只有当引用计数为0时,才会出发TCP发送终止连接请求。

    以上是服务器端的基本代码,总的代码如下:

    #include <sys/socket.h>
    #include <iostream>
    #include <cygwin/in.h>
    #include <cstring>
    #include <unistd.h>
    
    int main() {
        std::cout<<"running server"<<std::endl;
        //创建TCP套接字
        //AF_INET:网络连接,ipv4
        //SOCK_STREAM:TCP连接
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd<0) {
            std::cout<<"create socket error!"<<std::endl;
            return 0;
        }
        std::cout<<"create socket: "<<fd<<std::endl;
    
        //命名套接字
        struct sockaddr_in myaddr;
        memset((void *)&myaddr, 0, sizeof(myaddr));
        //关于htonl和htons,参考以下网页:ntohs, ntohl, htons,htonl的比较和详解
        //https://blog.csdn.net/haoxiaodao/article/details/73162663
        myaddr.sin_family = AF_INET;
        myaddr.sin_addr.s_addr = htonl(INADDR_ANY);
        myaddr.sin_port = htons(6666);
        if (bind(fd, (struct sockaddr*)&myaddr, sizeof(myaddr)) < 0) {
            std::cout<<"name socket error!"<<std::endl;
            return 0;
        }
        std::cout<<"name socket"<<std::endl;
    
        //创建监听队列
        if (listen(fd, 5) < 0) {
            std::cout<<"listen failed"<<std::endl;
            return 0;
        }
        std::cout<<"=============listening, port = 6666"<<std::endl;
    
        //等待并接受连接
        const int MAXBUF = 4096;
        char buff[MAXBUF];
        struct sockaddr_in client_addr;
        int client_addr_len = sizeof(client_addr);
        int client_fd;
        while (1) {
            client_fd = accept(fd, (struct sockaddr*)&client_addr, &client_addr_len);
            if (client_fd < 0) {
                std::cout<<"connect error"<<std::endl;
                continue;
            }
            //接收数据
            int nbytes = read(client_fd, buff, MAXBUF);
            std::cout<<"get infomation: "<<buff<<std::endl;
            //关闭套接字
            close(client_fd);
        }
        close(fd);
        return 0;
    }

    相比与服务器端,客户端的区别主要在于连接指定计算机的端口和向socket中写入信息。

    连接制定计算机的端口:

    connect函数

    int connect (int, const struct sockaddr *, socklen_t);

    第一个参数是socket描述子,第二个是目标服务器的地址,第三个是地址struct的大小。

    关于目标服务器地址的构造,需要利用函数inet_pton(),代码如下:

        const char* server = "127.0.0.1";
        struct sockaddr_in server_addr;
        memset((char*)&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(6666);
        inet_pton(AF_INET, server, &server_addr.sin_addr);

    构造完目标服务器的地址,就可以调用connect()函数了。

        if (connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cout<<"connect failed"<<std::endl;
            return 0;
        }

    向socket中写入信息:

    跟上面接收信息对应,使用了write()函数

        const int MAXBUF = 4096;
        char buffer[MAXBUF] = "hello TCP";
        int nbytes = write(fd, buffer, 10);

    以上就是客户端与服务器端相比不同的部分,客户端的基本代码如下:

    #include <sys/socket.h>
    #include <iostream>
    #include <cstring>
    #include <cygwin/in.h>
    #include <arpa/inet.h>
    #include <unistd.h>
    
    int main() {
        int fd = socket(AF_INET, SOCK_STREAM, 0);
        if (fd < 0) {
            std::cout<<"socket error"<<std::endl;
            return 0;
        }
    
        const char* server = "127.0.0.1";
        struct sockaddr_in server_addr;
        memset((char*)&server_addr, 0, sizeof(server_addr));
        server_addr.sin_family = AF_INET;
        server_addr.sin_port = htons(6666);
        inet_pton(AF_INET, server, &server_addr.sin_addr);
    
        if (connect(fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) < 0) {
            std::cout<<"connect failed"<<std::endl;
            return 0;
        }
    
        const int MAXBUF = 4096;
        char buffer[MAXBUF] = "hello TCP";
        int nbytes = write(fd, buffer, 10);
    
        close(fd);
    }

    参考资料:

    1. TCP套接字编程入门 https://blog.csdn.net/lihao21/article/details/64624796?locationNum=7&fps=1

    2. 《后台开发核心技术与应用实践》

  • 相关阅读:
    Spark介绍与环境搭建
    Kafka基本操作
    Hadoop的HDFS概述
    hadoop环境搭建
    常用小工具
    mac机
    Eclipse使用
    微信公众号开发
    PM2
    JS 零散知识点
  • 原文地址:https://www.cnblogs.com/wangzhao765/p/9161589.html
Copyright © 2011-2022 走看看