网络通讯做为互联网的技术支持,已被普遍应用在软件开发中,不管是Web,服务端,客户端仍是桌面应用,都是必须掌握的一门技术。web
在软件开发层面实现远程数据交换的编程技术。
复制代码
要熟悉网络编程,首先须要学习网络协议的相关知识。面试
什么网络协议呢?网络协议是为网络中进行数据交换定义的规则,以实现按此规范进行传输数据,就可在整个互联网中进行数据交换的目标。因此说网络协议是网络通讯的基础。编程
网络协议中其中最著名就是TCP/IP协议族。TCP/IP协议族一般被认为是一个四层的协议系统。从上到下依次为:服务器
从TCP/IP协议族各层次的职责来看,网路数据的传递是从上到下依次传递。以Http协议为为例,具体的数据传输过程如图所示:网络
固然,这只是数据的流向,实际上网络数据在传输过程当中须要封装与分解,具体的过程如图所示(图片来源于百度图片):socket
简单来讲,数据从本机上传到网络以前,会在TCP/IP协议族的每一层添加协议首部,到达目标主机后,目标主机再进行分解,从而获取须要的数据。学习
在众多的网络协议中,使用最普遍的应该就是TCP协议(传输控制协议)了,它是一种面向链接,可靠的,基于字节流的传输层协议。其运行过程分为三个阶段:创建链接、交换数据、断开链接。spa
在使用TCP协议交换数据前,需先创建一条链接,在不须要发送数据的时候,须要断开链接,以释放资源。操作系统
TCP协议创建链接时须要三次握手,其过程可以使用如下场景来描述:code
面试官:说一下TCP创建链接时三次握手的过程。
小明: 三次握手?
面试官: 嗯。
小明:握手完成了。
面试官:what?
复制代码
没错,从小明开始问三次握手到说握手完成就是三次握手的过程,具体的过程以下:
为何须要3次握手而不是2次呢?
由于客户端收到服务端的应答后,知道链接已创建成功,可是服务端并不知道本身发送的确认报文客户端是否收到,
因此须要客户端对服务端的报文进行确认。
复制代码
服务端必定能收到客户端发送的确认报文么?
不必定,若是收不到,那么链接就不会创建,因此,3次握手只是理论上确保创建链接的次数。那可否经过4次握手呢?
不行,再握下去就是鸡生蛋,蛋生鸡的问题了。
复制代码
TCP断开链接时须要四次握手,为何须要4次呢?这是因为TCP半关闭的性质形成的。所谓半关闭,就是能够发送数据,却不能接收数据或只能接收数据,不能发送数据。
四次握手的过程:(因为主动断开链接可发送在客户端,也可发送在服务端,因此下面以A,B来区分两端)
B端在对A端进行确认的时候为何不一样时发一个FIN呢? 能够同时发。分开发是为了考虑B端在对A端确认后,可能还会给A继续发送数据的状况。
下面以一张图来描述TCP的状态变迁过程(图片来自百度图片)
图片中的全部状态对应于TCP的创建和链接过程,下面简单介绍一下这几种状态:
LISTEN: 服务端状态,表示服务端正在等待客户端的链接请求,处于监听状态;
SYN收到:服务端状态,服务端已收到客户端的链接请求,并对客户端的请求发送了ACK确认(第二次握手完成);
SYN_SENT: 客户端状态,客户端发送SYN或数据后的状态(第一次握手完成);
ESTABLISHED: 客户端对服务端的SYN进行确认后处于ESTABLISHED,服务端收到客户端发送的ACK后也会处于
ESTABLISHED状态(三次握手完成后的状态);
FIN_WAIT_1: 主动关闭的一端的状态,发送FIN后的状态(断开链接时的第一次握手完成);
FIN_WAIT_2: 主动关闭的一端的状态,收到另外一端的ACK确认后的状态(断开链接时的第二次握手完成);
CLOSING:主动关闭的一端的状态,收到另外一端的FIN,并对其进行确认后的状态(客户端和服务端同时关闭的状况);
TIME_WAIT: 主动关闭的一端的状态,,收到另外一端的FIN或(FIN和ACK)后,对其进行确认后的状态
(断开链接时最后一次握手完成);
CLOSE_WAIT: 被动关闭的一端的状态,收到另外一端发送的FIN并对其进行确认后的状态(断开链接时第二次握手完成);
LAST_ACK: 被动关闭的一端的状态,发送FIN后的状态(断开链接时第三次握手完成);
CLOSED:链接完全断开;
复制代码
TCP/IP协议族诞生以后,各个平台(Window, Unix)就按照此协议规范在系统层面为开发网络程序提供了统一的接口——Socket。经过这个面向传输层协议的系统接口,咱们可经过TCP/UDP协议快速实现网络数据的交换,同时也可用来实现应用层协议,如HTTP, SSL等。
Socket是操做系统为上层应用实现网络数据交换提供的接口,咱们可经过如下场景来理解:
当你给别人打电话的时候首先要确认打给谁,其次确认打哪一个号码,经过这两个条件就可准确的联系到对方。那么在网络中传输数据也是一样的道理,在网络中定位主机是经过IP来实现的,一个IP表明了一台主机,可是每台主机有不少个端口号,因此要准确地与某个应用进行数据交换,除了IP地址外,还须要一个端口号。有了这两个条件,就可经过Socket实现数据交换。因而可知,Socket其实就至关于一部手机,两部手机之间创建一条通路便可实现通话。
代码实现(Linux C编程):
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <netdb.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
int sockfd = 0, n = 0;
char recvBuff[1024];
struct sockaddr_in serv_addr;
if(argc != 2)
{
printf("\n Usage: %s <ip of server> \n",argv[0]);
return 1;
}
memset(recvBuff, '0',sizeof(recvBuff));
// 建立socket
if((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0)
{
printf("\n Error : Could not create socket \n");
return 1;
}
// 设置IP和端口
memset(&serv_addr, '0', sizeof(serv_addr));
serv_addr.sin_family = AF_INET;
serv_addr.sin_port = htons(5000);
if(inet_pton(AF_INET, argv[1], &serv_addr.sin_addr)<=0)
{
printf("\n inet_pton error occured\n");
return 1;
}
// 链接到指定的IP和端口 -> 链接成功后即三次握手完成
if( connect(sockfd, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0)
{
printf("\n Error : Connect Failed \n");
return 1;
}
// 读数据
while ( (n = read(sockfd, recvBuff, sizeof(recvBuff)-1)) > 0)
{
recvBuff[n] = 0;
if(fputs(recvBuff, stdout) == EOF)
{
printf("\n Error : Fputs error\n");
}
}
if(n < 0)
{
printf("\n Read error \n");
}
close(scokfd);
return 0;
}
复制代码
代码实现:(Linux C编程)
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <time.h>
int main(int argc, char *argv[])
{
int listenfd = 0, connfd = 0;
struct sockaddr_in serv_addr;
char sendBuff[1025];
time_t ticks;
// 建立socket
listenfd = socket(AF_INET, SOCK_STREAM, 0);
memset(&serv_addr, '0', sizeof(serv_addr));
memset(sendBuff, '0', sizeof(sendBuff));
// 绑定IP地址和端口
serv_addr.sin_family = AF_INET;
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY);
serv_addr.sin_port = htons(5000);
bind(listenfd, (struct sockaddr*)&serv_addr, sizeof(serv_addr));
// 监听客户端口的链接请求 -> 对应于状态图中的LISTEN状态
listen(listenfd, 10);
while(1)
{
// 接收客户端的请求 -> 与客户端三次握手完成
connfd = accept(listenfd, (struct sockaddr*)NULL, NULL);
ticks = time(NULL);
snprintf(sendBuff, sizeof(sendBuff), "%.24s\r\n", ctime(&ticks));
// 向客户端发送数据
write(connfd, sendBuff, strlen(sendBuff));
// 关闭socket
close(connfd);
sleep(1);
}
}
复制代码
代码出处 https://www.thegeekstuff.com/2011/12/c-socket-programming/
经过以上代码,咱们对socket有了一个简单的认识,同时也了解了数据交换的基本流程。后面会对基于TCP协议的HTTP协议进行一个详细的介绍。
《TCP/IP详解 卷一》