怎么用C实现Http POST功能向Http服务器上传文件

怎么用C实现Http POST功能向Http服务器上传文件,第1张

用socket就行了。

和服务器建立请求。

然后发送请求报文"\r\n\r\n"结束之后是数据

post分为,application/x-www-form-urlencoded和multipart/form-data boundary=

要是上传文件,就得使用multipart/form-data boundary=...

服务器那边根据boundary来解析出数据。

代码很长,如下:

/*server.h*/

#pragma comment(lib, "WS2_32")

#include <WinSock2.h>

#include <iostream>

//#include <stdio.h>

#include <assert.h>

#ifndef COMMONDEF_H

#define COMMONDEF_H

#define MAX_PACKET_SIZE 10240// 数据包的最大长度,单位是sizeof(char)

#define MAXFILEDIRLENGTH 256 // 存放文件路径的最大长度

#define PORT 4096// 端口号

#define SERVER_IP"127.0.0.1" // server端的IP地址

// 各种消息的宏定义

#define INVALID_MSG -1 // 无效的消息标识

#define MSG_FILENAME 1 // 文件的名称

#define MSG_FILELENGTH 2 // 传送文件的长度

#define MSG_CLIENT_READY3 // 客户端准备接收文件

#define MSG_FILE 4 // 传送文件

#define MSG_SENDFILESUCCESS5 // 传送文件成功

#define MSG_OPENFILE_ERROR10 // 打开文件失败,可能是文件路径错误找不到文件等原因

#define MSG_FILEALREADYEXIT_ERROR 11 // 要保存的文件已经存在了

class CCSDef

{

public:

#pragma pack(1) // 使结构体的数据按照1字节来对齐,省空间

// 消息头

struct TMSG_HEADER

{

charcMsgID // 消息标识

TMSG_HEADER(char MsgID = INVALID_MSG)

: cMsgID(MsgID)

{

}

}

// 请求传送的文件名

// 客户端传给服务器端的是全路径名称

// 服务器传回给客户端的是文件名

struct TMSG_FILENAME : public TMSG_HEADER

{

char szFileName[256] // 保存文件名的字符数组

TMSG_FILENAME()

: TMSG_HEADER(MSG_FILENAME)

{

}

}

// 传送文件长度

struct TMSG_FILELENGTH : public TMSG_HEADER

{

long lLength

TMSG_FILELENGTH(long length)

: TMSG_HEADER(MSG_FILELENGTH), lLength(length)

{

}

}

// Client端已经准备好了,要求Server端开始传送文件

struct TMSG_CLIENT_READY : public TMSG_HEADER

{

TMSG_CLIENT_READY()

: TMSG_HEADER(MSG_CLIENT_READY)

{

}

}

// 传送文件

struct TMSG_FILE : public TMSG_HEADER

{

union // 采用union保证了数据包的大小不大于MAX_PACKET_SIZE * sizeof(char)

{

char szBuff[MAX_PACKET_SIZE]

struct

{

int nStart

int nSize

char szBuff[MAX_PACKET_SIZE - 2 * sizeof(int)]

}tFile

}

TMSG_FILE()

: TMSG_HEADER(MSG_FILE)

{

}

}

// 传送文件成功

struct TMSG_SENDFILESUCCESS : public TMSG_HEADER

{

TMSG_SENDFILESUCCESS()

: TMSG_HEADER(MSG_SENDFILESUCCESS)

{

}

}

// 传送出错信息,包括:

// MSG_OPENFILE_ERROR:打开文件失败

// MSG_FILEALREADYEXIT_ERROR:要保存的文件已经存在了

struct TMSG_ERROR_MSG : public TMSG_HEADER

{

TMSG_ERROR_MSG(char cErrorMsg)

: TMSG_HEADER(cErrorMsg)

{

}

}

#pragma pack()

}

#endif

/*server.cpp*/

#include "server.h"

char g_szNewFileName[MAXFILEDIRLENGTH]

char g_szBuff[MAX_PACKET_SIZE + 1]

long g_lLength

char* g_pBuff = NULL

// 初始化socket库

bool InitSocket()

// 关闭socket库

