c++实现TCP&UDP

做网络通信作业之前的学习  !(>。<)!

一.TCP

1.服务端流程

        1.创建socket套接字

                socket套接字可以理解成网络接口,只有通过了socket套接字才能跟对应的电脑进行通信

        2.给这个socket绑定一个端口号

                IP地址是指定电脑的 端口号是指定电脑上面某个软件的

        3.给socket开启监听属性

                这个socket只能用来接收连接 不能用来做通讯

        4.等待客户端连接

        5.开始通讯

        6.关闭连接

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

int main()
{
	//windows上使用网络功能需要开始网络权限
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	//1.创建socket套接字
	/*socket(
		int af,  //协议地址簇 ipv4/ipv6 对应 AF_INET/AF_INET6
		int type,	//类型	流式协议/帧式协议 对应 SOCK_STREAM/SOCK_DGRAM
		int protocol	//保护协议 tcp/udp不用填保护协议 直接填0
	);*/

	//此处是服务端socket,所以我们一般起名叫监听socket
	SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);

	//SOCKET是一个无符号的长整型,就是一个整数,可以直接打印出来
	//未开启网络结果为-1 即无效的socket -> INVALID_SOCKET
	if (INVALID_SOCKET == listen_socket){
		printf("create listen socket failed !!! errocode: %d\n", GetLastError());
		return -1;
	}


	//2.给这个socket绑定一个端口号
	//struct sockaddr_in {
	//	ADDRESS_FAMILY sin_family;	//协议地址簇
	//	USHORT sin_port;	//端口号
	//	IN_ADDR sin_addr;	//IP地址
	//	CHAR sin_zero[8];	//保留字节 -> 协议升级会用
	//};
	
	//大小端问题:
	//	一个数字在计算机中存储以二进制存储,对端口来讲占两个字节
	//	编程中最小单位我们通常用字节来算,很少用位算
	//	存数据的时候会有先后的问题 8080 -> 1F90(大端序号,高位放前面【千百个十】)
	//	但是本地电脑的存储方式是以小端序存储的【个十百千】
	//unsigned short a = 8080;
	//unsigned short* p = &a;

	struct sockaddr_in local = { 0 };
	local.sin_family = AF_INET;
	local.sin_port = htons(8080);	//大小端问题 中间设备使用的是大端序

	//服务端 选项 网卡 127.0.0.1(本地环回)只接受哪个网卡的数据
	//一般写0.0.0.0 不管哪个数据库来 只要有我都接受
	
	//INADDR_ANY整数 占4个字节
	//local.sin_addr.s_addr = htonl(INADDR_ANY);

	//手动指定
	local.sin_addr.s_addr = inet_addr("0.0.0.0"); //字符串IP转换成整数IP
	
	//绑定 给上面定义的socket绑定我们指定的内容
	/*int bind(
		SOCKET s,	//对哪个socket进行绑定
		const struct sockaddr FAR * name,	//sockaddr的结构
		int namelen	//长度
	);  返回整数类型*/

	//为什么使用sockaddr结构而不是sockaddr_in结构?(两个结构体的大小一模一样)
	/*struct sockaddr {
		ADDRESS_FAMILY sa_family;
		CHAR sa_data[14];
	}   用这个方便扩展  通用的结构*/ 
	if (-1 == bind(listen_socket, (struct sockaddr*)&local, sizeof(local))) {
		printf("bind socket failed !!! errocode: %d\n", GetLastError());
		return -1;
	}


	//3.给socket开启监听属性
	/*int listen(
		SOCKET s,
		int backlog	//半连接队列长度
	);*/
	if (-1 == listen(listen_socket, 10)) {
		printf("start listen socket failed !!! errocode: %d\n", GetLastError());
		return -1;
	}
	//以上准备工作结束

	
	//4.等待客户端连接
	//返回的客户端socket才是跟客户端可以通讯的一个socket
	//listen_socket唯一的作用就是等待连接 最后返回一个socket出来
	
	//accept()是阻塞函数,等到有客户端连接进来就接受连接,然后染回,否则就阻塞
	/*SOCKET accept(
		SOCKET s,	//监听socket
		struct sockaddr * addr,	//客户端的IP地址和端口号
		int * addrlen	//结构的大小  为什么是指针 因为可填可不填 要填就要和上面的addr都填
	);*/

	//如果要跟多个客户端通讯 需要有while循环

	while (1) 
	{
		SOCKET client_socket = accept(listen_socket, NULL, NULL);
		if (INVALID_SOCKET == client_socket)
			continue;

		//5.开始通讯(B/S) 
		//通过浏览器访问一个网站的时候 
		//浏览器会发送一个http请求 http是tcp的上层协议 所以写tcp是可以接收http的报文的
		
		//接收发送的报文
		char buffer[1024] = { 0 };
		/*int recv(
				SOCKET s,	//客户端socket
				char* buf,	//接受的数据存到哪里
				int len,	//接受的长度
				int flags	//0
			);*/
		recv(client_socket, buffer, 1024, 0);

		printf("%s\n", buffer);


		//6.关闭连接
		closesocket(client_socket);
	}

	
	return 0;
}

