流式套接字为网络应用程序提供了可靠的、面向链接的双向数据传输服务,实现了数据无差错、无重复的发送。它内设流量控制,被传输的数据看做是无记录便捷的字节流,在TCP/IP协议簇中,使用TCP协议来实现字节流传输,当用户想要发送大批量的数据或者对数据传输有较高要求的时候,就可使用流式套接字。固然,它适合于大多数应用场景,也是初学者使用套接字编程的主要方法。ios
TCP协议是一个面链接的传输层协议,提供高可靠性字节流传输服务,主要用与一次传输要交换大量报文情形。
为了维护传输的的可靠性,TCP增长了许多开销,如:确认、流量控制、计时器以及链接管理等。程序员
端到端通讯:TCP提供给应用面向链接的接口。TCP链接时端到端的,客户应用程序在一端,服务器在另外一端。
创建可靠链接:TCP要求客户应用程序在与服务器交换数据前,先链接服务器,保证链接可靠创建,创建链接测试了网络的连通性。若是有故障发生,阻碍了分组到达远端系统,或者服务器不接受链接,那么企图链接就会失败,客户就会获得通知。
可靠交付:一旦创建链接,TCP保证数据将按发送时的顺序交付,没有丢失,也没有重复,若是由于故障而不能创建可靠交付,发送方会获得通知。
具备流控的传输:TCP控制数据传输的效率,防止发送数据的速率快与接收方的接收速率,所以TCP能够用于从快速计算机向慢速计算机传输数据。
双工传输:在任什么时候候,单个TCP链接都容许同时双向传送数据,并且不会相互影响,所以客户能够向服务器发送请求,而服务器能够经过同一个链接发送应答。
流模式:TCP从发送方向接收方发送没有报文边界的字节流。web
TCP数据被封装在一个IP数据包中!!!以下图所示:
下图则显示了TCP首部的数据格式,若是不记选项字段,他们一般是20个字节。
面试
信号 | 做用 |
---|---|
URG | 紧急指针是否有效 |
ACK | 确认号是否有效 |
PSH | 提示接收端应用程序马上从TCP缓冲区把数据读走 |
RST | 对方要求从新创建链接; 咱们把携带RST标识的称为复位报文 |
SYN | 请求创建链接; 咱们把携带SYN标识的称为同步报文段 |
FIN | 通知对方, 本端要关闭了, 咱们称携带FIN标识的为结束报文段 |
为了创建一条TCP链接,须要如下三个步骤来实现:编程
注意(面试官必考):通常由客户决定什么时候终止链接,由于客户进程一般由用户交互控制,例如Telnet的用户会键入quit命令来终止进程,既然一个TCP链接是双工的(即数据在两个方向上能同时传递),那么每一个方向必须单独关闭。终止一个链接要通过四次交互,当一方完成它的数据发送任务以后,发送一个FIN报文段来终止这个方向的链接。当一段收到FIN,他必须通知应用层另外一端已经终止了那个方向的数据传输。发送FIN报文段一般是应用层进行关闭的结果。下图显示了“四次挥手”的过程:服务器
在该链接关闭过程当中咱们发现,当四次挥手完成后,客户并无直接关闭链接,而是进入TIME_WAIT状态,且此状态会保留两个最大段生存时间(2MSL),等待2MSL时间以后,客户也关闭链接并释放它的资源。网络
为何须要TIME WAIT状态呢?设立TIME WAIT有两个目的:并发
一般状况下,仅有主动关闭链接的一方会进人 TIME WAIT状态。RFC793 中定义MSL为2分钟,在这个定义下,链接在TIME WAIT状态下保持4分钟,而实际中,MSL的值在不一样的TCP协议实现中的定义并不相同。若是链接处于TIME WAIT状态期间有报文段到达,则从新启动一个2MSL计时器。socket
在客户和服务器创建链接和断开链接的交互过程当中,双方端点所经历的TCP状态发生了次特物为发生网络环境异常时,这些状态的变迁有助于理解和解释基于流式套接字的应用程序在运行中的表现。tcp
方式套接字基于可靠的数据流传输服务,这种服务的特色是面向链接、可靠。面向链接占决定了 流式套接字的传输代价大,且只适合于一对的数据传输;而可靠的特 点意味下层应用程序在设计开发时不须要过多地考虑数据传输过程当中的丢失、乱序、重复问题。总结来看,流式套接字适合在如下场合使用:
1大数据量的数据传输应用。流式套接字适合文件传输这类大数据量传输的应用,传输的内容能够是任意大的数据,其类型能够是ASCII文本,也能够是二进制文件。在这种应用数据传输量大,对数据传输的可靠性要求比较高,且与数据传输的代价相比,链接场景下,维护的代价微乎其微。
2)可靠性要求高的传输应用。流式套接字适合应用在可靠性要求高的传输应用中,在这种状况下,可靠性是传输过程首先要知足的要求,若是应用程序选择使用UDP协议或其余不可靠的传输服务承载数据,那么为了不数据丢失、乱序、重复等问题,程序员必需要考虑以上诸多问题带来的应用程序的错误,由此带来复杂的编码代价。
流式套接字的网络通讯过程是在链接成功创建的基础上完成的。
(1)基于流式套接字的服务器进程的通讯过程在通讯过程当中,服务器进程做为服务提供方,被动接受链接请求,决定接受或拒绝该请求,并在已创建好的链接上完成数据通讯。其基本通讯过程以下:
1 ) Windows Sockets DLL初始化,协商版本号;
2)建立套接字,指定使用TCP (可靠的传输服务)进行通讯;
3)指定本地地址和通讯端口;
4)等待客户的链接请求;
5)进行数据传输;
6)关闭套接字;
7)结束对Windows Sockets DLL的使用,释放资源。
(2)基于流式套接字的客户进程的通讯过程
在通讯过程当中,客户进程做为服务请求方,主动请求创建链接,等待服务器的链接确在已创建好的链接上完成数据通讯。其基本通讯过程以下:
1 ) Windows Sockets DLL初始化,协商版本号;
2)建立套接字,指定使用TCP (可靠的传输服务)进行通讯;
3)指定服务器地址和通讯端口;
4)向服务器发送链接请求;
5)进行数据传输;
6)关闭套接字;
7)结束对Windows Sockets DLL的使用,释放资源。
客户端:
#define _CRT_SECURE_NO_WARNINGS 1 // ShuruxinxClient.cpp : 客户端程序,用户能够从键盘输入信息并发送给服务器。 // #include<iostream> #include <WinSock2.h> #include <Windows.h> #include <WS2tcpip.h> #include<string.h> #pragma comment (lib,"ws2_32.lib") #pragma warning(disable :4996) #define SERVER_PORT "8888" #define BUFFER_LEN 512 using namespace std; #define SERVER_PORT "8888" #define BUFFER_LEN 512 int main(int argc, char * argv[]) { struct addrinfo* result = NULL, *ptr = NULL, hints; WSADATA wsaData; SOCKET ConnectSocket; char sendbuf[BUFFER_LEN]; char recvbuf[BUFFER_LEN]; int iResult; if (argc != 1) { printf("Usage: %s server ip address\n", argv[0]); return 1; } iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; iResult = getaddrinfo(NULL, SERVER_PORT, &hints, &result);//将输入参数argv[1]中指定的服务器信息写入result if (iResult != 0) { printf("getaddrinfo failed with error: %d\n", iResult); WSACleanup(); return 1; } ConnectSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);//使用result指定的信息建立套接字 if (ConnectSocket == INVALID_SOCKET) { printf("socket failed with error: %ld\n", WSAGetLastError()); WSACleanup(); return 1; } iResult = connect(ConnectSocket, result->ai_addr, result->ai_addrlen);//使用套接字ConnectSocket向result中指定的服务器请求链接 if (iResult == SOCKET_ERROR) { printf("connect failed with error: %ld\n", iResult); closesocket(ConnectSocket); WSACleanup(); return 1; } freeaddrinfo(result);//释放动态分配的地址信息结构体result while (gets_s(sendbuf) != NULL) { //从键盘获取输入字符串 if (*sendbuf == 'Q') { closesocket(ConnectSocket); return 0; } iResult = send(ConnectSocket, sendbuf, strlen(sendbuf), 0); if (iResult == SOCKET_ERROR) { printf("send failed with error: %d\n", WSAGetLastError()); closesocket(ConnectSocket); WSACleanup(); return 1; } do { memset(recvbuf, 0, BUFFER_LEN * sizeof(char)); iResult = recv(ConnectSocket, recvbuf, strlen(recvbuf), 0); if (iResult > 0) { printf("Received message from client: %d\n", iResult); } else if (iResult == 0) { printf("请继续输入要发送数据:"); } else { printf("recv failed with error:%d\n", WSAGetLastError()); } } while (iResult > 0); } closesocket(ConnectSocket); WSACleanup(); return 0; }
服务器:
// DuokehuServer.cpp : 为多客户提供服务的服务器端程序。 // #define _CRT_SECURE_NO_WARNINGS 1 #include<iostream> #include <WinSock2.h> #include <Windows.h> #include <WS2tcpip.h> #include<string.h> #pragma comment (lib,"ws2_32.lib") #pragma warning(disable :4996) #define SERVER_PORT "8888" #define BUFFER_LEN 512 using namespace std; int main(int argc, char * argv[]) { WSADATA wsaData; SOCKET ListenSocket = INVALID_SOCKET; SOCKET ClientSocket = INVALID_SOCKET; struct addrinfo hints, *result = NULL; struct sockaddr_in clientaddr; char sendbuf[BUFFER_LEN]; char recvbuf[BUFFER_LEN]; int iResult, isendResult; memset(recvbuf, 0, BUFFER_LEN * sizeof(char)); iResult = WSAStartup(MAKEWORD(2, 2), &wsaData); if (iResult != 0) { printf("WSAStartup failed with error: %d\n", iResult); return 1; } ZeroMemory(&hints, sizeof(hints)); hints.ai_family = AF_INET; hints.ai_socktype = SOCK_STREAM; hints.ai_protocol = IPPROTO_TCP; hints.ai_flags = AI_PASSIVE; iResult = getaddrinfo(NULL, SERVER_PORT, &hints, &result); if (iResult != 0) { printf("getaddrinfo failed with error %d\n", iResult); WSACleanup(); return 1; } ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (ListenSocket == INVALID_SOCKET) { printf("socket failed with error %d\n", WSAGetLastError()); freeaddrinfo(result); closesocket(ListenSocket); WSACleanup(); return 1; } iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen); if (iResult == SOCKET_ERROR) { printf("bind failed with error %d\n", WSAGetLastError()); freeaddrinfo(result); closesocket(ListenSocket); WSACleanup(); return 1; } freeaddrinfo(result); iResult = listen(ListenSocket, SOMAXCONN); if (iResult == SOCKET_ERROR) { printf("listen failed with error %d\n", WSAGetLastError()); freeaddrinfo(result); closesocket(ListenSocket); WSACleanup(); return 1; } for (;;) { int addrlenth = sizeof(clientaddr); ClientSocket = accept(ListenSocket, (struct sockaddr*)& clientaddr, &addrlenth); if (iResult == INVALID_SOCKET) { printf("accept failed with error %d\n", WSAGetLastError()); closesocket(ListenSocket); WSACleanup(); return 1; } char* peeraddr = inet_ntoa(clientaddr.sin_addr); do { iResult = recv(ClientSocket, recvbuf, BUFFER_LEN, 0); if (iResult > 0) { printf("接收客户端的消息: %s\n", recvbuf); ZeroMemory(&recvbuf, sizeof(hints)); isendResult = send(ClientSocket, sendbuf, strlen(sendbuf), 0); if (isendResult == SOCKET_ERROR) { printf("send failed with error %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); break; } printf("接收成功\n"); } else if (iResult == 0) { printf("Connection closing...\n"); iResult = shutdown(ClientSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed with error %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); break; } closesocket(ClientSocket); WSACleanup(); break; } else { printf("recv failed with error:%d\n", WSAGetLastError()); iResult = shutdown(ClientSocket, SD_SEND); if (iResult == SOCKET_ERROR) { printf("shutdown failed with error %d\n", WSAGetLastError()); closesocket(ClientSocket); WSACleanup(); break; } } } while (iResult > 0); } closesocket(ListenSocket); WSACleanup(); return 0; }
通讯截图以下: