zoukankan      html  css  js  c++  java
  • 套接字通信

    背景

    基于C语言,对linux系统下套接字通信相关的知识点进行梳理,比如重点概念的理解,重点操作函数的解析等,最后附上相关示例代码。

    概念

    套接字分类

    • 流式套接字(SOCK_STREAM)
    • 数据报套接字(SOCK_DGRAM)
    • 原始套接字

    流式套接字

    使用TCP(传输控制协议)进行数据传输,可以保证数据传输的准确性。

    数据报套接字

    使用UDP(使用者数据报协议)进行数据传输,不能保证接收的数据的准确性。

    相关数据结构

    struct sockaddr

    #include <sys/socket.h>
    struct
    sockaddr {   unsigned short sa_family;//地址协议族 char sa_data[14];//地址(ip + port) };

      struct sockaddr 是通用的套接字地址,长度为16字节。

    struct sockaddr_in

    #include <netinet/in.h>
    /* Internet address. */
    struct in_addr
    {
      uint32_t s_addr;
    };
    /* Structure describing an Internet socket address. */
    struct sockaddr_in
    {
      unsigned short sa_family;
      uint16_t sin_port; /* Port number. */必须是网络字节序
      struct in_addr sin_addr; /* Internet address. */必须是网络字节序
      unsigned char sin_zero[8];/* Pad to size of `struct sockaddr'. */
    };

       internet环境下套接字的地址形式,长度也是16字节;

      因为bind()函数的套接字地址类型是通用类型,所以现在通行的做法是,使用struct sockaddr_in绑定ip和端口,然后强转成struct sockaddr类型  

    本机转换

    由于struct sockaddr_in的Ip和端口是数据需要发送到网络端,所以类型必须是网络字节序;

    端口的转换需要用到下面的htons

    #include <arpa/inet.h>
    uint16_t htons(uint16_t hostshort); uint32_t htonl(uint32_t hostlong); uint32_t ntohl(uint32_t netlong); uint16_t ntohs(uint16_t netshort);

    其实,ip的转换也可以用htonl,但入参是uint32_t,需要先将一个字符串类型的IP换算成数值类型再传参;
    考虑到htonl的使用有些繁琐,一般我们使用下面的函数来进行地址的转换:

    #include <sys/socket.h>
    #include <netinet/in.h>
    #include <arpa/inet.h>
    in_addr_t inet_addr(const char *cp);
    int inet_aton(const char *cp, struct in_addr *inp);
    char *inet_ntoa(struct in_addr in);

      inet_addr()和inet_aton()都可以用于获取一个网络字节序的地址;
      inet_ntoa是逆操作;

      

    #define INADDR_ANY ((in_addr_t) 0x00000000)

       INADDR_ANY是一个宏定义,数值是网络字节序,等价于inet_addr("0.0.0.0"),功能是代码所有本机IP

    socket()

    #include <sys/types.h>
    #include <sys/socket.h>
    int socket(int domain, int type, int protocol);

      domain 网络通信协议族,一般写AF_INET
      type 通信类型,SOCK_STREAM|SOCK_DGRAM
      protocol 定义额外的一个通信协议。通常只需要一个协议,所以这里填0
      返回:成功返回一个可用套接字;失败返回-1,并重置errno

    setsockopt

    #include <sys/types.h>
    #include <sys/socket.h>
    int setsockopt(int sockfd, int level, int optname,const void *optval, socklen_t optlen);

      主要用于设置端口复用

    fcntl

    #include <unistd.h>
    #include <fcntl.h> 
    int fcntl(int fd, int cmd); 
    int fcntl(int fd, int cmd, long arg); 
    int fcntl(int fd, int cmd, struct flock *lock);

      常用于设置套接字非阻塞读

    bind()

    #include <sys/types.h>
    #include <sys/socket.h>
    int bind(int sockfd, struct sockaddr *my_addr, socklen_t addrlen);

      给套接字绑定到 “本机通信地址”返回:
      成功返回一个可用套接字;失败返回-1,并重置errno

    connect()

    #include <sys/types.h> 
    #include <sys/socket.h>
    int connect(int sockfd, struct sockaddr *serv_addr, socklen_t addrlen); 

      将套接字与远程服务器通信地址绑定
      返回:成功返回一个可用套接字;失败返回-1,并重置errno

    listen()

    int listen(int sockfd, int backlog); 

      sockfd一般是服务器的网络侦听套接字,backlog是连接队列的长度(等待接受连接请求)
      返回:成功返0;失败返回-1并重置errno

    accept()

    #include <sys/types.h>
    #include <sys/socket.h>
    int accept(int s, struct sockaddr *addr, socklen_t *addrlen);

      返回一个成功建立连接的新套接字
      返回:成功返0;失败返回-1并设置errno

    send()

    #include <sys/types.h>
    #include <sys/socket.h>
    int send(int s, const void *msg, size_t len, int flags);
    int sendto(int s, const void *msg, size_t len, int flags, const struct sockaddr *to, socklen_t tolen);
    int sendmsg(int s, const struct msghdr *msg, int flags);

      s 套接字
      msg 待发送的数据
      len 数据长度
      flags 填0

    close()

    #include <unistd.h>
    int close(int fd);

      完全关闭连接

     

    #include <sys/socket.h>
    int shutdown(int sockfd, int how);
        how:
        --SHUT_RD      关闭读端
        --SHUT_WR      关闭写端
        --SHUT_RDWR    关闭读写(同close())

      相比close,有更多的控制

    示例代码

     文件描述符设置阻塞与非阻塞

    参考:https://www.cnblogs.com/xuyh/p/3273082.html

    #include <stdio.h>
    #include <sys/types.h>
    #include <unistd.h>
    #include <sys/stat.h>
    #include <fcntl.h>
    #include <string.h>
    
    /**********************使能非阻塞I/O********************
    *int flags;
    *if(flags = fcntl(fd, F_GETFL, 0) < 0)
    *{
    *    perror("fcntl");
    *    return -1;
    *}
    *flags |= O_NONBLOCK;
    *if(fcntl(fd, F_SETFL, flags) < 0)
    *{
    *    perror("fcntl");
    *    return -1;
    *}
    *******************************************************/
    
    /**********************关闭非阻塞I/O******************
    flags &= ~O_NONBLOCK;
    if(fcntl(fd, F_SETFL, flags) < 0)
    {
        perror("fcntl");
        return -1;
    }
    *******************************************************/
    
    int main()
    {
        char buf[10] = {0};
        int ret;
        int flags;
        
        //使用非阻塞io
        if(flags = fcntl(STDIN_FILENO, F_GETFL, 0) < 0)
        {
            perror("fcntl");
            return -1;
        }
        flags |= O_NONBLOCK;
        if(fcntl(STDIN_FILENO, F_SETFL, flags) < 0)
        {
            perror("fcntl");
            return -1;
        }
    
        while(1)
        {
            sleep(2);
            ret = read(STDIN_FILENO, buf, 9);
            if(ret == 0)
            {
                perror("read--no");
            }
            else
            {
                printf("read = %d
    ", ret);
            }
            
            write(STDOUT_FILENO, buf, 10);
            memset(buf, 0, 10);
        }
    
        return 0;
    }

    Socket服务器(多进程)

    #include <stdio.h>                                                                                                                    
    #include <stdlib.h>
    #include <string.h>//memset
    #include <errno.h>
    #include <unistd.h>//fork
    
    #include <sys/types.h>//socket
    #include <netinet/in.h>//sockaddr
    #include <sys/socket.h>//sockaddr
    #include <arpa/inet.h>//sockaddr
    #include <sys/wait.h>//wait
    
    #define PORT 8888
    #define BACKLOG 128
    #define MAXLENGTH 1024
    
    int main()
    {
        int listenfd;//侦听套接字
        int new_fd;//新连接
        int ret;//返回值检查
        struct sockaddr_in my_addr;//本机地址
        struct sockaddr_in addr;//远程地址
        socklen_t addrlen;
        char buffer[MAXLENGTH] = {0};
    
        listenfd = socket(AF_INET, SOCK_STREAM, 0); 
        if (listenfd == -1){
            perror("socket");
            return -1; 
        }   
    
        memset(&my_addr, 0, sizeof(struct sockaddr_in));
        my_addr.sin_family = AF_INET;
        my_addr.sin_port = htons(PORT);
        my_addr.sin_addr.s_addr = inet_addr("192.168.6.131");
    
        int opt = 1;
        setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, (const void*)&opt, sizeof(opt));
    
        ret = bind(listenfd, (struct sockaddr*)&my_addr, sizeof(struct sockaddr));
        if (ret == -1){
            perror("bind");
            return -1; 
        }   
    
        ret = listen(listenfd, BACKLOG);
        if (ret == -1){
            perror("listen");
            return -1; 
        }   
    
        while (1) 
        {   
            addrlen = sizeof(struct sockaddr_in);
            new_fd = accept(listenfd, (struct sockaddr*)&addr, &addrlen);
            if (new_fd == -1){
                perror("accept");
                continue;
            } 
    
            printf("new connection: %s
    ",
                    inet_ntoa(addr.sin_addr));
    
            pid_t child = -1;
            child = fork();
            if (child == -1){
                perror("fork");
                continue;
            }
            if (child == 0){
                /**
                 *子进程,与远程客户端通信
                 */
                char *cur_addr = inet_ntoa(addr.sin_addr);
                while (1)
                {
    
                    memset(buffer, 0, MAXLENGTH);
                    ret = recv(new_fd, buffer, MAXLENGTH, 0);
                    if (ret == -1){
                        perror("recv");
                    }
                    else if (ret == 0){
                        printf("client %s close.
    ", cur_addr);
                        close(new_fd);
                        exit(0);
                    }
                    else
                    {
                        printf("receive %dBytes data
    ", ret);
    
                        buffer[ret-1] = ' ';
                        strncat(buffer, "world", 6);
                        ret = send(new_fd, buffer, ret+6, 0);
                        if (ret == -1){
                            perror("send");
                        }
                    }
    
    
                }//---while over
                exit(0);
            }
            else
            {
                /**
                 * 主进程,尝试回收子进程,并继续接收新连接。
                 */
                pid_t child = -1;
                while ((child = waitpid(-1, NULL, WNOHANG)) > 0)
                {
                    printf("wait child[%d] success.
    ", child);
                }
            }
        }//---while over
        return 0;
    }

    客户端

    #include <stdio.h>                                                                                                                    
    #include <stdlib.h>
    #include <string.h>//memset
    #include <errno.h>
    #include <unistd.h>//fork
    
    #include <sys/types.h>//socket
    #include <netinet/in.h>//sockaddr
    #include <sys/socket.h>//sockaddr
    #include <arpa/inet.h>//sockaddr
    #include <sys/wait.h>//wait
    
    #define PORT 8888
    #define BACKLOG 128
    #define MAXLENGTH 1024
    
    int main()
    {
        int sockfd;//连接套接字
        int ret;//返回值检查
        struct sockaddr_in remote_addr;//本机地址
        char buffer[MAXLENGTH] = {0};
    
        sockfd = socket(AF_INET, SOCK_STREAM, 0); 
        if (sockfd == -1){
            perror("socket");
            return -1; 
        }   
    
        memset(&remote_addr, 0, sizeof(struct sockaddr_in));
        remote_addr.sin_family = AF_INET;
        remote_addr.sin_port = htons(PORT);
        remote_addr.sin_addr.s_addr = inet_addr("192.168.6.131");
    
        ret = connect(sockfd, (struct sockaddr*)&remote_addr, sizeof(struct sockaddr));
        if (ret == -1){
            perror("connect");
            return -1; 
        }   
    
        strncpy(buffer, "hello", 6); 
        while (1) 
        {   
            char *stdin_ret;
            stdin_ret = fgets(buffer, MAXLENGTH, stdin);
            if (stdin_ret == NULL){
                perror("fgets");
                continue;
            }   
            ret = send(sockfd, buffer, strlen(stdin_ret), 0); 
            if (ret == -1){
                perror("send");
            }   
    
            memset(buffer, 0, MAXLENGTH);
            ret = recv(sockfd, buffer, MAXLENGTH, 0); 
            if (ret == -1){
                perror("recv");
            }   
            else if (ret == 0){ 
                printf("server have closed me.
    ");
                close(sockfd);
                break;
            }
            else
            {
                printf("%s
    ", buffer);
            }
        }//---while over
        return 0;
    }

    参考

    fcntl函数详解:https://www.cnblogs.com/xuyh/p/3273082.html

    多进程服务器:https://blog.csdn.net/coolwriter/article/details/80496826

    socket编程:https://www.cnblogs.com/liushui-sky/p/5609535.html

  • 相关阅读:
    神经网络回顾-感知机
    遗传算法杂记
    差分进化算法DE和粒子群算法PSO
    遗传算法GA
    Caffe学习 五 conv_layer与im2col
    Selenium
    Selenium
    Selenium
    Selenium
    Selenium
  • 原文地址:https://www.cnblogs.com/orejia/p/12128866.html
Copyright © 2011-2022 走看看