• 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 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74

    client.cpp

    #include 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50

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

    在这里插入图片描述

    多线程模型

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

    server.cpp

    #include 
    #include 
    
    #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;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72
    • 73
    • 74
    • 75
    • 76
    • 77
    • 78
    • 79
    • 80
    • 81
    • 82
    • 83
    • 84
    • 85
    • 86
    • 87
    • 88
    • 89
    • 90
    • 91
    • 92
    • 93
    • 94
    • 95
    • 96
    • 97
    • 98

    client.cpp

    #include 
    
    #include  //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
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61
    • 62
    • 63
    • 64
    • 65
    • 66
    • 67
    • 68
    • 69
    • 70
    • 71
    • 72

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

    参考文献

    1. Windows网络编程, socket开发
    2. I/O多路复用
  • 相关阅读:
    通用后台管理系统前端界面Ⅵ——首页、登录页、404页面
    MAC OS使用docker部署RocketMQ,解决宿主机无法访问broker
    无胁科技-TVD每日漏洞情报-2022-8-17
    js人民币转换大写函数
    元素可以定位到,但是不进行点击--问题解决js点击处理
    《Oracle系列》Oracle 索引使用情况查看
    python资源库
    Scala | 宽窄依赖 | 资源调度与任务调度 | 共享变量 | SparkShuffle | 内存管理
    【DL with Pytorch】第 2 章 : 神经网络的构建块
    gitlab上传文件
  • 原文地址:https://blog.csdn.net/Star_ID/article/details/126611536