网络编程——基于Windows编写服务器端和客户端

参考

  1. 《TCP/IP网络编程》 尹圣雨

基于Windows编写服务器端和客户端

Windows套接字(Winsock)大部分是参考BSD系列UNIX套接字设计的,所以很多地方与Linux套接字类似。因此,只需要更改Linux环境下编好的一部分网络程序内容,就能在Windows平台下运行

为什么同时学习Linux和Windows

大多数项目都在Linux系列的操作系统下开发服务器端,而多数客户端是在Windows平台下开发的。而且,有时程序还需要在两个平台之间相互切换

Windows套接字编程的预备

  1. 导入头文件WinSock2.h
  2. 链接ws2_32.lib库。VS:快捷键Alt+F7–>配置属性–>链接器–>输入–>附加依赖项

Winsock初始化

首先必须调用WSAStartup函数,设置程序中的Winsock版本,并初始化相应版本的库

#include <WinSock2.h>
int WSAStartup(WORD wVersionRequested, LPWSADATA lpWSAData);

成功时返回0,失败时返回非零的错误代码值。wVersionRequested:要用的Winsock版本信息;lpWSAData:WSADATA结构体变量的地址

其中,WORD是unsigned short类型,高8位为副版本号,低8位为主版本号。例如,版本2.2,应传递0x0202。也可以借助MAKEWORD宏函数则能轻松构建WORD型版本信息,例如:

MAKEWORD(1, 2);  //主版本为1,副版本为2,返回0x0201

另外,第二个参数lpWSAData的类型LPWSADATA是WSADATA的指针类型,调用完函数后,相应参数中将填充已初始化的库信息

Winsock编程的套路

int main()
{
	WSADATA wsaData;
	// ......
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)
	{
		ErrorHandling("WSAStartup() error!");
	}
	// ......
	return 0;
}

如何注销库

利用WSACleanup()函数注销库,调用时,Winsock相关库将归还Windows操作系统,无法再调用Winsock函数时才调用该函数,但通常都在程序结束之前调用

#include <WinSock2.h>

int WSACleanup(void);

基于Windows的套接字相关函数

(1)socket函数,与Linux下的socket函数提供相同功能

#include <WinSock2.h>

SOCKET socket(int af, int type, int protocol);

成功时返回套接字句柄,失败时返回INVALID_SOCKET

(2)bind函数,调用其分配IP地址和端口号

#include <WinSock2.h>

int bind(SOCKET s, const struct sockaddr* name, int namelen);

成功时返回0,失败时返回SOCKET_ERROR

(3)listen函数,调用其使套接字可接收客户端连接

#include <WinSock2.h>

int listen(SOCKET s, int backlog);

成功时返回0,失败时返回SOCKET_ERROR
(4)accept函数,调用其受理客户端连接请求

#include <WinSock2.h>

SOCKET accept(SOCKET s, struct sockaddr* addr, int* addrlen);

成功时返回套接字句柄,失败时返回INVALID_SOCKET

(5)connect函数,调用其从客户端发送连接请求

#include <WinSock2.h>

int connect(SOCKET s, const struct sockaddr* name, int namelen);

成功时返回0,失败时返回SOCKET_ERROR

(6)closesocket函数,调用其关闭套接字

#include <WinSock2.h>

int closesocket(SOCKET s);

成功时返回0,失败时返回SOCKET_ERROR

Windows中的文件句柄和套接字句柄

Windows中通过调用系统函数创建文件时,返回句柄(handle),相当于Linux中的文件描述符。只不过Windows中要区分文句柄和套接字句柄

Winsock函数中的socket和accept函数的返回值类型SOCKET,就是为了保存套接字句柄整型值(unsigned short)的新数据类型,由typedef声明定义

创建基于Windows的服务器端

