Windows——socket客户端与服务端

作者:小 琛
欢迎转载,请标明出处

Windows下的socket与Linux下有些许不同,有关Windows下socket初始化内容,翻看前篇博客:
Windows下socket初始化

客户端与服务端编程流程

在这里插入图片描述

相关函数

创建套接字

网络编程中,绕不开的一个话题:字节序问题

  • addrinfo 结构体

typedef struct addrinfo {
int ai_flags;
int ai_family;
int ai_socktype;
int ai_protocol;
size_t ai_addrlen;
char *ai_canonname;
struct sockaddr *ai_addr;
struct addrinfo *ai_next;
} ADDRINFOA, *PADDRINFOA;

  • Getaddrinfo()函数

INT WSAAPI getaddrinfo(
[in, optional] PCSTR pNodeName,
[in, optional] PCSTR pServiceName,
[in, optional] const ADDRINFOA *pHints,
[out] PADDRINFOA *ppResult );

参数1:ip地址,为空则默认主机地址
参数2:端口号
参数3:输入的参数,相关设定
参数4:输出参数,后续使用
返回值:0为成功,非0失败

使用方法:用addrinfo 结构体填充信息后,用getaddrinfo函数得到后续使用的网络编程addrinfo结构体。

#include <windows.h>
#include <winsock2.h>
#include <ws2tcpip.h>
#include <stdlib.h>
#include <iostream>

#define port "1999"
#pragma comment (lib, "Ws2_32.lib")
#pragma comment (lib, "Mswsock.lib")
#pragma comment (lib, "AdvApi32.lib")
using std::cout;
using std::endl;
int main() {
	WSADATA wsaData;
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		printf("WSAStartup error\n");
		return 0;
	}
	//打印相关信息
	cout << "Version is :" << LOBYTE(wsaData.wVersion) << "." << HIBYTE(wsaData.wVersion) << std::endl;
	cout << "High Version is :" << LOBYTE(wsaData.wHighVersion) << "." << HIBYTE(wsaData.wHighVersion) << std::endl;
	cout << "Description: " << wsaData.szDescription << endl;
	cout << "SystemStatus: " << wsaData.szSystemStatus << endl;
	cout << "Max Count Socket:" << wsaData.iMaxSockets << endl;
	cout << "Max UdnDn: " << wsaData.iMaxUdpDg << endl;

	struct addrinfo* result;
	struct addrinfo addr;

	//设定
	ZeroMemory(&addr, sizeof(addr));
	addr.ai_family = AF_UNSPEC;
	addr.ai_socktype = SOCK_STREAM;
	addr.ai_protocol = IPPROTO_TCP;

	int res = getaddrinfo(NULL, "1999", &addr, &result);
	cout << result->ai_family << "->" << result->ai_addr << "->" << result->ai_next << std::endl;
}

在这里插入图片描述

绑定套接字

int bind (int sockfd, const struct sockaddr* addr, socklen_t addrlen)

参数:

  • sockfd:套接字操作句柄
  • addr:为更好的兼容不同协议的地址信息而设定的结构体
  • struct sockaddr_in
    {
    sin_family;//告诉内核以何种协议去解析,例如传AF_INET即ipv4
    sin_port;端口信息
    sin_addr.s_addr;uint32_t类型的ip地址
    }
bool Bind(string& ip, unsigned short port){
		struct sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_port = htons(port);
		addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
		int len = sizeof(struct sockaddr_in);
		int res = bind(sock, (struct sockaddr*)&addr, len);
		if (res = SOCKET_ERROR){
			cout << "bind error" << WSAGetLastError()<< endl;
			return false;
		}
		return true;
	}

侦听套接字

监听:

int listen(int sockfd, int backlog )

参数:

  • sockfd:套接字描述符
  • backlog:已完成连接队列的大小。可以指定大小,若当前队列已满,会丢弃新来的连接,从以完成连接队列中获取创建完成的新连接
    返回值:
    失败返回-1,否则返回1
    3.监听状态
    已完成连接队列:三次握手完成的连接集合
    未完成连接队列:处于三次握手当中的连接集合