bool CloseSocket()

// 解析消息进行相应的处理

bool ProcessMsg(SOCKET sClient)

// 监听Client的消息

void ListenToClient()

// 打开文件

bool OpenFile(CCSDef::TMSG_HEADER* pMsgHeader, SOCKET sClient)

// 传送文件

bool SendFile(SOCKET sClient)

// 读取文件进入缓冲区

bool ReadFile(SOCKET sClient)

int main()

{

InitSocket()

ListenToClient()

CloseSocket()

return 0

}

void ListenToClient()

{

// 创建socket套接字

SOCKET sListen = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)

if (SOCKET_ERROR == sListen)

{

printf("Init Socket Error!\n")

return

}

// 绑定socket到一个本地地址

sockaddr_in sin

sin.sin_family = AF_INET

sin.sin_port = htons(PORT)

sin.sin_addr.S_un.S_addr = INADDR_ANY

if (::bind(sListen, (LPSOCKADDR)&sin, sizeof(sockaddr_in)) == SOCKET_ERROR)

{

printf("Bind Error!\n")

return

}

// 设置socket进入监听状态

if (::listen(sListen, 10) == SOCKET_ERROR)

{

printf("Listen Error!\n")

return

}

printf("Listening To Client...\n")

// 循环接收client端的连接请求

sockaddr_in ClientAddr

int nAddrLen = sizeof(sockaddr_in)

SOCKET sClient

while (INVALID_SOCKET == (sClient = ::accept(sListen, (sockaddr*)&ClientAddr, &nAddrLen)))

{

}

while (true == ProcessMsg(sClient))

{

}

// 关闭同客户端的连接

::closesocket(sClient)

::closesocket(sListen)

}

bool InitSocket()

{

// 初始化socket dll

WSADATA wsaData

WORD socketVersion = MAKEWORD(2, 2)

if (::WSAStartup(socketVersion, &wsaData) != 0)

{

printf("Init socket dll error\n")

return false

}

return true

}

bool CloseSocket()

{

// 释放winsock库

::WSACleanup()

if (NULL != g_pBuff)

{

delete [] g_pBuff

g_pBuff = NULL

}

return true

}

bool ProcessMsg(SOCKET sClient)

{

int nRecv = ::recv(sClient, g_szBuff, MAX_PACKET_SIZE + 1, 0)

if (nRecv >0)

{

g_szBuff[nRecv] = '\0'

}

// 解析命令

CCSDef::TMSG_HEADER* pMsgHeader = (CCSDef::TMSG_HEADER*)g_szBuff

switch (pMsgHeader->cMsgID)

{

case MSG_FILENAME:// 文件名

{

OpenFile(pMsgHeader, sClient)

}

break

case MSG_CLIENT_READY: // 客户端准备好了,开始传送文件

{

SendFile(sClient)

}

break

case MSG_SENDFILESUCCESS: // 传送文件成功

{

printf("Send File Success!\n")

return false

}

break

case MSG_FILEALREADYEXIT_ERROR: // 要保存的文件已经存在了

{

printf("The file reay to send already exit!\n")

return false

}

break

}

return true

}

bool ReadFile(SOCKET sClient)

{

if (NULL != g_pBuff)

{

return true

}

// 打开文件

FILE *pFile

if (NULL == (pFile = fopen(g_szNewFileName, "rb"))) // 打开文件失败

{

printf("Cannot find the file, request the client input file name again\n")

CCSDef::TMSG_ERROR_MSG tMsgErrorMsg(MSG_OPENFILE_ERROR)

::send(sClient, (char*)(&tMsgErrorMsg), sizeof(CCSDef::TMSG_ERROR_MSG), 0)

return false

}

// 把文件的长度传回到client去

fseek(pFile, 0, SEEK_END)

g_lLength = ftell(pFile)

printf("File Length = %d\n", g_lLength)

CCSDef::TMSG_FILELENGTH tMsgFileLength(g_lLength)

::send(sClient, (char*)(&tMsgFileLength), sizeof(CCSDef::TMSG_FILELENGTH), 0)

// 处理文件全路径名,把文件名分解出来

char szDrive[_MAX_DRIVE], szDir[_MAX_DIR], szFname[_MAX_FNAME], szExt[_MAX_EXT]

_splitpath(g_szNewFileName, szDrive, szDir, szFname, szExt)

strcat(szFname,szExt)

CCSDef::TMSG_FILENAME tMsgFileName

strcpy(tMsgFileName.szFileName, szFname)

printf("Send File Name: %s\n", tMsgFileName.szFileName)

::send(sClient, (char*)(&tMsgFileName), sizeof(CCSDef::TMSG_FILENAME), 0)

// 分配缓冲区读取文件内容

g_pBuff = new char[g_lLength + 1]

if (NULL == g_pBuff)

{

return false

}

fseek(pFile, 0, SEEK_SET)

fread(g_pBuff, sizeof(char), g_lLength, pFile)

g_pBuff[g_lLength] = '\0'

fclose(pFile)

return true

}

