网络编程
在 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连接实例:
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