bool Listen(int count = 5){
		int res = listen(sock, count);
		if (res == SOCKET_ERROR){
			cout << "listen error" << endl;
			return false;
		}
		return true;
	}

连接套接字

发送连接 int connect(int sockfd, const struct sockaddr* addr, socklen_t
addrlen)

参数

  • sockfd:套接字描述符
  • addr:服务端地址信息,需要在代码中配置
  • addrlen:地址信息长度
  • 返回值
    失败返回-1,否则为1
bool Connect(string& ip, unsigned short port){
		struct sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_port = htons(port);
		addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
		int len = sizeof(sockaddr_in);
		int res = connect(sock, (struct sockaddr*)&addr, len);
		if (res == SOCKET_ERROR){
			cout << "connect error" << endl;
			return false;
		}
		return true;
	}

接受连接

获取连接 int accept (int sockfd, struct sockaddr* addr,socklen_t addrlen)

参数

  • sockfd:侦听套接字描述符
  • addr:客户端的地址信息
  • addrlen:客户端地址信息长度
  • 返回值
    返回新创建出来socket的sockfd,通过新创建出来的socket为客户端服务。
bool Accept(struct sockaddr_in* addr, WinSockTcp* ts){
		int len = sizeof(addr);
		ts->sock = accept(sock, (struct sockaddr*)addr, &len);
		if (ts->sock == INVALID_SOCKET){
			cout << "accept error:" << WSAGetLastError() << endl;
			return false;
		}
		return true;
	}

注意
该接口是从已完成连接的队列中获取连接,因此当已完成队列中没有新的连接时候,会阻塞,知道获取新的连接

发送、接收数据

发送数据 sszie_t send(int sockfd, const void* buf, size_t len, int flags)

参数

  • sockfd:套接字描述符,要使用accept的返回值
  • buf:要发送的数据
  • len:发送的数据长度
  • flags:发送方式,0为阻塞发送
  • 返回值:
    大于0:发送数据的长度
    等于0:标识对端关闭了连接
    小于0:失败

接收数据 ssize_t recv(int sockfd, void* buf, size_t len, int flags)

参数

  • sockfd:套接字描述符,要使用accept的返回值
  • buf:接收数据的存放
  • len:最大接收长度
  • flags:接收方式,0为阻塞接收,MSG_PEEK为探测接收
  • 返回值
    大于0:发送数据的长度
    等于0:标识对端关闭了连接
    小于0:失败
bool Send(const string data){
		int size = send(sock, data.c_str(), data.size(), 0);
		if (size < 0){
			cout << "send error" << endl;
			return false;
		}
		return true;
	}
	bool Recv(string* data){
		char buff[1024];
		int size = recv(sock, buff, sizeof(buff), 0);
		if (size < 0){
			cout << "recv error" << endl;
			return false;
		}
		else if (size == 0){
			cout << "peer shutdown connect" << endl;
			return false;
		}
		else{
			(*data).assign(buff, size);
			return true;
		}
	}

断开连接

  1. 当服务器将数据发送到客户端时,可以调用 shutdown 函数以指定 SD _ SEND 来关闭套接字的发送端。 这允许客户端释放此套接字的某些资源。 服务器应用程序仍然可以在套接字上接收数据。
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR) {
    printf("shutdown failed: %d\n", WSAGetLastError());
    closesocket(ClientSocket);
    WSACleanup();
    return 1;
}
  1. 当客户端应用程序接收到数据后,将调用 closesocket函数来关闭套接字。
    当使用 Windows 套接字 DLL 完成客户端应用程序时,将调用 WSACleanup函数以释放资源。
// cleanup
closesocket(ClientSocket);
WSACleanup();

return 0;

实战

将socket所有的操作写成一个类,winsock.h

#include <iostream>
#include <vector>
#include <winsock2.h>
#include <string>
#pragma comment(lib,"ws2_32.lib")
using std::cin;
using std::cout;
using std::endl;
using std::vector;
using std::string;

