网络编程
(本文很多概念源自书本《Linux高性能服务器编程》)
TCP/IP协议族
TCP/IP协议族是一个四层协议系统,分别是应用层、传输层、网络层、网络接口。每一层完成不同的功能,且通过若干协议来实现,上层协议使用下层协议提供的服务。
协议 | |
---|---|
应用层 | TFTP、FTP、Telnet、DNS |
传输层 | TCP、UDP |
网际层 | ICMP、IP、ARP、RARP |
网络接口 | FDDI、PDN、PPP、IEEE 802.1A、IEEE 802.2 |
1.传输层
传输层为两台主机上的应用程序提供端到端(end to end )的通信,传输层只关心通信的起始端和目的端,而不在乎数据包的中转过程。
传输层协议主要有两个:TCP、UDP
TCP协议(Transmission Control Protocol,传输控制协议)为应用层提供可靠的、面向连接的和基于流的服务。TCP协议使用超时重传、数据确认等方式来确保数据包被正确地发送至目的端,因此TCP是可靠的。使用TCP协议通信的双方必须先建立TCP连接,并在内核中为该连接维持一些必要的数据结构,比如连接的状态、读写缓冲区,以及诸多定时器。当通信结束时,双方必须关闭连接以释放这些内核数据。基于流的数据没有边界的限制,它源源不断的从通信的一端流入另一端。发送端可以逐字节地向数据流中写入数据,接收端也可以逐字节地将它们读出。
TCP协议头
TCP三次握手,四次挥手
建立连接时的三次握手:
1.客户端向服务器发送连接请求,报文段包含SYN标志,是一个同步报文段,包含ISN值为4930的序号;
2.服务器同意与客户端建立连接,同时发送自己的ISN值为1207的序号,并对第一个同步报文段进行确认,确认值为4931;
3.客户端对第二个同步报文段进行确认,确认值为1208;
关闭连接的四次挥手:
1.第四个报文段包含FIN标志,因此是结束报文段,客户端请求关闭连接;
2.服务器确认该结束报文段,确认值4932;
3.服务器发送自己的结束报文段;
4.客户端确认该结束报文段,确认值1209;
UDP(User Datagram Protocol,用户数据报协议),它为应用层提供不可靠、无连接和基于数据报的服务。“不可靠”意味着UDP协议无法保证数据从发送端正确的传送到目的端。如果数据在中途丢失,或者目的端通过数据校验发现数据错误而将其丢弃,则UDP协议只是简单的通知应用程序发送失败,因此,使用UDP协议的引用程序通常要自己处理数据确认超时重传等逻辑。UDP协议是无连接的,即通信双方不保持一个长久的联系,因此应用程序每次发送数据都要明确指定接收端的地址。基于数据报地服务,是相对基于流的服务而言的,每个UDP数据报都有一个长度,接收端必须以该长度为最小单位将其内容一次性读出,否则数据将被截断。
UDP协议头
2.应用层
应用层负责处理应用程序的逻辑,比如文件传输、名称查询和网络管理。
Telnet:远程登录协议,它使我们能在本地完成远程任务;
DNS(Domain Name Service):域名解析协议,提供机器域名到IP地址的转换;
TFTP(Trivial File Transfer Protocol):简单文件传输协议,是TCP/IP协议族中的一个用来在客户机与服务器之间进行简单文件传输的协议,提供不复杂、开销不大的文件传输服务。
FTP(File Transfer Protocol):文件传输协议,用于Internet上的控制文件的双向传输;
封装:应用程序数据在发送到物理网络上之前,将沿着协议栈从上往下依次传递,每层协议都将在上层数据的基础上加上自己的头部信息,以实现该层的功能;
3.网际层
网络层实现数据包的选路和转发,通信的两台主机一般不是直接相连的,而是通过多个中间节点(路由器)连接的,网络层就是选择这些中间节点,以确保两台主机之间的通信路径。同时,网络层对上层协议隐藏了网络拓扑连接的细节,使得在传输层和网络应用程序看来,通信的双方是直接相连的。
IP协议:根据数据包的目的IP地址来决定如何投递它,如果数据包不能直接发送给目标主机,那么IP协议就为它寻找下一个适合的下一跳(next hop)路由器,并将数据包交付给该路由器来转发,多次重复这一过程,数据包最终到达目标主机,或者由于发送失败而被丢弃。
IP主要有以下四个主要功能:
1.数据传送
2.寻址
3.路由选择
4.数据报文的分段
IP报头
4.网络接口层
网络接口把数据链路层和物理层放在一起,对应TCP/IP概念模型的网络接口。对应的网络协议主要是:Ethernet、FDDI和能传输IP数据包的任何协议。
TCP与UDP的比较
TCP:一种可靠的、面向连接的和基于流的服务
UDP:不可靠、无连接和基于数据报的服务
TCP:一对一的
UDP:适用广播和多播
TCP:执行速度慢
UDP:执行速度快
socket
Linux中的网络编程通过socket(套接字)接口实现,socket是一种文件描述符。
进行Socket编程的常用函数有:
socket:创建一个socket
bind:用于绑定IP地址和端口号到socket
connect:该函数用于绑定之后的client端与服务器建立连接
listen:设置能处理的最大连接要求,Listen()并未开始接收连线,只是设置socket为listen模式。
accept:用来接受socket连接。
send:发送数据
recv:接收数据
TCP
对于TCP中的socket的使用:
首先是创建socket:
int socket(int family, int type, int protocol);
family:地址族,对于IPV4,为AF_INET;
type:为socket的类型共分为3种,SOCK_STREAM,SOCK_DGRM,原始套接字;
SOCK_STREAM:TCP使用,流式套接字
SOCK_DGRM:UDP使用,数据报套接字
原始套接字:原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新的网络协议的测试等
protocol:一般设置为0即可;
将TCP的服务器和客户端中使用到的函数举例说明如下:
TCP服务器:
- 创建一个socket,用函数socket()
- 绑定IP地址、端口等信息到socket上,用函数bind()
3.设置允许的最大连接数,用函数listen()
4.接收客户端上来的连接,用函数accept()
5.收发数据,用函数send()和recv(),或者read()和write()
6.关闭网络连接
#define PORT 8888
int sockfd, fd, opt = 1, ret, length;
char buf[100] = {0};
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("spckfd");
exit(1);
}
setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = PF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(-1 == ret)
{
perror("bind");
exit(1);
}
ret = listen(sockfd, 100);
if(-1 == ret)
{
perror("listen");
exit(1);
}
length = sizeof(client_addr);
fd = accept(sockfd, (struct sockaddr *)&client_addr, &length);
if(-1 == fd)
{
perror("accept");
exit(1);
}
ret = recv(fd, buf, sizeof(buf), 0);
if(-1 == ret)
{
perror("recv");
exit(1);
}
ret = send(fd, buf, sizeof(buf), 0);
if(-1 == ret)
{
perror("recv");
exit(1);
}
close(fd);
close(sockfd);
TCP客户端
1.创建一个socket,用函数socket()
2.设置要连接的对方的IP地址和端口等属性
3.连接服务器,用函数connect()
4.收发数据,用函数send()和recv(),或者read()和write()
5.关闭网络连接
#define PORT 8888
int sockfd, ret;
char buf[100] = {0};
struct sockaddr_in server_addr;
sockfd = socket(PF_INET, SOCK_STREAM, 0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = PF_INET;
server_addr.sin_port = PORT;
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = connect(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(-1 == ret)
{
perror("connect");
exit(1);
}
ret = send(sockfd, buf, strlen(buf), 0);
if(-1 == ret)
{
perror("send");
exit(1);
}
ret = recv(sockfd, buf, sizeof(buf), 0);
if(-1 == ret)
{
perror("recv");
exit(1);
}
close(sockfd);
UDP服务器
1.创建一个socket,用函数socket()
2.绑定IP地址、端口等信息到socket上,用函数bind()
3.循环接收数据,用函数recvfrom()
4.关闭网络连接
#define PORT 8888
int sockfd, ret, length;
char buf[100] = {0};
struct sockaddr_in server_addr;
struct sockaddr_in client_addr;
sockfd = socket(PF_INET, SOCK_DGRAM, 0);
if(-1 == ret)
{
perror("socket");
exit(1);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = PF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(-1 == ret)
{
perror("bind");
exit(1);
}
length = sizeof(client_addr);
ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, &length);
if(-1 == ret)
{
perror("recvform");
exit(1);
}
ret = sendto(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&client_addr, sizeof(client_addr));
if(-1 == ret)
{
perror("sendto");
exit(1);
}
close(sockfd);
UDP客户端
1.创建一个socket,用函数socket()
2.绑定IP地址、端口等信息到socket上,用函数bind()
3.设置对方的IP地址和端口等属性
4.发送数据,用函数sendto()
5.关闭网络连接
#define PORT 8888
int sockfd, ret, length;
char buf[100] = {0};
struct sockaddr_in server_addr;
sockfd = socket(PF_INET, SOCK_DGRAM, 0);
if(-1 == sockfd)
{
perror("socket");
exit(1);
}
bzero(&server_addr, sizeof(server_addr));
server_addr.sin_family = PF_INET;
server_addr.sin_port = htons(PORT);
server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");
ret = sendto(sockfd, buf, strlen(buf), 0, (struct sockaddr *)&server_addr, sizeof(server_addr));
if(-1 == ret)
{
perror("sendto");
exit(1);
}
length = sizeof(server_addr);
ret = recvfrom(sockfd, buf, sizeof(buf), 0, (struct sockaddr *)&server_addr, &length);
if(-1 == ret)
{
perror("recvfrom");
exit(1);
}
close(sockfd);