#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hServSock, hClntSock;
	SOCKADDR_IN servAddr, clntAddr;
	int szClntAddr;
	char message[] = "Hello World!";
	if (argc != 2)
	{
		printf("Usage : %s <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) // 初始化套接字库
	{
		ErrorHandling("WSAStartup() error!");
	}
	
	hServSock = socket(PF_INET, SOCK_STREAM, 0);   // 创建套接字
	if (hServSock == INVALID_SOCKET)
	{
		ErrorHandling("socket() error");
	}

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = htonl(INADDR_ANY);
	servAddr.sin_port = htons(atoi(argv[1]));

	if (bind(hServSock, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)  // 给该套接字分配IP地址与端口号
	{
		ErrorHandling("bind() error");
	}

	if (listen(hServSock, 5) == SOCKET_ERROR)                                     // 使创建的套接字成为服务器端套接字
	{
		ErrorHandling("listen() error");
	}

	szClntAddr = sizeof(clntAddr);
	hClntSock = accept(hServSock, (SOCKADDR*)&clntAddr, &szClntAddr);             // 受理客户端连接请求
	if (hClntSock == INVALID_SOCKET)
	{
		ErrorHandling("accept() error");
	}

	send(hClntSock, message, sizeof(message), 0);                                 // 向连接的客户端传输数据
	closesocket(hClntSock);
	closesocket(hServSock);
	WSACleanup();                                                                 // 注销初始化的套接字库
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}
创建基于Windows的客户端
#include <stdio.h>
#include <stdlib.h>
#include <WinSock2.h>
#include <Ws2tcpip.h>
void ErrorHandling(char* message);

int main(int argc, char* argv[])
{
	WSADATA wsaData;
	SOCKET hSocket;
	SOCKADDR_IN servAddr;

	char message[30];
	int strLen;
	if (argc != 3)
	{
		printf("Usage : %s <IP> <port>\n", argv[0]);
		exit(1);
	}

	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0)           // 初始化Winsock库
	{
		ErrorHandling("WSAStartup() error!");
	}

	hSocket = socket(PF_INET, SOCK_STREAM, 0);               // 创建套接字
	if (hSocket == INVALID_SOCKET)
	{
		ErrorHandling("socket() error");
	}

	memset(&servAddr, 0, sizeof(servAddr));
	servAddr.sin_family = AF_INET;
	//servAddr.sin_addr.s_addr = inet_addr(argv[1]);
	inet_pton(AF_INET, argv[1], &servAddr.sin_addr);
	servAddr.sin_port = htons(atoi(argv[2]));

	if (connect(hSocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR)     // 通过套接字向服务器发出连接请求
	{
		ErrorHandling("connect() error!");
	}

	strLen = recv(hSocket, message, sizeof(message) - 1, 0);                          // 接收服务器发来的数据
	if (strLen == -1)
	{
		ErrorHandling("read() error!");
	}
	printf("Message from server: %s \n", message);

	closesocket(hSocket);
	WSACleanup();                                                                     // 注销Winsock库
	return 0;
}

void ErrorHandling(char* message)
{
	fputs(message, stderr);
	fputc('\n', stderr);
	exit(1);
}

基于Windows的I/O函数

Linux中套接字也是文件,因而可以通过文件I/O函数read和write进行数据传输。而Windows严格区分文件I/O函数和套接字I/O函数

(1)send()函数

#include <WinSock2.h>

int send(SOCKET s, const char* buf, int len, int flags);

成功时返回传输字节数,失败时返回SOCKET_ERROR。s:表示数据传输对象连接的套接字句柄值;buf:保存待传输数据的缓冲地址值;len:要传输的字节数;flags:传输数据时用到的多种选项信息

(2)recv()函数

#include <WinSock2.h>

int recv(SOCKET s, const char* buf, int len, int flags);

成功时返回接收的字节数(收到EOF时为0),失败时返回SOCKET_ERROR。s:表示数据传输对象连接的套接字句柄值;buf:保存待传输数据的缓冲地址值;len:要传输的字节数;flags:传输数据时用到的多种选项信息

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值