// 打开文件

bool OpenFile(CCSDef::TMSG_HEADER* pMsgHeader, SOCKET sClient)

{

CCSDef::TMSG_FILENAME* pRequestFilenameMsg = (CCSDef::TMSG_FILENAME*)pMsgHeader

// 对文件路径名进行一些处理

char *p1, *p2

for (p1 = pRequestFilenameMsg->szFileName, p2 = g_szNewFileName

'\0' != *p1

++p1, ++p2)

{

if ('\n' != *p1)

{

*p2 = *p1

}

if ('\\' == *p2)

{

*(++p2) = '\\'

}

}

*p2 = '\0'

ReadFile(sClient)

return true

}

// 传送文件

bool SendFile(SOCKET sClient)

{

if (NULL == g_pBuff)

{

ReadFile(sClient)

}

int nPacketBufferSize = MAX_PACKET_SIZE - 2 * sizeof(int)// 每个数据包存放文件的buffer大小

// 如果文件的长度大于每个数据包所能传送的buffer长度那么就分块传送

for (int i = 0i <g_lLengthi += nPacketBufferSize)

{

CCSDef::TMSG_FILE tMsgFile

tMsgFile.tFile.nStart = i

if (i + nPacketBufferSize + 1>g_lLength)

{

tMsgFile.tFile.nSize = g_lLength - i

}

else

{

tMsgFile.tFile.nSize = nPacketBufferSize

}

//printf("start = %d, size = %d\n", tMsgFile.tFile.nStart, tMsgFile.tFile.nSize)

memcpy(tMsgFile.tFile.szBuff, g_pBuff + tMsgFile.tFile.nStart, tMsgFile.tFile.nSize)

::send(sClient, (char*)(&tMsgFile), sizeof(CCSDef::TMSG_FILE), 0)

Sleep(0.5)

}

delete [] g_pBuff

g_pBuff = NULL

return true

}

/*client.h同server.h*/

/*client.cpp*/

#include "client.h"

long g_lLength = 0

char* g_pBuff = NULL

char g_szFileName[MAXFILEDIRLENGTH]

char g_szBuff[MAX_PACKET_SIZE + 1]

SOCKET g_sClient

// 初始化socket库

bool InitSocket()

// 关闭socket库

bool CloseSocket()

// 把用户输入的文件路径传送到server端

bool SendFileNameToServer()

// 与server端连接

bool ConectToServer()

// 打开文件失败

bool OpenFileError(CCSDef::TMSG_HEADER *pMsgHeader)

// 分配空间以便写入文件

bool AllocateMemoryForFile(CCSDef::TMSG_HEADER *pMsgHeader)

// 写入文件

bool WriteToFile(CCSDef::TMSG_HEADER *pMsgHeader)

// 处理server端传送过来的消息

bool ProcessMsg()

int main()

{

InitSocket()

ConectToServer()

CloseSocket()

return 0

}

// 初始化socket库

bool InitSocket()

{

// 初始化socket dll

WSADATA wsaData

WORD socketVersion = MAKEWORD(2, 2)

if (::WSAStartup(socketVersion, &wsaData) != 0)

{

printf("Init socket dll error\n")

exit(-1)

}

return true

}

// 关闭socket库

