分类
代码

C/C++ Socket编程

上个月的计算机网络课程设计结课了,整理一下。
共四个题:

  1. 实现简单的服务器-客户端通信。
  2. 实现1.的多线程版。
  3. 用RawSocket监听网络。
  4. 实现文件传输。

进入正题之前先插一个小题目:

『智力题』AB各有一把钥匙和锁,现在A要用加锁的盒子通过快递C传一个东西给B,但是如果C能打开盒子的话就会私吞这个东西,问AB该怎么做才能确保东西能从A传给B。(面试题)

文章最后可能有答案。


第一题纯属上手,熟悉一般流程。
服务器端

  1. 引入头文件
  2. 设置编译链接选项
  3. 加载套接字库
  4. 创建套接字
  5. 初始化地址族变量
  6. 绑定到地址和端口
  7. 监听
  8. 等待连接
  9. 发送接收

客户端第6步开始是

  1. 连接服务器
  2. 发送接收

点击显示/隐藏代码

#include<iostream>
#include<WinSock2.h>
#include<ctime>
#pragma comment(lib, "ws2_32.lib")
//#pragma comment(lib, "Ws2_32.lib")
#define CONNECT_NUM_MAX 10
#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;


//http://www.js-code.com/C_yuyan/20150918/4488.html
int main(int argc, char** argv)
{
	int port = 1234;
	if (argc > 1)
	{
		port = atoi(argv[1]);
		if (port < 1024 || port>65534)
		{
			printf("Port error. Use %s port\n", argv[0]);
		}
	}
	//加载套接字库
	WSADATA wsaData;
	int iRet = 0;
	iRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iRet != 0)
	{
		cout << "WSAStartup(MAKEWORD(2, 2), &wsaData) execute failed!" << endl;;
		return -1;
	}
	if (2 != LOBYTE(wsaData.wVersion) || 2 != HIBYTE(wsaData.wVersion))
	{
		WSACleanup();
		cout << "WSADATA version is not correct!" << endl;
		return -1;
	}

	//创建套接字
	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (serverSocket == INVALID_SOCKET)
	{
		cout << "serverSocket = socket(AF_INET, SOCK_STREAM, 0) execute failed!" << endl;
		return -1;
	}

	//初始化服务器地址族变量
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(port);

	//绑定
	iRet = bind(serverSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR));
	if (iRet == SOCKET_ERROR)
	{
		cout << "bind(serverSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) execute failed!" << endl;
		return -1;
	}


	//监听
	iRet = listen(serverSocket, CONNECT_NUM_MAX);
	if (iRet == SOCKET_ERROR)
	{
		cout << "listen(serverSocket," << CONNECT_NUM_MAX << ") execute failed!" << endl;
		return -1;
	}

	//等待连接_接收_发送
	SOCKADDR_IN clientAddr;
	int len = sizeof(SOCKADDR);
	//while (1)
	//{
	SOCKET connSocket = accept(serverSocket, (SOCKADDR*)&clientAddr, &len);
	if (connSocket == INVALID_SOCKET)
	{
		cout << "accept(serverSocket, (SOCKADDR*)&clientAddr, &len) execute failed!" << endl;
		return -1;
	}
	// 'inet_ntoa': Use inet_ntop() or InetNtop() instead or define _WINSOCK_DEPRECATED_NO_WARNINGS to disable deprecated API warnings
	// http://stackoverflow.com/questions/26947496/deprecated-commands-in-visual-c 使用#include<Ws2tcpip.h>下的inet_ntop()
	// 属性——C/C++——常规——SDK检查 改为否
	// http://jingyan.baidu.com/article/1709ad8097e5904634c4f03e.html

	char sendBuf[100];
	memset(sendBuf, 0, sizeof(sendBuf));
	sprintf_s(sendBuf, "Welcome %s.  type date to get time,q to exit.", inet_ntoa(clientAddr.sin_addr));
	send(connSocket, sendBuf, strlen(sendBuf) + 1, 0);
	char recvBuf[100];
	while (true)
	{
		memset(recvBuf, 0, sizeof(recvBuf));
		recv(connSocket, recvBuf, 100, 0);
		printf("%s\n", recvBuf);
		if (strcmp(recvBuf, "date") == 0)
		{
			time_t t = time(0);
			//http://www.cnblogs.com/mfryf/archive/2012/02/13/2349360.html
			strftime(sendBuf, sizeof(sendBuf), "%Y/%m/%d %X %A 本年第 %j 天 %z", localtime(&t));

			send(connSocket, sendBuf, strlen(sendBuf) + 1, 0);
		}
		else if (strcmp(recvBuf, "q") == 0) {
			sprintf_s(sendBuf, "BYE");
			send(connSocket, sendBuf, strlen(sendBuf) + 1, 0);
			closesocket(connSocket);
			exit(0);
		}
		else {
			sprintf_s(sendBuf, "Type date to get time,q to exit.");
			send(connSocket, sendBuf, strlen(sendBuf) + 1, 0);
		}
	}

	//}

	system("pause");
	return 0;
}
#include <iostream>
#include <string>
#include <winsock2.h>
using namespace std;
#define BYE "BYE"
#pragma comment(lib, "ws2_32.lib")
int main(int argc, char** argv)
{
	string host = "127.0.0.1";
	int port = 1234;
	if (argc == 3)
	{
		host = argv[1];
		port = atoi(argv[2]);
		if (port < 1024 || port>65534)
		{
			printf("Port error.Use %s host port\n", argv[0]);
		}
	}
	else if (argc != 1)
	{
		printf("Error. Use %s host port\n", argv[0]);
	}
	//加载套接字库
	WSADATA wsaData;
	int iRet = 0;
	iRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iRet != 0)
	{
		cout << "WSAStartup(MAKEWORD(2, 2), &wsaData) execute failed!" << endl;
		return -1;
	}
	if (2 != LOBYTE(wsaData.wVersion) || 2 != HIBYTE(wsaData.wVersion))
	{
		WSACleanup();
		cout << "WSADATA version is not correct!" << endl;
		return -1;
	}

	//创建套接字
	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (clientSocket == INVALID_SOCKET)
	{
		cout << "clientSocket = socket(AF_INET, SOCK_STREAM, 0) execute failed!" << endl;
		return -1;
	}

	//初始化服务器端地址族变量
	SOCKADDR_IN srvAddr;
	srvAddr.sin_addr.S_un.S_addr = inet_addr(host.c_str());
	srvAddr.sin_family = AF_INET;
	srvAddr.sin_port = htons(port);

	//连接服务器
	iRet = connect(clientSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
	if (0 != iRet)
	{
		cout << "connect(clientSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR)) execute failed!" << endl;
		return -1;
	}

	char recvBuf[100];
	char sendBuf[100];
	int count = 0;
	while (true)
	{
		memset(recvBuf, 0, sizeof(recvBuf));
		memset(sendBuf, 0, sizeof(sendBuf));
		//接收消息
		recv(clientSocket, recvBuf, 100, 0);
		printf("%s\n", recvBuf);
		if (strcmp(recvBuf, BYE) == 0)
		{
			//清理
			closesocket(clientSocket);
			WSACleanup();
			break;
		}
//		scanf("%d", &count);
//
//		if (count == 1)
//			sprintf(sendBuf, "date");
//		else if (count==0)
//			sprintf(sendBuf, "q");
//		else
//			sprintf(sendBuf, "unknow");
		string tmp;
		cin >> tmp;
		strcpy(sendBuf, tmp.c_str());

		count++;
		send(clientSocket, sendBuf, strlen(sendBuf) + 1, 0);
	}
	return 0;
}


第二题客户端代码没有变化。

第二题,多线程版本。
新建线程,可以指定新线程执行的函数以及要传递的参数。
要传递的参数,自定义的结构体。THREAD_DATA threadData1(connSocket, clientAddr, count++);
新建线程,ThreadProc是自己实现线程函数,threadData就是参数。HANDLE thread = CreateThread(nullptr, 0, ThreadProc, &threadData1, 0, nullptr);

点击显示/隐藏代码

#include<iostream>
#include<WinSock2.h>
#include<ctime>
#include<vector>
#pragma comment(lib, "ws2_32.lib")
//#pragma comment(lib, "Ws2_32.lib")
#define CONNECT_NUM_MAX 10
#define _WINSOCK_DEPRECATED_NO_WARNINGS
using namespace std;


