现在的位置: 首页 > 综合 > 正文

基于visual c++之windows核心编程代码分析(10)实现socket通信

2013年10月05日 ⁄ 综合 ⁄ 共 13165字 ⁄ 字号 评论关闭
在多台计算机之间实现通信,最常见的方法有两种:Socket通信与UDP通信。
Socket是一种基于TCP/IP协议,建立稳定连接的点对点通信,它的特点是安全性高,数据
不会丢失,但是很占系统资源。
在JAVA中,ServerSocket类和Socket类为我们实现了Socket通信,建立通信的一般步骤是:
1。建立服务器
ServerSocket ss = new ServerSocket(端口号);
Socket socket = ss.accept();
这样,我们就已经建立了服务器,其中accept()方法会阻塞,知道有客户发送一个连接请求,我们可以通过socket.getInputStream()和socket.getOutputStream()来获得输入输出流,如调用socket.getInputStream()获得一个输入流,实际上这个流就是连接对方的一个输出流,流的操作与文件流操作相同,我们可以用操作文件的方法来操作它们。
2。建立客户端
Socket socket = new Socket(主机名,端口号)
客户端只需这一句代码就可以与服务器取得连接,这里的主机名应为服务器的IP地址,端口号是服务器用来监听该程序的端口,同样可以通过socket.getInputStream()和socket.getOutputStream()来获得输入输出流。在以上程序中,已经实现了一个最简单的客户端和服务器的通信。但是,还有一些问题。
首先,这个通信只执行一次,程序就将结束。因为我们只读了一次输入流,如果想要建立客户与服务器之间的稳定的会话,就要用到多线程:
Thread thread = new Thread(new Sender());
thread.start();

InputStream input = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(input));
while(true){
br.readLine();
}
其中,Sender是一个实现了Runnable接口的类,用它来专门负责发送数据,主线程只需要不听地接受数据就行。Sender类中的run()方法应该具有以下代码:
PrintWriter pw = new PrintWriter(socket.getOutputStream());
while(true){
pw.println(数据);
}
即使按上面的方式做了,程序还是有问题,因为它只能在一个时间内为一个客户服务,不能同时为多个客户服务,如多要想同时为多个客户服务,服务器应具有类似以下的代码:
ServerSocker ss = new ServerSocker(端口号);
socket = null;
while(true){
socket = ss.accept();
Thread thread1 = new Thread(new Sender());
thread1.start();
Thread thread2 = new Thread(new Receiver());
thread2.start();
}
在这里,新开启了2个线程分别负责接收和发送。Receiver是一个与Sender非常相似的类,它主要用来接收数据。在客户端,我们同样应开启2个线程:
Socket socket = new Socket(服务器IP,端口号);
Sender sender = new Sender(socket);
Thread thread1 = new Thread(sender);
thread1.start();

Receiver receiver = new Receiver(socket);
Thread thread2 = new Thread(receiver);
thread2.start();

我们来亲自动手实现案例

实现客户端

/* 头文件 */
#include <stdio.h>
#include "winsock2.h"
/* 常量 */
#define RECV_BUFFER_SIZE 8192

