网络知识杂记

1、网络

intemet表示使用常见协议族互联的多个网络。Intemet表示可使用TCP/IP通信的世界范围的主机集合o Intemet是一个intemet。
DNS是一个分布式数据库,提供主机名和IP地址之间的映射(反之亦然)。
域名建立是有层次的,以.com、.Org、.goV、.in、.uk和.edu等域结尾。
1.1、端口号
端口号是16位的非负整数(范围是0 - 65535)。标准的端口号由Intemet号码分配机构(IANA)分配。包括熟知端口号(0- 1023)、注册端口号(1024-49151)和动态/私有端口号(49152 - 65535)。
端口号在物理上没有指任何东西。它们被用于确定正确的接收数据的具体服务。对于客户机/服务器应用,一台服务器首先“绑定”到一个端口号,然后一个或多个客户机可使用某种特定的传输协议与一台服务器上的端口号建立连接。从这个意义上来说,端口号的功能更像电话号码的扩展,差别是它们通常是由某个标准来分配。
熟知端日用于识别很多众所周知的服务,例如安全外壳协议(SSH,端口22)、 FTP (端口20和21)、 Telnet远程终端协议(端口23)、电子邮件/简单邮件传输协议(SMTP,端口25)、域名系统(DNS,端口53)、超文本传输协议或Web(HTTP和HTTPS,端口80和443)、交互式邮件访问协议(IMAP和IMAPS,端口143和993)、简单网络管理协议(SNMP,端口161和162)、轻量级目录访问协议(LDAP,端口389)。
注册端口号提供给有特殊权限的客户机或服务器。
动态/私有端口号基本不受监管。端口号又称为临时端口号。

2、网络模型

2.1、OSI模型:7层(tcp/ip未采纳)

OSI:开放系统互连标准

链路层:大多数的链路技术(例如以太网和Wi-Fi)在每个分组中包
含一个协议标识符字段,用于指出链路层帧携带的协议(IP是这种协议)
分层体系结构的一个主要优点是具有协议复用的能力。当某层的一个
称为协议数据单元(PDU)的对象(分组、消息等)被低层携带时,这个过程称为在相邻低
层的封装(作为不透明数据)。
封装通常与分层一起使用。单纯的封装涉及获得某层的PDU,并在低层将它作为不透明(无须
解释)的数据来处理。封装发生在发送方,拆封(还原操作)发生在接收方。多数协议在封装
过程中使用头部,少数协议也使用尾部。

2.2、ARPANET参考模型(tcp/ip协议族采纳)

ARP(地址解析协议)–>链路层
IPv4专用协议,用于多接入链路层协议,完成IP层使用地址和链路层使用地址之间的转换。
IPv6地址映射功能作为ICMPv6的一部分

3、包格式解析

ping包
request传输数据包(格式同reply传输数据包)
0000   80 fa 5b 6f 0f 6e 00 0d 61 9e 6c 32 08 00 45 00
0010   00 3c 05 f9 00 00 40 01 04 84 c0 a8 77 7a c0 a8
0020   77 79 08 00 4d 4e 00 01 00 0d 61 62 63 64 65 66
0030   67 68 69 6a 6b 6c 6d 6e 6f 70 71 72 73 74 75 76
0040   77 61 62 63 64 65 66 67 68 69
/******************************与reply区别**********************************/
/*************************************************************************
*Type(0x22): 8 (Echo (ping) request)
************************************************************************/

reply传输数据包(icmp格式)
/*********************************************************************/
0000   00 0c 29 ec ef d2 80 fa  5b 6f 0f 6e 08 00 45 00  
0010   00 54 5b a6 00 00 80 01  00 00 c0 a8 77 79 c0 a8 
0020   77 0d 00 00 05 a8 0c cc  00 a0 4f ca dc 5d 00 00 
0030   00 00 f6 f0 0b 00 00 00  00 00 10 11 12 13 14 15   
0040   16 17 18 19 1a 1b 1c 1d  1e 1f 20 21 22 23 24 25  
0050   26 27 28 29 2a 2b 2c 2d  2e 2f 30 31 32 33 34 35   
0060   36 37   
/*********************************************************************/
网络数据报格式:链路层&网络层&ICMP
/***********链路层(2.5)-->14 bytes********************
Ethernet II, Src: Clevo_6f:0f:6e (80:fa:5b:6f:0f:6e), Dst: Giga-Byt_9e:6c:32 (00:0d:61:9e:6c:32)
*目标(mac)地址(0x00~0x05)
*源(mac)地址(0x06~0x0b)
*type(0x0c~0x0d):0x8000 表示此帧数据为IPv4数据报,xO806和Ox86DD分别表示
ARP和IPv6
****************End***********************************/
/***********网络层(3)-->tcp/ip(20 bytes)*******************
Internet Protocol Version 4, Src: 192.168.119.121, Dst: 192.168.119.122
*版本(0x0e低4位): 当前版本为4   
*header Length(0x0e高4位): 20个字节为5 ,(网络数据报中所有header Length控制字应该为len/4)
*Difference Serverce Filed(0x0f):
*Totle length(0x10~0x11):84-->丢弃Mac地址(链路层)后长度(即网络层数据长度),即是总长减14字节
*identification(0x12~0x13):2bytes
*flags(0x14~0x15):Reserved bit ,Don't fragment,More fragments,
        fragment offset。
        常见为0x0000 或者0x4000。
*time to alive(0x16):存活时间,一般没过一个路由节点减1,当该值为0时丢弃此包。
*网络协议(0x17):0x01表示icmp,常见的值包括1 (ICMP)、 2 (IGMP)、 4 (IPv4)、6 (TCP)和17 (UDP)。
       数值4 (和41,表示IPv6 )-->它表示一个IP数据报可能出现在另一个IP数据报的有效载荷中
*header checksum(0x18~0x19):
*源IP(0x1a~0x1d)
*目标IP(0x1e~0x21):
*************End*********************************/  
/***********网络层(3.5)->icmp******************* 
Internet Control Message Protocol 
*Type(0x22):0表示Echo (ping) reply,8表示Echo (ping) request
*Code(0x23):
*checksum(0x24~0x25)
*ID(0x26~0x27):BE:0x0ccc   LE:0xcc0c
*序列号(0x28~0x29):
*传输时间(0x2a~0x31):
*传输数据:
*************End*********************************/