//http://blog.csdn.net/luoweifu/article/details/46835437
#define NAME_LINE   40
//定义线程函数传入参数的结构体
typedef struct __THREAD_DATA
{
	int id;
	SOCKET connSocket;
	SOCKADDR_IN clientAddr;

	__THREAD_DATA(SOCKET socket, SOCKADDR_IN client, int _id = 0)
	{
		connSocket = socket;
		clientAddr = client;
		id = _id;
	}
}THREAD_DATA;

//线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	THREAD_DATA* pThreadData = static_cast<THREAD_DATA*>(lpParameter);
	SOCKET connSocket = pThreadData->connSocket;
	SOCKADDR_IN clientAddr = pThreadData->clientAddr;
	int id = pThreadData->id;

	printf("客户端%d已连接\n", id);
	char sendBuf[100];
	memset(sendBuf, 0, sizeof(sendBuf));
	sprintf_s(sendBuf, "Welcome %s, You are No.%d.  type date to get time,q to exit.", inet_ntoa(clientAddr.sin_addr), id);
	send(connSocket, sendBuf, strlen(sendBuf) + 1, 0);
	char recvBuf[100];
	while (true)
	{
		memset(recvBuf, 0, sizeof(recvBuf));
		recv(connSocket, recvBuf, 100, 0);
		printf("[客户端%d命令] %s\n", id, recvBuf);
		if (strcmp(recvBuf, "date") == 0)
		{
			time_t t = time(0);
			//http://www.cnblogs.com/mfryf/archive/2012/02/13/2349360.html
			strftime(sendBuf, sizeof(sendBuf), "%Y/%m/%d %X %A 本年第 %j 天 %z", localtime(&t));
			send(connSocket, sendBuf, strlen(sendBuf) + 1, 0);
		}
		else if (strcmp(recvBuf, "q") == 0) {
			sprintf_s(sendBuf, "BYE");
			send(connSocket, sendBuf, strlen(sendBuf) + 1, 0);
			closesocket(connSocket);
			//CloseHandle()
			break;
		}
		else {
			sprintf_s(sendBuf, "Type date to get time,q to exit.");
			send(connSocket, sendBuf, strlen(sendBuf) + 1, 0);
		}
	}
	return 0L;
}

//http://www.js-code.com/C_yuyan/20150918/4488.html
int main(int argc, char** argv)
{
	int port = 1234;
	if (argc > 1)
	{
		port = atoi(argv[1]);
		if (port < 1024 || port>65534)
		{
			printf("Port error. Use %s port\n", argv[0]);
		}
	}
	//加载套接字库
	WSADATA wsaData;
	int iRet = 0;
	iRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iRet != 0)
	{
		cout << "WSAStartup(MAKEWORD(2, 2), &wsaData) execute failed!" << endl;;
		return -1;
	}
	if (2 != LOBYTE(wsaData.wVersion) || 2 != HIBYTE(wsaData.wVersion))
	{
		WSACleanup();
		cout << "WSADATA version is not correct!" << endl;
		return -1;
	}

	//创建套接字
	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (serverSocket == INVALID_SOCKET)
	{
		cout << "serverSocket = socket(AF_INET, SOCK_STREAM, 0) execute failed!" << endl;
		return -1;
	}

	//初始化服务器地址族变量
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(port);
	//绑定
	iRet = bind(serverSocket, reinterpret_cast<SOCKADDR*>(&addrSrv), sizeof(SOCKADDR));
	if (iRet == SOCKET_ERROR)
	{
		cout << "bind(serverSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) execute failed!" << endl;
		return -1;
	}


	//监听
	iRet = listen(serverSocket, CONNECT_NUM_MAX);
	if (iRet == SOCKET_ERROR)
	{
		cout << "listen(serverSocket," << CONNECT_NUM_MAX << ") execute failed!" << endl;
		return -1;
	}

	//等待连接_接收_发送
	SOCKADDR_IN clientAddr;
	int len = sizeof(SOCKADDR);
	vector<HANDLE> threads;
	int count = 0;
	cout << "Server is started\n";
	while (1)
	{
		SOCKET connSocket = accept(serverSocket, reinterpret_cast<SOCKADDR*>(&clientAddr), &len);
		if (connSocket == INVALID_SOCKET)
		{
			cout << "accept(serverSocket, (SOCKADDR*)&clientAddr, &len) execute failed!" << endl;
			return -1;
		}
		THREAD_DATA threadData1(connSocket, clientAddr, count++);
		HANDLE thread = CreateThread(nullptr, 0, ThreadProc, &threadData1, 0, nullptr);
		threads.push_back(thread);
	}
	return 0;
}

第二题,使用Select实现
参见:windows 和 linux 套接字中的 select 机制浅析

  • 1. 调用 FD_ZERO 来初始化套接字状态;
  • 2. 调用 FD_SET 将感兴趣的套接字描述符加入集合中(每次循环都要重新加入,因为 select 更新后,会将一些没有满足条件的套接字移除队列);
  • 3. 设置等待时间后,调用 select 函数 — 更新套接字的状态;
  • 4. 调用 FD_ISSET,来判断套接字是否有相应状态,然后做相应操作,比如,如果套接字可读,就调用 recv 函数去接收数据。

关键技术:套接字队列和状态的表示与处理。

点击显示/隐藏代码

//http://www.cnblogs.com/lidabo/p/3804411.html
//windows 和 linux 套接字中的 select 机制浅析

// server.cpp :   
// 程序中加入了套接字管理队列,这样管理起来更加清晰、方便,当然也可以不用这个东西  

#include "winsock.h"  
#include "stdio.h"
#include <ctime>
#include <iostream>
#pragma comment (lib,"wsock32.lib")  
#define MAX 64

//Socket列表
struct socket_list {
	SOCKET MainSock;
	int num;
	SOCKET sock_array[MAX];
};
//初始化列表
void init_list(socket_list *list)
{
	int i;
	list->MainSock = 0;
	list->num = 0;
	for (i = 0; i < MAX; i++) {
		list->sock_array[i] = 0;
	}
}
//插入一个Socket到列表
void insert_list(SOCKET s, socket_list *list)
{
	int i;
	for (i = 0; i < MAX; i++) {
		if (list->sock_array[i] == 0) {
			list->sock_array[i] = s;
			list->num += 1;
			break;
		}
	}
}
//从列表删除一个Socket
void delete_list(SOCKET s, socket_list *list)
{
	int i;
	for (i = 0; i < MAX; i++) {
		if (list->sock_array[i] == s) {
			list->sock_array[i] = 0;
			list->num -= 1;
			break;
		}
	}
}

void make_fdlist(socket_list *list, fd_set *fd_list)
{
	int i;
	//FD_SET(s,*set),向集合中加入一个套接口描述符
	//(如果该套接口描述符 s 没在集合中,并且数组中已经设置的个数小于最大个数时,就把该描述符加入到集合中,集合元素个数加 1)。
	//这里是将 s 的值直接放入数组中。
	FD_SET(list->MainSock, fd_list);
	for (i = 0; i < MAX; i++) {
		if (list->sock_array[i] > 0) {
			FD_SET(list->sock_array[i], fd_list);
		}
	}
}

/*
1. 调用 FD_ZERO 来初始化套接字状态;
2. 调用 FD_SET 将感兴趣的套接字描述符加入集合中(每次循环都要重新加入,因为 select 更新后,会将一些没有满足条件的套接字移除队列);
3. 设置等待时间后,调用 select 函数 -- 更新套接字的状态;
4. 调用 FD_ISSET,来判断套接字是否有相应状态,然后做相应操作,比如,如果套接字可读,就调用 recv 函数去接收数据。
关键技术:套接字队列和状态的表示与处理。*/

