在之前的认识TCP/IP协议中了解了OSI七层网络模型和TCP/IP四层模型,和TCP/IP中通信的三次握手、四次挥手,还用Socket写过聊天室的通信,但直到昨天才发现自己对Socket的通信原理的理解是模糊的,所以打算再梳理一下。
OSI七层网络模型和TCP/IP四层模型
先来看下OSI七层网络模型和TCP/IP四层模型有何区别,其实本质上他们是一样的,都是对一个网络通信过程的分层模型,只是分层时候侧重点有所不同。
我们知道TCP/IP协议是互联网**协议(簇)**的统称,对网络通信制定了一系列相应的规则,是通信的基础,它提供点对点的链接机制,将数据应该如何封装、定址、传输、路由以及在目的地如何接收,都加以标准化。而OSI模型是开放式系统互联通信参考模型。因为OSI是是先有模型后有进行实践,所以它是一个完整的宏观模型,包括了硬件层(物理层),当然也包含了很多协议(比如DNS解析协议等),而TCP/IP则相反,先有协议和应用再提出了模型,且是参照的OSI模型对其分层的。TCP/IP更加侧重的是互联网通信核心(也是就是围绕TCP/IP协议展开的一系列通信协议)的分层。
二者最大的不同在于OSI是一个理论上的网络通信模型,而TCP/IP则是实际运行的网络协议。
Socket是什么?
说了那么多,开始进入正题吧。下面是TCP/IP的四层模型,但为什么我们没有看到Socekt呢?它应该把放在哪一层?
socket是在应用层和传输层之间的一个抽象层,socket本质是编程接口(API),它把TCP/IP层复杂的操作抽象为几个简单的接口供应用层调用以实现进程在网络中通信。TCP/IP只是一个协议栈,必须要具体实现,同时还要提供对外的操作接口(API),这就是Socket接口。通过Socket,我们才能使用TCP/IP协议。
JDK的java.net包下有两个类:Socket和ServerSocket,在Client和Server建立连接成功后,两端都会产生一个Socket实例,操作这个实例,完成所需的会话,而我们就通过这些API进行网络编程,不需要去关心底层的实现了。 Socket连接过程分为三个步骤:服务器监听,客户端请求,连接确认。
Socket的工作原理
我们只是会用Socekt进行通信的编程了,但socket通信流程究竟是什么样的呢?废话不扯,还是直接上张图来理解吧。
socket是"打开—读/写—关闭"模式的实现。
先来看看客户端和服务器端的实现吧,来分析一下具体步骤!
客户端:
服务器端:
1.服务器端先初始化Socket。( listenfd 从名称看就是为了要监听而创建的socket描述符)
那bind 是干嘛?是为了声明说我要占用这个端口了, 你们都别用了。所以2.绑定端口(bind)
接着 3.listen函数才是真正开始对端口监听了。
接下来是个死循环啊,啊啊也对,因为服务器端需要一直提供服务,只能坐以待命。那这个accept是干啥的呢?
4.调用accept阻塞,等待客户端来连接我。
为什么使用了listenfd , 然后返回了一个新的connfd ? 你还记得服务器要应付很多的客户端发起的连接, 所以它一定得把各个客户端区分开,怎么区分呢? 那只有用一个新的socket来表示, 可以看到后面接受/发送(写和读)消息的操作都是基于connfd 来做的。 至于之前的listenfd , 它只起到一个大门的作用了, 意思是说,欢迎敲门, 进门之后我将为你生成一个独一无二的socket描述符!(引子张大胖的Socekt,o((⊙﹏⊙))o.)
这时有个客户端初始化一个Socket,然后5.该客户端连接服务器(connect),连接成功则建立连接。此时服务器的accept 相当于和客户端的connect 一起完成了TCP的三次握手 !
连接建立以后6.客户端发送发送数据请求 7.服务器接收请求并处理,然后回应数据给客户端 8.客户端读取到的数据,最后关闭连接。 这样一次完整的交互就结束了。
还有一个问题就是socket指的是 (IP, Port), 现在我已经有了一个listenfd 的socket, 端口是80 然后每次客户端发起连接还要创建新的connfd, 因为80端口已经被占用,难道服务器端会为每个连接都创建新的端口吗?
其实新创建的connfd 并没有使用新的端口号,也是用的80, (在实现聊天室的时候,我们只是为每一个客户端的连接单独创建一个线程去处理,但并没有为每个连接都创建新的端口。然而这样处理是有漏洞的,昨晚就被问到了(′д` )…彡…彡,然后我想了下也是,如果客户端很多,那这样做服务器不得崩了才怪,那怎么解决了?我答了下用线程池吧…又扯了)
因为可以这么理解,这个socket描述符指向一个数据结构, 例如 listenfd 指向的结构是这样的:
而一旦accept 新的连接, 新的connfd 就会生成, 像下面的表格, 就生成了两个connfd , 它们俩服务器端的ip和port都是相同的, 但是客户端的IP和Port是不同的, 自然就可以区分开来了。
所以socket 得通过五元组(协议, 客户端IP, 客户端Port, 服务器端IP, 服务器端Port)来确定。
TCP/IP协议参考模型把所有的TCP/IP系列协议归类到四个抽象层中
应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等
传输层:TCP,UDP
网络层:IP,ICMP,OSPF,EIGRP,IGMP
数据链路层:SLIP,CSLIP,PPP,MTU
每一抽象层建立在低一层提供的服务上,并且为高一层提供服务
要想理解socket编程怎么通过socket关键词实现服务器和客户端通讯,必须得实现的了解tcp/ip是怎么通讯的,在这个的基础上在去理解socket的握手通讯
在tcp/ip协议中,tcp通过三次握手建立起一个tcp的链接,大致如下
第一次握手:客户端尝试连接服务器,向服务器发送syn包,syn=j,客户端进入SYN_SEND状态等待服务器确认
第二次握手:服务器接收客户端syn包并确认(ack=j+1),同时向客户端发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手
三次握手如下图:
根据tcp的三次握手,socket也定义了三次握手,也许是参考tcp的三次握手,一些计算机大神们画出了socket的三次握手的模型图
模型图如下:
在上面图的基础上,如果我们得到上面的图形,需要我们自己开发一些接口,来满足上面的通讯的三次握手,问题就出来了,我们会需要开发哪些函数
4:socket的一些接口函数原理
通过上面的图,我们清楚,我们好比一些泛型的程序员,一些理论提供者提供给了我们上面的图形的理论,我们需要做的就是讲上面的图形的抽象化的东西具体化
第一次握手:客户端需要发送一个syn j 包,试着去链接服务器端,于是客户端我们需要提供一个链接函数
第二次握手:服务器端需要接收客户端发送过来的syn J+1 包,然后在发送ack包,所以我们需要有服务器端接受处理函数
第三次握手:客户端的处理函数和服务器端的处理函数
三次握手只是一个数据传输的过程,但是,我们传输前需要一些准备工作,比如将创建一个套接字,收集一些计算机的资源,将一些资源绑定套接字里面,以及接受和发送数据的函数等等,这些功能接口在一起构成了socket的编程
下面大致的按照客户端和服务端将所需的函数详细的列举出来
上面的两个图都概述了socket的通讯原理
补充:大小端
大端:大端模式是指数据的高字节存在低地址,低字节存在高地址;
小端:小端模式是指数据的高字节存在高地址,低字节存在低地址;
网络字节序:使用大端存储模式
网络与主机字节转换函数:htons()、ntohs()、htonl()、ntohl() (注意:s 就是short l是long h是host n是network)
htons 把unsigned short类型从主机序转换到网络序
htonl 把unsigned long类型从主机序转换到网络序
ntohs 把unsigned short类型从网络序转换到主机序
ntohl 把unsigned long类型从网络序转换到主机序
5:socket的一个例子,总结上述的问题
详细就不在说明,通过一段代码详细的解释
客户端的代码:
[cpp] view plain copy
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
//SOCKET前的一些检查,检查协议库的版本,为了避免别的版本的socket,并且通过
//WSAStartup启动对应的版本,WSAStartup的参数一个是版本信息,一个是一些详细的细节,注意高低位
//WSAStartup与WSACleanup对应
int err;
WORD versionRequired;
WSADATA wsaData;
versionRequired=MAKEWORD(1,1);
err=WSAStartup(versionRequired,&wsaData);//协议库的版本信息
//通过WSACleanup的返回值来确定socket协议是否启动
if (!err)
{
printf("客户端嵌套字已经打开!\n");
}
else
{
printf("客户端的嵌套字打开失败!\n");
return 0;//结束
}
//创建socket这个关键词,这里想一下那个图形中的socket抽象层
//注意socket这个函数,他三个参数定义了socket的所处的系统,socket的类型,以及一些其他信息
SOCKET clientSocket=socket(AF_INET,SOCK_STREAM,0);
//socket编程中,它定义了一个结构体SOCKADDR_IN来存计算机的一些信息,像socket的系统,
//端口号,ip地址等信息,这里存储的是服务器端的计算机的信息
SOCKADDR_IN clientsock_in;
clientsock_in.sin_addr.S_un.S_addr=inet_addr("127.0.0.1");
clientsock_in.sin_family=AF_INET;
clientsock_in.sin_port=htons(6000);
//前期定义了套接字,定义了服务器端的计算机的一些信息存储在clientsock_in中,
//准备工作完成后,然后开始将这个套接字链接到远程的计算机
//也就是第一次握手
connect(clientSocket,(SOCKADDR*)&clientsock_in,sizeof(SOCKADDR));//开始连接
char receiveBuf[100];
//解释socket里面的内容
recv(clientSocket,receiveBuf,101,0);
printf("%s\n",receiveBuf);
//发送socket数据
send(clientSocket,"hello,this is client",strlen("hello,this is client")+1,0);
//关闭套接字
closesocket(clientSocket);
//关闭服务
WSACleanup();
return 0;
}
对应的服务端的代码
[cpp] view plain copy
#include <winsock2.h>
#include <stdio.h>
#pragma comment(lib,"ws2_32.lib")
int main()
{
//创建套接字,socket前的一些检查工作,包括服务的启动
WORD myVersionRequest;
WSADATA wsaData;
myVersionRequest=MAKEWORD(1,1);
int err;
err=WSAStartup(myVersionRequest,&wsaData);
if (!err)
{
printf("已打开套接字\n");
}
else
{
//进一步绑定套接字
printf("嵌套字未打开!");
return 0;
}
SOCKET serSocket=socket(AF_INET,SOCK_STREAM,0);//创建了可识别套接字
//需要绑定的参数,主要是本地的socket的一些信息。
SOCKADDR_IN addr;
addr.sin_family=AF_INET;
addr.sin_addr.S_un.S_addr=htonl(INADDR_ANY);//ip地址
addr.sin_port=htons(6000);//绑定端口
bind(serSocket,(SOCKADDR*)&addr,sizeof(SOCKADDR));//绑定完成
listen(serSocket,5);//其中第二个参数代表能够接收的最多的连接数
SOCKADDR_IN clientsocket;
int len=sizeof(SOCKADDR);
while (1)
{
//第二次握手,通过accept来接受对方的套接字的信息
SOCKET serConn=accept(serSocket,(SOCKADDR*)&clientsocket,&len);//如果这里不是accept而是conection的话。。就会不断的监听
char sendBuf[100];
sprintf(sendBuf,"welcome %s to bejing",inet_ntoa(clientsocket.sin_addr));//找对对应的IP并且将这行字打印到那里
//发送信息
send(serConn,sendBuf,strlen(sendBuf)+1,0);
char receiveBuf[100];//接收
recv(serConn,receiveBuf,strlen(receiveBuf)+1,0);
printf("%s\n",receiveBuf);
closesocket(serConn);//关闭
WSACleanup();//释放资源的操作
}
return 0;
}
‘我们只是简单的使用Socket与ServerSocket就完事了,那是因为底层为我们做了这么多的工作封装好让我们站在巨人肩上编程的。
————————————————
版权声明:本文为CSDN博主「ziyonghong」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。
原文链接:https://blog.csdn.net/ziyonghong/article/details/83663403