基于Window网络编程课程设计(刘琰著)写tcp和udp双回射服务器思想及代码实现

再写一遍双回射,主要还是按照书上走,也方便自己回顾理解

而且这个代码完美解决了tcp阻塞问题,其实看懂这个代码也理解了为什么上篇的代码网络编程——实现tcp和udp的双回射服务器(c++)-CSDN博客

会被阻塞,读者可以自己思考下

本书还是采用的是select的方法来实现双回射的服务器。

一、基本思路流程图:

                               

                                          

一、主要实现过程:

创建三个套接字:

SOCKET tcp_sock = INVALID_SOCKET;//tcp监听套接字
SOCKET udp_sock = INVALID_SOCKET;//udp套接字
SOCKET tcp_clnt_sock = INVALID_SOCKET;//tcp连接套接字

初始化套接字:

if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
    printf("WSAStartup failed");
}
对监听和udp两个套接字进行基本操作:

tcp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
udp_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);

//这一步上次文章有讲到,目的是能够快速重新使用该端口

int opt = 1;
setsockopt(tcp_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
setsockopt(udp_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));

tcp_addr.sin_family = AF_INET;
tcp_addr.sin_addr.s_addr = INADDR_ANY;
tcp_addr.sin_port = htons(3000);

udp_addr.sin_family = AF_INET;
udp_addr.sin_addr.s_addr = INADDR_ANY;
udp_addr.sin_port = htons(3000);


分别为tcp_sock和udp_sock绑定端口:


bind(tcp_sock, (struct sockaddr*)&tcp_addr, sizeof(tcp_addr));
bind(udp_sock, (struct sockaddr*)&udp_addr, sizeof(udp_addr));

监听套接字:

 listen(tcp_sock, 5);//监听

select 函数进行 I/O 多路复用时,用来设置和管理需要检测的套接字集合时的一些基本化操作:

fd_set readfds;

//这行代码声明了一个类型为 fd_set 的变量 readfds。fd_set 是一个数据结构,用于存储一组需要监视的套接字

FD_ZERO(&readfds);

//这行代码调用 FD_ZERO 宏,初始化 readfds 集合,将其清空。FD_ZERO 宏将 fd_set 结构中的所有位都设置为 0,表示开始时没有任何套接字被监视。

FD_SET(tcp_sock, &readfds);

//这行代码调用 FD_SET 宏,将 TCP 套接字 tcp_sock 添加到 readfds 集合中。FD_SET 宏会在 fd_set 结构中对应的套接字位置设置为 1,表示这个套接字被加入到监视集合中。

FD_SET(udp_sock, &readfds);

//这行代码将 UDP 套接字 udp_sock 也添加到 readfds 集合中,操作与添加 TCP 套接字类似

通过循环,检查套接字活动:

while (1) {
    fd_set fds = readfds;
    int activity = select(0, &fds, NULL, NULL, NULL);  

    if ((activity < 0) && (errno != EINTR)) {
        printf("select error");
    }

    if (FD_ISSET(tcp_sock, &fds)) {
            查看tcp监听套接字,如果有,进行accept,将将连接套接字移入readfds集合
    }

    else if (FD_ISSET(udp_sock, &fds)) {
        //如果是udp套接字,进行回射操作
    }

    else
    {
      剩下的只可能是tcp的连接套接字了,直接进行回射操作

    }
}

三、具体代码

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS 
#define _CRT_SECURE_NO_WARNINGS

