Socket网络通信
前言
1、接口(套接字)是什么?
套接字是一个由主机本地应用程序所建立,被操作系统所控制的一个接口;应用程序可以依靠这个接口,使用传输层所提供的服务,从而实现跨网络发送给其他应用进程(从其他应用进程中接收消息)
常见最多的例程一般就是用Socket建立一个服务端,一个客户端,而操作系统想要实现两个程序之间的通信,是通过操控中间介质,也就是我们的套接字,来实现的。
LINUX系统下,一切的操作都是文件,而我们程序猿操作的也仍然是中间介质,就是文件描述符,这对于套接字就比较好类比了。
一、TCP协议
1.TCP协议特点
TCP是面向连接的,在传输开始时,是需要先建立连接,三次握手后,才开始传输数据;和UDP协议不同,能直接传递。
TCP提供可靠性,能实现丢失重传。实际上,网络传输上,比如在网线通信过程中,所受的干扰,或者网卡性能上,来不及接收等等可能,物理层上都有可能造成通信失败;TCP每个通信包都加了协议在其中,就支持丢失重传,但不代表着一直丢失就一直重传,也会存在丢包的情况。但基于整个通信而言,它是相对稳妥的。
如果数据包过长的时候,它会被切割成多个数据包进行传输。单对单进行通信的时候,按次序就行收发,基本不存在问题;但网络上不同,经过多个路由器、交换机等等,这些期间数据的传输序列都会发生变化,在监听包的时候,就可以发现经过多个这些中转设备时,虽然数据包1先发出,数据包2后发出,但如果传输路径数据包2比数据包1要短的话,数据包2有可能就先到达目标设备了,就有可能造成数据先后顺序对不上了。这时,TCP本身提供的序列号就给每个包,在建立3次握手连接后,提供顺序的序号就行排列,由底层按照这个序列号,排序好再发送给目标设备
TCP还提供流量控制,限定客户端以多快的速度来接收数据
TCP的连接是全双工的(举例:千兆网卡有一组接口里有两根发送线两根接收线,发送和接收同时进行,互不干扰)
2.TCP协议头
通过捉包工具,其实可以分析出来,我们的数据包内容其实分为以上的部分。
在上一章中讲到了,TCP层是基于端口号的,有源端口和目的端口。
序号:是确保数据包按顺序发送,在三次握手以后进行排序
确认号:在三次握手时用到
数据偏移:除了上方20个固定字节大小的首部以外(下方还有一个长度可变的选项)致使整个TCP协议头大小可变,真实数据位置就需要进行一定的偏移
(和UDP协议不同,UDP协议头固定,用比较机械的固定大小解包方式便能获得用户数据)
6 bit 协议类型:在建立三次握手时,需要告诉对方,当前的数据包是什么类型的,对方收到信息给予的回包也要标注确认包的数据包类型,通过标志位来标识数据包类型
窗口:实现排序实现数据可靠性的主要内容
检验和:收发数据做的校验(类似sum校验),发生错误的概率相对较小
紧急指针:指向紧急数据的最后一个字节的位置,表示这个包目前有紧急数据,控制流量会等对方全部接收完后才会发送下一次的数据;但紧急数据只要你确定这是个紧急情况,会把这些数据优先发过去(应用不是特别多)
3.窗口分析
发送方和接收方都会存在独立的窗口
发送方会确认它内部的数据什么时候才需要前移(等数据都发完的时候、超时);接收方会确认它内部的数据什么时候才需要往后移(等数据都接收完的时候);窗口往下移的时候便可以接收新的数据;
窗口基本上就是一个缓存的大小,发送数据和读取数据都不是调用接口后就立即能发送或者接收,都是会排到窗口队列中,等待窗口进行发送;接收也同样的道理,需要等窗口数据处理完后(做好排序后)才能被用户读到
所以说是实现数据可靠性的主要部分
二、程序示例
1.创建一个或N个Socket
这里使用的是 DEV C++ 的编辑编译环境,需要配置一下编译链接内容“工具”–>“编译选项”,
在“编译器”–>“编译时加入以下指令”键入
-lwsock32
因为在WINDOWS下需要引用库才能使用其中的API接口
#include <windows.h>
#include <stdio.h>
int main(int argc,char* argv[])
{
int i;
//1、初始化动态链接库
WSADATA ws;//定义一个结构体对象
/*
MAKEWORD定义版本号,把结构体传过去,把对动态库的引用+1 ;
Windows下对动态库的引用不会反复添加,当所有程序都退出这个库的时候,才会从内存卸载
*/
WSAStartup(MAKEWORD(2,2),&ws);//不调用的话,会导致Socket失败
//4、 为了演示资源数量,循环创建一下Socket
for(i=0;i<2000;i++)
{
//2、创建 Socket 套接字
//AF_INET是属于TCP/IP的网络通信协议,用户还可以使用其他的一些诸如蓝牙之类的协议 ;传输层是TCP Or UDP ;最后一个参数在做原始套接字的时候可能会用到
int sock = socket(AF_INET,SOCK_STREAM,0);
//对于Socket通信的步骤,每一步都必须判断是否成功,成功的判断标准就是返回值是否<0
if(sock == -1)
{
//Windows一般资源耗尽才会创建失败,一般很难失败;但Linux上的每个任务分配的(句柄数量)都是有限的 ~ 默认1024个
printf("Create Socket Failed !\r\n");
return -1;
}
printf("[%d]",sock);
//3 Or 5、打印后关闭掉 Socket 或者 循环创建时,不关闭
closesocket(sock); //会发现,如果循环创建时,原资源没关闭时则一直被占用,没有释放,所以只能使用新资源去创建 (Linux 系统下单个进程对于文件句柄资源有限则,所以会出现创建失败)
//Linux Console: ulimit -n #查询单进程最大限制资源数 ulimit -n 3000 可以把最大限制资源数设置到 3000
}
return 0;
}
上面的程序为了演示一下,在Windows环境下,对于Socket函数的创建,以及查看能最大申请的资源