1. 套接字

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

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

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

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

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

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

在Linux系统中,一切皆文件,因此Linux中的socket也是一种文件,因此在数据的传输过程中,可以使用文件IO相关的函数进行操作。而在Windows系统中,文件和socket是有区别的。

2. 套接字的类型:

面向连接的套接字(SOCKET_STREAM)

如果socket函数的第二个参数为SOCKET_STREAM,则会创建面向连接的的套接字,即TCP套接字。面向连接的套接字(TCP套接字)传输数据的方式与传送到传输物品类似,即只要传送带本身没有问题,就不会导致数据的丢失。且较晚传输的数据不会先到达,保证了数据的按序传递,TCP套接字的传输方式具有如下特点:

1. 传输过程中数据不会丢失

2. 数据按序传输

3. 传输的数据不存在数据边界

在收发数据的套接字内部有缓冲区,通过套接字传输的数据 将保存在缓冲区中,套接字收到数据并不意味着马上调用read函数。只要不超过数组容量,则有可能在数据填充满缓冲区之后通过调用1次read函数读取全部数据。也有可能通过多次调用read函数读取所有数据。也就是说在TCP套接字中,调用read,write方法的次数并无太大意义,所以说TCP套接字不存在数据边界。

如果接收的套接字接受速度较慢,导致接收缓冲区被填满,此时发送的套接字便会停止数据发送,直到接收端调用read函数读取数据使得缓冲区中有空余位置时,发送端套接字才会继续接着发送,因此不会造成数据丢失,而且在传输过程中如果发生数据丢失,还会进行数据的重新传输。因此TCP套接字除特殊情况外不会发生数据丢失。

面向消息的套接字(SOCKET_DGRAM)

如果socket函数的第二个参数为SOCKET_DGRAM,则会创建面向消息的套接字,即UDP套接字。UDP套接字传输数据类似于高速移动的摩托快递。其传输方式具有如下的特点:

1.强调快速传输而非顺序传输 (不一定保证次序)

2.传输的数据可能丢失,也可能损毁,没有数据重传机制

3. 传输的数据有边界

4. 限制每次传输的数据的大小

类似于两件包裹发送至统一目的地,只要以最快的速度交给用户即可。在输送过程中无需保证包裹的次序。包裹有大小限制且在传送的过程中存在损毁或者丢失的风险。且如果分多次发送包裹,接收者也需要分多次进行接收,即“传输的数据具有边界“,因此UDP套接字是一种”不可靠的,不按序传递的,以数据的高速传输为目的的套接字“。

-----------------------------------------分割线--------------------------------------------

简单的服务端程序:《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_WARNINGSvoid 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    运算符号
*/

运行结果如下图所示:

客户端输入以及结果展示

服务端输出:

