Linux网络编程(虚拟机网络连接,UDP,TCP/IP协议,IP地址,端口号,字节序,socket系统调用,套接字编程)
1.网络编程相关知识:
1.1.虚拟机网络连接:
桥接:桥接到物理机网卡上,虚拟机、物理机、外网都可以进行相互访问
仅主机:虚拟机仅和物理机进行通信
NAT:和桥接对比,外网不能访问虚拟机
1.2.常用命令:
ifconfig命令:
查看以及配置linux下的网络
#ifconfig //查看网络配置
#ifconfig ens33 192.168.15.2 //设置ens33网络
vim /etc/network/interfaces
#ifconfig ens33 up/down
打开或者关闭网卡
ping命令
测试当前网络和另一个IP地址是否网络连通
ping xxxxip地址 //一直阻塞终端
ping -c%d xxxip地址 //ping %d 次
重启网络
service networking restart
1.3.关于网络:
TCP/IP协议架构与OSI七层模型:
TCP/IP协议:
传输控制/网际协议(Transfer Control Protocol/Internet Protocol) 又称作网络通讯协议
是Internet最基本的协议、Internet国际互联网络的基础,由网络层的IP协议和传输层的TCP协议组成
2.传输层:
2.1.通信协议:
TCP:
利用套接字进行通信
TCP协议编程框架:
特点:
传输安全可靠,传输过程有三次握手,实时性差
密码验证等
基于连接的通信(可靠通信):
通信前两个进程先握手建立连接,数据不会丢失
TCP三次握手建立连接:
UDP:
利用套接字进行通信
特点:
传输不可靠,实时性强
用于音频,视频传输
UDP编程框架:
基于无连接通信(不可靠通信)
通信前无连接,数据可能丢失
UDP
2.2.IP地址:
组成:
网络ID:
指定 了主机所属的网络
主机ID:
标识了位于该网络中的主机
分类:
ipv4: 包含32位
0.0.0.0~255.255.255.255
点分十进制形式,将地址的4个字节写成以个十进制数字
ipv6:128位
区分:
子网掩码
划分:网络ID全为1,主机ID全为0
表示:
192.168.1.0/24
可分配的因特网地址为 192.168.1.1---------192.168.1.254
主机ID全为0的用来标识网络本身
主机ID全为1标识子网广播地址
255.255.255.0
特殊IP:
127.0.0.1:回环地址,分配给主机名localhost
127.0.0.0/8中的所有地址均可分配为回环地址,通常使用127.0.0.0;
INADDR_ANY: ipv4通配地址,常用于将socket绑定到多宿主机的应用程序
2.3.IP转换函数:
ipv4:
原型:
int inet_aton(const char *cp, struct in_addr *inp);
//点分十进制IP-->网络字节的32位二进制数值
int inet_aton(const char *cp, struct in_addr *inp);
//点分十进制IP-->网络字节的32位二进制数值
int inet_aton(const char *cp, struct in_addr *inp);
//点分十进制IP-->网络字节的32位二进制数值
char *inet_ntoa(struct in_addr in);
//32-->点分十进制
in_addr_t inet_addr(const char *cp);
//点分十进制IP-->网络字节的32位二进制数值并返回
所属头文件:
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
参数:
cp:存放点分十进制IP地址字符串
inp:传出参数,保存32位二进制数值
typedef uint32_t in_addr_t;
struct in_addr {
in_addr_t s_addr;
};
ipv4和ipv6:
原型:
int inet_pton(int af, const char *src, void *dst);
//类比inet_aton
const char *inet_ntop(int af, const void *src,char *dst, socklen_t cnt);
所属头文件:
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
参数:
af:AF_INET:表示为ipv4
AF_INET6:表示为ipv6
cnt为转换后的长度(字符串的长度)
2.3:端口号:
一个是16位无符号整形,0-65535
区分一台主机接收到的数据包应该转交给哪个进程来进行处理:
1到1023(1到255之间为众所周知端口,256~1023端口通常由UNIX系统占用)
注册端口:1024~49150; 可以使用
动态或私有端口:49151~65535
套接字和端口:
2.4:地址名字转化:
原型:
struct hostent* gethostbyname(const char* hostname);
//将域名(www.baidu.com)或主机名转换为 IP 地址
struct hostent* gethostbyaddr(const char* addr, size_t len, int family);
//将 IP 地址转换为域名或主机名
所属头文件:
#include <netdb.h>
参数:
hostname:指向存放域名或主机名的字符串
struct hostent
{
char *h_name; /正式主机名/
char **h_aliases; /主机别名/
int h_addrtype; /主机 IP 地址类型 IPv4 为 AF_INET/
int h_length; /主机 IP 地址字节长度,对于 IPv4 是 4 字节,即 32 位/
char **h_addr_list; /主机的 IP 地址/
}
addr :IP 地址,通过函数 inet_aton()转换
len : IP 地址的长度, AF_INET 为 4。
family 可用 AF_INET:Ipv4 或 AF_INET6: Ipv6
2.5:字节序:
不同类型CPU的主机中,内存存储多字节整数序列有两种方法,称为主机字节序(HBO):
小端序(little-endian)
存储数据是先存最低有效位
低序字节存储在低地址,Intel、AMD等采用的是这种方式
大端序(big-endian)
存储数据是先存最高有效位
高序字节存储在低地址,ARM、Motorola等所采用
网络中传输的数据必须按网络字节序,即大端字节序
当应用进程将整数送入socket前,需要转化成网络字节序;当应用进程从socket取出整数后,要转化成主机节序
转化:
uint32_t htonl(uint32_t hostlong);
//本函数将一个32位数从主机字节顺序转换成无符号长整型网络字节顺序
//host ----> network long
uint16_t htons(uint16_t hostshort);
//将一个无符号短整型的主机数值转换为网络字节顺序
//short
uint32_t ntohl(uint32_t netlong);
//将一个无符号长整形数从网络字节顺序转换为主机字节顺序。
uint16_t ntohs(uint16_t netshort);
//将一个16位数由网络字节顺序转换为主机字节顺序。
2.5:socket系统调用:
socket()
系统调用创建一个新的socket
bind()
系统调用将一个socket绑定到一个地址上,统称,服务器需要使用这个调用来将其socket绑定到一个总所周知的地址上使得客户端能够定位到该socket上
listen()
系统调用允许一个流socket接收来自其他socket的介接入链接
accept()
系统调用在一个监听流socket上接受来自一个队等应用程序的连接,并可选的返回对等socket的地址
connect()
系统调用建立与另一个socket之间的连接
3.套接字编程:
服务器操作流程:
3.1:什么是套接字:
网络接口函数集,打开的网络,对比文件操作中的文件描述符,socket有描述符、IP地址、端口号等
分类:
流式套接字(SOCK_STREAM):
提供可靠的、面向连接的通信流,保证数据传输的正确性和顺序性—TCP
数据报(SOCK_DGRAM):
无连接的服务,独立报文传输,无序,不保证是可靠、无差错的----UDP
原始套接字(SOCK_RAW):很少用,对底层协议直接访问,用于一些协议开发
3.2:创建socket,用socket函数:
原型:
int socket(int domain, int type, int protocol);
头文件:
#include <sys/socket.h>
参数:
domain: 网域
AF_INET :IPv4
AF_INET6 :IPv6
AF_UNIX :本地通讯
type:选择传输协议 tcp /udp
SOCK_STREAM ;tcp
SOCK_DGRAM : udp
protocol: 基本废弃,一般赋0
返回值:
成功返回描述网络套接字sockfd,失败返回-1
3.3:绑定ip和端口号等信息socket
用bind函数:
功能:
绑定一个端口号和 IP 地址,使套接口与指定的端口号和 IP 地址相关联。
原型:
int bind(int sockfd, const struct sockaddr *my_addr, socklen_t addrlen);
头文件:
#include <sys/socket.h>
参数:
sockfd 为前面 socket 的返回值
my_addr:封装ip地址和端口号:
struct sockaddr //此结构体不常用
{
unsigned short int sa_family; //调用 socket()时
的 domain 参数,即 AF_INET 值。
char sa_data[14]; //最多使用 14 个字符长度
};
#include<netinet/in.h>
struct sockaddr_in //常用的结构体
{
unsigned short int sin_family; //AF_INET
uint16_t sin_port; //为使用的 port 编号
= htons(12345)
struct in_addr sin_addr; //为 IP 地址
unsigned char sin_zero[8]; //未使用
};
addrlen:第二个参数大小
返回值:
成功则返回 0,失败返回-1
3.4:设置允许的最大连接数:
listen函数:
功能:
使服务器的这个端口和 IP 处于监听状态,等待网络中某一客户机的连接请求。如果客户端有连接请求,端口就会接受这个连接。
原型:
int listen(int sockfd, int backlog);
头文件:
#include <sys/socket.h>
参数:
sockfd 为前面 socket 的返回值.即 sfd
backlog 指定同时能处理的最大连接要求,通常为 10 或者 5。 最大值可设至 128
返回值:
成功则返回 0,失败返回-1
3.5:等待来自客户端的连接请求:
accept函数:
功能:
接受远程计算机的连接请求,建立起与客户机之间的通信连接
原型
int accept(int sockfd, struct sockaddr *addr,socklen_t *addrlen);
头文件:
#include <sys/socket.h>
参数:
sockfd 为前面 socket 的返回值.即 sfd
addr:用于接受客户端的ip地址和端口号
addrlen:第二个参数大小
返回值:
返回新的套接字描述符,专门用于与建立的客户端通信,失败-1
3.6:收发数据:
TCP:
发送:
功能:
用新的套接字发送数据给指定的远端主机
原型
ssize_t send(int s, const void *buf, size_t len, int flags);
头文件:
#include <sys/socket.h>
参数:
s:新的套接字
buf:要发送的数据缓冲区
len: 数据长度
flags: 一般赋0 .阻塞
返回值:
成功返回真正发送的数据长度,失败-1
接收:
功能:
用新的套接字来接收远端主机传来的数据,并把数据存到由参数 buf 指向的内存空间
原型:
ssize_t recv(int s, void *buf, size_t len, int flags);
头文件:
#include <sys/socket.h>
参数:
s:新的套接字
buf:存放接收数据的缓冲区
len: 数据长度
flags: 一般赋0 .阻塞
返回值:
成功返回真正接收的数据长度,失败-1
udp:
发送:
原型:
ssize_t sendto(int s, const void *buf, size_t len, int
flags, const struct sockaddr *to, socklen_t tolen);
头文件:
#include <sys/socket.h>
参数:
s:新的套接字
buf:要发送的数据缓冲区
len: 数据长度
flags: 一般赋0 .阻塞
to:服务器ip和端口号
tolen;上一个参数大小
返回值:
成功返回真正发送的数据长度,失败-1
接收:
原型:
ssize_t recvfrom(int s, void *buf, size_t len, int flags,
struct sockaddr *from, socklen_t *fromlen);
头文件:
#include <sys/socket.h>
参数:
s:新的套接字
buf:要发送的数据缓冲区
len: 数据长度
flags: 一般赋0 .阻塞
from:源机地址和端口号
tolen:地址长度
返回值:
成功返回真正接收的数据长度,失败-1
3.7:关闭网络连接:
close(sockfd);
客户端操作流程:
3.1: 创建socket,用socket函数;
3.2:设置要连接的服务器的ip地址和端口等属性
连接服务器
用connect函数:
功能:
用来请求连接远程服务器, 将参数 sockfd 的 socket 连至参数 serv_addr 指定的服务器 IP和端口号上去
原型:
int connect(int sockfd, const struct sockaddr*serv_addr, socklen_t addrlen);
头文件:
#include <sys/socket.h>
参数:
sockfd 为前面 socket 的返回值,即 sfd
serv_addr 为结构体指针变量,存储着远程服务器的 IP 与端口号信息。
addrlen 表示结构体变量的长度
返回值:
成功则返回 0,失败返回-1
3.3:收发数据,send和recv或者read和write
3.4:关闭网络连接
案例演示:
实现不同主机之间的服务器和客户端之间的通信交流:
(下面代码只能实现一台主机和服务器之间的通信)
参考代码:
sever.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define PORT 6789
#define IP "192.168.43.156"
/*
TCP 服务器
1. 创建套节字
2. 绑定 IP 端口
3. 监听
4. 等待连接
5. 收发数据
6. 关闭
*/
int main()
{
int ret;
//创建套节字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket");
return -1;
}
printf("连接套节字 创建成功\n");
//绑定
struct sockaddr_in addr,cli_addr;
int len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(IP);
ret = bind(sockfd,(struct sockaddr *)&addr,len);
if(ret == -1)
{
perror("bind");
return -1;
}
printf("绑定成功\n");
//监听
ret = listen(sockfd,3);
if(ret == 0)
printf("监听成功\n");
else
return -1;
//等待连接
int comfd = accept(sockfd,(struct sockaddr *)&cli_addr,&len);
if(comfd == -1)
return -1;
printf("客户端 %s 已连接\n",inet_ntoa(cli_addr.sin_addr));
//收发数据
char r_buf[100]={0};
char w_buf[100]={0};
while(1)
{
memset(r_buf,0,sizeof(r_buf));
ret = recv(comfd,r_buf,sizeof(r_buf),0);
if(ret == -1)
return -1;
printf("server recv: %s\n",r_buf);
printf("server 发送:");
memset(w_buf,0,sizeof(w_buf));
scanf("%s",w_buf);
send(comfd,w_buf,sizeof(w_buf),0);
}
//关闭
close(sockfd);
close(comfd);
return 0;
}
client.c
#include <stdio.h>
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <string.h>
#include <unistd.h>
#define PORT 6789
#define IP "192.168.43.156"
/*
TCP 客户端
1. 创建套节字
2. 连接服务器
3. 收发数据
4. 关闭
*/
int main()
{
int ret;
//创建套节字
int sockfd = socket(AF_INET,SOCK_STREAM,0);
if(sockfd == -1)
{
perror("socket");
return -1;
}
printf("连接套节字 创建成功\n");
//连接
struct sockaddr_in addr;
int len = sizeof(struct sockaddr_in);
addr.sin_family = AF_INET;
addr.sin_port = htons(PORT);
addr.sin_addr.s_addr = inet_addr(IP);
ret = connect(sockfd,(struct sockaddr *)&addr,len);
if(ret == -1)
{
perror("bind");
return -1;
}
printf("连接成功\n");
//收发数据
char r_buf[100]={0};
char w_buf[100]={0};
while(1)
{
printf("client 发送:");
memset(w_buf,0,sizeof(w_buf));
scanf("%s",w_buf);
send(sockfd,w_buf,sizeof(w_buf),0);
memset(r_buf,0,sizeof(r_buf));
ret = recv(sockfd,r_buf,sizeof(r_buf),0);
if(ret == -1)
return -1;
printf("client recv: %s\n",r_buf);
}
//关闭
close(sockfd);
return 0;
}
ifconfig
运行结果:
补充:重要的数据结构:
数据结构体:
struct sockaddr {
unsigned short sa_family; /地址族/ ipv4 ipv6
char sa_data[14]; /*14 节的协议地址,包含该 socket 的 IP 地址和端口号。
*/
};
常用结构体:
struct sockaddr_in {
__kernel_sa_family_t sin_family; /* short int型 地址族 2 个字节 Address family /
__be16 sin_port; / unsigned short int 型 端口号 2 个字节,区分应用 Port number
/
struct in_addr sin_addr; / IP 地址 4 个字节 Internet address */
/*填充 0 以保持与 struct sockaddr 同样大小 Pad to size of `struct sockaddr’.
*/
unsigned char __pad[SOCK_SIZE - sizeof(short int) -
sizeof(unsigned short int) - sizeof(struct in_addr)];
};
sa_family:
AF:
address family
地址族
PF:
procotol family
协议族
sa_data:
14字节的特定协议地址
参考结构流程图:
Linux网络编程
注:需要使用xmind软件进行查看