IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> TCP/IP网络编程 -> 正文阅读

[网络协议]TCP/IP网络编程

1. 套接字

套接字是由操作系统提供的网络数据通信软件设备,,即使对网络数据传输原理不了解,也能够使用套接字完成网络数据传输。为了与远程计算机进行数据传输,需要连接到英特网,套接字就是进行网络连接的工具。

服务端:接收连接请求的套接字创建流程如下:

1. 调用socket函数创建套接字

2. 调用bind函数分配IP地址和端口号(port)

3. 调用listen函数,套接字转为可接受请求状态

4. 调用accept函数接收连接请求

简单的服务端程序:《TCP/IP网络编程》书籍中的例子做了修改

例子:基于windows的服务端/客户端简socket通信单实现

功能:客户端输入计算表达式,再通过将表达式组成消息报文,发送给服务端,由服务端计算表达式的值,计算完成后再将结果返回给客户端,客户端对结果进行相应的展示:

消息报文格式如下所示:

索引含义
0C消息头
1K
20数据长度(第四个字节以后的数据长度)
30
40参与运算的数字个数
50
60第一个运算数
70
80
90
100第 n 个运算数
110
120
130
14+? -? ?*? /运算符
15
16
17
18

服务端代码实现:

server.cpp

/*
   简易计算器服务端代码
*/

#include "stdafx.h"
#include <stdio.h>
#include <iostream>
#include <WinSock2.h>

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

#define BUFF_SIZE   100         // 定义接收数据缓冲区字节大小
#define MESSAGE_HEAD_SIZE   4   // 消息头大小
#define OPERAND_SIZE   4        // 运算数所占字节大小
#define RESULT_SIZE    4        // 计算结果所占的字节数
#define RESULT_OVERFLOW  -999999    // 计算结果溢出

typedef unsigned short ushort;
typedef INT32 int32;
typedef INT16 int16;


void error_handle(char* message)
{
	printf("%s\n", message);
	system("pause");
	exit(1);
}


int main()
{
	WSADATA wsadata;
	SOCKET serverSocket, clientSocket;
	sockaddr_in serverAddr;

	ushort port = 30100;      // 定义端口号

	// 定义缓冲区
	char buffer[BUFF_SIZE];
	memset(buffer, 0, BUFF_SIZE);

	int32 result = 0;

	int recvLen   = 0;        // 接收长度
	int recvCount = 0;        // 接收数据计数

	// 初始化socket库
	if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)
	{
		error_handle("Failed to init wsadata");
	}

	// 初始化服务端套接字
	serverSocket = socket(PF_INET, SOCK_STREAM, 0);
	
	// 服务端地址绑定
	memset(&serverAddr, 0, sizeof(serverAddr));
	serverAddr.sin_family = AF_INET;
	serverAddr.sin_addr.s_addr = INADDR_ANY;
	serverAddr.sin_port = htons(port);
	int serverAddrSize = sizeof(serverAddr);

	// 绑定端口
	if (bind(serverSocket, (sockaddr*)&serverAddr, sizeof(serverAddr)) == SOCKET_ERROR)
	{
		error_handle("Failed to bind socket");
	}

	if (listen(serverSocket, 5) == SOCKET_ERROR)
	{
		error_handle("Falied to listen");
	}

	while (true)
	{
		// 等待接收连接
		printf("Waiting for connction from client!\n");

		clientSocket = accept(serverSocket, (sockaddr*)&serverAddr, &serverAddrSize);

		if (clientSocket == INVALID_SOCKET)
		{
			printf("Failed to get connect from client!\n");
			continue;
		}

		printf("Successfully get connect from client!\n");

		recvLen = recv(clientSocket, buffer, BUFF_SIZE, 0);    // 先读取四个字节的数据

		if (recvLen < 4)
		{
			// 数据包数据缺失
			result = 0;                     // 记得result清零
			memset(buffer, 0, BUFF_SIZE);   // buffer记得清零
			continue;                       // 重新等待接收连接
		}

		// 校验消息头
		if (buffer[0] != 'C' || buffer[1] != 'K')
		{
			printf("The message header is wrong!\n");

			result = 0;     // 记得result清零
			memset(buffer, 0, BUFF_SIZE);       // buffer记得清零

			continue;
		}

		// 解析数据长度
		int dataLen = buffer[2] | (buffer[3] << 8);

		// recvLen = MESSAGE_HEAD_SIZE;

		while (recvLen < (dataLen + 2 + 2))
		{
			recvCount = recv(clientSocket, &buffer[recvLen], BUFF_SIZE-1, 0);      // 计算实际接收的数据的个数
			recvLen += recvCount;
		}

		printf("Successfully recv messgae.\n");

		int operand_count = buffer[MESSAGE_HEAD_SIZE] | (buffer[MESSAGE_HEAD_SIZE+1] << 8);      // 运算数的数量

		char caloperator = buffer[MESSAGE_HEAD_SIZE + 2 + operand_count*OPERAND_SIZE];

		if (caloperator != '+' &&
			caloperator != '-' &&
			caloperator != '*' &&
			caloperator != '/')
		{
			// error_handle("Operator is invalid!");
			printf("Operator %c is invalid!", caloperator);

			result = 0;     // 记得result清零

			memset(buffer, 0, BUFF_SIZE);       // buffer记得清零

			continue;
		}

		for (int i=0; i<operand_count; ++i)
		{
			int32 operand = *(int32*)&buffer[MESSAGE_HEAD_SIZE + 2 + i * OPERAND_SIZE];

			if (i == 0)
			{
				result = operand;
				continue;
			}

			if (caloperator == '+')
			{
				result += operand;
			}

			else if (caloperator == '-')
			{
				result -= operand;
			}

			else if (caloperator == '*')
			{
				result *= operand;
			}

			else if (caloperator == '/')
			{
				if (operand == 0)
				{
					printf("The reuslt is overflow because the number is divided by zero");
					result = RESULT_OVERFLOW;
					break;
				}

				result /= operand;
			}

		}

		// 返回结果
		send(clientSocket, (char*)&result, RESULT_SIZE, 0);

		closesocket(clientSocket);

		result = 0;     // 记得result清零

		memset(buffer, 0, BUFF_SIZE);       // buffer记得清零
	}

	WSACleanup();

	system("pause");
    return 0;
}