bool CloseSocket()

{

// 关闭套接字

::closesocket(g_sClient)

// 释放winsock库

::WSACleanup()

return true

}

// 与server端连接进行文件的传输

bool ConectToServer()

{

// 初始化socket套接字

if (SOCKET_ERROR == (g_sClient = ::socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)))

{

printf("Init Socket Error!\n")

exit(-1)

}

sockaddr_in servAddr

servAddr.sin_family = AF_INET

servAddr.sin_port = htons(PORT)

servAddr.sin_addr.S_un.S_addr = ::inet_addr(SERVER_IP)

if (INVALID_SOCKET == (::connect(g_sClient, (sockaddr*)&servAddr, sizeof(sockaddr_in))))

{

printf("Connect to Server Error!\n")

exit(-1)

}

// 输入文件路径传输到server端

SendFileNameToServer()

// 接收server端传过来的信息,直到保存文件成功为止

while (true == ProcessMsg())

{

}

return true

}

// 把用户输入的文件路径传送到server端

bool SendFileNameToServer()

{

char szFileName[MAXFILEDIRLENGTH]

printf("Input the File Directory: ")

fgets(szFileName, MAXFILEDIRLENGTH, stdin)

// 把文件路径发到server端

CCSDef::TMSG_FILENAME tMsgRequestFileName

strcpy(tMsgRequestFileName.szFileName, szFileName)

if (SOCKET_ERROR == ::send(g_sClient, (char*)(&tMsgRequestFileName), sizeof(CCSDef::TMSG_FILENAME), 0))

{

printf("Send File Name Error!\n")

exit(-1)

}

return true

}

// 处理server端传送过来的消息

bool ProcessMsg()

{

CCSDef::TMSG_HEADER *pMsgHeader

int nRecv = ::recv(g_sClient, g_szBuff, MAX_PACKET_SIZE + 1, 0)

pMsgHeader = (CCSDef::TMSG_HEADER*)g_szBuff

switch (pMsgHeader->cMsgID)

{

case MSG_OPENFILE_ERROR: // 打开文件错误

{

OpenFileError(pMsgHeader)

}

break

case MSG_FILELENGTH:// 文件的长度

{

if (0 == g_lLength)

{

g_lLength = ((CCSDef::TMSG_FILELENGTH*)pMsgHeader)->lLength

printf("File Length: %d\n", g_lLength)

}

}

break

case MSG_FILENAME: // 文件名

{

return AllocateMemoryForFile(pMsgHeader)

}

break

case MSG_FILE: // 传送文件,写入文件成功之后退出这个函数

{

if (WriteToFile(pMsgHeader))

{

return false

}

}

break

}

return true

}

// 打开文件失败

bool OpenFileError(CCSDef::TMSG_HEADER *pMsgHeader)

{

if (NULL != g_pBuff)

return true

assert(NULL != pMsgHeader)

printf("Cannot find file!Please input again!\n")

// 重新输入文件名称

SendFileNameToServer()

return true

}

// 查找是否已经存在了要保存的文件,同时分配缓冲区保存文件

bool AllocateMemoryForFile(CCSDef::TMSG_HEADER *pMsgHeader)