错误码查找

大小端问题

中间设备使用的是大端序(路由器) 要从主机转换成中间件能使用的 -> htons()

不安全问题:把安全开发关掉

程序一旦弹出

代表监听已经开启了

模拟时输入127.0.0.1:8080  可以收到数据 --> tcp连接没问题

2.客户端流程

        1.创建socket套接字(通过套接字连接到服务端)

        2.连接服务器

        3.开始通讯

        4.关闭连接

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

int main()
{
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	//1.创建socket套接字(通过套接字连接到服务端)
	SOCKET client_socket = socket(AF_INET, SOCK_STREAM, 0);
	if (INVALID_SOCKET == client_socket) {
		print("create socket failed!!!\n");
		return -1;
	}

	//2.连接服务器
	struct sockaddr_in target;
	target.sin_family = AF_INET;
	target.sin_port = htons(8080);
	target.sin_addr.s_addr = inet_addr("127.0.0.1");

	if (-1 == connect(client_socket, (struct sockaddr*)&target, sizeof(target)))
	{
		printf("connet server failed !!!\n");
		closesocket(client_socket);
		return -1;
	}

	//3.开始通讯 send recv
	//回显服务器 -> 发送什么会回复什么
	while (1)
	{
		char sbuffer[1024] = { 0 };
		printf("please enter: ");
		scanf("%s", sbuffer);

		send(client_socket, sbuffer, strlen(sbuffer), 0);

		//立马接收
		char rbuffer[1024] = { 0 };
		int ret = recv(client_socket, rbuffer, 1024, 0);
		if (ret <= 0) break;
		printf("%s\n", rbuffer);
	}

	//4.关闭连接
	closesocket(client_socket);

	return 0;
}

tcp服务器客户端的重点在于服务端

因为存在的问题比较多:发送的消息怎么进行处理,如何同时接收多个客户端进行连接

3.实现多线程的客户端

服务端不应该主动断开连接而是由客户端断开

//连接一旦完成进入线程
        //Windows当中创建线程
        createThread(NULL,0,thread_func, &client_socket);
存在问题因为client_socket在栈区,所以可能socket销毁而线程都还没有启动(线程的运行是抢时间片的步骤,有可能线程在启动的时刻没抢过主线程,主线程已经把客户端socket销毁了)

所以我们要单独创建一个客户端socket,单独申请一个内存

服务端代码更改

#define _CRT_SECURE_NO_WARNINGS 1
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

//定义一个线程函数 当做线程启动入口
DWORD WINAPI thread_func(LPVOID lpThreadParameter)
{
	//传入client_socket 解引用拿到后 释放地址
	SOCKET client_socket = *(SOCKET*)lpThreadParameter;
	free(lpThreadParameter);
	while (1) {
		//5.开始通讯(B/S) 
		//通过浏览器访问一个网站的时候 
		//浏览器会发送一个http请求 http是tcp的上层协议 所以写tcp是可以接收http的报文的

		//接收发送的报文
		char buffer[1024] = { 0 };
		/*int recv(
				SOCKET s,	//客户端socket
				char* buf,	//接受的数据存到哪里
				int len,	//接受的长度
				int flags	//0
			);*/
		int ret = recv(client_socket, buffer, 1024, 0);
		if (ret <= 0) break;
		printf("%llu:%s\n",client_socket, buffer);

		//回显 接收完之后立马发送回去
		send(client_socket, buffer, (int)strlen(buffer), 0);

	}

	//断开连接 打印一下
	printf("socket: %llu,disconnect.\n", client_socket);

	//6.关闭连接
	closesocket(client_socket);
	
	return 0;
}

