Windows下网络编程及多线程模型

Socket编程

要想客户端和服务器能在网络中通信,那必须得使用 Socket 编程。

服务端首先调用 socket() 函数,创建网络协议为 IPv4,以及传输协议为 TCPSocket ,接着调用 bind() 函数,给这个 Socket 绑定一个 IP 地址和端口。然后,服务端可以调用 listen() 函数进行监听,进入了监听状态后,通过调用 accept() 函数,来从内核获取客户端的连接,如果没有客户端连接,则会阻塞等待客户端连接的到来。

客户端在创建好 Socket 后,调用 connect() 函数发起连接,该函数的参数要指明服务端的 IP 地址和端口号,接着就是开始三次握手

TCP 连接的过程中,服务器的内核实际上为每个 Socket 维护了两个队列:

  • 半连接队列
  • 全连接队列

TCP 全连接队列不为空后,服务端的 accept() 函数,就会从内核中的 TCP 全连接队列里拿出一个已经完成连接的 Socket 返回应用程序,后续数据传输都用这个 Socket。因此,进行三次握手的Socket和传输数据的Socket是不一样的。

连接建立后,客户端和服务端就开始相互传输数据了,双方都可以通过 read()write() 函数来读写数据。

下面看一下同步阻塞的方式实现的Socket通信。将两份cpp文件放在不同工程中,分别生成。

server.cpp

#include <stdio.h>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
	//1. 请求协议版本
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
    
    
		printf("request failed!\n");
		return -1;
	}
	printf("request protocol success!\n");

	//2. 创建Socket
	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (serverSocket == SOCKET_ERROR) {
    
    
		printf("create failed!\n");
		WSACleanup();
		return -2;
	}
	printf("create socket success!\n");

	//3.创建协议族
	SOCKADDR_IN addr = {
    
     0 };
	addr.sin_family = AF_INET; //协议版本
	addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.127");
	addr.sin_port = htons(10086);//0-65535 10000左右

	//4.绑定
	int r = bind(serverSocket, (sockaddr*)&addr, sizeof addr);
	if (-1 == r) {
    
    
		printf("bind failed!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -2;
	}
	printf("bind success!\n");

	//5.监听
	r = listen(serverSocket, 10);
	if (-1 == r) {
    
    
		printf("listen failed!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -2;
	}
	printf("listen success!\n");

	//6.等待客户端连接    阻塞   
	SOCKADDR_IN cAddr = {
    
     0 };
	int len = sizeof(cAddr);
	SOCKET clientSocket = accept(serverSocket, (sockaddr*)&cAddr, &len);
	if (SOCKET_ERROR == clientSocket) {
    
    
		printf("serverd failed!\n");
		closesocket(clientSocket);
		WSACleanup();
		return -2;
	}
	printf("one client(%s) has connected server!\n", inet_ntoa(cAddr.sin_addr));

	//7.通信
	char buff[1024];
	while (1) {
    
    
		r = recv(clientSocket, buff, 1023, NULL);
		if (r > 0) {
    
    
			buff[r] = 0; // 添加'\0'
			printf(">>%s\n", buff);
		}
	}
	return 0;
}

client.cpp

#include <stdio.h>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

int main()
{
    
    
	//1. 请求协议版本
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
    
    
		printf("request failed!\n");
		return -1;
	}
	printf("request protocol success!\n");

	//2. 创建Socket
	SOCKET clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (clientSocket == SOCKET_ERROR) {
    
    
		printf("create failed!\n");
		WSACleanup();
		return -2;
	}
	printf("create socket success!\n");

	//3.获取服务器协议族
	SOCKADDR_IN addr = {
    
     0 };
	addr.sin_family = AF_INET; //协议版本
	addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.127");
	addr.sin_port = htons(10086);//0-65535 10000左右

	//4.连接服务器
	int r = connect(clientSocket, (sockaddr*)&addr, sizeof addr);
	if (r == -1) {
    
    
		printf("connecting server failed!\n");
		return -1;
	}
	printf("connecting server success!\n");

	//5.通信
	char buff[1024];
	while (1) {
    
    
		memset(buff, 0, 1024);
		printf("please enter your words: ");
		gets_s(buff);
		r = send(clientSocket, buff, strlen(buff), NULL);
	}

	return 0;
}

通信效果
在这里插入图片描述

在这里插入图片描述

多线程模型

同步阻塞的方式只能让服务器服务一个客户端,当服务器没有结束当前客户端的网络I/O时,其他客户端是无法连接服务器的。因此,我们可以使用多线程模型来处理多客户端的请求。

server.cpp

