您现在的位置是:首页 >技术杂谈 >windows下创建一个socket客户端和服务端网站首页技术杂谈

windows下创建一个socket客户端和服务端

master cat 2024-06-23 18:01:02
简介windows下创建一个socket客户端和服务端

初始化Winsock库

在windows下创建一个socket,可以借助Winsock库来实现。
WSADATA是Windows Socket API的一个结构体,WSADATA结构体包含Winsock版本号实现此Winsock服务的最高版本号。
描述此Winsock实现的可读字符串。提供实现的关于Winsock的需要的基本信息。

	WORD wVer = MAKEWORD(2,2);
	WSADATA lpWSAD;
	int ret;
	ret = WSAStartup(wVer, &lpWSAD);
	if (ret != 0)
	{
		std::cout<<"Winsock error"<<std::endl;
		return;			
	}
	// 与MAKEWORD(2,2)中设定参数的作对比,有误退出
	if ( LOBYTE( lpWSAD.wVersion ) != 2 || HIBYTE( lpWSAD.wVersion ) != 2 )
	{
		// 调用WSACleanup()函数来释放所占用的资源
		std::cout<<WSACleanup()<< std::endl;
		return; 
	}
  • WSAStartup()函数是当应用程序要使用Winsock库时,需要首先调用WSAStartup()函数对其进行初始化,以确保Winsock可用并设置所需版本号。
  • 两个参数
    • WORD wVersionRequested:指定请求的Winsock库的版本号,通常使用MAKEWORD(majorVersion, minorVersion)宏将主版本号和次版本号合并为一个16位无符号整数传递。
    • LPWSADATA lpWSAData:指向WSADATA结构体变量的指针,用于接收初始化结果和Winsock相关信息。变量类型为WSADATA结构体变量。
  1. WORD wVer = MAKEWORD(2,2);
    • WORD 是一个16位无符号整数的变量名,通常用于指定应用程序想要使用的Winsock库的版本号,调用WSAStartup()函数中设置Winsock库的版本号,以确保应用程序仅使用支持此版本的Winsock库,从而提高应用程序的稳定性和可靠性。
    • 参数1代表主版本号(major version),参数2代表次版本号(minor version)。通过使用MAKEWORD宏函数可以方便地将主版本号和次版本号合并为一个WORD类型的数据。
  2. WSADATA lpWSAD;
    • 使用Winsock库时,程序通常需要先声明一个WSADATA结构体变量,并在调用 WSAStartup() 函数之前将这个结构体变量传递给该函数以初始化Winsock库。
  • WSAStartup()函数返回值为0表示初始化成功,否则表示失败,并且可以调用WSAGetLastError()函数获取具体的错误代码。注意,每次调用WSAStartup()函数后,必须调用WSACleanup()函数来释放所占用的资源

创建socket对象

初始化Winsock库之后需要创建socket对象

	SOCKET sfd;
	// 创建一个TCP网络套接字(socket),该套接字可以用于在网络上建立和管理TCP连接
	sfd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (sfd == INVALID_SOCKET)
	{
		cout<<"socket 创建失败"<<endl;
		return -1;
	}

	struct sockaddr_in serveraddr;
	memset(&serveraddr,0,sizeof(struct sockaddr));
	serveraddr.sin_family = AF_INET;
	serveraddr.sin_port = htons(8000);
	inet_pton(AF_INET, "192.168.154.123", &serveraddr.sin_addr);
  1. SOCKET sfd

    • SOCKET sfd是一个套接字类型的变量名,表示套接字描述符
    • 在使用Winsock库进行网络编程时,创建和管理套接字是必不可少的一项操作。套接字是一种标识通信端点的对象进程可以通过它们进行数据传输和收发命令
    • 在Winsock的实现中,套接字被视为一种特殊的文件描述符,因此sfd也被称为socket句柄或文件句柄
    • sfd保存了生成的套接字的标识信息,在进行各种操作时需要使用它来引用正确的套接字。
  2. socket()函数

  3. struct sockaddr_in serveraddr 是一个用于表示 IPv4 套接字地址的数据结构体。客户端或服务器端通过填写该结构体来指定通信对端的地址和端口号等信息。

    • sa_family:地址族类型,通常为AF_INET表示IPv4类型。
    • sin_port:16位整数,表示目标主机的端口号。由于存在字节序转换问题,通常需要使用htons()函数将本地字节序转换为网络字节序。
    • sin_addr:32位整数,表示目标主机的IP地址。由于存在字节序转换问题,通常需要使用inet_addr()或inet_pton()等函数进行转换。
    • sin_zero:8个字节长的填充字段,用于保证数据长度匹配。

服务端bind绑定端口

	//SOCKET sfd;
	//struct sockaddr_in serveraddr;
	// .... 填入IP地址和端口号
	int ret;
	ret = bind(sfd, (struct sockaddr *)&serveraddr, sizeof(struct sockaddr));
	if (ret == SOCKET_ERROR)
	{
		cout<<"bind error"<<endl;
		return -1;
	}
  • bind()函数是用于将一个本地地址(包括IP地址和端口号)绑定到一个套接字上,使得套接字与该地址关联起来,从而可以监听客户端的连接请求。
  • bind返回值,返回值为 0,表示地址绑定成功;如果执行失败,则返回 -1
  • bind() 函数执行出错时,它会返回 SOCKET_ERROR,该宏定义为 -1,表示执行绑定操作失败。此时可以通过调用 WSAGetLastError() 函数获取 socket 相关的错误代码。
    // 端口号被占用:当要绑定的端口号已经被其他进程使用时,bind() 函数会返回一个 EADDRINUSE 错误。
    // 地址不可用:当尝试绑定一个地址但该地址不可用(比如是保留地址或非法地址等)时,会返回一个 EADDRNOTAVAIL 错误。