/*************************************
* main
* 功能 socket通信客户端
**************************************/
void main(int argc, char* argv[])
{
	// 变量定义
	SOCKADDR_IN clientService;// 地址
	SOCKET ConnectSocket;// socket
	WSADATA wsaData;// 库
	LPVOID recvbuf;// 接收缓存
	int bytesSent;
	int bytesRecv = 0;
	char sendbuf[32] = "get information";// 默认发送的数据

	// 初始化socket库,	保存ws2_32.dll已经加载
	int iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != NO_ERROR)
		printf("Error at WSAStartup()\n");

	// 创建socket
	ConnectSocket = socket(AF_INET, // IPv4
		SOCK_STREAM, // 顺序的、可靠的、基于连接的、双向的数据流通信
		IPPROTO_TCP// 使用TCP协议
		);
	if (ConnectSocket == INVALID_SOCKET)
	{
		printf("Error at socket(): %ld\n", WSAGetLastError());
		WSACleanup();
		return;
	}

	// 设置服务端的通信协议、IP地址、端口
	clientService.sin_family = AF_INET;
	clientService.sin_addr.s_addr = inet_addr( "127.0.0.1" );
	clientService.sin_port = htons( 10000 );

	// 连接到服务端
	if ( connect(
		ConnectSocket, // socket
		(SOCKADDR*) &clientService, // 地址
		sizeof(clientService) // 地址的大小
		) == SOCKET_ERROR)
	{
		printf( "Failed to connect(%d)\n",WSAGetLastError() );
		WSACleanup();
		return;
	}
	// 准备发送数据
	// 如果输入参数是-d,那么发送的数据是“download file”否则是"get information"
	if(argc ==2 && (!lstrcmp(argv[1], "-d")))
	{
		lstrcpyn(sendbuf, "download file", 32);
	}
	// 向服务端发送数据
	bytesSent = send( ConnectSocket, // socket
		sendbuf,// 发送的数据 
		lstrlen(sendbuf)+1,// 数据长度
		0 );// 无标志

	if(bytesSent == SOCKET_ERROR)
	{
		printf( "send error (%d)\n", WSAGetLastError());
		closesocket(ConnectSocket);
		return;
	}
	printf( "Bytes Sent: %ld\n", bytesSent );

	// 准备接收数据
	recvbuf = HeapAlloc(GetProcessHeap(), 0, RECV_BUFFER_SIZE);
	// 循环接收
	while( bytesRecv != SOCKET_ERROR )
	{
		//Sleep(50);
		bytesRecv = recv( ConnectSocket, // socket
			recvbuf, // 接收数据缓存
			RECV_BUFFER_SIZE,// 缓存大小
			0 );// 无标志
		if ( bytesRecv == 0 )
		{
			printf( "Connection Closed.\n");
			break;
		}
		// TODO,处理接收的数据,这里只简单的将收到的数据大小显示
		printf( "Bytes Recv: %ld\n", bytesRecv );
	}
	HeapFree(GetProcessHeap(), 0, recvbuf);
	WSACleanup();
	return;
}

实现服务端

/* 头文件 */
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
/* 常量 */
#define DEFAULT_PORT "10000" // 端口
#define MAX_REQUEST 1024 // 接收数据的缓存大小
#define BUF_SIZE 4096 // 发送数据的缓存大小

/*************************************
* CommunicationThread
* 功能	用于接收和发送数据的线程
*			为每一个连接的客户端创建一个接收发送数据的线程,
*			可以使用多个客户端同时连接到服务端
* 参数	lpParameter,SOKCET
**************************************/
DWORD WINAPI CommunicationThread(
								 LPVOID lpParameter
								 )
{
	DWORD dwTid = GetCurrentThreadId();
	// 获得参数sokcet
	SOCKET socket = (SOCKET)lpParameter;
	// 为接收数据分配空间
	LPSTR szRequest = HeapAlloc(GetProcessHeap(),0, MAX_REQUEST);
	int iResult;
	int bytesSent;// 用于保存send的返回值,实际发送的数据的大小

	// 接收数据
	iResult = recv(socket, // socket
		szRequest, // 接收缓存
		MAX_REQUEST, // 缓存大小
		0);// 标志
	if (iResult == 0)// 接收数据失败,连接已经关闭
	{
		printf("Connection closing...\n");
		HeapFree(GetProcessHeap(), 0 ,szRequest);
		closesocket(socket);
		return 1;
	}
	else if (iResult == SOCKET_ERROR)// 接收数据失败,socket错误
	{
		printf("recv failed: %d\n", WSAGetLastError());
		HeapFree(GetProcessHeap(), 0 ,szRequest);
		closesocket(socket);
		return 1;
	}
	else if (iResult > 0) // 接收数据成功
	{
		// 显示接收到的数据
		printf("\tCommunicationThread(%d)\tBytes received: %d\n", dwTid, iResult);
		printf("\tCommunicationThread(%d)\trequest string is (%s)\n",dwTid, szRequest);

		// 如果接收到的数据是"download file"
		if (lstrcmpi(szRequest, "download file") == 0)
		{
			// 读取文件download.txt将发送
			HANDLE hFile;
			LPVOID lpReadBuf; // 发送缓存
			DWORD dwBytesRead;
			DWORD dwFileSize;
			DWORD dwSendFile = 0;
			hFile = CreateFile("download.txt",
				GENERIC_READ,
				FILE_SHARE_READ,
				NULL,
				OPEN_EXISTING,
				FILE_ATTRIBUTE_NORMAL,
				NULL);

			if (hFile == INVALID_HANDLE_VALUE)
			{
				printf("\tCommunicationThread\tCould not open file (error %d)\n", 
					GetLastError());
				send(socket, "error", 6, 0);
				closesocket(socket);
				return 1;
			}
			// 分配发送数据缓存
			lpReadBuf = HeapAlloc(GetProcessHeap(), 0 , BUF_SIZE);
			// 获取文件大小
			dwFileSize = GetFileSize(hFile, NULL);
			// 循环发送
			while(1)
			{
				// 读文件到缓存
				if(!ReadFile(hFile, lpReadBuf, BUF_SIZE, &dwBytesRead, NULL))
				{
					printf("\tCommunicationThread\tCould not read from file (error %d)\n", 
						GetLastError());
					closesocket(socket);
					CloseHandle(hFile);
					return 1;
				}
				// 发送读取的文件数据
				bytesSent = send(socket, lpReadBuf, dwBytesRead, 0);
				if( bytesSent == SOCKET_ERROR)
				{
					printf("\tCommunicationThread\tsend error %d\n", 
						WSAGetLastError());
					closesocket(socket);
					CloseHandle(hFile);
					return 1;
				}
				// 显示发送数据的大小
				printf("\tCommunicationThread(%d)\tsend %d bytes\n", dwTid,  bytesSent);
				// 累加,已经发送的大小
				dwSendFile += dwBytesRead;
				// 如果所有文件数据都已经发送
				if(dwSendFile == dwFileSize)
				{
					printf("\tCommunicationThread\tFile download ok\n");
					break;// 退出循环
				}
			}
			// 释放内存、关闭连接,关闭文件
			HeapFree(GetProcessHeap(), 0 , lpReadBuf);
			CloseHandle(hFile);
			closesocket(socket);
		}
		// 如果接收到的数据是"get information"
		else if (lstrcmpi(szRequest, "get information") == 0)
		{
			// 发送数据
			bytesSent = send(socket, // socket
				"this is information", // 数据
				lstrlen("this is information")+1, // 数据长度
				0);// 标志
			// 判断是否成功
			if( bytesSent == SOCKET_ERROR)
			{
				printf("\tCommunicationThread\tsend error %d\n", 
					WSAGetLastError());
				closesocket(socket);
				return 1;
			}
			printf("\tCommunicationThread(%d)\tsend %d bytes\n",dwTid, bytesSent);
		}
		else// 收到未知数据
		{
			printf ("unreferenced request\n");
		}
	}
	// 释放接收数据缓存,关闭socket
	HeapFree(GetProcessHeap(), 0 ,szRequest);
	closesocket(socket);
	return 0;
}

