套接字(Socket)是用来实现主机与主机通信的一个接口,它屏蔽了底层协议,让用户能够实现各种类型的通信操作,实验室是网络通信中应用程序对应的进程与网络协议之间的接口,如下图:
最初,套接字由加利福尼亚大学伯克利分校开发的,他们是为了在Unix下实现TCP/IP协议,而开发了这样一个编程接口,方便调用网络操作,实际就是一套应用程序接口(API),此API称为Socket,现在Socket几乎是TCP/IP网络标准的API,很多基于TCP/IP的网络应用程序都是基于Socket编写的。
1.为什么要使用Socket?
应用层通过传输层进行数据通信时,TCP和UDP会同时遇到同时为多个应用程序进程提供并发服务的问题,多个TCP连接或多个应用程序进程可能需要通过同一个TCP协议端口传输数据,为了区别不同的应用程序进程和连接,许多计算机操作系统为应用程序与TCP/IP协议交互提供了一个Socket接口,区分不同应用程序进程间的网络通信和连接,套接字位于协议之上,屏蔽了不同网络协议之间的差异。
2.关于套接字
生成套接字,主要有三个参数:通信的目的IP地址、使用的传输层协议(TCP或UDP)和使用的端口号。将这三个参数结合起来,与一个Socket绑定,应用层就可以和传输层通过Socket接口,区分来自不同应用程序进程或网络连接的通信,实现数据连接的并发服务。
常用的套接字有三种:流(Stream)套接字、数据报(Datagram)套接字、原始(Raw)套接字。
流套接字:提供可靠的双向顺序数据流,可保证数据在传输过程中不会丢失、破坏或重复出现。流套接字是通过INET地址族的TCP实现,它提供了双向、有序、无重复的及无记录边界的数据流服务,它是面向连接的,在发送数据前,双方必须建立数据传输链路,同时还必须对传输的数据进行验证,确保数据的准确性。
数据报套接字:也提供双向的数据传输,但并不对数据的传输进行担保,即数据可能会以错误的顺序传递,甚至丢失或破坏,这种类型的套接字是通过INET地址族的UDP协议实现的,它是面向无连接的,也支持双向的数据流,但不保证数据的准确性,但保留了记录边界,并不保证接收端是否在侦听,数据报套接字传输效率比较高。
原始套接字:可以让进程直接访问底层协议,例如:可以在某个以太网设备上打开原始套接字,然后获取原始的IP数据传输信息。原始套接字可以构造原始的IP数据报文,而前面的两种套接字只能接收到用户数据,因此可以用原始套接字对网络数据进行操作,例如可以接收原始IP报文分析数据内容,很多嗅探器就是用此实现的。还可以利用原始IP报文进行构造网络数据包,然后发送到网络上,网络端口扫描就是利用此来做的,所以原始套接字用在网络安全编程领域比较广泛。
3.套接字基本操作
(1).创建套接字:SOCKET socket(int af,int type,int protocol) 其中返回值是一个SOCKET类型,其实就是一个整数,表示套接字句柄
af:协议的地址的地址族,IPv4对应的是AF_INET,也是使用最多的类型;
type:协议的套接字类型,可以是SOCKET_STREAM(使用流套接字)、SOCKET_DGRM(使用的是数据报套接字)等
protocol:使用的协议类型,如面向连接的是IPPROTO_TCP,面向无连接的是IPPROTO_UDP
(2)绑定套接字:int bind();
(3)监听套接字:int listen();
(4)接收套接字:SOCKET accept();
(5)链接套接字:int connect();
(6)发送数据:int send()(面向连接)/int sendto()(面向无连接)
(7)接收数据:int recv()(面向连接)/int revcfrom()(面向无连接)
(8)关闭套接字:int closesocket()
4.服务类型
(1)面向连接
服务器端执行步骤:
1)使用函数WSAStartup()初始化套接字;
2)调用函数Socket()构造一个套接字;
3)用函数bind()进行绑定;
4)用函数listen()进行监听;
5)用函数accept()接收客户端的连接,此时服务器阻塞,直到有客户端连接服务器才返回,并且创建一个新的套接字,用来传输数据;
6)在新的套接字上使用rec()函数接收数据;也可以调用send()发送数据,注意,此时所有的操作都是在新的套接字上;
7)在使用完套接字传送数据后,可以调用函数closesocket()来关闭套接字,释放所有的资源;
8)最后调用函数WSACleanup()释放WinSock
客户端执行步骤:
1)使用函数WSAStartup()初始化套接字;
2)调用函数socket()构造一个套接字;
3)调用函数connect()来连接服务器;
4)连接服务器成功后,可以用函数send()发送数据,也可以用函数revc()接收数据;
5)数据传送完毕后,使用closesocket()来关闭套接字;
6)使用WSACleanup()释放Winsock;
(2)面向无连接
服务器端的执行顺序:
1)使用函数WSAStartup()初始化套接字;
2)调用函数socket()构造一个套接字;
3)调用函数recvfrom()接收数据,也可以使用函数sendto()发送数据,注意,此时再调用recvfrom()函数之后,它就阻塞在那里,等待客户端发送数据,然后才能返回;
4)等待数据传输完毕,关闭套接字;
5)最后使用WSACleanup()释放Winsock。
客户端的执行顺序:
1)使用函数WSAStartup()初始化套接字;
2)调用函数socket()构造一个套接字;
3)然后使用bind()进行绑定;
4)使用函数sendto()发送数据,也可使用recvfrom()接收数据,注:此时不需要用connect()函数与服务器进行连接,因为是无链接。
5)等待数据传输完毕,关闭套接字;
6)最后使用WSACleanup()释放Winsock。