TCP连接三次握手
第一次握手:客户端发送包
0000   00 0c 29 ec ef d2 80 fa  5b 6f 0f 6e 08 00 45 00   
0010   00 34 fd 1e 40 00 80 06  00 00 c0 a8 77 79 c0 a8  
0020   77 0c c2 34 1f 40 f4 2f  b4 34 00 00 00 00 80 02  
0030   fa f0 6f fd 00 00 02 04  05 b4 01 03 03 08 01 01   
0040   04 02                                             
/*************************同ping中的固定格式********************************************/
网络数据报格式:链路层&网络层
/***********链路层(2.5)-->14 bytes********************
*目标(mac)地址(0x00~0x05)
*源(mac)地址(0x06~0x0b)
*type(0x0c~0x0d):0x8000 表示此帧数据为IPv4数据报,xO806和Ox86DD分别表示
ARP和IPv6
****************End***********************************/
/***********网络层(3)-->tcp/ip(20 bytes)*******************
*版本(0x0e低4位): 当前版本为4   
*header Length(0x0e高4位): 20个字节为5 ,(网络数据报中所有header Length应该为len/4)
*Difference Serverce Filed(0x0f):
*Totle length(0x10~0x11):84-->丢弃Mac地址(链路层)后长度(即网络层数据长度),即是总长减14字节
*identification(0x12~0x13):2bytes
*flags(0x14~0x15):Reserved bit ,Don't fragment,More fragments,
        fragment offset。
        常见为0x0000 或者0x4000。
*time to alive(0x16):存活时间
*网络协议(0x17):0x01表示icmp,常见的值包括1 (ICMP)、 2 (IGMP)、 4 (IPv4)、6 (TCP)和17 (UDP)。
       数值4 (和41,表示IPv6 )-->它表示一个IP数据报可能出现在另一个IP数据报的有效载荷中
*header checksum(0x18~0x19):
*源IP(0x1a~0x1d)
*目标IP(0x1e~0x21):
/******************************************************************************/
/******************************************************************************/
/******************************************************************************/
/*************************传输层格式(32bytes)******************************************** 
Transmission Control Protocol, Src Port: 49716, Dst Port: 8000, Seq: 0, Len: 0
*源端口(0x22~0x23):
*目标端口(0x24~0x25):
*序列号Sequence number(0x26~0x29): (relative sequence number)
*[Next sequence number: 0    (relative sequence number)]
*响应号Acknowledgment number(0x2a~0x2d): 0  
*Header Length(0x2e高4位--4bit): 32 bytes为8。(网络数据报中所有header Length控制字应该为len/4)
*Flags(0x2e低4位~0x2f--12bit):
    000. .... .... = Reserved: Not set
    ...0 .... .... = Nonce: Not set
    .... 0... .... = Congestion Window Reduced (CWR): Not set
    .... .0.. .... = ECN-Echo: Not set
    .... ..0. .... = Urgent: Not set
    .... ...0 .... = Acknowledgment: Not set
    .... .... ..1. = Syn: Set
    [Expert Info (Chat/Sequence): Connection establish request (SYN): server port 8000]
    .... .... ...0 = Fin: Not set
*Window size value(0x30~0x31):
*Checksum(0x32~0x33): 
*Urgent pointer(0x34~0x35):  
*Options: (12 bytes), Maximum segment size, No-Operation (NOP), Window scale, No-Operation (NOP), No-Operation (NOP), SACK permitted
    TCP Option - Maximum segment size: 1460 bytes
        Kind(0x): Maximum Segment Size (2)
        Length(0x37): 4
        MSS Value(0x38~0x39): 1460
    TCP Option - No-Operation (NOP)
        Kind(0x3a): No-Operation (1)
    TCP Option - Window scale: 8 (multiply by 256)
        Kind(0x3b): Window Scale (3)
        Length(0x3c): 3
        Shift count(0x3d): 8[Multiplier: 256]
    TCP Option - No-Operation (NOP)
        Kind(0x3e): No-Operation (1)
    TCP Option - No-Operation (NOP)
        Kind(0x3f): No-Operation (1)    
    TCP Option - SACK permitted   
        Kind(0x40): SACK Permitted (4)     
        Length(0x41): 2  
*[Timestamps]:没使用 
    [Time since first frame in this TCP stream: 0.000000000 seconds]
    [Time since previous frame in this TCP stream: 0.000000000 seconds]
***************End*********************************/ 