int main()
{
	//windows上使用网络功能需要开始网络权限
	WSADATA wsaData;
	WSAStartup(MAKEWORD(2, 2), &wsaData);

	//1.创建socket套接字
	/*socket(
		int af,  //协议地址簇 ipv4/ipv6 对应 AF_INET/AF_INET6
		int type,	//类型	流式协议/帧式协议 对应 SOCK_STREAM/SOCK_DGRAM
		int protocol	//保护协议 tcp/udp不用填保护协议 直接填0
	);*/

	//此处是服务端socket,所以我们一般起名叫监听socket
	SOCKET listen_socket = socket(AF_INET, SOCK_STREAM, 0);

	//SOCKET是一个无符号的长整型,就是一个整数,可以直接打印出来
	//未开启网络结果为-1 即无效的socket -> INVALID_SOCKET
	if (INVALID_SOCKET == listen_socket) {
		printf("create listen socket failed !!! errocode: %d\n", GetLastError());
		return -1;
	}


	//2.给这个socket绑定一个端口号
	//struct sockaddr_in {
	//	ADDRESS_FAMILY sin_family;	//协议地址簇
	//	USHORT sin_port;	//端口号
	//	IN_ADDR sin_addr;	//IP地址
	//	CHAR sin_zero[8];	//保留字节 -> 协议升级会用
	//};

	//大小端问题:
	//	一个数字在计算机中存储以二进制存储,对端口来讲占两个字节
	//	编程中最小单位我们通常用字节来算,很少用位算
	//	存数据的时候会有先后的问题 8080 -> 1F90(大端序号,高位放前面【千百个十】)
	//	但是本地电脑的存储方式是以小端序存储的【个十百千】
	//unsigned short a = 8080;
	//unsigned short* p = &a;

	struct sockaddr_in local = { 0 };
	local.sin_family = AF_INET;
	local.sin_port = htons(8080);	//大小端问题 中间设备使用的是大端序

	//服务端 选项 网卡 127.0.0.1(本地环回)只接受哪个网卡的数据
	//一般写0.0.0.0 不管哪个数据库来 只要有我都接受

	//INADDR_ANY整数 占4个字节
	//local.sin_addr.s_addr = htonl(INADDR_ANY);

	//手动指定
	local.sin_addr.s_addr = inet_addr("0.0.0.0"); //字符串IP转换成整数IP

	//绑定 给上面定义的socket绑定我们指定的内容
	/*int bind(
		SOCKET s,	//对哪个socket进行绑定
		const struct sockaddr FAR * name,	//sockaddr的结构
		int namelen	//长度
	);  返回整数类型*/

	//为什么使用sockaddr结构而不是sockaddr_in结构?(两个结构体的大小一模一样)
	/*struct sockaddr {
		ADDRESS_FAMILY sa_family;
		CHAR sa_data[14];
	}   用这个方便扩展  通用的结构*/
	if (-1 == bind(listen_socket, (struct sockaddr*)&local, sizeof(local))) {
		printf("bind socket failed !!! errocode: %d\n", GetLastError());
		return -1;
	}


	//3.给socket开启监听属性
	/*int listen(
		SOCKET s,
		int backlog	//半连接队列长度
	);*/
	if (-1 == listen(listen_socket, 10)) {
		printf("start listen socket failed !!! errocode: %d\n", GetLastError());
		return -1;
	}
	//以上准备工作结束


	//4.等待客户端连接
	//返回的客户端socket才是跟客户端可以通讯的一个socket
	//listen_socket唯一的作用就是等待连接 最后返回一个socket出来

	//accept()是阻塞函数,等到有客户端连接进来就接受连接,然后染回,否则就阻塞
	/*SOCKET accept(
		SOCKET s,	//监听socket
		struct sockaddr * addr,	//客户端的IP地址和端口号
		int * addrlen	//结构的大小  为什么是指针 因为可填可不填 要填就要和上面的addr都填
	);*/

	//如果要跟多个客户端通讯 需要有while循环

	while (1)
	{
		SOCKET client_socket = accept(listen_socket, NULL, NULL);
		if (INVALID_SOCKET == client_socket)
			continue;

		//有新的连接产生 打印一下
		printf("new connnet, socket: %llu\n", client_socket);

		//单独申请一个客户端
		SOCKET* sockfd = (SOCKET*)malloc(sizeof(SOCKET));
		*sockfd = client_socket;

		//连接一旦完成进入线程
		//Windows当中创建线程
		CreateThread(NULL,0,thread_func, sockfd, 0, NULL);
	}


	return 0;
}

代码实现

二.UDP