#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#pragma comment (lib,"Ws2_32.lib")
#pragma comment (lib,"Mswsock.lib")
#pragma comment (lib,"AdvApi32.lib")
#define BUF_SIZE 1024
int main() {
	WSADATA wsaData;
	SOCKET tcp_sock = INVALID_SOCKET;//tcp监听套接字
	SOCKET udp_sock = INVALID_SOCKET;//udp套接字
	SOCKET tcp_clnt_sock = INVALID_SOCKET;//tcp连接套接字
	struct sockaddr_in tcp_addr; 
	struct sockaddr_in udp_addr; 
	int addr_len = sizeof(struct sockaddr_in);
	// Initialize Winsock
	if (WSAStartup(MAKEWORD(2, 2), &wsaData) != 0) {
		printf("WSAStartup failed");
	}

	tcp_sock = socket(PF_INET, SOCK_STREAM, IPPROTO_TCP);
	udp_sock = socket(PF_INET, SOCK_DGRAM, IPPROTO_UDP);
	
	int opt = 1;
	setsockopt(tcp_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
	setsockopt(udp_sock, SOL_SOCKET, SO_REUSEADDR, (const char*)&opt, sizeof(opt));
	
	tcp_addr.sin_family = AF_INET;
	tcp_addr.sin_addr.s_addr = INADDR_ANY;
	tcp_addr.sin_port = htons(3000);

	udp_addr.sin_family = AF_INET;
	udp_addr.sin_addr.s_addr = INADDR_ANY;
	udp_addr.sin_port = htons(3000);
	//分别为tcp_sock和udp_sock绑定端口
	bind(tcp_sock, (struct sockaddr*)&tcp_addr, sizeof(tcp_addr));
	bind(udp_sock, (struct sockaddr*)&udp_addr, sizeof(udp_addr));
	
	listen(tcp_sock, 5);//监听
	// select 函数进行 I/O 多路复用时,用来设置和管理需要检测的套接字集合
	fd_set readfds;//这行代码声明了一个类型为 fd_set 的变量 readfds。fd_set 是一个数据结构,用于存储一组需要监视的套接字
	FD_ZERO(&readfds);//这行代码调用 FD_ZERO 宏,初始化 readfds 集合,将其清空。FD_ZERO 宏将 fd_set 结构中的所有位都设置为 0,表示开始时没有任何套接字被监视。
	FD_SET(tcp_sock, &readfds);//这行代码调用 FD_SET 宏,将 TCP 套接字 tcp_sock 添加到 readfds 集合中。FD_SET 宏会在 fd_set 结构中对应的套接字位置设置为 1,表示这个套接字被加入到监视集合中。
	FD_SET(udp_sock, &readfds);//这行代码将 UDP 套接字 udp_sock 也添加到 readfds 集合中,操作与添加 TCP 套接字类似

	while (1) {
		fd_set fds = readfds;
		int activity = select(0, &fds, NULL, NULL, NULL);
		if ((activity < 0) && (errno != EINTR)) {
			printf("select error");
		}
		
		if (FD_ISSET(tcp_sock, &fds)) {
			 tcp_clnt_sock = accept(tcp_sock, (struct sockaddr*)&tcp_addr, &addr_len);
			if (tcp_clnt_sock == -1)
				printf("accept error");
			FD_SET(tcp_clnt_sock, &readfds);
		}

		else if (FD_ISSET(udp_sock, &fds)) {
			char buffer[BUF_SIZE];
			memset(buffer, 0, BUF_SIZE);
			int n = recvfrom(udp_sock, buffer, BUF_SIZE, 0, (struct sockaddr*)&udp_addr, &addr_len);
			if (n > 0)
			{
				printf("服务器接收到UDP数据:%s\n", buffer);
				//回射数据
				n = sendto(udp_sock, buffer, n, 0, (struct sockaddr*)&udp_addr, addr_len);
				if (n == SOCKET_ERROR)
				{
					printf("send 函数调用错误,错误号:%ld\n", WSAGetLastError());
					closesocket(udp_sock);
					FD_CLR(udp_sock, &fds);
				}
				else
				{
					printf("服务器回射UDP数据:%s\n", buffer);
					printf("\n");
				}
			}
			else if (n == 0)
			{
				//连接关闭
				printf("当前连接关闭。。。\n");
				closesocket(udp_sock);
				FD_CLR(udp_sock, &fds);
			}
			else
			{

				printf("recvfrom 函数调用错误,错误号:%d\n", WSAGetLastError());
				closesocket(udp_sock);
				FD_CLR(udp_sock, &fds);
			}
		}

		else
		{
			char recvbuf[BUF_SIZE];
			memset(recvbuf, 0, BUF_SIZE);
			int iResult = 0;
			//接收数据
			iResult = recv(tcp_clnt_sock, recvbuf, BUF_SIZE, 0);
			if (iResult > 0)
			{
				printf("服务器接收到TCP数据:%s\n", recvbuf);
				//回射数据
				iResult = send(tcp_clnt_sock, recvbuf, iResult, 0);
				if (iResult == SOCKET_ERROR)
				{
					printf("send 函数调用错误,错误号:%ld\n", WSAGetLastError());
					closesocket(tcp_clnt_sock);
					FD_CLR(tcp_clnt_sock, &fds);
				}
				else
				{
					printf("服务器回射TCP数据:%s\n", recvbuf);
					printf("\n");
				}
			}
			else if (iResult == 0)
			{
				//连接关闭
				printf("当前连接关闭。。。\n");
				closesocket(tcp_clnt_sock);
				FD_CLR(tcp_clnt_sock, &fds);
			}
			else
			{

				printf("recv 函数调用错误,错误号:%d\n", WSAGetLastError());
				closesocket(tcp_clnt_sock);
				FD_CLR(tcp_clnt_sock, &fds);
			}

		}


	}
	return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值