zoukankan      html  css  js  c++  java
  • 网络编程学习笔记:linux下的socket编程

    socket是进程通信的一种方式,通过调用一些API可以实现进程间通信,建立连接以及收发信息的过程如下图所示:

    这些函数的用法如下:

    1、int socket(int protocolFamily, int type, int protocol); 返回描述符sockfd

    • l  protocolFamily:协议族,AF_INET(IPV4)、AF_INET6(IPV6)、AF_LOCAL(或称AF_UNIX,unix域socket)、AF_ROUTE等。协议族决定了socket的地址类型,在通信中必须采用对应的地址,如AF_INET决定了用IPV4地址(32位)与端口号(16位),AF_UNIX决定了要用一个绝对路径名作为地址
    • l  type:指定socket类型。常用的类型有,SOCK_STREAM、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等
    • l  protocol:协议名

    调用socket创建之后,返回的描述符存在于协议族空间中,但没有一个具体的地址,必须要通过bind才行

    2、int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

    • l  sockfd:socket描述字,socket的返回值
    • l  addr:一个const struct *addr指针,指向要绑定的sockfd的协议地址。这个地址结构根据地址创建socket时的地址协议族的不同而不同

    如, IPV4:

    struct sockaddr_in {
    sa_family_t    sin_family; /* address family: AF_INET */
    in_port_t      sin_port;   /* port in network byte order */
    struct in_addr sin_addr;   /* internet address */
    };
    
    struct in_addr{
      uint32_t s_addr; // address in network byte order
    };
    • l  addrlen:对应的地址的长度

    3、int listen(int sockfd, int backlog);服务器监听函数

    • l  sockfd:socket描述字,socket的返回值
    • l  backlog: socket 可以排队的最大连接个数

      listen函数将socket变为被动类型的, 等待客户连接请求

    4、int connect(int sockfd, const struct *addr, socklen_t addrlen);

    • l  sockfd:socket描述字,socket的返回值
    • l  addr:服务器的socket地址
    • l  addrlen:socket地址的长度

    5、int accept(int sockfd, struct sockaddr* addr, socklen_t *addrlen);

    • l  sockfd:socket描述字,socket的返回值
    • l  addr:结果参数,接收一个返回值指向客户端的地址,如果对客户的地址不在乎,可以设置为NULL
    • l  addrlen:结果参数,接收上述addr结构大小,指明addr结构所占字节数,也可NULL

      accept()成功返回一个SOCKET描述符,表示接收到的套接字的描述符。否则返回值INVALID_SOCKET。

    6、ssize_t send(int sockfd, const void *buf, ssize_t len, int flags);

    • l  sockfd: socket描述字,socket的返回值
    • l  buf:要发送的数据buffer
    • l  len:要传送的数据大小
    • l  flag:一般取值为0,影响TCP首部的可选部分

      send将自己的数据copy到内核的send buffer,返回值为[-1,size]:

      • 返回-1说明发送数据失败,系统内部出问题了
      • 返回[0,size]:由于send是从内核的send buffer中写数据,那么send buffer中剩下的长度为m,返回min(m, size),返回0就是send buffer没有空间了

    7、ssize_t recv(int sockfd, void *buf, ssize_t len, int flags);

    各参数意义基本与send相同,recv操作将内核中的数据拷贝到应用程序内部

    返回值是[-1,size]:

      • 返回-1表明接收失败,socket失效、recv操作由于系统内部原因中断等原因
      • 返回0表明没有数据,在TCP中接收发送有一个timeout,当timeout的时候还没有数据返回0
      • 返回[1,size]:recv的操作时从内核拷贝数据,数据有多少拷贝多少,内核现在收到长度为m的数据,返回min(m, size);

    8、int close(int fd);

    一个echo小例子,客户端向服务器发送什么服务器就给客户端返回什么信息:

    1、服务器代码:

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<errno.h>
    #include<sys/types.h>   // 基本系统数据类型,是系统的基本系统数据类型的头文件
    #include<sys/socket.h>  
    #include<netinet/in.h>  // 互联网地址族
    #include<arpa/inet.h>   //IP地址转换函数inet_pton
    #include<unistd.h>      //close
    
    #include<iostream>
    
    int main(int argc, char** argv){
        int socketfd, bindfd, connectfd;
        char buffer[4096];
        struct sockaddr_in serverAddress;
        int sendSize, recvSize;
    
        //printf("================== create socket ======================
    ");
        // create socket
        socketfd = socket(AF_INET, SOCK_STREAM, 0);
        if(socketfd == -1){
            printf("Create socket error:%s(errno:%d)
    ", strerror(errno),errno);
            exit(0);
        }
    
        //printf("================== set address ========================
    ");
        //set server address
        memset(&serverAddress, 0, sizeof(serverAddress));
        serverAddress.sin_family = AF_INET;
        serverAddress.sin_port = htons(800);
        //serverAddress.sin_addr.s_addr = htonl(127.0.0.1);
        inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr);
    
        std::cout << serverAddress.sin_addr.s_addr << '
    ';
    
        //printf("================== bind address ========================
    ");
        // bind address to the socket
        bindfd = bind(socketfd, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
        if(bindfd == -1){
            printf("bind socket error:%s(errno:%d)
    ", strerror(errno),errno);
            exit(0);        
        }
    
        //printf("================== listen ========================
    ");
        if(listen(socketfd, 10) == -1){
            printf("listen socket error:%s(errno:%d)
    ",strerror(errno),errno);
            exit(0);
        }
    
        printf("================== waiting connect ========================
    ");
        while(1){
            sleep(2);
            // recvive a connect and accept
            connectfd = accept(socketfd, (struct sockaddr*)NULL, NULL);
            if(connectfd == -1){
                printf("connet socket error:%s(errno:%d)
    ",strerror(errno),errno);
                continue;        
            }
    
            // receive data
            recvSize = recv(connectfd, buffer, 4096, 0);
            if(recvSize == -1){
                printf("recvive data error:%s(errno:%d)
    ",strerror(errno),errno);
                continue;            
            }
            
            printf("%s
    ", buffer);
    
            // send data
            sendSize = send(connectfd, buffer, 4096, 0);
            if(sendSize == -1){
                printf("send data error:%s(errno:%d)
    ",strerror(errno),errno);
                continue;            
            }
            close(connectfd);
    
        }
        close(socketfd);
    
    
    }

    2、客户端代码:

    #include<stdio.h>
    #include<stdlib.h>
    #include<string.h>
    #include<errno.h>
    #include<sys/types.h>   // 基本系统数据类型,是系统的基本系统数据类型的头文件
    #include<sys/socket.h>  
    #include<netinet/in.h>  // 互联网地址族
    #include<arpa/inet.h>   //IP地址转换函数inet_pton
    #include<unistd.h>      //close
    
    #include<iostream>
    
    int main(){
        int socketfd, connectfd;
        int sendSize, recvSize;
    
        struct sockaddr_in serverAddress;
    
        char sendBuf[4096];
        char recvBuf[4096];
    
        printf("================== create socket ========================
    ");
        socketfd = socket(AF_INET, SOCK_STREAM, 0);
        if(socketfd == -1){
            printf("create socket error:%s(error%d)
    ", strerror(errno),errno);
            exit(0);
        }
    
        memset(&serverAddress, 0, sizeof(serverAddress));
        serverAddress.sin_family = AF_INET;
        serverAddress.sin_port = htons(800);
        //serverAddress.sin_addr.s_addr = htonl("127.0.0.1");    
        inet_pton(AF_INET, "127.0.0.1", &serverAddress.sin_addr);
    
        std::cout << serverAddress.sin_addr.s_addr << '
    ';
    
        connectfd = connect(socketfd, (struct sockaddr*)&serverAddress, sizeof(serverAddress));
        if(connectfd == -1){
            printf("connect server error:%s(error(%d))
    ", strerror(errno), errno);
            exit(0);
        }
    
        printf("send message to server:
    ");
        //std::cin >> sendBuf;
        fgets(sendBuf, 4096, stdin);
        //printf("%s
    ", sendBuf);
        sendSize = send(socketfd, sendBuf, strlen(sendBuf), 0);
        if(sendSize == -1){
            printf("send data error:%s(error%d)
    ", strerror(errno), errno);
            exit(0);
        }
    
        printf("wait echo from server:
    ");
    
        sleep(2);
        recvSize = recv(socketfd, recvBuf, sizeof(recvBuf), 0);
        if(recvSize < 0){
            printf("recvive echo error:%s(error%d)
    ", strerror(errno), errno);
            exit(0);
        }
        printf("%s", recvBuf);
    
        close(socketfd);
    
    }

    在这过程中遇到的几个问题:

    1、(客户端)errno 111:connection refused

      这个问题说明客户端没有找到应该连接的端口,需要做到:

    • 确保服务端在相应的端口监听;
    • 关闭防火墙(ubuntu下面的命令:sudo ufw disable);
    • 而且server端要 sudo 运行;

      由于我客户端和服务器端口号不匹配,就出现了这个问题

    2、(服务器)errno 14:bad address

      accept()函数的第二个参数指的是一个接收返回结果的缓存区,表示的是本次连接的客户端的地址,如果无所谓客户端地址可以写为NULL,而一旦给这个参数赋了非NULL得值,但是这个缓存区又不可写的话,就会出现bad address报错

    3、(服务器)errno 107: transport endpoint is not connected.

      这个问题,是由于我服务器端的recv和send函数的第一个参数写的本地socket描述符造成的,事实上,这应该是已连接的socket描述符

  • 相关阅读:
    解决centos yum源配置出现Couldn't resolve host 问题
    Centos7下MongoDB下载安装详细步骤
    PHP操作mongodb扩展的坑 及php7安装mongodb扩展
    阿里云 Composer 全量镜像
    centos beanstalkd 安装 与php调用
    centos与windows共享文件夹
    centos php 安装编译 常见报错
    [PHP] layui实现多图上传,图片自由排序,自由删除
    Vue-element-admin实现菜单根据用户权限动态加载
    迭代器的使用方法
  • 原文地址:https://www.cnblogs.com/Chilly2015/p/5790787.html
Copyright © 2011-2022 走看看