1.TCP与UDP服务端的不同之处

  1. Socket类型

    • TCP使用SOCK_STREAM类型,而UDP使用SOCK_DGRAM类型。
  2. 连接方式

    • TCP是面向连接的,需要通过connectlistenaccept函数建立连接。
    • UDP是无连接的,不需要建立连接,直接使用sendtorecvfrom函数发送和接收数据。
  3. 数据传输

    • TCP提供可靠的、有序的数据传输,保证数据完整性。
    • UDP提供不可靠的、无序的数据传输,可能丢包,但速度更快。
  4. 并发处理

    • TCP服务端通常需要为每个客户端连接创建一个线程或使用非阻塞I/O来处理并发。
    • UDP服务端通常在主循环中使用recvfromsendto处理所有客户端的数据,不需要为每个客户端创建线程。
    • (UDP服务端示例代码中没有使用线程,因为UDP是无连接的,通常不需要为每个客户端创建线程。如果需要处理大量并发UDP客户端,可以考虑使用线程池或非阻塞I/O。)
  5. 错误处理

    • TCP在建立连接时会进行错误处理,如连接失败会返回错误。
    • UDP在发送和接收数据时可能会遇到错误,如目标不可达等,需要在发送和接收时处理。
  6. 资源消耗

    • TCP由于需要维护连接状态,资源消耗相对较高。
    • UDP不需要维护连接状态,资源消耗相对较低。
  7. 适用场景

    • TCP适用于需要可靠传输的场景,如文件传输、网页浏览等。
    • UDP适用于对实时性要求高的场景,如视频会议、在线游戏等。

2.服务端代码

#define _CRT_SECURE_NO_WARNINGS 1
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<WinSock2.h>
#pragma comment(lib,"ws2_32.lib")

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    SOCKET udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (INVALID_SOCKET == udp_socket) {
        printf("create UDP socket failed !!! errocode: %d\n", GetLastError());
        return -1;
    }

    struct sockaddr_in local = { 0 };
    local.sin_family = AF_INET;
    local.sin_port = htons(8080);
    local.sin_addr.s_addr = inet_addr("0.0.0.0");

    if (-1 == bind(udp_socket, (struct sockaddr*)&local, sizeof(local))) {
        printf("bind socket failed !!! errocode: %d\n", GetLastError());
        return -1;
    }

    printf("UDP server is running\n");

    while (1) {
        char buffer[1024] = { 0 };
        struct sockaddr_in client_addr;
        int addr_len = sizeof(client_addr);

        int ret = recvfrom(udp_socket, buffer, 1024, 0, (struct sockaddr*)&client_addr, &addr_len);
        if (ret > 0) {
            printf("Received from %s:%d: %s\n", inet_ntoa(client_addr.sin_addr), ntohs(client_addr.sin_port), buffer);
            sendto(udp_socket, buffer, ret, 0, (struct sockaddr*)&client_addr, addr_len);  // Echo back
        }
    }

    closesocket(udp_socket);
    WSACleanup();
    return 0;
}

3.客户端代码

#define _CRT_SECURE_NO_WARNINGS 1
#define _WINSOCK_DEPRECATED_NO_WARNINGS
#include <stdio.h>
#include <string.h>
#include <WinSock2.h>
#pragma comment(lib, "ws2_32.lib")

int main()
{
    WSADATA wsaData;
    WSAStartup(MAKEWORD(2, 2), &wsaData);

    // 1. 创建socket套接字(通过套接字连接到服务端)
    SOCKET client_socket = socket(AF_INET, SOCK_DGRAM, 0);
    if (INVALID_SOCKET == client_socket) {
        printf("create socket failed!!!\n");
        return -1;
    }

    // 2. 定义服务器地址
    struct sockaddr_in server_addr;
    server_addr.sin_family = AF_INET;
    server_addr.sin_port = htons(8080);
    server_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

    // 3. 开始通讯 send recv
    while (1)
    {
        char sbuffer[1024] = { 0 };
        printf("please enter: ");
        scanf("%s", sbuffer);

        // 发送数据到服务器
        sendto(client_socket, sbuffer, strlen(sbuffer), 0, (struct sockaddr*)&server_addr, sizeof(server_addr));

        // 接收服务器回显
        char rbuffer[1024] = { 0 };
        int addr_len = sizeof(server_addr);
        int ret = recvfrom(client_socket, rbuffer, 1024, 0, (struct sockaddr*)&server_addr, &addr_len);
        if (ret <= 0) break;

        rbuffer[ret] = '\0'; // 添加字符串结束符
        printf("Received from server: %s\n", rbuffer);
    }

    // 4. 关闭socket
    closesocket(client_socket);
    WSACleanup(); // 释放 Winsock 资源

    return 0;
}

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值