第二次握手:客户端接收包
0000   80 fa 5b 6f 0f 6e 00 0c 29 ec ef d2 08 00 45 00   
0010   00 34 00 00 40 00 40 06 ca ed c0 a8 77 0c c0 a8   
0020   77 79 1f 40 c2 34 b8 d6 e2 fe f4 2f b4 35 80 12   
0030   fa f0 de 89 00 00 02 04 05 b4 01 01 04 02 01 03   
0040   03 07  
/***************与第一次握手的区别****************************/                                         
/*******************传输层协议*******************************
*Transmission Control Protocol, Src Port: 49716, Dst Port: 8000, Seq: 0, Len: 0
*Acknowledgment number(0x2a~0x2d): 1    (relative ack number)
*Flags(0x2e~0x2f): 0x012 (SYN, ACK)
    000. .... .... = Reserved: Not set
    ...0 .... .... = Nonce: Not set
    .... 0... .... = Congestion Window Reduced (CWR): Not set
    .... .0.. .... = ECN-Echo: Not set
    .... ..0. .... = Urgent: Not set
    .... ...1 .... = Acknowledgment: Set
    .... .... 0... = Push: Not set
    .... .... .0.. = Reset: Not set
    .... .... ..1. = Syn: Set
    .... .... ...0 = Fin: Not set
*Options: (12 bytes), Maximum segment size, No-Operation (NOP), No-Operation (NOP), SACK permitted, No-Operation (NOP), Window scale
    TCP Option - Maximum segment size: 1460 bytes
        Kind: Maximum Segment Size (2)
        Length: 4
        MSS Value: 1460
    TCP Option - No-Operation (NOP)
        Kind: No-Operation (1)
    TCP Option - No-Operation (NOP)
    TCP Option - SACK permitted
        Kind: SACK Permitted (4)
        Length: 2
        TCP Option - No-Operation (NOP)
    TCP Option - Window scale: 7 (multiply by 128)
        Kind: Window Scale (3)
        Length: 3
        Shift count: 7[Multiplier: 128]
**************************************************************/

第三次握手
0000   00 0c 29 ec ef d2 80 fa 5b 6f 0f 6e 08 00 45 00
0010   00 28 fd 1f 40 00 80 06 00 00 c0 a8 77 79 c0 a8
0020   77 0c c2 34 1f 40 f4 2f b4 35 b8 d6 e2 ff 50 10
0030   20 14 6f f1 00 00
/*************************传输层*******************************
*Sequence number: 1    (relative sequence number)
    [Next sequence number: 1    (relative sequence number)]
*Acknowledgment number: 1    (relative ack number)
*Flags: 0x010 (ACK)
    000. .... .... = Reserved: Not set
    ...0 .... .... = Nonce: Not set
    .... 0... .... = Congestion Window Reduced (CWR): Not set
    .... .0.. .... = ECN-Echo: Not set
    .... ...1 .... = Acknowledgment: Set
    .... .... 0... = Push: Not set
    .... .... .0.. = Reset: Not set
    .... .... ..0. = Syn: Not set
    .... .... ...0 = Fin: Not set
*无Options传输

三次握手过程:
SYN&seq=0-->seq=0&SYN&ACK=1-->seq=1&ACK=1


传输数据
server-->client

0000   80 fa 5b 6f 0f 6e 00 0c 29 ec ef d2 08 00 45 00
0010   00 3b e6 2f 40 00 40 06 e4 b6 c0 a8 77 0c c0 a8
0020   77 79 1f 40 c5 6a 53 b2 cf ed c6 d1 9a b0 50 18
0030   01 f6 2f 01 00 00 77 65 6c 63 6f 6d 65 20 74 6f
0040   20 73 65 72 76 65 72 0d 0a
/************************************************************************/
网络数据报格式:链路层&网络层&传输层&数据
/************************************************************************/
*Sequence number: 1    (relative sequence number)
*[Next sequence number: 20    (relative sequence number)]
*Acknowledgment number: 1    (relative ack number)
*Flags: 0x018 (PSH, ACK)
    .... ...1 .... = Acknowledgment: Set
    .... .... 1... = Push: Set
*无Options传输
/****************************************************************/
*数据(0x36~0x48)