{

assert(NULL != pMsgHeader)

if (NULL != g_pBuff)

{

return true

}

首先http是一个应用层的协议,在这个层的协议,只是一种通讯规范,也就是因为双方要进行通讯,大家要事先约定一个规范。

1.连接 当我们输入这样一个请求时,首先要建立一个socket连接,因为socket是通过ip和端口建立的,所以之前还有一个DNS解析过程,把www.mycompany.com变成ip,如果url里不包含端口号,则会使用该协议的默认端口号。

DNS的过程是这样的:首先我们知道我们本地的机器上在配置网络时都会填写DNS,这样本机就会把这个url发给这个配置的DNS服务器,如果能够

找到相应的url则返回其ip,否则该DNS将继续将该解析请求发送给上级DNS,整个DNS可以看做是一个树状结构,该请求将一直发送到根直到得到结

果。现在已经拥有了目标ip和端口号,这样我们就可以打开socket连接了。

2.请求 连接成功建立后,开始向web服务器发送请求,这个请求一般是GET或POST命令(POST用于FORM参数的传递)。GET命令的格式为:GET 路径/文件名 HTTP/1.0

文件名指出所访问的文件,HTTP/1.0指出Web浏览器使用的HTTP版本。现在可以发送GET命令:

GET /mydir/index.html HTTP/1.0,

3.应答 web服务器收到这个请求,进行处理。从它的文档空间中搜索子目录mydir的文件index.html。如果找到该文件,Web服务器把该文件内容传送给相应的Web浏览器。

为了告知浏览器,,Web服务器首先传送一些HTTP头信息,然后传送具体内容(即HTTP体信息),HTTP头信息和HTTP体信息之间用一个空行分开。

常用的HTTP头信息有:

① HTTP 1.0 200 OK  这是Web服务器应答的第一行,列出服务器正在运行的HTTP版本号和应答代码。代码"200 OK"表示请求完成。

② MIME_Version:1.0 它指示MIME类型的版本。

③ content_type:类型 这个头信息非常重要,它指示HTTP体信息的MIME类型。如:content_type:text/html指示传送的数据是HTML文档。

④ content_length:长度值 它指示HTTP体信息的长度(字节)。

4.关闭连接:当应答结束后,Web浏览器与Web服务器必须断开,以保证其它Web浏览器能够与Web服务器建立连接。

下面我们具体分析其中的数据包在网络中漫游的经历

在网络分层结构中,各层之间是严格单向依赖的。“服务”是描述各层之间关系的抽象概念,即网络中各层向紧邻上层提供的一组 *** 作。下层是服务提供者,

上层是请求服务的用户。服务的表现形式是原语(primitive),如系统调用或库函数。系统调用是 *** 作系统内核向网络应用程序或高层协议提供的服务原

语。网络中的n层总要向n+1层提供比n-1层更完备的服务,否则n层就没有存在的价值。

传输层实现的是“端到端”通信,引进网间进程通信概念,同时也要解决差错控制,流量控制,数据排序(报文排序),连接管理等问题,为此提供不同的服

务方式。通常传输层的服务通过系统调用的方式提供,以socket的方式。对于客户端,要想建立一个socket连接,需要调用这样一些函数socket

() bind() connect(),然后就可以通过send()进行数据发送。

现在看数据包在网络中的穿行过程:

应用层

首先我们可以看到在应用层,根据当前的需求和动作,结合应用层的协议,有我们确定发送的数据内容,我们把这些数据放到一个缓冲区内,然后形成了应用层的报文data。

传输层

这些数据通过传输层发送,比如tcp协议。所以它们会被送到传输层处理,在这里报文打上了传输头的包头,主要包含端口号,以及tcp的各种制信息,这些信息是直接得到的,因为接口中需要指定端口。这样就组成了tcp的数据传送单位segment。tcp

是一种端到端的协议,利用这些信息,比如tcp首部中的序号确认序号,根据这些数字,发送的一方不断的进行发送等待确认,发送一个数据段后,会开启一个计

数器,只有当收到确认后才会发送下一个,如果超过计数时间仍未收到确认则进行重发,在接受端如果收到错误数据,则将其丢弃,这将导致发送端超时重发。通过

tcp协议,控制了数据包的发送序列的产生,不断的调整发送序列,实现流控和数据完整。

网络层

然后待发送的数据段送到网络层,在网络层被打包,这样封装上了网络层的包头,包头内部含有源及目的的ip地址,该层数据发送单位被称为packet。网络层开始负责将这样的数据包在网络上传输,如何穿过路由器,最终到达目的地址。在这里,根据目的ip地址,就需要查找下一跳路由的地址。首先在本机,要查找本机的路由表,在windows上运行route print就可以看到当前路由表内容,有如下几项:

Active Routes Default Route Persistent Route.

整个查找过程是这样的:

(1)根据目的地址,得到目的网络号,如果处在同一个内网,则可以直接发送。

(2)如果不是,则查询路由表,找到一个路由。

(3)如果找不到明确的路由,此时在路由表中还会有默认网关,也可称为缺省网关,IP用缺省的网关地址将一个数据传送给下一个指定的路由器,所以网关也可能是路由器,也可能只是内网向特定路由器传输数据的网关。

(4)

路由器收到数据后,它再次为远程主机或网络查询路由,若还未找到路由,该数据包将发送到该路由器的缺省网关地址。而数据包中包含一个最大路由跳数,如果超

过这个跳数,就会丢弃数据包,这样可以防止无限传递。路由器收到数据包后,只会查看网络层的包裹数据,目的ip。所以说它是工作在网络层,传输层的数据对

它来说则是透明的。

如果上面这些步骤都没有成功,那么该数据报就不能被传送。如果不能传送的数据报来自本机,那么一般会向生成数据报的应用程序返回一个“主机不可达”或 “网络不可达”的错误。

以windows下主机的路由表为例,看路由的查找过程

======================================================================

Active Routes:

Network DestinationNetmask Gateway Interface Metric

0.0.0.0 0.0.0.0 192.168.1.2 192.168.1.101 10

127.0.0.0 255.0.0.0 127.0.0.1 127.0.0.1 1

192.168.1.0 255.255.255.0 192.168.1.101 192.168.1.101 10

192.168.1.101 255.255.255.255 127.0.0.1 127.0.0.1 10

192.168.1.255 255.255.255.255 192.168.1.101 192.168.1.101 10

224.0.0.0240.0.0.0 192.168.1.101 192.168.1.101 10

255.255.255.255 255.255.255.255 192.168.1.101 192.168.1.101 1

Default Gateway:192.168.1.2

Network Destination 目的网段

Netmask 子网掩码

Gateway 下一跳路由器入口的ip,路由器通过interface和gateway定义一调到下一个路由器的链路,通常情况下,interface和gateway是同一网段的。

Interface 到达该目的地的本路由器的出口ip(对于我们的个人pc来说,通常由机算机A的网卡,用该网卡的IP地址标识,当然一个pc也可以有多个网卡)。

网关这个概念,主要用于不同子网间的交互,当两个子网内主机A,B要进行通讯时,首先A要将数据发送到它的本地网关,然后网关再将数据发送给B所在的网关,然后网关再发送给B。

默认网关,当一个数据包的目的网段不在你的路由记录中,那么,你的路由器该把那个数据包发送到哪里!缺省路由的网关是由你的连接上的default gateway决定的,也就是我们通常在网络连接里配置的那个值。

通常interface和gateway处在一个子网内,对于路由器来说,因为可能具有不同的interface,当数据包到达时,根据

Network

Destination寻找匹配的条目,如果找到,interface则指明了应当从该路由器的那个接口出去,gateway则代表了那个子网的网关地

址。

第一条 0.0.0.0 0.0.0.0 192.168.1.2192.168.1.101 10

0.0.0.0

代表了缺省路由。该路由记录的意思是:当我接收到一个数据包的目的网段不在我的路由记录中,我会将该数据包通过192.168.1.101这个接口发送到

192.168.1.2这个地址,这个地址是下一个路由器的一个接口,这样这个数据包就可以交付给下一个路由器处理,与我无关。该路由记录的线路质量

10。当有多个条目匹配时,会选择具有较小Metric值的那个。

第三条 192.168.1.0 255.255.255.0 192.168.1.101 192.168.1.101 10

联网段的路由记录:当路由器收到发往直联网段的数据包时该如何处理,这种情况,路由记录的interface和gateway是同一个。当我接收到一个数

据包的目的网段是192.168.1.0时,我会将该数据包通过192.168.1.101这个接口直接发送出去,因为这个端口直接连接着

192.168.1.0这个网段,该路由记录的线路质量

10 (因interface和gateway是同一个,表示数据包直接传送给目的地址,不需要再转给路由器)。

一般就分这两种情况,目的地址与当前路由器接口是否在同一子网。如果是则直接发送,不需再转给路由器,否则还需要转发给下一个路由器继续进行处理。

查找到下一跳ip地址后,还需要知道它的mac地址,这个地址要作为链路层数据装进链路层头部。这时需要arp协议,具体过程是这样的,查找arp

缓冲,windows下运行arp

-a可以查看当前arp缓冲内容。如果里面含有对应ip的mac地址,则直接返回。否则需要发生arp请求,该请求包含源的ip和mac地址,还有目的地

的ip地址,在网内进行广播,所有的主机会检查自己的ip与该请求中的目的ip是否一样,如果刚好对应则返回自己的mac地址,同时将请求者的ip

mac保存。这样就得到了目标ip的mac地址。

链路层

将mac地址及链路层控制信息加到数据包里,形成Frame,Frame在链路层协议下,完成了相邻的节点间的数据传输,完成连接建立,控制传输速度,数据完整。

物理层

物理线路则只负责该数据以bit为单位从主机传输到下一个目的地。

下一个目的地接受到数据后,从物理层得到数据然后经过逐层的解包 到 链路层 到 网络层,然后开始上述的处理,在经网络层 链路层 物理层将数据封装好继续传往下一个地址。

在上面的过程中,可以看到有一个路由表查询过程,而这个路由表的建立则依赖于路由算法。也就是说路由算法实际上只是用来路由器之间更新维护路由表,

真正的数据传输过程并不执行这个算法,只查看路由表。这个概念也很重要,需要理解常用的路由算法。而整个tcp协议比较复杂,跟链路层的协议有些相似,其

中有很重要的一些机制或者概念需要认真理解,比如编号与确认,流量控制,重发机制,发送接受窗口。

tcp/ip基本模型及概念

物理层

设备,中继器(repeater),集线器(hub)。对于这一层来说,从一个端口收到数据,会转发到所有端口。

链路层

协议:SDLC(Synchronous Data Link Control)HDLC(High-level Data Link

Control)

ppp协议独立的链路设备中最常见的当属网卡,网桥也是链路产品。集线器MODEM的某些功能有人认为属于链路层,对此还有些争议认为属于物理层设备。除

此之外,所有的交换机都需要工作在数据链路层,但仅工作在数据链路层的仅是二层交换机。其他像三层交换机、四层交换机和七层交换机虽然可对应工作在OSI

的三层、四层和七层,但二层功能仍是它们基本的功能。

因为有了MAC地址表,所以才充分避免了冲突,因为交换机通过目的MAC地址知道应该把这个数据转发到哪个端口。而不会像HUB一样,会转发到所有滴端口。所以,交换机是可以划分冲突域滴。

网络层

四个主要的协议:

网际协议IP:负责在主机和网络之间寻址和路由数据包。

地址解析协议ARP:获得同一物理网络中的硬件主机地址。

网际控制消息协议ICMP:发送消息,并报告有关数据包的传送错误。

互联组管理协议IGMP:被IP主机拿来向本地多路广播路由器报告主机组成员。

该层设备有三层交换机,路由器。

传输层

两个重要协议 TCP 和 UDP 。

端口概念:TCP/UDP 使用 IP 地址标识网上主机,使用端口号来标识应用进程,即 TCP/UDP 用主机 IP

地址和为应用进程分配的端口号来标识应用进程。端口号是 16 位的无符号整数, TCP 的端口号和 UDP

的端口号是两个独立的序列。尽管相互独立,如果 TCP 和 UDP

同时提供某种知名服务,两个协议通常选择相同的端口号。这纯粹是为了使用方便,而不是协议本身的要求。利用端口号,一台主机上多个进程可以同时使用

TCP/UDP 提供的传输服务,并且这种通信是端到端的,它的数据由 IP 传递,但与 IP

数据报的传递路径无关。网络通信中用一个三元组可以在全局唯一标志一个应用进程:(协议,本地地址,本地端口号)。

也就是说tcp和udp可以使用相同的端口。

可以看到通过(协议,源端口,源ip,目的端口,目的ip)就可以用来完全标识一组网络连接。

应用层

基于tcp:Telnet FTP SMTP DNS HTTP

基于udp:RIP NTP(网落时间协议)和DNS (DNS也使用TCP)SNMP TFTP


欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/tougao/11891540.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2023-05-19
下一篇 2023-05-19

发表评论

登录后才能评论

评论列表(0条)

保存