一、internet历史
1968年:ARPAnet阿帕网,是internet的雏形,传输数据不能跨主机、跨平台
1974年:TCP协议出现,能够实现跨主机平台操作,但是没有纠错功能
1983年:TCP/IP协议的出现
能够跨主机平台进行数据传输
能够进行数据纠错
协议:双方事先约定好的一组规则
二、网络协议模型
1.OSI七层协议模型
应用层:应用程序实现网络通信的接口
表示层:对数据进行加密解密解析
会话层:建立网络通信节点
传输层:实现点对点的通信
网络层:路由寻址
数据链路层:数据帧格式封装,实现数据纠错
物理层:屏蔽物理硬件差异,实现光电信号的转换
物、数、网、传、会、表、应
2.TCP/IP四层协议模型
应用层:HTTP(超文本传输协议)、FTP(文件传输协议)、NSF(网络挂载协议)、SSH(远程登录协议)
传输层:TCP协议、UDP协议
网络层:IP协议(路由寻址)、ICMP(跨传输层的通信协议)、IGMP(广播、组播)
物理网络接口层:以太网协议、ARP(ip->MAC)、RARP(MAC->ip)、ppp
TCP:
面向连接,是一种安全可靠、有序的传输层协议,它能保证数据在传输过程中,不丢失、不失序
应用场景:登录程序、传输重要文件
UDP:
无连接,是一个不安全可靠的传输层协议,他不能保证数据在传输过程中,不出错、不丢失、不失序
应用场景:流媒体软件、大型音视频传输
三、网络编程预备知识
1.IP地址
IP地址的作用:在网络中唯一标识一台主机
IP类型:
IPV4:32bit-4字节数据
表达形式:
(1)点分十进制: "192.168.16.163" 是个字符串
(1)二进制: "1.1.1.1"----->
00000001 00000001 00000001 00000001
IPV6:128bit
2.port --端口号
端口号:唯一标识一个进程,本质是一个unsigned short类型的数据
取值范围:0~65535 0不能使用
1~1023:系统端口
1024~5000:特殊应用程序
5001~65535:系统预留给用户使用的端口
3.套接字 – socket
套接字的本质:特殊的文件描述符
可以通过read/write进行数据传输
4.字节序
字节序:数据在内存中储存的方式
大端序(网络字节序):高位的数据存放在低地址位
arm架构、交换机、路由器
小端序(主机字节序):地位的数据存放在低地址位
x86架构计算机
字节序转换函数:
主机字节序转换为网络字节序:
htons(6666);
网络字节序转换为主机字节序:
ntohs(6666);
5.数据的封包和拆包
![在这里插入图片描述](https://img-blog.csdnimg.cn/4820871365424d91b0f12de16efbe705.png)
6.linux没有网络图标
重启网络管理员:
sudo service network-manager restart
四、基于socket的TCP通信 --相关函数
![在这里插入图片描述](https://img-blog.csdnimg.cn/326f16d4fe314ef48b3829b7ad0cd9f5.png)
1.创建套接字 – socket()
#include <sys/types.h>
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
返回值:成功返回套接字(sockfd),失败返回-1;
参数:
domain:地址族
AF_UNIX:本地套接字
AF_INET:IPV4
AF_INET6:IPV6
type:套接字类型
SOCK_STREAM:流式套接字
SOCK_DGARM:数据报套接字
protocol:默认填0
2.绑定本机IP地址和端口号 --bind()
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:成功返回0,失败返回-1
参数:
sockfd:socket函数创建得到的套接字
addr:
服务器地址结构的首地址
注意地址结构体和参数不一致应该强转
addrlen:
服务器地址结构的大小
IPV4对应的地址结构(每一个协议对应的地址结构不同,具体看手册):
man 7 ip
struct sockaddr_in {
sa_family_t sin_family;
in_port_t sin_port;
struct in_addr sin_addr;
};
struct in_addr {
uint32_t s_addr;
};
eg:
struct sockaddr_in caddr;
caddr.sin_family=AF_INET;
caddr.sin_port=htons(6666);
caddr.sin_addr.s_addr=inet_addr("192.168.16.74");
bind(sockfd,(struct sockaddr*)&caddr,sizeof(caddr));
地址转换 inte_addr()/inet_ntoa()
unsigned long inet_addr(char *address);
功能:点分十进制(字符串)--->整型数
返回值:成功返回32bit的整型数,失败返回-1
参数:
address:是以NULL结尾的点分十进制IPV4字符串
eg:
struct in_addr addr;
addr.s_addr = inet_addr("192.168.1.100");
char *inet_ntoa(struct in_arrr address);
功能:ip地址的整型数-->点分十进制(字符串)
返回值:成功返回字符串,失败返回NULL
参数:
address:ip地址的整型数
字节序转换 htons()/ntohs()
字节序转换函数:
主机字节序转换为网络字节序:
htons(6666);
网络字节序转换为主机字节序:
ntohs(6666);
3.设置监听套接字 --listen()
#include <sys/types.h>
#include <sys/socket.h>
int listen(int sockfd, int backlog);
返回值:成功返回0,失败返回-1
参数:
sockfd:套接字
backlog:客户端连接等待的最大队列元素个数,
但不是只能连接这么多个,连接后会退出队列,让后续网络进入队列
![在这里插入图片描述](https://img-blog.csdnimg.cn/b6a4149987d04b6099a4dc8de41c8bf9.png)
监听套接字
4.等待客户端连接 --accept()
#include <sys/types.h>
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值:
成功返回一个新的套接字,一般称他为通信套接字(connfd)或者读写套接字(rcvfd)
失败返回-1
参数:
sockfd:套接字
addr:
客户端地址结构的首地址,可以从该结构体中获取客户端的地址信息
注意地址结构体和参数不一致应该强转
addrlen:
客户端地址结构的大小的地址
5.向服务器发起连接 --connect()
#include <sys/types.h>
#include <sys/socket.h>
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:成功返回0,失败返回-1
参数:
sockfd:套接字
addr:连接服务器的地址结构的首地址
注意地址结构体和参数不一致应该强转
addrlen:地址结构的大小
注:和服务器的绑定代码一样
eg:客户端给服务器发送消息,服务器接受消息
服务器:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
printf("socket successful!\n");
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6666);
saddr.sin_addr.s_addr = inet_addr("192.168.16.163");
int s_len = sizeof(saddr);
int ret = bind(sockfd, (struct sockaddr *)&saddr, s_len);
if(ret < 0)
{
perror("bind");
exit(-1);
}
printf("bind successful!\n");
ret = listen(sockfd, 7);
if(ret < 0)
{
perror("listen");
exit(-1);
}
printf("listen successful!\n");
struct sockaddr_in caddr;
memset(&caddr, 0, sizeof(caddr));
int c_len = sizeof(caddr);
while(1)
{
printf("wait for a new client...\n");
int connfd = accept(sockfd, (struct sockaddr *)&caddr, &c_len);
if(connfd < 0)
{
perror("accept");
exit(-1);
}
printf("link successful! ip -- %s port -- %d\n", inet_ntoa(caddr.sin_addr), ntohs(caddr.sin_port));
char buf[64] = {0};
while(1)
{
memset(buf, 0, 64);
ret = read(connfd, buf, 64);
if(ret < 0)
{
perror("read");
exit(-1);
}
if(ret == 0)
{
printf("%s leave!\n", inet_ntoa(caddr.sin_addr));
break;
}
printf("recv %dbytes:%s\n", ret, buf);
}
close(connfd);
}
return 0;
}
客户端:
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
printf("socket successful!\n");
struct sockaddr_in saddr;
saddr.sin_family = AF_INET;
saddr.sin_port = htons(6666);
saddr.sin_addr.s_addr = inet_addr("192.168.16.163");
int s_len = sizeof(saddr);
int ret = connect(sockfd, (struct sockaddr *)&saddr, s_len);
if(ret < 0)
{
perror("connect");
exit(-1);
}
printf("link successful!\n");
char buf[64] = {0};
while(1)
{
fgets(buf, 64, stdin);
buf[strlen(buf)-1] = '\0';
ret = write(sockfd, buf, strlen(buf));
if(ret < 0)
{
perror("write");
exit(-1);
}
}
}
close(sockfd);
return 0;