int main(int argc, char* argv[])
{
	int port = 1234;
	if (argc > 1)
	{
		port = atoi(argv[1]);
		if (port < 1024 || port>65534)
		{
			printf("Port error. Use %s port\n", argv[0]);
		}
	}
	//s是Server,sock是客户端
	SOCKET s, sock;
	struct sockaddr_in ser_addr, remote_addr;
	int len;
	char buf[128];
	WSAData wsa;
	int retval;
	struct socket_list sock_list;
	fd_set readfds, writefds, exceptfds;
	timeval timeout;        //select 的最多等待时间,防止一直等待  
	int i;
	unsigned long arg;

	WSAStartup(0x101, &wsa);
	s = socket(AF_INET, SOCK_STREAM, 0);
	ser_addr.sin_family = AF_INET;
	ser_addr.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	ser_addr.sin_port = htons(port);
	i = bind(s, reinterpret_cast<sockaddr*>(&ser_addr), sizeof(ser_addr));
	if (i == SOCKET_ERROR)
	{
		std::cout << "bind error\n";
		return -1;
	}
	i = listen(s, 5);
	if (i == SOCKET_ERROR)
	{
		std::cout << "listen error\n";
		return -1;
	}
	timeout.tv_sec = 5;     // 如果套接字集合中在 1s 内没有数据,select 就会返回,超时 select 返回 0  
	timeout.tv_usec = 0;
	init_list(&sock_list);

	//FD_ZERO(*set),是把集合清空(初始化为 0,确切的说,是把集合中的元素个数初始化为 0,并不修改描述符数组). 
	//使用集合前,必须用 FD_ZERO 初始化,否则集合在栈上作为自动变量分配时,fd_set 分配的将是随机值,导致不可预测的问题。
	FD_ZERO(&readfds);
	FD_ZERO(&writefds);
	FD_ZERO(&exceptfds);

	sock_list.MainSock = s;
	arg = 1;
	ioctlsocket(sock_list.MainSock, FIONBIO, &arg);
	std::cout << "Server is started\n";
	while (1) {
		make_fdlist(&sock_list, &readfds);
		//make_fdlist(&sock_list,&writefds);  
		//make_fdlist(&sock_list,&exceptfds);  

		retval = select(0, &readfds, &writefds, &exceptfds, &timeout);// 超过这个时间,就不阻塞在这里,返回一个 0 值。  
		if (retval == SOCKET_ERROR) {
			retval = WSAGetLastError();
			printf("error code=%d", retval);
			break;
		}
		if (retval == 0) {
			//printf("select() is time-out! There is no data or new-connect coming!\n");
			continue;
		}

		char sendBuf[128];
		//FD_ISSET(s,*set),检查描述符是否在集合中,如果在集合中返回非 0 值,否则返回 0.
		//它的宏定义并没有给出具体实现,但实现的思路很简单,就是搜索集合,判断套接字 s 是否在数组中。
		if (FD_ISSET(sock_list.MainSock, &readfds)) {
			len = sizeof(remote_addr);
			sock = accept(sock_list.MainSock, reinterpret_cast<sockaddr*>(&remote_addr), &len);
			if (sock == SOCKET_ERROR)
				continue;
			printf("accept a connection\n");
			insert_list(sock, &sock_list);

			memset(sendBuf, 0, sizeof(sendBuf));
			sprintf_s(sendBuf, "Welcome.  type date to get time,q to exit.");
			send(sock, sendBuf, strlen(sendBuf) + 1, 0);

		}
		for (i = 0; i < MAX; i++) {
			if (sock_list.sock_array[i] == 0)
				continue;
			sock = sock_list.sock_array[i];

			if (FD_ISSET(sock, &readfds)) {
				//接受客户端数据,返回数据长度
				retval = recv(sock, buf, 128, 0);
				if (retval == 0) {
					closesocket(sock);
					printf("close a socket\n");
					delete_list(sock, &sock_list);
					continue;
				}
				if (retval == -1) {
					retval = WSAGetLastError();
					if (retval == WSAEWOULDBLOCK)
						continue;
					closesocket(sock);
					printf("close a socket\n");
					delete_list(sock, &sock_list);   // 连接断开后,从队列中移除该套接字  
					continue;
				}
				buf[retval] = 0;
				printf("->%s\n", buf);

				memset(sendBuf, 0, sizeof(sendBuf));
				if (strcmp(buf, "date") == 0)
				{
					time_t t = time(nullptr);
					strftime(sendBuf, sizeof(sendBuf), "%Y/%m/%d %X %A 本年第 %j 天 %z", localtime(&t));
					send(sock, sendBuf, strlen(sendBuf) + 1, 0);
				}
				else if (strcmp(buf, "q") == 0)
				{
					sprintf_s(sendBuf, "BYE");
					send(sock, sendBuf, strlen(sendBuf) + 1, 0);
					closesocket(sock);
					printf("close a socket\n");
					delete_list(sock, &sock_list);
				}
				else
				{
					sprintf_s(sendBuf, "Type date to get time,q to exit.");
					send(sock, sendBuf, strlen(sendBuf) + 1, 0);
				}
			}
			//if(FD_ISSET(sock,&writefds)){  
			//}  
			//if(FD_ISSET(sock,&exceptfds)){  

		}
		FD_ZERO(&readfds);
		FD_ZERO(&writefds);
		FD_ZERO(&exceptfds);
	}
	closesocket(sock_list.MainSock);
	WSACleanup();
	return 0;
}


第三题,原始套接字。
首先定义协议头部的结构体,然后对收到的数据进行解析即可。
具体细节可以看代码,注释还算详细。

点击显示/隐藏代码

#define RAW_SOCKET_

#ifdef RAW_SOCKET_
#include <WinSock2.h>
#include <iostream>
#pragma comment(lib, "ws2_32.lib")

#define MAXPOCKETSIZE 65535

typedef struct _iphdr				//定义IP首部 
{
	unsigned char	h_verlen;		//4位首部长度+4位IP版本号 
	unsigned char	tos;			//8位服务类型TOS 
	unsigned short	total_len;		//16位总长度(字节) 
	unsigned short	ident;			//16位标识 
	unsigned short	frag_and_flags;	//3位标志位 
	unsigned char	ttl;			//8位生存时间 TTL 
	unsigned char	proto;			//8位协议 (TCP, UDP 或其他) 
	unsigned short	checksum;		//16位IP首部校验和 
	unsigned long	sourceIP;		//32位源IP地址 
	unsigned long	destIP;			//32位目的IP地址 
}IP_HEADER;

typedef struct _udphdr			//定义UDP首部
{
	unsigned short uh_sport;    //16位源端口
	unsigned short uh_dport;    //16位目的端口
	unsigned short uh_len;		//16位UDP包长度
	unsigned short uh_sum;		//16位校验和
}UDP_HEADER;

typedef struct _tcphdr			//定义TCP首部 
{
	unsigned short	th_sport;	//16位源端口 
	unsigned short	th_dport;	//16位目的端口 
	unsigned long	th_seq;		//32位序列号 
	unsigned long	th_ack;		//32位确认号 
	char			th_lenres;	//4位首部长度/6位保留字 
	char			th_flag;	//6位标志位
	unsigned short	th_win;		//16位窗口大小
	unsigned short	th_sum;		//16位校验和
	unsigned short	th_urp;		//16位紧急数据偏移量
}TCP_HEADER;

typedef struct _icmphdr {
	unsigned char  icmp_type;
	unsigned char  icmp_code; /* type sub code */
	unsigned short icmp_cksum;
	unsigned short icmp_id;
	unsigned short icmp_seq;
	/* This is not the std header, but we reserve space for time */
	unsigned long icmp_timestamp;
}ICMP_HEADER;

using namespace std;

void DecodeICMPPacket(char* pData)
{
	ICMP_HEADER *p_icmp_header = reinterpret_cast<ICMP_HEADER*>(pData);
	cout << "ICMP信息\tICMP Type:" << p_icmp_header->icmp_type << "\t\tCode:" << p_icmp_header->icmp_code << endl;
	switch (p_icmp_header->icmp_type)
	{
	case 0:cout << "回显答复Echo Response\n"; break;
	case 8:cout << "请求回显Echo Request\n"; break;
	case 3:cout << "目标不可达Destination Unreachable\n"; break;
	case 11:cout << "数据包超时Datagram Timeout(TTL=0)\n"; break;
	}
}

void DecodeUDPPacket(char* pData) {
	UDP_HEADER *p_udp_header = reinterpret_cast<UDP_HEADER*>(pData);
	cout << "UDP信息\t源端口=" << ntohs(p_udp_header->uh_sport)
		<< "\t\t目的端口=" << ntohs(p_udp_header->uh_dport) << endl;
	cout << (pData + 8);
}