class WinSockTcp
{
private:
	SOCKET sock;
public:
	WinSockTcp(){}
	~WinSockTcp(){}
	/*
	函数功能:WinSock的初始化
	输入参数:WSAData结构体,作为一个输出参数,包含版本信息
	返回值:成功返回true,失败返回false
	*/
	bool StartUp(WSAData& wsadata){
		int res = WSAStartup(MAKEWORD(2, 2), &wsadata);
		if (res != 0){
			return false;
			cout << "SOCKET StartUp Error" << endl;
		}
		else{
			return true;
		}
	}
	/*
	函数功能:WinSock的创建
	输入参数:无
	返回值:成功返回true,失败返回false
	*/
	bool CreateSocket(){
		sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
		if (sock == INVALID_SOCKET){
			cout << "Create Socket Error" << endl;
			return false;
		}
		return true;
	}
	/*
	函数功能:绑定地址信息
	输入参数:需绑定的ip,需绑定的端口
	返回值:成功返回true,失败返回false
	*/
	bool Bind(string& ip, unsigned short port){
		struct sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_port = htons(port);
		addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
		int len = sizeof(struct sockaddr_in);
		int res = bind(sock, (struct sockaddr*)&addr, len);
		if (res == SOCKET_ERROR){
			cout << "bind error" << WSAGetLastError()<< endl;
			return false;
		}
		return true;
	}
	/*
	函数功能:开始监听
	输入参数:监听队列的最大长度
	返回值:成功返回true,失败返回false
	*/
	bool Listen(int count = 5){
		int res = listen(sock, count);
		if (res == SOCKET_ERROR){
			cout << "listen error" << endl;
			return false;
		}
		return true;
	}
	/*
	函数功能:接收连接函数
	输入参数:sockaddr_in结构体用于得到连接者信息,新创建的服务socket的类
	返回值:成功返回true,失败返回false
	*/
	bool Accept(struct sockaddr_in* addr, WinSockTcp* ts){
		int len = sizeof(addr);
		ts->sock = accept(sock, (struct sockaddr*)addr, &len);
		if (ts->sock == INVALID_SOCKET){
			cout << "accept error:" << WSAGetLastError() << endl;
			return false;
		}
		return true;
	}
	/*
	函数功能:发起连接函数
	输入参数:发送连接请求的ip,发送连接请求的端口
	返回值:成功返回true,失败返回false
	*/
	bool Connect(string& ip, unsigned short port){
		struct sockaddr_in addr;
		addr.sin_family = AF_INET;
		addr.sin_port = htons(port);
		addr.sin_addr.S_un.S_addr = inet_addr(ip.c_str());
		int len = sizeof(sockaddr_in);
		int res = connect(sock, (struct sockaddr*)&addr, len);
		if (res == SOCKET_ERROR){
			cout << "connect error" << endl;
			return false;
		}
		return true;
	}
	/*
	函数功能:发送数据端口
	输入参数:需要发送的数据
	返回值:成功返回true,失败返回false
	*/
	bool Send(const string data){
		int size = send(sock, data.c_str(), data.size(), 0);
		if (size < 0){
			cout << "send error" << endl;
			return false;
		}
		return true;
	}/*
	函数功能:接收数据端口
	输入参数:接收数据的string
	返回值:成功接收返回true,失败返回false
	*/
	bool Recv(string* data){
		char buff[1024];
		int size = recv(sock, buff, sizeof(buff), 0);
		if (size < 0){
			cout << "recv error" << endl;
			return false;
		}
		else if (size == 0){
			cout << "peer shutdown connect" << endl;
			return false;
		}
		else{
			(*data).assign(buff, size);
			return true;
		}
	}
	/*
	函数功能:关闭socket,释放资源
	输入参数:无
	返回值:无
	*/
	void CloseSocket(){
		closesocket(sock);
		sock = -1;
	}
	/*
	函数功能:禁止socket
	输入参数:无
	返回值:无
	*/
	void ShutDownSocket(){
		shutdown(sock,SD_BOTH);
	}
	static void CleanUp(){
		WSACleanup();
	}
};

