• 网络编程


    网络编程

    在 Linux 中的网络编程是通过 socket 接口来进行的。

    socket概述

    socket 接口是一种特殊的 I/O,它也是一种文件描述符。

    每一个 socket 都用一个半相关描述{协议,本地地址、本地端口}来表示。

    一个完整的套接字则用一个相关描述{协议,本地地址、本地端口、远程地址、远程端口}。

    socket有3种类型:

    1.流式 socket(SOCK_STREAM)

    提供可靠的、面向连接的通信流;它使用 TCP 协议,从而保证了数据传输的
    正确性和顺序性。

    2.数据报 socket(SOCK_DGRAM)

    定义了一种无连接的服务,数据通过相互独立的报文进行传输,是无序的,
    并且不保证是可靠、无差错的。它使用数据报协议 UDP。

    3.原始 socket

    允许对底层协议如 IP 或 ICMP 进行直接访问,它功能强大但使用较为不便,主要用于一些协议的开发。

    socket等效的2种结构表示:
    struct sockaddr {
    	unsigned short sa_family; /*地址族*/
    	char sa_data[14]; /*14 字节的协议地址,包含该 socket 的 IP 地址和端口号。*/
    };
    
    struct sockaddr_in {
    	short int sa_family; /*地址族*/
    	unsigned short int sin_port; /*端口号*/
    	struct in_addr sin_addr; /*IP 地址*/
    	unsigned char sin_zero[8]; /*填充 0 以保持与 struct sockaddr 同样大小*/
    };
    
    struct in_addr {
        in_addr_t s_addr; /* 网络字节序,一般为unsigned int类型 */
    };
    
    数据存储优先顺序:

    计算机数据存储有两种字节优先顺序:高位字节优先和低位字节优先。

    Internet 上数据以高位字节优先顺序在网络上传输,因此在有些情况下,需要对这两种字节存储优先顺序进行相互转化。

    SYNOPSIS

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

    通常 16 位的 IP 端口号用 s 代表,而 IP 地址用 l 来代表。

    地址格式转化:

    通常用户在表达地址时采用的是点分十进制表示的数值(或者是以冒号分开的十进制IPv6 地址),而在通常使用的 socket 编程中所使用的则是二进制值,这就需要将这两个数值
    进行转换。

    这里inet_pton函数是将点分十进制地址映射为二进制地址,而inet_ntop是将二进制地
    址映射为点分十进制地址。

    SYNOPSIS

       #include <arpa/inet.h>
    
       int inet_pton(int af, const char *src, void *dst);
    

    SYNOPSIS

       #include <arpa/inet.h>
    
       const char *inet_ntop(int af, const void *src,
                             char *dst, socklen_t size);
    

    af: AF_INET - IPV4; AF_INET6 - IPV6

    实例:

    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <unistd.h>
    #include <sys/socket.h>
    #include <netinet/in.h>
    int main (void)
    {
    	char IPdotdec[20]; //存放点分十进制IP地址
    	struct in_addr s; // IPv4地址结构体
    	// 输入IP地址
    	printf("Please input IP address: ");
    	scanf("%s", IPdotdec);
    	// 转换
    	inet_pton(AF_INET, IPdotdec, (void *)&s);
    	printf("inet_pton: 0x%x
    ", s.s_addr); // 注意得到的字节序
    	// 反转换
    	inet_ntop(AF_INET, (void *)&s, IPdotdec, 16);
    	printf("inet_ntop: %s
    ", IPdotdec);
    
    	return 0;
    }
    

    结果如下:

    可以看出,inet_pton: 0x10000ac 为网络字节序(大端模式),个人PC为小端模式。

    xxx@xxx-pc:~/Documents$ ./a.out 
    Please input IP address: 172.0.0.1
    inet_pton: 0x10000ac
    inet_ntop: 172.0.0.1
    
    名字地址转化:

    通常,人们在使用过程中都不愿意记忆冗长的 IP 地址,使用主机名将会是很好的选择。在 Linux 中,同样有一些函数可以实现主机名和地址的转化。

    其中 gethostbyname 是将主机名转化为 IP 地址,gethostbyaddr 则是逆操作,是
    将 IP 地址转化为主机名,另外 getaddrinfo 还能实现自动识别 IPv4 地址和 IPv6 地址。
    它们涉及以下的结构体:

    struct hostent {
        char  *h_name;            /* official name of host */
        char **h_aliases;         /* alias list */
        int    h_addrtype;        /* host address type */
        int    h_length;          /* length of address */
        char **h_addr_list;       /* list of addresses */
    }
    #define h_addr h_addr_list[0] /* for backward compatibility */
    
    struct addrinfo {
        int              ai_flags;/*AI_PASSIVE,AI_CANONNAME;*/
        int              ai_family;/*地址族*/
        int              ai_socktype;/*socket 类型*/
        int              ai_protocol;/*协议类型*/
        socklen_t        ai_addrlen;/*地址长度*/
        struct sockaddr *ai_addr;/*socket 结构体*/
        char            *ai_canonname;/*主机名*/
        struct addrinfo *ai_next;/*下一个指针链表*/
    };
    

    SYNOPSIS

       #include <netdb.h>
       extern int h_errno;
    
       struct hostent *gethostbyname(const char *name);
    
       #include <sys/socket.h>       /* for AF_INET */
       struct hostent *gethostbyaddr(const void *addr,
                                     socklen_t len, int type);
    

    SYNOPSIS

       #include <sys/types.h>
       #include <sys/socket.h>
       #include <netdb.h>
    
       int getaddrinfo(const char *node, const char *service,
                       const struct addrinfo *hints,
                       struct addrinfo **res);
    

    socket编程

    • socket()

    该函数用于建立一个 socket 连接, 可指定 socket 类型等信息。在建立了 socket 连接之后,可对 socketadd 或 sockaddr_in 进行初始化,以保存所建立的 socket 信息。

    SYNOPSIS

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

    domain:

       Name                Purpose                          Man page
       AF_UNIX, AF_LOCAL   Local communication              unix(7)
       AF_INET             IPv4 Internet protocols          ip(7)
       AF_INET6            IPv6 Internet protocols          ipv6(7)
       AF_IPX              IPX - Novell protocols
       AF_NETLINK          Kernel user interface device     netlink(7)
       AF_X25              ITU-T X.25 / ISO-8208 protocol   x25(7)
       AF_AX25             Amateur radio AX.25 protocol
       AF_ATMPVC           Access to raw ATM PVCs
       AF_APPLETALK        Appletalk                        ddp(7)
       AF_PACKET           Low level packet interface       packet(7)
    

    常用type:

       SOCK_STREAM     Provides sequenced, reliable, two-way, connection-based byte streams.  An out-of-band data transmission mechanism may be supported.
    
       SOCK_DGRAM      Supports datagrams (connectionless, unreliable messages of a fixed maximum length).
    
       SOCK_RAW        Provides raw network protocol access.
    

    protoco:0 - type类型对应的的默认协议

    • bind()

    该函数是用于将本地 IP 地址绑定端口号的,若绑定其他地址则不能成功。另外,它主要用于 TCP 的连接,而在 UDP 的连接中则无必要。

    SYNOPSIS

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
    
       int bind(int sockfd, const struct sockaddr *addr,
                socklen_t addrlen);
    
    • connect()

    该函数在 TCP 中是用于 bind 的之后的 client 端,用于与服务器端建立连接。

    SYNOPSIS

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

    addr: 服务器端的地址

    • listen()

    SYNOPSIS

       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>
    
       int listen(int sockfd, int backlog);
    

    backlog: 请求连接队列中允许的最大请求数

    • accept()

    SYNOPSIS

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

    addr: 保存客户端地址

    • 发送和接受数据

    SYNOPSIS

       #include <sys/types.h>
       #include <sys/socket.h>
    
       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
    
       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
    

    SYNOPSIS

       #include <sys/types.h>
       #include <sys/socket.h>
    
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
    
       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
    
    • TCP和UDP socket框架图

    TCP
    UDP

    TCP连接实例:

    Server:

    #include <sys/types.h>
    #include <sys/socket.h>
    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <unistd.h>
    #include <netinet/in.h>
    
    #define SERVPORT 3333
    #define BACKLOG 10
    #define MAXDATASIZE 6
    
    int main()
    {
    	struct sockaddr_in server_sockaddr,client_sockaddr;
    	int sin_size = 0,recvbytes = 0;
    	int sockfd,client_fd;
    	char buf[MAXDATASIZE] = {0};
    
    	/* 建立 socket 连接 */
    	if((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1){
    		perror("socket");
    		exit(1);
    	}
    	printf("socket success!,sockfd=%d
    ",sockfd);
    
    	/* 设置 sockaddr_in 结构体中相关参数 */
    	server_sockaddr.sin_family=AF_INET;
    	server_sockaddr.sin_port=htons(SERVPORT);
    	server_sockaddr.sin_addr.s_addr=INADDR_ANY;	/* 0.0.0.0 的IP,表示主机上所有网卡的IP */
    	bzero(&(server_sockaddr.sin_zero),8);
    
    	/* 绑定函数 bind */
    	if(bind(sockfd,(struct sockaddr *)&server_sockaddr,sizeof(struct sockaddr))== -1){
    		perror("bind");
    		exit(1);
    	}
    	printf("bind success!
    ");
    
    	/* 调用 listen 函数 */
    	if(listen(sockfd,BACKLOG)== -1){
    		perror("listen");
    		exit(1);
    	}
    	printf("listening....
    ");
    
    	/* 调用 accept 函数,等待客户端的连接 */
    	if((client_fd=accept(sockfd,(struct sockaddr *)&client_sockaddr,&sin_size))== -1){
    		perror("accept");
    		exit(1);
    	}
    	printf("clent socket connect ! client sockfd=%d
    ",client_fd);
    
    	/* 调用 recv 函数接收客户端的请求 */
    	if((recvbytes=recv(client_fd,buf,MAXDATASIZE,0))== -1){
    		perror("recv");
    		exit(1);
    	}
    	printf("received a connection: %s
    ",buf);
    	close(sockfd);
    }
    

    client:

    #include <stdio.h>
    #include <stdlib.h>
    #include <errno.h>
    #include <string.h>
    #include <netdb.h>
    #include <sys/types.h>
    #include <netinet/in.h>
    #include <sys/socket.h>
    
    #define SERVPORT 3333
    #define MAXDATASIZE 100
    
    int main(int argc,char *argv[])
    {
    	int sockfd,sendbytes;
    	char buf[MAXDATASIZE] = {0};
    	struct sockaddr_in serv_addr;
    
    	if(argc < 2){
    		fprintf(stderr,"Please enter the server's hostname!
    ");
    		exit(1);
    	}
    
    	/* 创建 socket */
    	if((sockfd=socket(AF_INET,SOCK_STREAM,0))== -1){
    		perror("socket");
    		exit(1);
    	}
    	printf("client socket fd = %d
    ", sockfd);	
    
    	/* 设置 sockaddr_in 结构体中相关参数 */
    	serv_addr.sin_family=AF_INET;
    	serv_addr.sin_port=htons(SERVPORT);
    	inet_pton(AF_INET, argv[1], &serv_addr.sin_addr);
    	//inet_aton("127.0.0.1", &serv_addr.sin_addr);	
    	bzero(&(serv_addr.sin_zero),8);
    
    	/* 调用 connect 函数主动发起对服务器端的连接 */
    	if(connect(sockfd,(struct sockaddr *)&serv_addr,sizeof(struct sockaddr))== -1){
    		perror("connect");
    		exit(1);
    	}
    
    	/* 发送消息给服务器端 */
    	if((sendbytes=send(sockfd,"hello",5,0))== -1){
    		perror("send");
    		exit(1);
    	}
    
    	close(sockfd);
    }
    

    结果如下:

    用回环地址和本机IP都可以通信:

    xxx@xxx-pc:~/Documents$ ./client 127.0.0.1
    client socket fd = 3
    
    xxx@xxx-pc:~/Documents$ ./client 180.107.45.144
    client socket fd = 3
    
    xxx@xxx-pc:~/Documents$ ./server 
    socket success!,sockfd=3
    bind success!
    listening....
    clent socket connect ! client sockfd=4
    received a connection: hello
  • 相关阅读:
    Django----博客文章数据返回
    Django----admin模块初识
    Django搭建博客文章---模型层
    Js 提交 form 表单
    sql 查询 between and 和 >= <= 比较
    C# 未能加载项目文件
    不一样的 Null
    Js实现table单双行交替色
    C# 判断一个string型的时间格式是否正确
    利用 Label 小小的提升一下用户体验
  • 原文地址:https://www.cnblogs.com/fuluwwa/p/6786742.html
Copyright © 2020-2023  润新知