网络编程

网络编程

在 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
原文地址:https://www.cnblogs.com/fuluwwa/p/6786742.html