client-->server

0000   00 0c 29 ec ef d2 80 fa 5b 6f 0f 6e 08 00 45 00
0010   00 3b fd 75 40 00 80 06 00 00 c0 a8 77 79 c0 a8
0020   77 0c c5 6a 1f 40 c6 d1 9a b0 53 b2 d0 00 50 18
0030   20 14 70 04 00 00 77 65 6c 63 6f 6d 65 20 74 6f
0040   20 73 65 72 76 65 72 00 00

*Sequence number: 1    (relative sequence number)
*[Next sequence number: 20    (relative sequence number)]
*Acknowledgment number: 20    (relative ack number)
*Flags: 0x018 (PSH, ACK)
    .... ...1 .... = Acknowledgment: Set
    .... .... 1... = Push: Set
/***************************************************/
/***************************************************
*Data (19 bytes)




/******************************************************

ACK

0000   80 fa 5b 6f 0f 6e 00 0c  29 ec ef d2 08 00 45 00
0010   00 28 e6 30 40 00 40 06  e4 c8 c0 a8 77 0c c0 a8
0020   77 79 1f 40 c5 6a 53 b2  d0 00 c6 d1 9a c3 50 10
0030   01 f6 d4 14 00 00 00 00  00 00 00 00
/**********************************************************
Ethernet II, Src: Vmware_ec:ef:d2 (00:0c:29:ec:ef:d2), Dst: Clevo_6f:0f:6e (80:fa:5b:6f:0f:6e)
*Padding(0x36~0x3b): 000000000000
***********************************************************************************************
*Flags: 0x010 (ACK)
    .... ...1 .... = Acknowledgment: Set
*********************************************************/

网络传输中当收发数据后,会向发送端返回一个ACK包,通过ACK包来确表示数据接收完成。
push&seq=1–>ACK=20&push&seq=1–>ACK&seq=20

4、TcP/lP中的复用、分解和封装

自底向上地说明从链路层开始如何进PDU行分解,这里使用以太网作为例子。以太网帧包含
一个48位的目的地址(又称为链路层或介质访问控制(MAC)地址)和一个16位的以太网类型字段, 0x0800 (十六进制)表示这个帧包含IPv4数据报,0x0806和0x86DD分别表示ARP和IPv6,假设目的地址与接收方的一个地址匹配,这个帧将被接收并校验差错,以太网类型字段用于选择处理它的网络层协议。
如果接收到的帧包含一个IP数据报,以太网头部和尾部信息将被清除,并将剩余字节(包
含帧的有效载荷)交给IP来处理。 IP检测一系列的字段,包括数据报中的目的IP地址。如果
目的地址与自己的一个IP地址匹配,并且数据报头部(IP不检测有效载荷)没有错误,则检
测8位的IPv4协议字段(在IPv6中称为下一个头部字段),以决定接下来调用哪个协议来处理。
常见的值包括1 (ICMP)、 2 (IGMP)、 4 (IPv4)、 6 (TCP)和17 (UDP)。数值4 (和41,表示
IPv6 )的含义是有趣的,因为它表示一个IP数据报可能出现在另一个IP数据报的有效载荷中。
如果网络层(IPv4或IPv6)认为传人的数据报有效,并且已确定正确的传输层协议,则
将数据报(必要时由分片重组而成)交给传输层处理。在传输层中,大部分协议(包括TCP
和UDP)通过端日号将复用分解到适当的应用。

5、window/MFC实例

    //TODO : 客户端代码 client
    WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return FALSE;
    tcp_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);//ipv4 ,流格式套接字(面向连接套接字),tcp协议
    	//tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
	//udp_sock = socket(AF_INET, SOCK_dGRAM, IPPROTO_UDP);//ipv6,数据报套接字(无连接套接字),udp协议
    if (tcp_sock == INVALID_SOCKET)
    {
		MessageBox(_T("套接字创建失败! "));
		goto err;
		//return;
	}
 
	sockaddr_in addr;
	InetPton(AF_INET, _T("192.168.118.13"),&(addr.sin_addr.S_un.S_addr));//_T("192.168.118.13")
	addr.sin_port = htons(8888);
	addr.sin_family = AF_INET;
	int ret=connect(tcp_sock,(sockaddr*)&addr,sizeof(addr));
	if (ret == SOCKET_ERROR)
	{
		MessageBox(_T( "connect fail!!"));
		goto err;
		//return ;
	}
	
	char buf[100] = "1234";
	ret=send(tcp_sock,(char *)&buf, sizeof(txBuf), 0);
	if (ret == SOCKET_ERROR)
	{
		MessageBox(_T("send error!"));
		return ;
	}
//TODO : server.h 
#pragma once
#include <WS2tcpip.h>
#include <afxstr.h>

//#define IP "192.168.118.19"

#ifndef __DEBUG
	#define DBG_printf(c) MessageBox(c);
#else 
	#define DBG_printf(c) 
#endif

class Sock
{
public:
	int initWsa()
	{
		WSADATA wsaData;
		if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
			return -1;
		return 0;
	}