void DecodeTCPPacket(char* pData)
{
	TCP_HEADER *p_tcp_header = reinterpret_cast<TCP_HEADER*>(pData);
	cout << "TCP信息\t源端口=" << ntohs(p_tcp_header->th_sport)
		<< "\t\t目的端口=" << ntohs(p_tcp_header->th_dport) << endl;
	cout << (pData + 20);
}
void DecodeIPPacket(char *pData)
{
	IP_HEADER *p_ip_header = reinterpret_cast<IP_HEADER*>(pData);
	in_addr source, dest;
	char szSourceIP[32], szDestIP[32];
	source.S_un.S_addr = p_ip_header->sourceIP;
	dest.S_un.S_addr = p_ip_header->destIP;
	strcpy(szSourceIP, inet_ntoa(source));
	strcpy(szDestIP, inet_ntoa(dest));
	cout << "IP信息\t";
	cout << "源地址=" << szSourceIP << "\t目标地址=" << szDestIP << endl;
	int nHeaderLen = (p_ip_header->h_verlen & 0xf)*sizeof(ULONG);

	switch (p_ip_header->proto)
	{
	case IPPROTO_TCP:DecodeTCPPacket(pData + nHeaderLen); break;
	case IPPROTO_UDP:DecodeUDPPacket(pData + nHeaderLen); break;
	case IPPROTO_ICMP:DecodeICMPPacket(pData + nHeaderLen); break;
	default:cout << "协议号:" << p_ip_header->proto << endl;
	}
}

int main(int argc, char* argv[])
{
	//加载套接字库
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 0), &wsaData);

	SOCKET sock = socket(AF_INET, SOCK_RAW, IPPROTO_IP);
	if (sock == INVALID_SOCKET)
	{
		printf("[Error Code=%d]Create Raw Socket Error.Access Denied.创建原始套接字失败,需要管理员权限。\n",
			WSAGetLastError());
		return -1;
	}
	//获取本地地址
	char sHostName[256];
	SOCKADDR_IN addr_in;
	struct hostent *hptr;
	gethostname(sHostName, sizeof(sHostName));
	if ((hptr = gethostbyname(sHostName)) == nullptr)
	{
		cout << "获取本地IP地址出错 Error Code=" << WSAGetLastError() << endl;
		WSACleanup();
		return -1;
	}
	//显示本地所有IP地址
	char **pptr = hptr->h_addr_list;
	cout << "本机 IP 列表:\n";
	while (*pptr != nullptr)
	{
		cout << inet_ntoa(*reinterpret_cast<struct in_addr*>(*pptr)) << endl;
		pptr++;
	}
	cout << "输入要监听的接口的IP地址:\n>";
	char snfIP[20];
	cin.getline(snfIP, sizeof(snfIP));


	// 填充SOCKADDR_IN结构 
	addr_in.sin_family = AF_INET;
	addr_in.sin_port = htons(0);
	addr_in.sin_addr.S_un.S_addr = inet_addr(snfIP);

	// 把 sock 绑定到本地地址上 
	if (bind(sock, reinterpret_cast<PSOCKADDR>(&addr_in), sizeof(addr_in)) == SOCKET_ERROR)
	{
		printf("[Error Code=%d]Bind Error.绑定错误\n", WSAGetLastError());
		closesocket(sock);
		WSACleanup();
		return -1;
	}

	//设为混杂模式.dwValue为输入输出参数,为1时执行,0时取消 
	DWORD dwValue = 1;

	// 设置 SOCK_RAW 为SIO_RCVALL,以便接收所有的IP包。其中SIO_RCVALL 
	// 的定义为: #define SIO_RCVALL _WSAIOW(IOC_VENDOR,1) 
#define SIO_RCVALL _WSAIOW(IOC_VENDOR,1)
	int ioctlsckterr = ioctlsocket(sock, SIO_RCVALL, &dwValue);
	if (ioctlsckterr != NO_ERROR) {
		printf("[Error Code=%d]Error at ioctlsocket()设置网卡为混杂模式出错\n", WSAGetLastError());
		closesocket(sock);
		WSACleanup();
		return -1;
	}

	char buff[MAXPOCKETSIZE];
	int nRet;
	while (1)
	{
		memset(buff, 0, sizeof(buff));
		nRet = recv(sock, buff, sizeof(buff), 0);
		if (nRet <= 0)
		{
			cout << "[Error Code=" << WSAGetLastError() << "]抓取数据出错." << endl;
			break;
		}
		cout << "\n-------------------------------------------------------------------------------\n";
		DecodeIPPacket(buff);
	}
	closesocket(sock);
	WSACleanup();
	return 0;
}

#endif

#ifdef PING____
/*http://download.csdn.net/detail/geoff08zhang/4571358*/
/*************************************************************************
*
* Copyright (c) 2002-2005 by Zhang Huiyong All Rights Reserved
*
* FILENAME:  Ping.c
*
* PURPOSE :  Ping 程序.
*
* AUTHOR  :  张会勇
*
* BOOK    :  <<WinSock网络编程经络>>
*
**************************************************************************/

#include <stdio.h>
#include <winsock2.h>

#pragma comment(lib, "ws2_32.lib")    /* WinSock使用的库函数 */

/* ICMP 类型 */
#define ICMP_TYPE_ECHO          8
#define ICMP_TYPE_ECHO_REPLY    0

#define ICMP_MIN_LEN            8  /* ICMP 最小长度, 只有首部 */
#define ICMP_DEF_COUNT          4  /* 缺省数据次数 */
#define ICMP_DEF_SIZE          32  /* 缺省数据长度 */
#define ICMP_DEF_TIMEOUT     1000  /* 缺省超时时间, 毫秒 */
#define ICMP_MAX_SIZE       65500  /* 最大数据长度 */

/* IP 首部 -- RFC 791 */
struct ip_hdr
{
	unsigned char vers_len;     /* 版本和首部长度 */
	unsigned char tos;          /* 服务类型 */
	unsigned short total_len;   /* 数据报的总长度 */
	unsigned short id;          /* 标识符 */
	unsigned short frag;        /* 标志和片偏移 */
	unsigned char ttl;          /* 生存时间 */
	unsigned char proto;        /* 协议 */
	unsigned short checksum;    /* 校验和 */
	unsigned int sour;          /* 源 IP 地址 */
	unsigned int dest;          /* 目的 IP 地址 */
};

/* ICMP 首部 -- RFC 792 */
struct icmp_hdr
{
	unsigned char type;         /* 类型 */
	unsigned char code;         /* 代码 */
	unsigned short checksum;    /* 校验和 */
	unsigned short id;          /* 标识符 */
	unsigned short seq;         /* 序列号 */

								/* 这之后的不是标准 ICMP 首部, 用于记录时间 */
	unsigned long timestamp;
};

struct icmp_user_opt
{
	unsigned int  persist;  /* 一直 Ping            */
	unsigned int  count;    /* 发送 ECHO 请求的数量 */
	unsigned int  size;     /* 发送数据的大小       */
	unsigned int  timeout;  /* 等待答复的超时时间   */
	char          *host;    /* 主机地址     */
	unsigned int  send;     /* 发送数量     */
	unsigned int  recv;     /* 接收数量     */
	unsigned int  min_t;    /* 最短时间     */
	unsigned int  max_t;    /* 最长时间     */
	unsigned int  total_t;  /* 总的累计时间 */
};

/* 随机数据 */
const char icmp_rand_data[] = "abcdefghigklmnopqrstuvwxyz0123456789"
"ABCDEFGHIJKLMNOPQRSTUVWXYZ";

struct icmp_user_opt user_opt_g = {
	0, ICMP_DEF_COUNT, ICMP_DEF_SIZE, ICMP_DEF_TIMEOUT, NULL,
	0, 0, 0xFFFF, 0
};

unsigned short ip_checksum(unsigned short *buf, int buf_len);


/**************************************************************************
*
* 函数功能: 构造 ICMP 数据.
*
* 参数说明: [IN, OUT] icmp_data, ICMP 缓冲区;
*           [IN] data_size, icmp_data 的长度;
*           [IN] sequence, 序列号.
*
* 返 回 值: void.
*
**************************************************************************/
void icmp_make_data(char *icmp_data, int data_size, int sequence)
{
	struct icmp_hdr *icmp_hdr;
	char *data_buf;
	int data_len;
	int fill_count = sizeof(icmp_rand_data) / sizeof(icmp_rand_data[0]);

	/* 填写 ICMP 数据 */
	data_buf = icmp_data + sizeof(struct icmp_hdr);
	data_len = data_size - sizeof(struct icmp_hdr);

	while (data_len > fill_count)
	{
		memcpy(data_buf, icmp_rand_data, fill_count);
		data_len -= fill_count;
	}

	if (data_len > 0)
		memcpy(data_buf, icmp_rand_data, data_len);

	/* 填写 ICMP 首部 */
	icmp_hdr = (struct icmp_hdr *)icmp_data;

	icmp_hdr->type = ICMP_TYPE_ECHO;
	icmp_hdr->code = 0;
	icmp_hdr->id = (unsigned short)GetCurrentProcessId();
	icmp_hdr->checksum = 0;
	icmp_hdr->seq = sequence;
	icmp_hdr->timestamp = GetTickCount();

	icmp_hdr->checksum = ip_checksum((unsigned short*)icmp_data, data_size);
}