#include <stdio.h>
#include <windows.h>

#pragma comment(lib, "ws2_32.lib")

SOCKADDR_IN cAddr = {
    
     0 };
int len = sizeof(cAddr);
SOCKET clientSocket[1024];
int count = 0;

void communication(int idx) {
    
    
	char buff[1024];
	int r;
	while (1) {
    
    
		r = recv(clientSocket[idx], buff, 1023, NULL);
		if (r > 0) {
    
    
			buff[r] = 0;
			printf("%d:%s\n", idx, buff);
			//广播数据
			for (int i = 0; i < count; i++) {
    
    
				send(clientSocket[i], buff, strlen(buff), NULL);
			}
		}
	}
}

int main()
{
    
    
	//1. 请求协议版本
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
    
    
		printf("request failed!\n");
		return -1;
	}
	printf("request protocol success!\n");

	//2. 创建Socket
	SOCKET serverSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (serverSocket == SOCKET_ERROR) {
    
    
		printf("create failed!\n");
		WSACleanup();
		return -2;
	}
	printf("create socket success!\n");

	//3.创建协议族
	SOCKADDR_IN addr = {
    
     0 };
	addr.sin_family = AF_INET; //协议版本
	addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.127");
	addr.sin_port = htons(10086);//0-65535 10000左右

	//4.绑定
	int r = bind(serverSocket, (sockaddr*)&addr, sizeof addr);
	if (-1 == r) {
    
    
		printf("bind failed!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -2;
	}
	printf("bind success!\n");

	//5.监听
	r = listen(serverSocket, 10);
	if (-1 == r) {
    
    
		printf("listen failed!\n");
		closesocket(serverSocket);
		WSACleanup();
		return -2;
	}
	printf("listen success!\n");

	//6.等待客户端连接    阻塞   
	while (1) {
    
    
		clientSocket[count] = accept(serverSocket, (sockaddr*)&cAddr, &len);
		if (SOCKET_ERROR == clientSocket[count]) {
    
    
			printf("serverd failed!\n");
			closesocket(serverSocket);
			WSACleanup();
			return -2;
		}
		printf("one client(%s) has connected server!\n", inet_ntoa(cAddr.sin_addr));

		CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)communication, (char*)count, NULL, NULL);
		count++;
	}

	//7.通信
	char buff[1024];
	while (1) {
    
    
		r = recv(clientSocket[count], buff, 1023, NULL);
		if (r > 0) {
    
    
			buff[r] = 0; // 添加'\0'
			printf(">>%s\n", buff);
		}
	}
	return 0;
}

client.cpp

#include <stdio.h>

#include <graphics.h> //easyX

#pragma comment(lib, "ws2_32.lib")

SOCKET clientSocket;
HWND hWnd;

int count = 0;

void received() {
    
    
	char recvBuff[1024];
	int r;
	while (1) {
    
    
		r = recv(clientSocket, recvBuff, 1023, NULL);
		if (r > 0) {
    
    
			recvBuff[r] = 0;
			outtextxy(0, count * 20, recvBuff);
			count++;
		}
	}
}

int main()
{
    
    
	initgraph(300, 400, SHOWCONSOLE);

	//1. 请求协议版本
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);
	if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
    
    
		printf("request failed!\n");
		return -1;
	}
	printf("request protocol success!\n");

	//2. 创建Socket
	clientSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
	if (clientSocket == SOCKET_ERROR) {
    
    
		printf("create failed!\n");
		WSACleanup();
		return -2;
	}
	printf("create socket success!\n");

	//3.获取服务器协议族
	SOCKADDR_IN addr = {
    
     0 };
	addr.sin_family = AF_INET; //协议版本
	addr.sin_addr.S_un.S_addr = inet_addr("192.168.1.127");
	addr.sin_port = htons(10086);//0-65535 10000左右

	//4.连接服务器
	int r = connect(clientSocket, (sockaddr*)&addr, sizeof addr);
	if (r == -1) {
    
    
		printf("connecting server failed!\n");
		return -1;
	}
	printf("connecting server success!\n");

	//5.通信
	char buff[1024];
	CreateThread(NULL, NULL, (LPTHREAD_START_ROUTINE)received, NULL, NULL, NULL);
	while (1) {
    
    
		memset(buff, 0, 1024);
		printf("please enter your words: ");
		gets_s(buff);
		r = send(clientSocket, buff, strlen(buff), NULL);
	}

	return 0;
}

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

参考文献

  1. Windows网络编程, socket开发
  2. I/O多路复用

猜你喜欢

转载自blog.csdn.net/Star_ID/article/details/126611536