	~Sock()
	{
		WSACleanup();
	}
};

class Server :public  Sock
{
public:
	Server();
	int initCfg(CString ip=CString("192.168.118.3"),int ipPort=8888);
	int initCfg(int ipPort = 8888);
	int acceptClient();
	int sendData(int len);
	int recvData(int len);
	~Server();
private:
	SOCKET server_socket;
	SOCKET client_socket;
	SOCKADDR_IN server_sockAddr;
	SOCKADDR_IN client_sockAddr;
	char* rxBuf;
	char* txBuf;
	int flag;
	
};


//TODO : server.cpp
#include "pch.h"
#include "server.h"
#include <WinUser.h>

Server::Server()
{
	rxBuf = new char[200];
	txBuf = new char[200];
	flag = 0;
}

int Server::initCfg(CString ip, int ipPort)//特定ip地址配置
{
	//WSAStartup配置启动socket
	/*
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return ;// -1;
	*/

	//int ret =initWsa();
	if (initWsa() != 0)//WSAStartup()调用
	{
		return -1;
	}

	server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (server_socket == INVALID_SOCKET)
	{
		return  -1;
	}

	server_sockAddr.sin_family = AF_INET;
	server_sockAddr.sin_port = htons(ipPort);
	InetPton(AF_INET, (PCSTR)(ip), &server_sockAddr.sin_addr.S_un.S_addr);


	if (bind(server_socket, (sockaddr*)& server_sockAddr, sizeof(server_sockAddr)) == SOCKET_ERROR)
	{
		//DBG_printf(_T("bind failed !"));
		//MessageBox(_T("dafdfdf"));
		//MessageBox(_T("套接字创建失败! "));

		return -1;
	}

	if (listen(server_socket, 5) == SOCKET_ERROR)
	{
		closesocket(server_socket);
		return -1;
	}

	int client_sockAddrLen = sizeof(client_sockAddr);
	client_socket = accept(server_socket, (sockaddr*)& client_sockAddr, &client_sockAddrLen);
	if (client_socket == INVALID_SOCKET)
	{
		closesocket(client_socket);
		return -1;
	}
}

int Server::initCfg(int ipPort)//任意IP配置
{
	/*
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return ;// -1;
	*/

	//int ret =initWsa();
	if (initWsa() != 0)//WSAStartup()调用
	{
		return -1;
	}

	server_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (server_socket == INVALID_SOCKET)
	{
		return  -1;
	}

	server_sockAddr.sin_family = AF_INET;
	server_sockAddr.sin_port = htons(8888);
	InetPton(AF_INET, INADDR_ANY, &server_sockAddr.sin_addr.S_un.S_addr);


	if (bind(server_socket, (sockaddr*)& server_sockAddr, sizeof(server_sockAddr)) == SOCKET_ERROR)
	{
		//DBG_printf(_T("bind failed !"));
		//MessageBox(_T("dafdfdf"));
		//MessageBox(_T("套接字创建失败! "));

		return -1;
	}

	if (listen(server_socket, 5) == SOCKET_ERROR)
	{
		closesocket(server_socket);
		return -1;
	}
	flag = 1;//cfg_ok
	return 0;
}

int Server::acceptClient()
{
	int client_sockAddrLen = sizeof(client_sockAddr);
	client_socket = accept(server_socket, (sockaddr*)& client_sockAddr, &client_sockAddrLen);
	if (client_socket == INVALID_SOCKET)
	{

		flag = 1;
		closesocket(client_socket);
		return -1;
	}
	flag = 2;//accept_ok
	return 0;
}

int Server::sendData(int len)
{
	char* ptxBuf = txBuf;
	if (send(client_socket, ptxBuf, len, 0)>0)
	{
		flag = 3;// R/T ok
		return 0;
	}
	flag = 2;
	return -1;
}


int Server::recvData(int len)
{
	char *prxBuf = rxBuf;
	if (recv(client_socket, rxBuf, len, 0)>0)
	{
		flag = 3; // R / T ok
		return 0;
	}
	flag = 2;
	return -1;
}

Server::~Server()
{
	closesocket(server_socket);
}
//TODO : client.h
#pragma once
#include "pch.h"
#include "server.h"
#include <WS2tcpip.h>
//#include <afxstr.h>

class Client :public  Sock
{
public:
	Client();
	int initCfg(CString ip = CString("192.168.118.3"), int ipPort = 8888);
	int sendData(int len);
	int recvData(int len);
	~Client();
private:
	SOCKET client_socket;
	SOCKADDR_IN client_sockAddr;
	char* rxBuf;
	char* txBuf;
	int flag;
};



//TODO : client.cpp
#include "pch.h"
#include "Client.h"

Client::Client()
{
	rxBuf = new char[200];
	txBuf = new char[200];
	flag = 0;
}