/**************************************************************************
*
* 函数功能: 解析接收到的数据.
*
* 参数说明: [IN] buf, 数据缓冲区;
*           [IN] buf_len, buf 的长度;
*           [IN] from, 对方的地址.
*
* 返 回 值: 成功返回 0, 失败返回 -1.
*
**************************************************************************/
int icmp_parse_reply(char *buf, int buf_len, struct sockaddr_in *from)
{
	struct ip_hdr *ip_hdr;
	struct icmp_hdr *icmp_hdr;
	unsigned short hdr_len;
	int icmp_len;
	unsigned long trip_t;

	ip_hdr = (struct ip_hdr *)buf;
	hdr_len = (ip_hdr->vers_len & 0xf) << 2; /* IP 首部长度 */

	if (buf_len < hdr_len + ICMP_MIN_LEN)
	{
		printf("[Ping] Too few bytes from %s\n", inet_ntoa(from->sin_addr));
		return -1;
	}

	icmp_hdr = (struct icmp_hdr *)(buf + hdr_len);
	icmp_len = ntohs(ip_hdr->total_len) - hdr_len;

	/* 检查校验和 */
	if (ip_checksum((unsigned short *)icmp_hdr, icmp_len))
	{
		printf("[Ping] icmp checksum error!\n");
		return -1;
	}

	/* 检查 ICMP 类型 */
	if (icmp_hdr->type != ICMP_TYPE_ECHO_REPLY)
	{
		printf("[Ping] not echo reply : %d\n", icmp_hdr->type);
		return -1;
	}

	/* 检查 ICMP 的 ID */
	if (icmp_hdr->id != (unsigned short)GetCurrentProcessId())
	{
		printf("[Ping] someone else's message!\n");
		return -1;
	}

	/* 输出响应信息 */
	trip_t = GetTickCount() - icmp_hdr->timestamp;
	buf_len = ntohs(ip_hdr->total_len) - hdr_len - ICMP_MIN_LEN;
	printf("%d bytes from %s:", buf_len, inet_ntoa(from->sin_addr));
	printf(" icmp_seq = %d  time: %d ms\n", icmp_hdr->seq, trip_t);

	user_opt_g.recv++;
	user_opt_g.total_t += trip_t;

	/* 记录返回时间 */
	if (user_opt_g.min_t > trip_t)
		user_opt_g.min_t = trip_t;

	if (user_opt_g.max_t < trip_t)
		user_opt_g.max_t = trip_t;

	return 0;
}

/**************************************************************************
*
* 函数功能: 接收数据, 处理应答.
*
* 参数说明: [IN] icmp_soc, 套接口描述符.
*
* 返 回 值: 成功返回 0, 失败返回 -1.
*
**************************************************************************/
int icmp_process_reply(SOCKET icmp_soc)
{
	struct sockaddr_in from_addr;
	int result, data_size = user_opt_g.size;
	int from_len = sizeof(from_addr);
	char *recv_buf;

	data_size += sizeof(struct ip_hdr) + sizeof(struct icmp_hdr);
	recv_buf = static_cast<char*>(malloc(data_size));

	/* 接收数据 */
	result = recvfrom(icmp_soc, recv_buf, data_size, 0,
		(struct sockaddr*)&from_addr, &from_len);
	if (result == SOCKET_ERROR)
	{
		if (WSAGetLastError() == WSAETIMEDOUT)
			printf("timed out\n");
		else
			printf("[PING] recvfrom_ failed: %d\n", WSAGetLastError());

		return -1;
	}

	result = icmp_parse_reply(recv_buf, result, &from_addr);
	free(recv_buf);

	return result;
}

/**************************************************************************
*
* 函数功能: 显示 ECHO 的帮助信息.
*
* 参数说明: [IN] prog_name, 程序名;
*
* 返 回 值: void.
*
**************************************************************************/
void icmp_help(char *prog_name)
{
	char *file_name;

	file_name = strrchr(prog_name, '\\');
	if (file_name != NULL)
		file_name++;
	else
		file_name = prog_name;

	/* 显示帮助信息 */
	printf(" usage:     %s host_address [-t] [-n count] [-l size] "
		"[-w timeout]\n", file_name);
	printf(" -t         Ping the host until stopped.\n");
	printf(" -n count   the count to send ECHO\n");
	printf(" -l size    the size to send data\n");
	printf(" -w timeout timeout to wait the reply\n");
	exit(1);
}

/**************************************************************************
*
* 函数功能: 解析命令行选项, 保存到全局变量中.
*
* 参数说明: [IN] argc, 参数的个数;
*           [IN] argv, 字符串指针数组.
*
* 返 回 值: void.
*
**************************************************************************/
void icmp_parse_param(int argc, char **argv)
{
	int i;

	for (i = 1; i < argc; i++)
	{
		if ((argv[i][0] != '-') && (argv[i][0] != '/'))
		{
			/* 处理主机名 */
			if (user_opt_g.host)
				icmp_help(argv[0]);
			else
			{
				user_opt_g.host = argv[i];
				continue;
			}
		}

		switch (tolower(argv[i][1]))
		{
		case 't':   /* 持续 Ping */
			user_opt_g.persist = 1;
			break;

		case 'n':   /* 发送请求的数量 */
			i++;
			user_opt_g.count = atoi(argv[i]);
			break;

		case 'l':   /* 发送数据的大小 */
			i++;
			user_opt_g.size = atoi(argv[i]);
			if (user_opt_g.size > ICMP_MAX_SIZE)
				user_opt_g.size = ICMP_MAX_SIZE;
			break;

		case 'w':   /* 等待接收的超时时间 */
			i++;
			user_opt_g.timeout = atoi(argv[i]);
			break;

		default:
			icmp_help(argv[0]);
			break;
		}
	}
}


int main(int argc, char **argv)
{
	WSADATA wsaData;
	SOCKET icmp_soc;
	struct sockaddr_in dest_addr;
	struct hostent *host_ent = NULL;

	int result, data_size, send_len;
	unsigned int i, timeout, lost;
	char *icmp_data;
	unsigned int ip_addr = 0;
	unsigned short seq_no = 0;

	if (argc < 2)
		icmp_help(argv[0]);

	icmp_parse_param(argc, argv);
	WSAStartup(MAKEWORD(2, 0), &wsaData);

	/* 解析主机地址 */
	ip_addr = inet_addr(user_opt_g.host);
	if (ip_addr == INADDR_NONE)
	{
		host_ent = gethostbyname(user_opt_g.host);
		if (!host_ent)
		{
			printf("[PING] Fail to resolve %s\n", user_opt_g.host);
			return -1;
		}

		memcpy(&ip_addr, host_ent->h_addr_list[0], host_ent->h_length);
	}

	icmp_soc = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);
	if (icmp_soc == INVALID_SOCKET)
	{
		printf("[PING] socket() failed: %d\n", WSAGetLastError());
		return -1;
	}

	/* 设置选项, 接收和发送的超时时间 */
	timeout = user_opt_g.timeout;
	result = setsockopt(icmp_soc, SOL_SOCKET, SO_RCVTIMEO,
		(char*)&timeout, sizeof(timeout));

	timeout = 1000;
	result = setsockopt(icmp_soc, SOL_SOCKET, SO_SNDTIMEO,
		(char*)&timeout, sizeof(timeout));

	memset(&dest_addr, 0, sizeof(dest_addr));
	dest_addr.sin_family = AF_INET;
	dest_addr.sin_addr.s_addr = ip_addr;

	data_size = user_opt_g.size + sizeof(struct icmp_hdr) - sizeof(long);
	icmp_data = static_cast<char*>(malloc(data_size));

	if (host_ent)
		printf("Ping %s [%s] with %d bytes data\n", user_opt_g.host,
			inet_ntoa(dest_addr.sin_addr), user_opt_g.size);
	else
		printf("Ping [%s] with %d bytes data\n", inet_ntoa(dest_addr.sin_addr),
			user_opt_g.size);

	/* 发送请求并接收响应 */
	for (i = 0; i < user_opt_g.count; i++)
	{
		icmp_make_data(icmp_data, data_size, seq_no++);

		send_len = sendto(icmp_soc, icmp_data, data_size, 0,
			(struct sockaddr*)&dest_addr, sizeof(dest_addr));
		if (send_len == SOCKET_ERROR)
		{
			if (WSAGetLastError() == WSAETIMEDOUT)
			{
				printf("[PING] sendto is timeout\n");
				continue;
			}

			printf("[PING] sendto failed: %d\n", WSAGetLastError());
			break;
		}

		user_opt_g.send++;
		result = icmp_process_reply(icmp_soc);

		user_opt_g.persist ? i-- : i; /* 持续 Ping */
		Sleep(1000); /* 延迟 1 秒 */
	}

	lost = user_opt_g.send - user_opt_g.recv;

	/* 打印统计数据 */
	printf("\nStatistic :\n");
	printf("    Packet : sent = %d, recv = %d, lost = %d (%3.f%% lost)\n",
		user_opt_g.send, user_opt_g.recv, lost, (float)lost * 100 / user_opt_g.send);

	if (user_opt_g.recv > 0)
	{
		printf("Roundtrip time (ms)\n");
		printf("    min = %d ms, max = %d ms, avg = %d ms\n", user_opt_g.min_t,
			user_opt_g.max_t, user_opt_g.total_t / user_opt_g.recv);
	}

	free(icmp_data);
	closesocket(icmp_soc);
	WSACleanup();

	return 0;
}

