文章目录
1 概述
所谓套接字(Socket),就是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象。一个套接字就是网络上进行进程通信的一端,提供了应用层进程利用网络协议交换数据的机制。从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口。
简单来说,套接字是人们抽象出来的一个概念,它其实就是应用程序通过网络协议来进行通讯的接口。
2 Socket的主要类型
2.1 流套接字(SOCK_STREAM)
流套接字用于提供面向连接、可靠的数据传输服务。该服务将保证数据能够实现无差错、无重复传送,并按顺序接收。流套接字之所以能够实现可靠的数据服务,原因在于其使用了传输控制协议,即TCP(The Transmission Control Protocol)协议。
2.2 数据报套接字(SOCK_DGRAM)
数据报套接字提供一个无连接的服务。该服务不能保证数据传输的可靠性,数据有可能在传输过程中丢失或出现数据重复,且无法保证顺序地接收到数据。数据报套接字使用UDP(User Datagram Protocol)协议进行数据的传输。由于数据报套接字不能保证数据传输的可靠性,对于有可能出现的数据丢失情况,需要在程序中做相应的处理。
2.3 原始套接字(SOCT_RAW)
原始套接字与标准套接字(标准套接字指的是前面介绍的流套接字和数据报套接字)的区别在于:
原始套接字可以读取内核没有处理的IP数据包,而流套接字只能读取TCP协议的数据,数据报套接字只能读取UDP协议的数据。因此,如果要访问其他协议发送的数据必须使用原始套接字。
3 TCP/IP三次握手建立链接
当你想要理解socket,你就必须先理解tcp/ip
,它们之间好比送信的线路和驿站的作用。
TCP/IP
协议不同于iso
的7个分层,他是根据这7个分层,将其重新划分,归类到四个抽象层中:
- 应用层:
TFTP
,HTTP
,SNMP
,FTP
,SMTP
,DNS
,Telnet
- 传输层:
TCP
,UDP
- 网络层:
IP
,ICMP
,OSPF
,EIGRP
,IGMP
- 数据链路层:
SLIP
,CSLIP
,PPP
,MTU
每一个抽象层建立在低一层提供的服务上,并且为高一层提供服务,如下图:
那么究竟TCP/IP
协议是怎么通讯建立链接的?这就四我要讲的三次握手。
TCP协议通过三个报文段完成连接的建立,这个过程称为三次握手,过程如下图所示。
第一次握手:建立连接时,客户端发送syn包(syn=j)到服务器,并进入SYN_SENT状态,等待服务器确认;SYN:同步序列编号(Synchronize Sequence Numbers)。
第二次握手:服务器收到syn包,必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN-RECV状态。
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端和服务器进入ESTABLISHED状态,完成三次握手。
一次完整的三次握手也就是:请求—应答—再次确认。
4 TCP socket编程实例
4.1 编程步骤
下图展示了TCP socket编程的步骤:
服务器端:
其过程是首先服务器方要先启动,并根据请求提供相应服务
- 打开一通信通道并告知本地主机,它愿意在某一公认地址上的某端口接收客户端请求;
- 等待客户请求到达该端口;
- 接收到客户端的服务请求时,处理该请求并发送应答信号。接收到并发服务请求,要激活一新进程来处理这个客户请求。新进程处理此客户请求,并不需要对其它请求作出应答。服务完成后,关闭此新进程与客户的通信链路,并终止。
- 返回第(2)步,等待另一客户请求。
- 关闭服务器。
客户端:
- 打开一通信通道,并连接到服务器所在主机的特定端口;
- 向服务器发服务请求报文,等待并接收应答;继续提出请求…
- 请求结束后关闭通信通道并终止。
4.2 代码实例
客户端代码:
#include <iostream>
#include <winsock2.h>
#pragma comment (lib,"ws2_32.lib")
using namespace std;
int main()
{
char sendBuf[1024];
char receiveBuf[1024];
while (1)
{
WSADATA wsadata;
if (0 == WSAStartup(MAKEWORD(2, 2), &wsadata))
{
cout << "客户端嵌套字已打开" << endl;
}
else
{
cout << "客户端嵌套字打开失败" << endl;
}
SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
SOCKADDR_IN client_in;
client_in.sin_addr.S_un.S_addr = inet_addr("172.21.32.239");//将网络地址字符串转换成二进制形式
client_in.sin_family = AF_INET;
client_in.sin_port = htons(6000);
connect(clientSocket, (SOCKADDR*)&client_in, sizeof(SOCKADDR));
recv(clientSocket, receiveBuf, 1024, 0);
cout << "收到:" << receiveBuf << endl;
/*printf_s("%s\n", receiveBuf);*/
cout << "发出:";
gets_s(sendBuf, 1024);
send(clientSocket, sendBuf, 1024, 0);
closesocket(clientSocket);
WSACleanup();
}
return 0;
}
服务端代码:
#include <winsock2.h>
#include <stdio.h>
#include <iostream>
#define _WINSOCK_DEPRECATED_NOWARNINGS
#pragma comment(lib,"ws2_32.lib")
using namespace std;
int main()
{
char sendBuf[1024];
char receiveBuf[1024];
while (1)
{
//创建套接字,socket前的一些检查工作.
//服务的启动
WSADATA wsadata;//wsa 即windows socket async 异步套接字
if (0 != WSAStartup(MAKEWORD(2, 2), &wsadata))
{
cout << "套接字未打开" << endl;
return 0;
}
else
{
cout << "已打开套接字" << endl;
}
//parm1:af 地址协议族 ipv4 ipv6
//parm2:type 传输协议类型 流式套接字(SOCK_STREAM),数据包套接字(SOCK_DGRAM)
//parm3:ptotoc1 使用具体的某个传输协议
SOCKET serSocket = socket(AF_INET, SOCK_STREAM, 0);//创建可识别的套接字
SOCKADDR_IN addr; //需要绑定的参数,主要是本地的socket的一些信息。
addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY); //ip地址,htonl即host本机 to:to n:net l:unsigned long 大端存储,低字节在高位
addr.sin_family = AF_INET;
addr.sin_port = htons(6000); //端口 htons将无符号短整型转化为网络字节序
bind(serSocket, (SOCKADDR*)&addr, sizeof(SOCKADDR));//绑定完成
listen(serSocket, 5); //监听窗口
SOCKADDR_IN clientsocket;
int len = sizeof(SOCKADDR);
SOCKET serConn = accept(serSocket, (SOCKADDR*)&clientsocket, &len);//于客户端建立链接
cout << "发出:";
gets_s(sendBuf, 1024);
send(serConn, sendBuf, 1024, 0);
recv(serConn, receiveBuf, 1024, 0);
cout << "收到:" << receiveBuf << endl;
closesocket(serConn);//关闭
WSACleanup();//释放资源
}
return 0;
}
4.3 API接口
1. 创建套接字—socket()
应用程序在使用套接字前,首先必须拥有一个套接字,系统调用socket()向应用程序提供创建套接字的手段,其调用格式如下:
SOCKE