int Client::initCfg(CString ip, int ipPort)//特定ip地址配置
{
	//WSAStartup配置启动socket
	/*
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
		return ;// -1;
	*/

	//int ret =initWsa();
	if (initWsa() != 0)//WSAStartup()调用
	{
		return -1;
	}

	client_socket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (client_socket == INVALID_SOCKET)
	{
		return  -1;
	}

	client_sockAddr.sin_family = AF_INET;
	client_sockAddr.sin_port = htons(ipPort);
	InetPton(AF_INET, (PCSTR)(ip), &client_sockAddr.sin_addr.S_un.S_addr);

	if (connect(client_socket, (sockaddr*)& client_sockAddr, sizeof(client_sockAddr)) != SOCKET_ERROR)
	{
		return -1;
	}
	flag = 0;
	return 0;
}



int Client::sendData(int len)
{
	char* ptxBuf = txBuf;
	if (send(client_socket, ptxBuf, len, 0) > 0)
	{
		flag = 3;// R/T ok
		return 0;
	}
	flag = 2;
	return -1;
}


int Client::recvData(int len)
{
	char* prxBuf = rxBuf;
	if (recv(client_socket, rxBuf, len, 0) > 0)
	{
		flag = 3; // R / T ok
		return 0;
	}
	flag = 2;
	return -1;
}

Client::~Client()
{
	closesocket(client_socket);
}

6. Socket粘包问题

什么时候需要考虑粘包问题

1:如果利用tcp每次发送数据,就与对方建立连接,然后双方发送完一段数据后,就关闭连接,这样就不会出现粘包问题(因为只有一种包结构,类似于http协议)。关闭连接主要要双方都发送close连接(参考tcp关闭协议)。如:A需要发送一段字符串给B,那么A与B建立连接,然后发送双方都默认好的协议字符如"hello give me sth abour yourself",然后B收到报文后,就将缓冲区数据接收,然后关闭连接,这样粘包问题不用考虑到,因为大家都知道是发送一段字符;
2:如果发送数据无结构,如文件传输,这样发送方只管发送,接收方只管接收存储就ok,也不用考虑粘包;
3:如果双方建立连接,需要在连接后一段时间内发送不同结构数据,如连接后,有好几种结构:
1)“hellogive me sth abour yourself”
2)“Don’tgive me sth abour yourself”
那这样的话,如果发送方连续发送这个两个包出去,接收方一次接收可能会是"hello give me sth abour yourselfDon’t give me sth abouryourself" 这样接收方就傻了,到底是要干嘛?不知道,因为协议没有规定这么诡异的字符串,所以要处理把它分包,怎么分也需要双方组织一个比较好的包结构,所以一般可能会在头加一个数据长度之类的包,以确保接收。

粘包出现原因:
在流传输中出现,UDP不会出现粘包,因为它有消息保护边界。
1 发送端需要等缓冲区满才发送出去,造成粘包
2 接收方不及时接收缓冲区的包,造成多个包接收

解决办法:
为了避免粘包现象,可采取以下几种措施:
一是对于发送方引起的粘包现象,用户可通过编程设置来避免,TCP提供了强制数据立即传送的操作指令push,TCP软件收到该操作指令后,就立即将本段数据发送出去,而不必等待发送缓冲区满;
二是对于接收方引起的粘包,则可通过优化程序设计、精简接收进程工作量、提高接收进程优先级等措施,使其及时接收数据,从而尽量避免出现粘包现象;
三是由接收方控制,将一包数据按结构字段,人为控制分多次接收,然后合并,通过这种手段来避免粘包。

以上提到的三种措施,都有其不足之处。
第一种编程设置方法虽然可以避免发送方引起的粘包,但它关闭了优化算法,降低了网络发送效率,影响应用程序的性能,一般不建议使用。
第二种方法只能减少出现粘包的可能性,但并不能完全避免粘包,当发送频率较高时,或由于网络突发可能使某个时间段数据包到达接收方较快,接收方还是有可能来不及接收,从而导致粘包。
第三种方法虽然避免了粘包,但应用程序的效率较低,对实时应用的场合不适合。
更为简洁的说法:
定包长
包尾加\r\n
包头加包体长度
网上说法:
个人比较喜欢的一种做法是给一帧数据加帧头帧尾,然后接收方不断接受并缓存收到的数据,根据帧头帧尾分离出一帧完整的数据,再分离各字段得到数据。

如果某个包出错了,怎么不断恢复?

发送消息时,每个消息长度在编程的时候就指定了。如果接收到的数据包有问题,我们可以通过消息长度来不断回复原来的数据包。

7. TCP例子

服务端:

#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    WSADATA wsaData;
    int port = 5099;
    char buf[] = "服务器: 欢迎登录......\n";

    // 加载套接字
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
         printf("加载套接字失败:%d......\n", WSAGetLastError());
         return 1;
    }
    // socket()
    SOCKET sockSrv = socket(AF_INET, SOCK_STREAM, 0);

    // 初始化IP和端口信息
    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port); // 1024以上的端口号
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    // bind()
    if (bind(sockSrv, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
    {
         printf("套接字绑定失败:%d......\n", WSAGetLastError());
         return 1;
    }
    // listen()
    if (listen(sockSrv, 10) == SOCKET_ERROR){
         printf("套接字监听失败:%d......\n", WSAGetLastError());
         return 1;
    }

    // 客户端信息
    SOCKADDR_IN addrClient;
    int len = sizeof(SOCKADDR);

    // 开始监听
    printf("服务端启动成功......开始监听...\n");
    while (1)
    {
         // 等待客户请求到来  
         SOCKET sockConn = accept(sockSrv, (SOCKADDR *)&addrClient, &len);
         if (sockConn == SOCKET_ERROR)
{
             printf("建立连接失败:%d......\n", WSAGetLastError());
             break;
         }
         printf("与客户端建立连接......IP:[%s]\n", inet_ntoa(addrClient.sin_addr));
         // 发送数据
         if (send(sockConn, buf, sizeof(buf), 0) == SOCKET_ERROR)
{
             printf("发送数据失败......\n");
             break;
         }

         char recvBuf[100];
         memset(recvBuf, 0, sizeof(recvBuf));
         // 接收数据
         recv(sockConn, recvBuf, sizeof(recvBuf), 0);
         printf("收到数据:%s\n", recvBuf);
         closesocket(sockConn);
    }

    // 关闭套接字
    closesocket(sockSrv);
    WSACleanup();
    system("pause");
    return 0;
}

客户端:

#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    WSADATA wsaData;
   int port = 5099;
    char buff[1024];
    memset(buff, 0, sizeof(buff));

    // 加载套接字
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
         printf("加载套接字失败:%d......\n", WSAGetLastError());
         return 1;
    }

    // 初始化IP和端口信息
    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port);
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");

    // socket()
    SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
    if (SOCKET_ERROR == sockClient)
   {
         printf("创建套接字失败:%d......\n", WSAGetLastError());
         return 1;

    // 向服务器发出连接请求

    if (connect(sockClient, (struct  sockaddr*)&addrSrv, sizeof(addrSrv)) == INVALID_SOCKET)
    {
         printf("连接服务器失败:%d......\n", WSAGetLastError());
         return 1;
    }
    else
    {
         // 接收数据
         recv(sockClient, buff, sizeof(buff), 0);
         printf("收到数据:%s\n", buff);
         // 发送数据
         char buf[] = "客户端:请求登录......";
         send(sockClient, buf, sizeof(buf), 0);
    }
    // 关闭套接字
    closesocket(sockClient);
    WSACleanup();
    return 0;
}

旧函数解决方式:

8、 UDP例子

服务端(接收方):

// UDPReceiverTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

int _tmain(int argc, _TCHAR* argv[])
{
    WSADATA wsaData;
    int port = 5099;
    // 加载套接字
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
         printf("加载套接字失败:%d......\n", WSAGetLastError());
         return 1;
    }
    // 初始化IP和端口信息
    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port);
    addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);

    // socket()
    SOCKET sockClient = socket(AF_INET,SOCK_DGRAM, 0);
    if (SOCKET_ERROR == sockClient){
         printf("创建套接字失败:%d......\n", WSAGetLastError());
         return 1;
    }

    // bind()
    if (bind(sockClient, (LPSOCKADDR)&addrSrv, sizeof(SOCKADDR_IN)) == SOCKET_ERROR)
    {
         printf("套接字绑定失败:%d......\n", WSAGetLastError());
         return 1;
    }
    SOCKADDR_IN addrClnt;
    int nLen = sizeof(SOCKADDR);
    // 消息
    char szMsg[1024];
    memset(szMsg, 0, sizeof(szMsg));
    // 等待客户请求到来
    printf("服务端启动成功......等待客户发送数据...\n");
    while (1)
    {
         // 接收数据
         if (SOCKET_ERROR != recvfrom(sockClient, szMsg, sizeof(szMsg), 0, (SOCKADDR*)&addrClnt, &nLen))
         {
             printf("发送方:%s\n", szMsg);
             char szSrvMsg[] = "收到...";
             // 发送数据
             sendto(sockClient, szSrvMsg, sizeof(szSrvMsg), 0, (SOCKADDR*)&addrClnt, nLen);
         }
    }
    // 上面为无线循环,以下代码不会执行
    // 关闭套接字
    closesocket(sockClient);
    WSACleanup();
    return 0;
}

客户端:

// UDPSenderTest.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include <stdio.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib") 