/**************************************************************************
*
* 函数功能: 计算校验和.
*
* 参数说明: [IN] buf, 数据缓冲区;
*           [IN] buf_len, buf 的字节长度.
*
* 返 回 值: 校验和.
*
**************************************************************************/
unsigned short ip_checksum(unsigned short *buf, int buf_len)
{
	unsigned long checksum = 0;

	while (buf_len > 1)
	{
		checksum += *buf++;
		buf_len -= sizeof(unsigned short);
	}

	if (buf_len)
	{
		checksum += *(unsigned char *)buf;
	}

	checksum = (checksum >> 16) + (checksum & 0xffff);
	checksum += (checksum >> 16);

	return (unsigned short)(~checksum);
}
#endif


第四题,传输文件。
设置各种消息类型,采用一问一答形式,比如刚建立连接时:

服务器:[MSG_CMD]我支持这些命令。
客户端:[MSG_FILENAME]输入一个命令< 我要a.txt文件>
服务器:[MSG_FILE_LENGTH]你怎么知道我有这个文件呢,返回文件长度。
客户端:[MSG_CLIENT_READY]收到长度表示服务器有这个文件,在客户端本地新建(或覆盖)这个文件。我准备好了,你从位置0开始传输内容吧。
服务器:[MSG_FILE]从位置0开始传输一个文件块。(假设一个块长1024)
客户端:[MSG_CLIENT_READY]我准备好了,你从1024开始传输吧。
服务器:[MSG_FILE]从1024开始传输文件块。
客户端:[MSG_FILE_SUCC]客户端计算知道文件传输完毕,就说接收完毕。
服务器:[MSG_FILE_CMD]哦,我知道了。我支持这些命令。

一轮结束。错误处理看代码吧。

点击显示/隐藏代码

#pragma once
#ifndef SEND_FILE_HEADER__
#define SEND_FILE_HEADER__

#define MAX_PACKET_SIZE				10240	// 数据包的最大长度, 单位是 sizeof(char)
#define	FILENAME_LENGTH				256		//文件名长度
#define MAX_FILE_SIZE				((MAX_PACKET_SIZE)-3*sizeof(long)-FILENAME_LENGTH-sizeof(char))
#define CMD_LENGTH					256		//命令描述的最大长度
// 各种消息的宏定义  
#define INVALID_MSG					-1  // 无效的消息标识
#define MSG_CMD						1	// 客户端刚连接时,服务器发送支持的命令
#define MSG_FILENAME				2   // 文件的名称  
#define MSG_FILELENGTH				3   // 传送文件的长度  
#define MSG_CLIENT_READY			4   // 客户端准备接收文件  
#define MSG_FILE					5   // 传送文件  
#define MSG_SEND_FILE_SUCCESS		6   // 传送文件成功  
#define MSG_OPENFILE_ERROR			7   // 打开文件失败, 可能是文件路径错误找不到文件等原因  
#define MSG_FILE_ALREADYEXIT_ERROR	8   // 要保存的文件已经存在了 
#define MSG_BYE						9	//断开
#define MSG_HELLO					10	//客户端发送Hello

#pragma pack(1)
/*消息头*/
struct tMSG_HEADER
{
	/*消息标识*/
	char cMsgId;
	tMSG_HEADER(char id = INVALID_MSG) :cMsgId(id) {};
};

struct tMSG_HELLO :tMSG_HEADER
{
	tMSG_HELLO() :tMSG_HEADER(MSG_HELLO) {}
};
/*服务器发送的命令*/
struct tMSG_CMD :tMSG_HEADER
{
	char commad[CMD_LENGTH];
	tMSG_CMD() :tMSG_HEADER(MSG_CMD) {}
};

/*传送的是文件名*/
struct tMSG_FILENAME :tMSG_HEADER
{
	/*文件名*/
	char szFileName[FILENAME_LENGTH];
	tMSG_FILENAME() :tMSG_HEADER(MSG_FILENAME) {}
};

/*传送的是文件长度*/
struct tMSG_FILE_LENGTH :tMSG_HEADER
{
	/*文件长度*/
	long lLength;
	char szFileName[FILENAME_LENGTH];
	tMSG_FILE_LENGTH(long l) :tMSG_HEADER(MSG_FILELENGTH), lLength(l) {}
};

/*客户端准备好了*/
struct tMSG_CLIENT_READY :tMSG_HEADER
{
	long lLastPosition, lLength;
	char szFileName[FILENAME_LENGTH];
	tMSG_CLIENT_READY(long l, long len) :tMSG_HEADER(MSG_CLIENT_READY), lLastPosition(l), lLength(len) {}
};

/*接下来传送的是文件*/
struct tMSG_FILE :tMSG_HEADER
{
	union // 采用 union 保证了数据包的大小不大于 MAX_PACKET_SIZE * sizeof(char)
	{
		char szBuff[MAX_PACKET_SIZE - sizeof(char)];///<strong>父结构体还有一个char类型的MsgId!!!</strong>
		struct
		{
			long lStart;
			long lSize;
			long lFileLength;
			char szFileName[FILENAME_LENGTH];
			char szBuff[MAX_FILE_SIZE];
		}tFile;
	};
	tMSG_FILE() : tMSG_HEADER(MSG_FILE) {}
};

/*传送文件成功*/
struct tMSG_SEND_FILE_SUCC :tMSG_HEADER
{
	char szFileName[FILENAME_LENGTH];
	tMSG_SEND_FILE_SUCC() :tMSG_HEADER(MSG_SEND_FILE_SUCCESS) {}
};

/*错误消息*/
struct tMSG_ERROR :tMSG_HEADER
{
	tMSG_ERROR(char ErrType) :tMSG_HEADER(ErrType) {}
};

/*断开*/
struct tMSG_BYE :tMSG_HEADER
{
	tMSG_BYE() :tMSG_HEADER(MSG_BYE) {}
};
#pragma pack()
#endif
/*
1.在前三个实验的基础上,将其改造为一个能传输指定文件名称的点对点文件传输软件
2.设计并实现一个支持多个客户端的文件传输服务器
3.客户端等待键盘输入文件名称,然后将文件名称传输给服务器,
  服务器在预先设置好的文件夹下查找该文件,如果发现同名文件,开始传输回客户端,
  客户端接收完文件后将文件以输入的文件名称保存在本地某个目录即可,否则告诉客户端文件不存在。
*/
#include <iostream>
#include <fstream>
#include <WinSock2.h>
#include "header.h"
#pragma comment(lib, "ws2_32.lib")
#define CONNECT_NUM_MAX 10

using namespace std;

/*发送服务器支持的命令*/
void SendCmd(SOCKET sClient)
{
	tMSG_CMD cmd;
	sprintf_s(cmd.commad, "Type filename to get a file, /q to exit.\n>");
	send(sClient, reinterpret_cast<char*>(&cmd), sizeof(cmd), 0);
}