listen监听端口

	//SOCKET sfd;
	//struct sockaddr_in serveraddr;
	// .... 填入IP地址和端口号
	
	int ret = listen(sfd, 10);
	if (ret == SOCKET_ERROR)
	{
		cout<<"listen fail"<<endl;
		return -1;
	}
  • 在调用 bind() 函数绑定端口之后,还需要调用 listen() 函数来监听该端口,等待客户端连接,第二个参数10表示请求队列的最大长度。当有新的客户端请求连接时,服务器会先把其加入到一个请求队列中,然后从该队列中按照 FIFO 的方式选择下一个客户端进行处理。因此,backlog 参数决定了请求队列的最大长度,如果队列已满,则后续的连接请求将被拒绝。
  • listen() 函数调用成功,返回值为 0;否则返回 SOCKET_ERROR。该错误代码可以通过调用 WSAGetLastError() 获取。

accept函数接收请求

	//SOCKET sfd;
	//struct sockaddr_in serveraddr;
	// .... 填入IP地址和端口号

	SOCKET cfd;
	struct sockaddr_in  addr;
	int  addrlen = sizeof(struct sockaddr);
	printf("accept 1
");
	// 用于接受客户端的连接请求并创建一个新的 socket 文件描述符
	cfd = accept(sfd, (struct sockaddr *)&addr, &addrlen);//阻塞等待
	printf("accept 2
");
	// INVALID_SOCKET 是在 Windows 平台上用于表示无效 socket 文件描述符的常量。在 Linux 和其他类 Unix 系统上,通常使用 -1 来表示无效的文件描述符。
	if (cfd == INVALID_SOCKET)
	{
		cout<<"accept fail"<<endl;
		return -1;
	}
  • 在调用 listen() 函数后,需要通过 accept() 函数来接收客户端的连接请求
  • accetp()函数返回值为一个套接字SOCKET,与bind(),和listen()函数的返回值类型不同。
  • 调用成功,则返回一个新的套接字(用于与连接到的客户端进行通信)。否则将返回 INVALID_SOCKET,INVALID_SOCKET 是在 Windows 平台上用于表示无效 socket 文件描述符的常量。在 Linux 和其他类 Unix 系统上,通常使用 -1 来表示无效的文件描述符。
  • accept() 函数应该在服务器应用程序中使用,它用于等待传入的连接。由于 accept() 函数会阻塞调用线程直到连接被接收,因此通常应该将其放在独立的线程中调用

将网络编程中二进制IP地址,转化为点分十进制输出

	// SOCKET cfd;
	// struct sockaddr_in  addr;
	char str[INET_ADDRSTRLEN];
	std::cout <<"IP地址:" << inet_ntop(AF_INET, &(addr.sin_addr), str, INET_ADDRSTRLEN) << "端口号:"<<ntohs(addr.sin_port);

  • AF_INET表示对IPv4地址进行转换
  • &(addr.sin_addr)表示获取结构体sockaddr_in中的IP地址并取地址传递
  • str表示存放转换后的字符串的变量地址,
  • INET_ADDRSTRLEN 是一个常量,表示传递给 str 缓冲区的大小。该常量定义在winsocke库中,#define INET_AD3DRSTRLEN 22,#define INET6_ADDRSTRLEN 65
  • ntohs(addr.sin_port)将sockaddr_in结构体的物理端口信息由网络字节序转换为主机字节序

客户端连接的connect函数

	int con;
	SOCKET sfd;
	// socket函数创建套接字...
	struct sockaddr_in  serveraddr;
  // 绑定端口
  
	unsigned long ul=1;
	con=ioctlsocket(sfd, FIONBIO, (unsigned long *)&ul);//将socket客户端connect设置成非阻塞模式。 
	if(con==SOCKET_ERROR)//如果设置失败。  
	{  
		printf("none block set fail
");
	} 
	  //将sfd连接至指定的服务器网络地址 serveraddr
	con = connect(sfd, (struct sockaddr*)&serveraddr, sizeof(struct sockaddr)); 
	if (con == SOCKET_ERROR)
	{
		cout<<"connect fail"<<endl;
		return -1;
	}
 
	return sfd;

connect函数执行成功时返回0,失败时返回-1,并设置相应的错误代码。

设置成非阻塞模式

  • ioctlsocket()是在Windows下的socket API中一个函数,。它允许应用程序查询和设置套接字参数,如非阻塞模式、带外数据标志、优先级等。
unsigned long ul=1;
con=ioctlsocket(sfd, FIONBIO, (unsigned long *)&ul);
	
int ioctlsocket(
    SOCKET s,  //s:要操作的套接字的描述符。
    long cmd,  //cmd:指定要执行的操作的命令代码。
    u_long* argp  //包含有关此特定命令的参数的指向缓冲区的指针。
);

其中第二段参数

  • FIONBIO 非阻塞I/O模式。如果argp不为零,则将套接字设置为非阻塞模式。
  • FIONREAD 可从套接字内读取的数据量(采用TCP)。argp是指向无符号长整型的指针。
  • SIOCATMARK 此命令检查传输控制协议(TCP)套接字上是否存在特殊关注的边缘,也就是在接收到带外标记或OOB数据后是否还有要处理的数据。

第三个变量

  • 变量ul的初始值为1,这是由于无符号长整型是32位的,也就是在二进制下有32位,因此设置为1即为将第一位设置为1,表示“真”。这可以使套接字处于非阻塞模式中,避免阻塞在IO操作上。

  • 如果操作成功,则返回值为0;否则返回SOCKET_ERROR并设置WSAGetLastError()以获得错误代码。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。