// 服务端/客户端通信的消息格式
/*
xx        xx              xx                 xxxx        xxxx      xxxx         x

标识符   数据长度    运算数的个数(2bytes)       操作数1     操作数2    操作数n    运算符号
*/


客户端代码:
client.cpp

/*
   建议服务器客户端代码
*/

#include "stdafx.h"
#include <iostream>
#include <stdlib.h>
#include <WinSock2.h>

using std::cout;
using std::endl;

// 类型定义
typedef unsigned short ushort;
typedef unsigned char uchar;
typedef INT32 int32;
typedef INT16 int16;

#define BUFFER_SIZE    100      // 定义缓冲区字节大小
#define OPERAND_SIZE   4        // 定义操作数的所占字节
#define OPERATOR_SIZE  2        // 定义操作符所占字节的大小
#define RESULT_SIZE    4        // 返回结果所占的字节数
#define RESULT_OVERFLOW  -999999    // 计算结果溢出


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

//#define _WINSOCK_DEPRECATED_NO_WARNINGS
//#define _CRT_SECURE_NO_WARNINGS

void error_handle(char* message)
{
	printf("%s\n", message);
	system("pause");
	exit(1);
}


int main(int argc, char* argv[])
{
	printf("Starting the calculate client...\n");

	// 定义数据区
	char buffer[BUFFER_SIZE];  
	memset(buffer, 0, sizeof(buffer));

	WSADATA wsadata;
	SOCKET hsocket;

	SOCKADDR_IN servAddr;       // 服务器端地址

	// 服务端的地址和端口号
	char ipAddr[] = "127.0.0.1";
	ushort port = 30100;   

	if (WSAStartup(MAKEWORD(2, 2), &wsadata) != 0)       // 返回0表示初始化成功
	{
		error_handle("Failed to init socket lib.");      // 初始化套接字相关的库失败
	}

	hsocket = socket(PF_INET, SOCK_STREAM, 0);
	if (hsocket == INVALID_SOCKET)
	{
		error_handle("Failed to create socket");
	}

	// 设置服务器地址以及端口
	memset(&servAddr, 0, sizeof(servAddr));      
	servAddr.sin_family = AF_INET;
	servAddr.sin_addr.s_addr = inet_addr(ipAddr);        // inet_addr将字符串IP地址转成整数,且转成网络字节序
	servAddr.sin_port = htons(port);

	// 连接服务器
	if (connect(hsocket, (SOCKADDR*)&servAddr, sizeof(servAddr)) == SOCKET_ERROR )
	{
		error_handle("Failed to connect to server\n");
	}
	else
	{
		printf("Successfully connected to server %s: %d\n", ipAddr, port);
	}

	ushort operand_count = 0;
	printf("Plaese input operand count: ");
	scanf("%d", &operand_count);

	// 填充消息头
	buffer[0] = 'C';
	buffer[1] = 'K';

	buffer[2] = 0;
	buffer[3] = 0;

	// 填充运算数个数
	buffer[4] = (char) operand_count & 0x00ff;
	buffer[5] = (char) operand_count & 0xff00;

	for (int i=0; i<operand_count; ++i)
	{
		printf("Please input operand %d: ", i + 1);
		scanf("%d", (int32*)&buffer[i*OPERAND_SIZE + 6]);       // 操作数占用四个字节
	}

	// 填充运算符
	printf("Please Input operator: ");
	scanf(" %c", &buffer[operand_count*OPERAND_SIZE + 6]);  // 这里的%c前面必须加入空格,否则会因为前面输入按下的空格,而导致这里将前面按下的空格直接读入,导致输入值错误

	// 填充数据长度
	int dataLen = (operand_count * OPERAND_SIZE) + 2 + 1;   // 前四个字节不计入数据长度
	buffer[2] = (dataLen & 0xff);
	buffer[3] = (dataLen >> 8) & 0xff;

	// 发送数据包
	send(hsocket, buffer, sizeof(buffer), 0);

	// 接收服务端的数据
	int result;
	recv(hsocket, (char*)&result, sizeof(result), 0);       
	 
	if (result != RESULT_OVERFLOW)
	{
		printf("The calculate result is %d.\n", result);
	}
	else
	{
		printf("The calculate result is overflow!");
	}

	closesocket(hsocket);

	WSACleanup();

	// 避免控制台不出现
	system("pause");
    return 0;
}


// 服务端/客户端通信的消息格式
/*
    xx        xx              xx                 xxxx        xxxx      xxxx         x

   标识符   数据长度    运算数的个数(2bytes)       操作数1     操作数2    操作数n    运算符号
*/

运行结果如下图所示:

客户端输入以及结果展示

?服务端输出:

?

?

  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-03-11 22:34:38  更:2022-03-11 22:34:55 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 6:48:59-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码