client.cc

int main(int argc, char*argv[])
{
	//if (argc != 3)
	//{
	//	printf("Start Server: ./svr [ip] [port]\n");
	//	return 0;
	//}
	//string ip = argv[1];
	//unsigned int port = atoi(argv[2]);
	string ip = "192.168.152.150";
	u_short port = 1999;
	WinSockTcp ts;
	WSADATA wsadata;
	if (!ts.StartUp(wsadata)){
		return 0;
	}
	if (!ts.Connect(ip, port))
		return 0;
	while (1){
		fflush(stdout);
		string data;
		cin >> data;
		ts.Send(data);
		
		ts.Recv(&data);
		cout << "server say:" << data << endl;
	}
	ts.ShutDownSocket();
	ts.CreateSocket();
	return 0;
}

server.h

int main(int argc, char*argv[])
{
	/*if (argc != 3)
	{
		printf("Start Server: ./svr [ip] [port]\n");
		return 0;
	}
	string ip = argv[1];
	unsigned int port = atoi(argv[2]);*/
	u_short port = 1999;
	string ip = "0.0.0.0";

	WinSockTcp ts;
	WSADATA wsadata;
	if (!ts.StartUp(wsadata))
		return 0;
	if (!ts.CreateSocket())
		return 0;
	if (!ts.Bind(ip, port))
		return 0;
	if (!ts.Listen(10))
		return 0;
	WinSockTcp svrsock;
	while (1){
		struct sockaddr_in addr;
		if (!ts.Accept(&addr, &svrsock))
			return 0;
		cout << "have new connect ,ip:" << addr.sin_addr.S_un.S_addr << "port:" << addr.sin_port << endl;
		string data;
		svrsock.Recv(&data);
		cout << "client say:" << data.c_str() << endl;
		cout << "please enter :" << endl;
		cin >> data;
		svrsock.Send(data);
	}
	svrsock.ShutDownSocket();
	svrsock.CloseSocket();
	ts.ShutDownSocket();
	ts.CloseSocket();
	ts.CleanUp();
	return 0;
}
  • 4
    点赞
  • 10
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
要接收服务端传输的大文件,你可以使用 Node.js 的 `net` 模块和 `fs` 模块。 以下是一个简单的示例代码,可以帮助你完成该任务: ```javascript const net = require('net'); const fs = require('fs'); // 创建一个 TCP 客户端 const client = new net.Socket(); // 连接到服务端 client.connect(8080, '127.0.0.1', () => { console.log('Connected to server'); // 发送请求获取文件 client.write('get file'); // 接收服务端传输的文件 let receivedBytes = 0; let fileSize = 0; let fileName = ''; let fileStream = null; client.on('data', (data) => { if (fileStream === null) { // 如果还没有开始接收文件,则先解析文件信息 const fileInfo = JSON.parse(data.toString()); fileSize = fileInfo.size; fileName = fileInfo.name; fileStream = fs.createWriteStream(fileName); } else { // 写入文件 fileStream.write(data); // 更新接收的字节数 receivedBytes += data.length; // 如果已经接收完整个文件,则关闭文件流和客户端连接 if (receivedBytes === fileSize) { console.log(`Received file "${fileName}" with size ${fileSize} bytes`); fileStream.end(); client.end(); } } }); }); ``` 在上面的代码中,我们首先使用 `net.Socket` 创建客户端,并连接到服务端。然后,我们发送请求获取文件,并在客户端接收服务端传输的数据。在接收到服务端传来的第一块数据时,我们解析文件信息(文件名和文件大小),并创建一个可写流(`fs.createWriteStream`)用于写入文件。接下来,我们在接收到服务端传来的后续数据时,将其写入文件流中。在写入完整个文件后,我们关闭文件流和客户端连接。 注意:上面的示例代码仅作为参考,具体实现可能因实际情况而异。例如,在实际应用中,你可能需要处理一些错误情况,例如服务端连接失败或断开连接等。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值