/*************************************
* int __cdecl main(void)
* 功能	socket服务端
**************************************/
int __cdecl main(void)
{
	WSADATA wsaData;
	SOCKET ListenSocket = INVALID_SOCKET;// 监听socket
	SOCKET ClientSocket = INVALID_SOCKET;// 连接socket
	struct addrinfo *result = NULL,
		hints;
	int iResult;// 保存返回结果

	// 初始化Winsock,保证Ws2_32.dll已经加载
	iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != 0)
	{
		printf("WSAStartup failed: %d\n", iResult);
		return 1;
	}
	// 地址
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	// 获取主机地址,保证网络协议可用等
	iResult = getaddrinfo(NULL, // 本机
		DEFAULT_PORT, // 端口
		&hints, // 使用的网络协议,连接类型等
		&result);// 结果
	if ( iResult != 0 )
	{
		printf("getaddrinfo failed: %d\n", iResult);
		WSACleanup();
		return 1;
	}

	// 创建socket,用于监听
	ListenSocket = socket(
		result->ai_family, // 网络协议,AF_INET,IPv4
		result->ai_socktype, // 类型,SOCK_STREAM
		result->ai_protocol);// 通信协议,TCP
	if (ListenSocket == INVALID_SOCKET)
	{
		printf("socket failed: %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return 1;
	}
	// 绑定到端口
	iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
	if (iResult == SOCKET_ERROR)
	{
		printf("bind failed: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	printf("bind\n");

	freeaddrinfo(result);// reuslt不再使用

	// 开始监听
	iResult = listen(ListenSocket, SOMAXCONN);
	printf("start listen......\n");
	if (iResult == SOCKET_ERROR)
	{
		printf("listen failed: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	while (1)
	{
		// 接收客户端的连接,accept函数会等待,直到连接建立
		printf("ready to accept\n");
		ClientSocket = accept(ListenSocket, NULL, NULL);
		// accept函数返回,说明已经有客户端连接
		// 返回连接socket
		printf("accept a connetion\n");
		if (ClientSocket == INVALID_SOCKET)
		{
			printf("accept failed: %d\n", WSAGetLastError());
			closesocket(ListenSocket);
			break;// 等待连接错误,退出循环
		}
		// 为每一个连接创建一个数据发送的接收线程,
		// 使服务端又可以立即接收其他客户端的连接
		if(!CreateThread(
			NULL,
			0,
			CommunicationThread, // 线程函数
			(LPVOID)ClientSocket, // 将socket作为参数
			0,
			NULL))
		{
			printf("Create Thread error (%d)", GetLastError());
			break;
		}
	}
	// 循环退出,释放DLL。
	WSACleanup();
	return 0;
}

 

/* 头文件 */
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdio.h>
/* 常量 */
#define DEFAULT_PORT "10000" // 端口
#define MAX_REQUEST 1024 // 接收数据的缓存大小
#define BUF_SIZE 4096 // 发送数据的缓存大小

/*************************************
* CommunicationThread
* 功能	用于接收和发送数据的线程
*			为每一个连接的客户端创建一个接收发送数据的线程,
*			可以使用多个客户端同时连接到服务端
* 参数	lpParameter,SOKCET
**************************************/
DWORD WINAPI CommunicationThread(
								 LPVOID lpParameter
								 )
{
	DWORD dwTid = GetCurrentThreadId();
	// 获得参数sokcet
	SOCKET socket = (SOCKET)lpParameter;
	// 为接收数据分配空间
	LPSTR szRequest = HeapAlloc(GetProcessHeap(),0, MAX_REQUEST);
	int iResult;
	int bytesSent;// 用于保存send的返回值,实际发送的数据的大小

	// 接收数据
	iResult = recv(socket, // socket
		szRequest, // 接收缓存
		MAX_REQUEST, // 缓存大小
		0);// 标志
	if (iResult == 0)// 接收数据失败,连接已经关闭
	{
		printf("Connection closing...\n");
		HeapFree(GetProcessHeap(), 0 ,szRequest);
		closesocket(socket);
		return 1;
	}
	else if (iResult == SOCKET_ERROR)// 接收数据失败,socket错误
	{
		printf("recv failed: %d\n", WSAGetLastError());
		HeapFree(GetProcessHeap(), 0 ,szRequest);
		closesocket(socket);
		return 1;
	}
	else if (iResult > 0) // 接收数据成功
	{
		// 显示接收到的数据
		printf("\tCommunicationThread(%d)\tBytes received: %d\n", dwTid, iResult);
		printf("\tCommunicationThread(%d)\trequest string is (%s)\n",dwTid, szRequest);

		// 如果接收到的数据是"download file"
		if (lstrcmpi(szRequest, "download file") == 0)
		{
			// 读取文件download.txt将发送
			HANDLE hFile;
			LPVOID lpReadBuf; // 发送缓存
			DWORD dwBytesRead;
			DWORD dwFileSize;
			DWORD dwSendFile = 0;
			hFile = CreateFile("download.txt",
				GENERIC_READ,
				FILE_SHARE_READ,
				NULL,
				OPEN_EXISTING,
				FILE_ATTRIBUTE_NORMAL,
				NULL);

			if (hFile == INVALID_HANDLE_VALUE)
			{
				printf("\tCommunicationThread\tCould not open file (error %d)\n", 
					GetLastError());
				send(socket, "error", 6, 0);
				closesocket(socket);
				return 1;
			}
			// 分配发送数据缓存
			lpReadBuf = HeapAlloc(GetProcessHeap(), 0 , BUF_SIZE);
			// 获取文件大小
			dwFileSize = GetFileSize(hFile, NULL);
			// 循环发送
			while(1)
			{
				// 读文件到缓存
				if(!ReadFile(hFile, lpReadBuf, BUF_SIZE, &dwBytesRead, NULL))
				{
					printf("\tCommunicationThread\tCould not read from file (error %d)\n", 
						GetLastError());
					closesocket(socket);
					CloseHandle(hFile);
					return 1;
				}
				// 发送读取的文件数据
				bytesSent = send(socket, lpReadBuf, dwBytesRead, 0);
				if( bytesSent == SOCKET_ERROR)
				{
					printf("\tCommunicationThread\tsend error %d\n", 
						WSAGetLastError());
					closesocket(socket);
					CloseHandle(hFile);
					return 1;
				}
				// 显示发送数据的大小
				printf("\tCommunicationThread(%d)\tsend %d bytes\n", dwTid,  bytesSent);
				// 累加,已经发送的大小
				dwSendFile += dwBytesRead;
				// 如果所有文件数据都已经发送
				if(dwSendFile == dwFileSize)
				{
					printf("\tCommunicationThread\tFile download ok\n");
					break;// 退出循环
				}
			}
			// 释放内存、关闭连接,关闭文件
			HeapFree(GetProcessHeap(), 0 , lpReadBuf);
			CloseHandle(hFile);
			closesocket(socket);
		}
		// 如果接收到的数据是"get information"
		else if (lstrcmpi(szRequest, "get information") == 0)
		{
			// 发送数据
			bytesSent = send(socket, // socket
				"this is information", // 数据
				lstrlen("this is information")+1, // 数据长度
				0);// 标志
			// 判断是否成功
			if( bytesSent == SOCKET_ERROR)
			{
				printf("\tCommunicationThread\tsend error %d\n", 
					WSAGetLastError());
				closesocket(socket);
				return 1;
			}
			printf("\tCommunicationThread(%d)\tsend %d bytes\n",dwTid, bytesSent);
		}
		else// 收到未知数据
		{
			printf ("unreferenced request\n");
		}
	}
	// 释放接收数据缓存,关闭socket
	HeapFree(GetProcessHeap(), 0 ,szRequest);
	closesocket(socket);
	return 0;
}

/*************************************
* int __cdecl main(void)
* 功能	socket服务端
**************************************/
int __cdecl main(void)
{
	WSADATA wsaData;
	SOCKET ListenSocket = INVALID_SOCKET;// 监听socket
	SOCKET ClientSocket = INVALID_SOCKET;// 连接socket
	struct addrinfo *result = NULL,
		hints;
	int iResult;// 保存返回结果

	// 初始化Winsock,保证Ws2_32.dll已经加载
	iResult = WSAStartup(MAKEWORD(2,2), &wsaData);
	if (iResult != 0)
	{
		printf("WSAStartup failed: %d\n", iResult);
		return 1;
	}
	// 地址
	ZeroMemory(&hints, sizeof(hints));
	hints.ai_family = AF_INET;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;
	hints.ai_flags = AI_PASSIVE;

	// 获取主机地址,保证网络协议可用等
	iResult = getaddrinfo(NULL, // 本机
		DEFAULT_PORT, // 端口
		&hints, // 使用的网络协议,连接类型等
		&result);// 结果
	if ( iResult != 0 )
	{
		printf("getaddrinfo failed: %d\n", iResult);
		WSACleanup();
		return 1;
	}

	// 创建socket,用于监听
	ListenSocket = socket(
		result->ai_family, // 网络协议,AF_INET,IPv4
		result->ai_socktype, // 类型,SOCK_STREAM
		result->ai_protocol);// 通信协议,TCP
	if (ListenSocket == INVALID_SOCKET)
	{
		printf("socket failed: %ld\n", WSAGetLastError());
		freeaddrinfo(result);
		WSACleanup();
		return 1;
	}
	// 绑定到端口
	iResult = bind( ListenSocket, result->ai_addr, (int)result->ai_addrlen);
	if (iResult == SOCKET_ERROR)
	{
		printf("bind failed: %d\n", WSAGetLastError());
		freeaddrinfo(result);
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	printf("bind\n");

	freeaddrinfo(result);// reuslt不再使用

	// 开始监听
	iResult = listen(ListenSocket, SOMAXCONN);
	printf("start listen......\n");
	if (iResult == SOCKET_ERROR)
	{
		printf("listen failed: %d\n", WSAGetLastError());
		closesocket(ListenSocket);
		WSACleanup();
		return 1;
	}
	while (1)
	{
		// 接收客户端的连接,accept函数会等待,直到连接建立
		printf("ready to accept\n");
		ClientSocket = accept(ListenSocket, NULL, NULL);
		// accept函数返回,说明已经有客户端连接
		// 返回连接socket
		printf("accept a connetion\n");
		if (ClientSocket == INVALID_SOCKET)
		{
			printf("accept failed: %d\n", WSAGetLastError());
			closesocket(ListenSocket);
			break;// 等待连接错误,退出循环
		}
		// 为每一个连接创建一个数据发送的接收线程,
		// 使服务端又可以立即接收其他客户端的连接
		if(!CreateThread(
			NULL,
			0,
			CommunicationThread, // 线程函数
			(LPVOID)ClientSocket, // 将socket作为参数
			0,
			NULL))
		{
			printf("Create Thread error (%d)", GetLastError());
			break;
		}
	}
	// 循环退出,释放DLL。
	WSACleanup();
	return 0;
}

 

 

 

抱歉!评论已关闭.