/*客户端输入的是文件名,返回文件长度,或文件不存在错误*/
void SendFileLength(SOCKET sClient, tMSG_HEADER *p_msg_header, int id)
{
	tMSG_FILENAME *p_msg_filename = static_cast<tMSG_FILENAME*>(p_msg_header);
	ifstream filein(p_msg_filename->szFileName, ios::in | ios::binary);
	if (!filein)
	{
		//文件不存在
		cout << "[Client " << id << "]<" << p_msg_filename->szFileName << "> File Not Found!\n";
		tMSG_ERROR ErrMsg(MSG_OPENFILE_ERROR);
		send(sClient, reinterpret_cast<char*>(&ErrMsg), sizeof(ErrMsg), 0);
	}
	else
	{
		//返回文件长度
		filein.seekg(0, ios::end);
		long length = filein.tellg();
		cout << "[Client " << id << "]<" << p_msg_filename->szFileName << "> File Length = " << length << endl;
		tMSG_FILE_LENGTH FileLength(length);
		strcpy(FileLength.szFileName, p_msg_filename->szFileName);
		send(sClient, reinterpret_cast<char*>(&FileLength), sizeof(FileLength), 0);
	}
	filein.close();
}

/*传送文件*/
void SendFile(SOCKET sClient, tMSG_HEADER *p_msg_header, int id)
{
	tMSG_CLIENT_READY *p_msg_client_ready = static_cast<tMSG_CLIENT_READY*>(p_msg_header);
	ifstream fin(p_msg_client_ready->szFileName, ios::in | ios::binary);
	if (!fin) { cout << "[Client " << id << "]<" << p_msg_client_ready->szFileName << "> Error Open File!\n"; }

	/*从上次结束的地方开始传送*/
	fin.seekg(p_msg_client_ready->lLastPosition, ios::beg);

	tMSG_FILE tSendFile;
	tSendFile.tFile.lStart = p_msg_client_ready->lLastPosition;
	tSendFile.tFile.lFileLength = p_msg_client_ready->lLength;
	strcpy(tSendFile.tFile.szFileName, p_msg_client_ready->szFileName);

	if (tSendFile.tFile.lFileLength - tSendFile.tFile.lStart > MAX_FILE_SIZE)
	{	//要传送的长度大于一次传送的长度
		tSendFile.tFile.lSize = MAX_FILE_SIZE;
	}
	else
	{	//要传送的文件长度可在这一次内传送完成
		tSendFile.tFile.lSize = tSendFile.tFile.lFileLength - tSendFile.tFile.lStart;
	}

	fin.read(tSendFile.tFile.szBuff, tSendFile.tFile.lSize);
	fin.close();
	//	cout << (int)tSendFile.cMsgId;
//	tMSG_HEADER *p_header = static_cast<tMSG_HEADER*>(&tSendFile);
//	cout << (int)p_header->cMsgId;
	send(sClient, reinterpret_cast<char*>(&tSendFile), sizeof(tSendFile), 0);
	cout << "[Client " << id << "]<" << tSendFile.tFile.szFileName << "> 已发送" << tSendFile.tFile.lSize + tSendFile.tFile.lStart << "/" << tSendFile.tFile.lFileLength << endl;
}

void SendSucc(SOCKET socket, tMSG_HEADER *p_msg_header, int id)
{
	tMSG_SEND_FILE_SUCC *succ = static_cast<tMSG_SEND_FILE_SUCC*>(p_msg_header);
	cout << "[Client " << id << "]<" << succ->szFileName << "> Send File Success!\n";
	SendCmd(socket);
}

/*处理消息,当要断开连接时返回false*/
bool ProcessMsg(SOCKET sClient, int id)
{
	char recvBuf[MAX_PACKET_SIZE];
	//接收消息
	int nRecv = recv(sClient, recvBuf, sizeof(recvBuf) + 1, 0);
	if (nRecv > 0)
	{
		recvBuf[nRecv] = '\0';
	}
	//解析
	tMSG_HEADER *p_msg_header = reinterpret_cast<tMSG_HEADER*>(recvBuf);
	switch (p_msg_header->cMsgId)
	{
	case MSG_HELLO:					SendCmd(sClient);							break;
	case MSG_FILENAME:				SendFileLength(sClient, p_msg_header, id);	break;
	case MSG_CLIENT_READY:			SendFile(sClient, p_msg_header, id);		break;
	case MSG_SEND_FILE_SUCCESS:		SendSucc(sClient, p_msg_header, id);		break;
	case MSG_FILE_ALREADYEXIT_ERROR:break;//覆盖客户端文件,因此这个暂时不需处理
	case MSG_BYE:return false;
	}
	return true;
}

//http://blog.csdn.net/luoweifu/article/details/46835437
#define NAME_LINE   40
//定义线程函数传入参数的结构体
typedef struct __THREAD_DATA
{
	int id;
	SOCKET connSocket;
	SOCKADDR_IN clientAddr;

	__THREAD_DATA(SOCKET socket, SOCKADDR_IN client, int _id = 0)
	{
		connSocket = socket;
		clientAddr = client;
		id = _id;
	}
}THREAD_DATA;

//线程函数
DWORD WINAPI ThreadProc(LPVOID lpParameter)
{
	THREAD_DATA* pThreadData = static_cast<THREAD_DATA*>(lpParameter);
	SOCKET connSocket = pThreadData->connSocket;
	SOCKADDR_IN clientAddr = pThreadData->clientAddr;
	int id = pThreadData->id;

	cout << "[Client " << id << "] Connected.已连接.\n";

	SendCmd(connSocket);

	while (true)
	{
		if (!ProcessMsg(connSocket, id))break;
	}
	cout << "[Client " << id << "] Disconnected.断开连接.\n";
	tMSG_BYE p_bye;
	send(connSocket, reinterpret_cast<char*>(&p_bye), sizeof(p_bye), 0);
	closesocket(connSocket);
	return 0L;
}

int main(int argc, char** argv)
{
	int port = 1234;
	if (argc > 1)
	{
		port = atoi(argv[1]);
		if (port < 1024 || port>65534)
		{
			printf("Port error. Use %s port\n", argv[0]);
		}
	}
	//加载套接字库
	WSADATA wsaData;
	int iRet = 0;
	iRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iRet != 0)
	{
		cout << "WSAStartup(MAKEWORD(2, 2), &wsaData) execute failed!" << endl;;
		return -1;
	}
	if (2 != LOBYTE(wsaData.wVersion) || 2 != HIBYTE(wsaData.wVersion))
	{
		WSACleanup();
		cout << "WSADATA version is not correct!" << endl;
		return -1;
	}

	//创建套接字
	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (serverSocket == INVALID_SOCKET)
	{
		cout << "serverSocket = socket(AF_INET, SOCK_STREAM, 0) execute failed!" << endl;
		return -1;
	}

	//初始化服务器地址族变量
	SOCKADDR_IN addrSrv;
	addrSrv.sin_addr.S_un.S_addr = htonl(INADDR_ANY);
	addrSrv.sin_family = AF_INET;
	addrSrv.sin_port = htons(port);
	//绑定
	iRet = bind(serverSocket, reinterpret_cast<SOCKADDR*>(&addrSrv), sizeof(SOCKADDR));
	if (iRet == SOCKET_ERROR)
	{
		cout << "bind(serverSocket, (SOCKADDR*)&addrSrv, sizeof(SOCKADDR)) execute failed!" << endl;
		return -1;
	}


	//监听
	iRet = listen(serverSocket, CONNECT_NUM_MAX);
	if (iRet == SOCKET_ERROR)
	{
		cout << "listen(serverSocket," << CONNECT_NUM_MAX << ") execute failed!" << endl;
		return -1;
	}

	//等待连接_接收_发送
	SOCKADDR_IN clientAddr;
	int len = sizeof(SOCKADDR);
	int count = 0;
	cout << "Server is started\n";
	while (1)
	{
		SOCKET connSocket = accept(serverSocket, reinterpret_cast<SOCKADDR*>(&clientAddr), &len);
		if (connSocket == INVALID_SOCKET)
		{
			cout << "accept(serverSocket, (SOCKADDR*)&clientAddr, &len) execute failed!" << endl;
			return -1;
		}

		THREAD_DATA threadData1(connSocket, clientAddr, count++);
		HANDLE thread = CreateThread(nullptr, 0, ThreadProc, &threadData1, 0, nullptr);
		CloseHandle(thread);
		//		//C++ 中 map 容器的说明和使用技巧
		//		//http://www.cnblogs.com/anywei/archive/2011/10/27/2226830.html
		//		g_Map->insert[threadData1.id] = thread;
	}
	return 0;
}
#include <iostream>
#include <fstream>
#include <string>
#include <winsock2.h>
#include "../FTPServer/header.h"
using namespace std;
#define BYE "BYE"
#pragma comment(lib, "ws2_32.lib")