TCP/IP网络编程(1)相关推荐

  1. 高等学校计算机科学与技术教材:tcp/ip网络编程技术基础,TCP/IP网络编程技术基础...

    TCP/IP网络编程技术基础 语音 编辑 锁定 讨论 上传视频 <TCP/IP网络编程技术基础>是2012年北京交通大学出版社出版的图书,作者是王雷. 书    名 TCP/IP网络编程技 ...

  2. TCP/IP网络编程之基于TCP的服务端/客户端(二)

    回声客户端问题 上一章TCP/IP网络编程之基于TCP的服务端/客户端(一)中,我们解释了回声客户端所存在的问题,那么单单是客户端的问题,服务端没有任何问题?是的,服务端没有问题,现在先让我们回顾下服 ...

  3. TCP/IP网络编程(3)

    基于DUP的服务端与客户端 在TCP/IP网络编程(2)中,介绍了TCP/IP的四层模型,传输层分为TCP和UDP两种方式,通过TCP套接字完成数据交换已经进行了介绍,下面介绍通过UDP套接字完成数据 ...

  4. 《TCP/IP网络编程》第20章

    <TCP/IP网络编程>第20章 同步方法分类及CRITICAL_SECTION同步 用户模式(User mode)和内核模式(Kernal mode) 用户模式同步 内核模式同步 基于C ...

  5. TCP/IP网络编程之多进程服务端(一)

    TCP/IP网络编程之多进程服务端(一) 进程概念及应用 我们知道,监听套接字会有一个等待队列,里面存放着不同客户端的连接请求,如果有一百个客户端,每个客户端的请求处理是0.5s,第一个客户端当然不会 ...

  6. TCP/IP网络编程之基于TCP的服务端/客户端(一)

    TCP/IP网络编程之基于TCP的服务端/客户端(一) 理解TCP和UDP 根据数据传输方式的不同,基于网络协议的套接字一般分为TCP套接字和UDP套接字.因为TCP套接字是面向连接的,因此又称为基于 ...

  7. TCP/IP网络编程之多进程服务端(二)

    TCP/IP网络编程之多进程服务端(二) 信号处理 本章接上一章TCP/IP网络编程之多进程服务端(一),在上一章中,我们介绍了进程的创建和销毁,以及如何销毁僵尸进程.前面我们讲过,waitpid是非 ...

  8. 网络编程+go+java,Go语言中的TCP/IP网络编程

    Go语言TCP/IP网络编程 乍一看,通过TCP/IP层连接两个进程会感觉可怕, 但是在Go语言中可能比你想象的要简单的多. TCP/IP层发送数据的应用场景 当然很多情况下,不是大多数情况下,使用更 ...

  9. tcp/ip网络编程--accept()函数返回的套接字

    tcp/ip网络编程–accept()函数返回的套接字 套接字:1)套接字是对网络中不同主机的应用进程之间进行双向通信的端点的抽象:一个套接字就是网络进程通信的一端.[1] 2)套接字是用来与另一个进 ...

最新文章

  1. sqoop mysql parquet_sqoop一些语法的使用
  2. 【PHPWord】页面Section
  3. xcode 自带的git工具创建项目流程
  4. 使用最新目标跟踪框mmtracking实现自己的目标跟踪项目
  5. 一步一步SharePoint 2007之十四:实现Form认证(4)——创建管理帐户
  6. jQuery 对象和 DOM 对象
  7. Jenkins中连接Git仓库时提示:error: The requested URL returned error: 401 Unauthorized while accessing
  8. intellij中重命名一个文件
  9. java模拟连接超时_Java:使用Toxiproxy模拟各种连接问题
  10. Linux安装Jenkins
  11. 数据卡片_E015 如何批量汇总工作簿数据,形成独立工作簿信息卡片
  12. java虚引用作用_深入理解Java中的引用(二)——强软弱虚引用
  13. Java 继承学习笔记
  14. mv强制覆盖 shell_生产力工具:shell 与 Bash 脚本
  15. python的namedtuple
  16. curses.h: No such file or directory
  17. 获取指定路径,扫描路径下指定类(仿Spring)
  18. vfp python_Visual Fox Pro和Python
  19. 课后作业3:软件分析与用户体验分析
  20. 微信商户平台 扣款测试规则

热门文章

  1. 大数据_Hbase-API访问_Java操作Hbase_数据操作_表删除_表获取所有数据---Hbase工作笔记0014
  2. Netty工作笔记0076---handler链调用机制实例3
  3. 安全验证框架使用笔记002---Shiro实现基本的身份验证
  4. Spring AOP 底层原理_001----AspectJ与CGLIB介绍
  5. basler 相机使用出现的问题
  6. 成功在家用ssh远程连上了学校电脑虚拟机当中的ubuntu(代价是虚拟机全部黑屏只能用SSH连接了!)
  7. java编写龟兔赛跑_java--龟兔赛跑模拟
  8. 卵巢鸿蒙不全怎么检查,卵巢黄体功能不足怎么办 这三个处理方法要了解
  9. 在教学中利用计算机软件,计算机软件在数学教学中的应用
  10. 【10天基于STM32F401RET6智能锁项目实战第2天】GPIO与寄存器