TCP/IP
在世界各地,各种各样的电脑运行着各自不同的操作系统,从而为大家服务,这些电脑在表达同一种信息的时候使用的方法千差万别。就好像圣经中上帝打乱了各地人的口音,让他们无法合作一样。计算机使用者意识到,计算机只是单兵作战并不能发挥太大的作用。也只有把它们联合在一起,电脑才会发挥出它最大的潜力。于是人们就想方设法的用电线或者光纤将电脑连接到了一起。
但是简单的连到一起是远远不够的,就好像语言不通的两个人互相见了面,完全不能交流信息。因而他们需要定义一些共通的东西来进行交流,TCP/IP就是应运而生。TCP/IP不是一个协议,而是一个协议族的统称。里面包括了IP协议、IMCP协议TCP协议以及我们更加熟悉的http、ftp、pop3协议等等。电脑有了这些协议,就好像学会了外语一样,就可以和其他的计算机终端做自由的交流了。
OSI参考模型
上图中给出的是OSI七层理想网络模型,但是5、6层对应会话层和表示层在实际运用中并不存在。
而物理层一般的我们也不会太关心,所以就会导致TCP/IP4层模型(不包括物理层)和TCP/IP5层模型(包括物理层)的说法,这些说法都没有错。
TCP/IP协议
TCP/IP协议族按照层次由上到下。层层包装。最上面的就是应用层了,这里面有http、ftp等我们熟悉的协议。
而第二层则是传输层,著名的TCP和UDP协议就是在这个层次。
第三层是网络层,IP协议就在这里,它负责对数据加上IP地址和其它的数据以确定传输的目标。
第四层是数据链路层,这个层次为待传输的数据加入一个以太网协议头并进行CRC编码,为最后的数据传输做准备。再往下则是硬件层了,负责网络的传输,这个层次的定义包括网线的制式,网卡的定义等等(这些我们就不用关心了因为我们也不做网卡),所以有些书并不把这个层次放在TCP/IP协议族里面,因为它几乎和TCP/IP协议族的编写者没有任何关系。发送协议的主机自上而下将数据按照协议封装,而接收数据的主机则按照协议从得到的数据包解开,最后拿到需要的数据。这种结构非常有栈的味道,所以某些文章也把TCP/IP协议族成为TCP/IP协议栈。
IP地址
网络上每一个节点都必须有一个独立的Internet地址(也叫做IP地址)。现在,通常使用的IP地址是一个32bit的数字,也就是我们常说的IPV4标准,这32bit的数字分成四组,也就是常见的255.255.255.255的样式。IPV4标准上,地址被分为五类,我们常用的是B类地址。具体的分类请参考其它文档。需要注意的是IP地址是网络号+主机号的组合,这非常重要。
域名系统
域名系统是一个分布的数据库,它提供将主机名(就是网址了)转换成IP地址的服务。
端口号
注意,这个号码是用在TCP,UDP上的一个逻辑号码,并不是一个硬件端口,我们平时说的某某端口封掉了,也只是在IP层次把带有这个号码的IP包给过滤掉了而已。
应用编程接口
现在常用的编程接口有SOCKET,还有一种现在已经淘汰了,它是TLI接口。
TCP通讯简单示例
下面使用TCP协议实现的服务端和客户端的简单通讯,当服务端连接到10客户端的时候,自动退出连接,同时也给所有的客户端发送一条退出命令,确保它们都能退出。如果服务端已经退出,还有客户端没有退出,那么客户端就会出现错误。
服务端
// EasyWinSocketDemoServer.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
const int MAX_CLIENT_NUMBER = 10;
int main()
{
WSAData wsaData;
int iErrorNumber = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iErrorNumber != 0)
{
printf("WSAStartup failed with error: %d\n", iErrorNumber);
return 1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
else
{
printf("The Winsock 2.2 dll was found okay\n");
}
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
sockaddr_in sockAddr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(10086);
sockAddr.sin_addr.S_un.S_un_b.s_b1 = 127;
sockAddr.sin_addr.S_un.S_un_b.s_b2 = 0;
sockAddr.sin_addr.S_un.S_un_b.s_b3 = 0;
sockAddr.sin_addr.S_un.S_un_b.s_b4 = 1;
bind(s, (sockaddr *)&sockAddr, sizeof(sockaddr_in));
listen(s, 1);
SOCKET socketCilentArray[MAX_CLIENT_NUMBER] = { INVALID_SOCKET };
int iNum = 0;
while (true)
{
socketCilentArray[iNum++] = accept(s, nullptr, nullptr);
char strBuf[MAXBYTE] = { 0 };
printf("client %d connected! %s\r\n", socketCilentArray[iNum - 1], strBuf);
if (iNum == MAX_CLIENT_NUMBER)
{
break;
}
}
for (int i = 0; i<MAX_CLIENT_NUMBER; ++i)
{
send(socketCilentArray[i], "Exit", 5, 0);
}
for (int i=0; i<MAX_CLIENT_NUMBER; ++i)
{
closesocket(socketCilentArray[i]);
}
Sleep(1000);
closesocket(s);
WSACleanup();
system("pause");
return 0;
}
客户端
// EasyWinSocketDemoClient.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <WinSock2.h>
#include <Windows.h>
#pragma comment(lib, "ws2_32.lib")
int main()
{
WSAData wsaData;
int iErrorNumber = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iErrorNumber != 0)
{
printf("WSAStartup failed with error: %d\n", iErrorNumber);
return 1;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2)
{
printf("Could not find a usable version of Winsock.dll\n");
WSACleanup();
return 1;
}
else
{
printf("The Winsock 2.2 dll was found okay\n");
}
SOCKET s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (s == INVALID_SOCKET)
{
wprintf(L"socket failed with error: %ld\n", WSAGetLastError());
WSACleanup();
return 1;
}
sockaddr_in sockAddr;
sockAddr.sin_family = AF_INET;
sockAddr.sin_port = htons(10086);
sockAddr.sin_addr.S_un.S_un_b.s_b1 = 127;
sockAddr.sin_addr.S_un.S_un_b.s_b2 = 0;
sockAddr.sin_addr.S_un.S_un_b.s_b3 = 0;
sockAddr.sin_addr.S_un.S_un_b.s_b4 = 1;
int iResult = connect(s, (sockaddr *)&sockAddr, sizeof(sockAddr));
if (iResult == SOCKET_ERROR)
{
wprintf(L"connect failed with error: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}
iResult = send(s, "Hello", sizeof("Hello") + 1, 0);
if (iResult == SOCKET_ERROR)
{
wprintf(L"send failed with error: %d\n", WSAGetLastError());
closesocket(s);
WSACleanup();
return 1;
}
printf("Bytes Sent: %d\n", iResult);
char buf[MAXBYTE] = { 0 };
while (true)
{
recv(s, buf, MAXBYTE, 0);
printf("Recv:%s\r\n", buf);
if (strcmp(buf, "Exit") == 0)
break;
}
closesocket(s);
WSACleanup();
system("pause");
return 0;
}
这里需要注意的是:
- 头文件的包含顺序,WinSock2.h一定要在Windows.h之前,否则编译就会出错;
- lib库的包函数,#pragma comment(lib, “ws2_32.lib”);
- 服务端需要等到所有客户端退出之后才能退出,否则客户端就会出问题。