int _tmain(int argc, _TCHAR* argv[])
{
    WSADATA wsaData;
    int port = 5099;
    // 加载套接字
    if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
    {
         printf("加载套接字失败:%d......\n", WSAGetLastError());
         return 1;
    }
    // socket()
    SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);
    if (SOCKET_ERROR == sockClient)
  {
         printf("创建套接字失败:%d......\n", WSAGetLastError());
         return 1;
    }
    // 初始化IP和端口信息
    SOCKADDR_IN addrSrv;
    addrSrv.sin_family = AF_INET;
    addrSrv.sin_port = htons(port);
    addrSrv.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");
    int nLen = sizeof(SOCKADDR);
    // 发送数据
    char szMsg[1024];
    memset(szMsg, 0, sizeof(szMsg));
    sendto(sockClient, szMsg, sizeof(szMsg), 0, (SOCKADDR*)&addrSrv, nLen);
    // 发送数据
    while (1)
    {
         // 初始化数据
         char szMsg[1024];
         memset(szMsg, 0, sizeof(szMsg));
         printf("请输入要发送的数据(输入q退出):");
         scanf("%s", &szMsg);
         // 退出循环
         if (!strcmp(szMsg, "q") || !strcmp(szMsg, "Q"))
         {
            break
         }
         // 发送数据
         sendto(sockClient, szMsg, sizeof(szMsg), 0, (SOCKADDR*)&addrSrv, nLen);
         // 清空缓存
         memset(szMsg, 0, sizeof(szMsg));
         // 接收数据
         if (SOCKET_ERROR != recvfrom(sockClient, szMsg, sizeof(szMsg), 0, (SOCKADDR*)&addrSrv, &nLen))
         {
             printf("接收方:%s\n", szMsg);
         }
    }
    // 关闭套接字
    closesocket(sockClient);
    WSACleanup();
    return 0;
}

9. TCP和 UDP 注意点

易忽略,出错的地方:socket()
TCP: SOCKET sockClient = socket(AF_INET, SOCK_STREAM, 0);
UDP: SOCKET sockClient = socket(AF_INET, SOCK_DGRAM, 0);

TCP不存在数据边界:
收到数据不意味着马上调用read()函数,只要不超过数组容量,则有可能数据填充满缓冲后通过一次read()函数调用读取全部,也有可能分成多次read()函数调用进行读取。如果传输出错就会提供重传服务。(套接字内部有一个由字节数组构成的缓冲)

9.1 结构体、图片传输方法

首先通讯双方需要统一结构体,示例:
struct Massage
{
    int nID;
    char strMsg[64];
};

发送方:
    // 结构体消息
    Massage stMsg;
    memset(stMsg.strMsg, 0, sizeof(stMsg.strMsg));
    stMsg.nID = 1001;
    strcpy(stMsg.strMsg, "Struct string");
    // ...
    sendto(sockClient, (char*)&stMsg, sizeof(stMsg) + 1, 0, (SOCKADDR*)&addrClnt, nLen);
接收方:
   // 结构体
   Massage stMsg;
   memset(stMsg.strMsg, 0, sizeof(stMsg.strMsg));
   memcpy(&stMsg, szMsg, sizeof(stMsg) + 1);
   printf("接收方:%d\t%s\n", stMsg.nID, stMsg.strMsg);

特别注意: sizeof(stMsg) + 1 两者必须保持一致。
拓展:发送文件
    // 图片
    struct Photo
    {
         int nSize;
         char buf[256];
    };
    Photo stPhoto;
    memset(stPhoto.buf, 0, sizeof(stPhoto.buf));
    // 发送文件
    printf("正在发送文件......\n");
    while (fp1)
    {
         // 读取文件内容到buf中,每次读256字节,返回值表示实际读取的字节数
         int nCount = fread(stPhoto.buf, 1, sizeof(stPhoto.buf), fp1);
         stPhoto.nSize = nCount;
         //printf("read %d byte\n", nCount);
         // 如果读取的字节数不大于0,说明读取出错或文件已经读取完毕
         if (nCount <= 0)
         {
             sprintf(stPhoto.buf, "finish\n");
             sendto(sockClient, (char*)&stPhoto, sizeof(stPhoto), 0, (SOCKADDR*)&addrSrv, nLen);
             printf("文件发送完成......\n");
             break;
         }
         sendto(sockClient, (char*)&stPhoto, sizeof(stPhoto), 0, (SOCKADDR*)&addrSrv, nLen);
    }

接收文件:
    printf("正在接收文件......\n");
    while (1)
    {
         // 接收图片
         if (SOCKET_ERROR != recvfrom(sockClient, szFileInfo, sizeof(szFileInfo), 0, (SOCKADDR*)&addrClnt, &nLen))
         {
          	 memcpy(&stPhoto, szFileInfo, sizeof(stPhoto));
             if (0 == strncmp(stPhoto.buf, "finish", 6))
             {
                  printf("文件接收完成......\n");
                  break;
             }
             int n = fwrite(stPhoto.buf, 1, stPhoto.nSize, fp2);
             //printf("write %d byte\n", n);
         }
    }

9.2 常见错误

包含<windows.h>和winsock.h后重定义问题:
[解决方案]
由以上代码可以看出如果在没有定义WIN32_LEAN_AND_MEAN宏的大前
提下windows.h有可能包含winsock.h 头文件,因此我们得出一个很简单
的解决方法就是在包含<windows.h>之前定义WIN32_LEAN_AND_MEAN宏,如
下所示:
#define WIN32_LEAN_AND_MEAN
#include <windows.h>

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值