1. 套接字
套接字是由操作系统提供的网络数据通信软件设备,,即使对网络数据传输原理不了解,也能够使用套接字完成网络数据传输。为了与远程计算机进行数据传输,需要连接到英特网,套接字就是进行网络连接的工具。
服务端:接收连接请求的套接字创建流程如下:
1. 调用socket函数创建套接字
2. 调用bind函数分配IP地址和端口号(port)
3. 调用listen函数,套接字转为可接受请求状态
4. 调用accept函数接收连接请求
简单的服务端程序:《TCP/IP网络编程》书籍中的例子做了修改
例子:基于windows的服务端/客户端简socket通信单实现
功能:客户端输入计算表达式,再通过将表达式组成消息报文,发送给服务端,由服务端计算表达式的值,计算完成后再将结果返回给客户端,客户端对结果进行相应的展示:
消息报文格式如下所示:
索引 | 值 | 含义 | 0 | C | 消息头 | 1 | K | 2 | 0 | 数据长度(第四个字节以后的数据长度) | 3 | 0 | 4 | 0 | 参与运算的数字个数 | 5 | 0 | 6 | 0 | 第一个运算数 | 7 | 0 | 8 | 0 | 9 | 0 | 10 | 0 | 第 n 个运算数 | 11 | 0 | 12 | 0 | 13 | 0 | 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 运算符号
*/
运行结果如下图所示:
客户端输入以及结果展示
?服务端输出:
?
?
|