void ReplayCMD(SOCKET socket, tMSG_HEADER *p_msg_header)
{
	string sClientCommad;
	tMSG_CMD *p_cmd = static_cast<tMSG_CMD*>(p_msg_header);
	cout << p_cmd->commad;
	cin >> sClientCommad;
	if (strcmp(sClientCommad.c_str(), "/q") == 0)
	{
		//cout << "客户端输入的是/q\n";
		/*断开连接*/
		tMSG_BYE p_bye;
		send(socket, reinterpret_cast<char*>(&p_bye), sizeof(p_bye), 0);
	}
	else
	{
		//cout << "客户端输入的是文件名:" << sClientCommad << endl;
		/*发送文件名*/
		tMSG_FILENAME p_filename;
		strcpy(p_filename.szFileName, sClientCommad.c_str());
		send(socket, reinterpret_cast<char*>(&p_filename), sizeof(p_filename), 0);
	}
}

void SayHello(SOCKET socket)
{
	tMSG_HELLO hello;
	send(socket, reinterpret_cast<char*>(&hello), sizeof(hello), 0);
}

void ReadyToRecvFile(SOCKET socket, tMSG_HEADER *p_msg_header)
{
	tMSG_FILE_LENGTH *p_file_length = static_cast<tMSG_FILE_LENGTH*>(p_msg_header);
	cout << "[" << p_file_length->szFileName << "] Length=" << p_file_length->lLength << " ";
	/*若文件已存在则先删除trunc*/
	ofstream fout(p_file_length->szFileName, ios::out | ios::trunc);
	if (!fout) { cout << "[" << p_file_length->szFileName << "] Open File Error!\n"; }
	fout.close();
	cout << "Ready to recv file... 准备接收文件...\n";
	tMSG_CLIENT_READY ready(0L, p_file_length->lLength);
	strcpy(ready.szFileName, p_file_length->szFileName);
	send(socket, reinterpret_cast<char*>(&ready), sizeof(ready), 0);
}

void RecvFile(SOCKET socket, tMSG_HEADER *p_msg_header)
{
	tMSG_FILE *p_msg_file = static_cast<tMSG_FILE*>(p_msg_header);
	long start = p_msg_file->tFile.lStart;
	long sum = p_msg_file->tFile.lFileLength;
	long size = p_msg_file->tFile.lSize;
	cout << "[" << p_msg_file->tFile.szFileName << "] Recveived: " << start + size << "/" << sum << ", ";
	ofstream fout(p_msg_file->tFile.szFileName, ios::out | ios::binary | ios::app);
	fout.write(p_msg_file->tFile.szBuff, p_msg_file->tFile.lSize);
	cout << "File Written.已写入 ";
	if (start + size < sum)//没有接收完
	{
		cout << "准备接受余下内容\n";
		tMSG_CLIENT_READY ready(start + size, sum);
		strcpy(ready.szFileName, p_msg_file->tFile.szFileName);
		send(socket, reinterpret_cast<char*>(&ready), sizeof(ready), 0);
	}
	else//接收完毕
	{
		cout << "All Recveived !已接收\n";
		tMSG_SEND_FILE_SUCC succ;
		strcpy(succ.szFileName, p_msg_file->tFile.szFileName);
		send(socket, reinterpret_cast<char*>(&succ), sizeof(succ), 0);
	}
	fout.close();
}

int main(int argc, char** argv)
{
	string host = "127.0.0.1";
	int port = 1234;
	if (argc == 3)
	{
		host = argv[1];
		port = atoi(argv[2]);
		if (port < 1024 || port>65534)
		{
			printf("Port error.Use %s host port\n", argv[0]);
		}
	}
	else if (argc != 1)
	{
		printf("Error. Use %s host port\n", argv[0]);
	}
	//加载套接字库
	WSADATA wsaData;
	int iRet = 0;
	iRet = WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (iRet != 0)
	{
		cout << "WSAStartup(MAKEWORD(2, 2), &wsaData) execute failed!" << endl;
		return -1;
	}
	if (2 != LOBYTE(wsaData.wVersion) || 2 != HIBYTE(wsaData.wVersion))
	{
		WSACleanup();
		cout << "WSADATA version is not correct!" << endl;
		return -1;
	}

	//创建套接字
	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, 0);
	if (clientSocket == INVALID_SOCKET)
	{
		cout << "clientSocket = socket(AF_INET, SOCK_STREAM, 0) execute failed!" << endl;
		return -1;
	}

	//初始化服务器端地址族变量
	SOCKADDR_IN srvAddr;
	srvAddr.sin_addr.S_un.S_addr = inet_addr(host.c_str());
	srvAddr.sin_family = AF_INET;
	srvAddr.sin_port = htons(port);

	//连接服务器
	iRet = connect(clientSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR));
	if (0 != iRet)
	{
		cout << "connect(clientSocket, (SOCKADDR*)&srvAddr, sizeof(SOCKADDR)) execute failed!" << endl;
		return -1;
	}

	char szBuff[MAX_PACKET_SIZE + 1];
	while (true)
	{
		memset(szBuff, 0, sizeof(szBuff));
		//接收消息
		int nRecv = recv(clientSocket, szBuff, MAX_PACKET_SIZE, 0);
		//		cout << "接收数据长度=" << nRecv << endl;
		if (nRecv > 0)
		{
			szBuff[nRecv] = '\0';
		}
		else
		{
			closesocket(clientSocket);
			WSACleanup();
			return 0;
		}
		//解析
		tMSG_HEADER *p_msg_header = reinterpret_cast<tMSG_HEADER*>(szBuff);
		//		cout << (int)p_msg_header->cMsgId;
		switch (p_msg_header->cMsgId)
		{
		case MSG_CMD:/*服务器命令*/
			ReplayCMD(clientSocket, p_msg_header);
			break;
		case MSG_OPENFILE_ERROR:/*文件不存在*/
			cout << "File Not Found!服务器上无此文件\n";
			SayHello(clientSocket);
			break;
		case MSG_FILELENGTH:/*文件存在,客户端准备接收*/
			ReadyToRecvFile(clientSocket, p_msg_header);
			break;
		case MSG_FILE:/*接收文件*/
			RecvFile(clientSocket, p_msg_header);
			break;
		case MSG_BYE:
			closesocket(clientSocket);
			WSACleanup();
			return 0;
		default:
			cout << "Invalid data.\n";
			//SayHello(clientSocket);

			break;
		}
		//		Sleep(500);
	}
}


提示一

双重加密,或交换公钥。

解法1

a锁上给b,b再把他的锁锁上给a,a拿到把自己的锁解下,再传给b

解法2

可以先让b把打开的锁传给a(公钥),然后a使用盒子装好目标文件后加上b的锁(使用公钥加密),继而传给b,最后b解锁(私钥))

————其实都是QQ好友的评论2333

代码可在github上找到:https://github.com/YouthLin/Network


“C/C++ Socket编程”上的4条回复

『智力题』AB各有一把钥匙和锁,现在A要用加锁的盒子通过快递C传一个东西给B,但是如果C能打开盒子的话就会私吞这个东西,问AB该怎么做才能确保东西能从A传给B。
说一下我个人关于这个题目的解答:
物品:一个可以上锁的盒子,锁A,锁B,钥匙A,钥匙B。
分配:(假设A,B之间的传输从开始)
A:把盒子,锁A,锁B,钥匙A给A。
B: 让B持有钥匙B。
传输过程:A把锁A放进盒子里,用锁B给盒子上锁,通过C传递给B,B拿到盒子以后用钥匙B打开盒子,取出东西后,再把自己想回复给A的东西和锁B一起放进盒子里,用锁A给盒子上锁,再通过C传回给A。即可完成一次传输,最终状态与最初相同,可以重复完成上述过程反复进行传输。

自己看过java的socket编程,很浅,这次看了c++的,c++的感觉更深入些

发表回复

您的电子邮箱地址不会被公开。 必填项已用*标注

[/鼓掌] [/难过] [/调皮] [/白眼] [/疑问] [/流泪] [/流汗] [/撇嘴] [/抠鼻] [/惊讶] [/微笑] [/得意] [/大兵] [/坏笑] [/呲牙] [/吓到] [/可爱] [/发怒] [/发呆] [/偷笑] [/亲亲]