2-3 建立简易TCP服务端、客户端

文章目录

  • 2-3 建立简易TCP服务端、客户端
    • 0-前言
    • 1-服务端简易功能
    • 2-客户端简易功能
    • 3-代码逻辑
    • 4-服务端
      • 4-1 建立socket
      • 4-2 绑定端口
        • 4-2-1 sockaddr
        • 4-2-2 sockaddr_in
      • 4-3 监听端口
      • 4-4 等待客户端连接
      • 4-5 发送数据
      • 4-6 关闭socket
      • 4-7 服务端全部程序
    • 5-客户端
      • 5-1 建立socket
      • 5-2 连接服务器
      • 5-3 接收服务器信息
      • 5-4 关闭socket
      • 5-5 客户端全部程序
    • 6-测试最终程序

0-前言

【C++百万并发网络通信】系列是跟着【张远东】老师的视频来复现的

希望能通过博客的方式不断坚持学习,也希望偶然间看到这篇博客的你也能一起加油!

笔记目录:【C++百万并发网络通信-笔记目录】

更新时间:

2020.12.30 完成服务端简易功能
2021.1.5 完成客户端简易功能,测试服务端与客户端成功

1-服务端简易功能

  • 建立socket
  • 绑定端口 bind
  • 监听网络端口 listen
  • 等待客户端连接 accept
  • 向客户端发送数据 send
  • 关闭socket closesocket

注意这里省略了【接收客户端数据】recv这一功能,即服务端只能【发】


2-客户端简易功能

  • 建立socket
  • 连接服务器 connect
  • 接收服务器信息 recv
  • 关闭socket closesocket

注意这里省略了【向服务端发送数据】send这一功能,即客户端只能【收】,这就好比我们有一部只能接电话的手机,不能打电话。


3-代码逻辑

#define WIN32_LEAN_AND_MEAN#include<Windows.h>
#include<WinSock2.h>#pragma comment(lib, "ws2_32.lib")//加入静态链接库int main()
{WORD ver = MAKEWORD(2, 2);//WORD版本号WSADATA dat;//一种数据结构//启动windows socket 2.x环境WSAStartup(ver, &dat);//-------------------//--建立简易TCP客户端// 1 建立socket// 2 连接服务器 connect// 3 接收服务器信息 recv// 4 关闭socket closesocket//--建立简易TCP服务端// 1 建立socket// 2 绑定端口 bind// 3 监听端口 listen// 4 等待客户端连接 accept// 5 向客户端发送消息 send// 6 关闭socket closesocket//-------------------//清除Windows socket环境WSACleanup();//关闭windows socket网络环境return 0;
}

4-服务端

首先开始在当前【解决方案】下,新建一个【项目】EasyTcpServer,别忘了修改【输出目录】和【中间目录】,忘了可以看这里【VS2019新建项目、解决方案、多项目生成、防止文件污染】

重新生成新项目时,别忘了右键设为启动项目,如果报出下面的错误,就是#pragma那句忘了解注释,一定要解开如下:

4-1 建立socket

// 1 建立socket
SOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);

socket()函数用于根据指定的地址族、数据类型和协议来分配一个套接口的描述字及其所用的资源。

函数声明:int socket( int af, int type, int protocol);

af:一个地址描述。仅支持AF_INET格式,也就是说ARPA Internet地址格式。AF_INET代表IPv4格式的网络地址。

type:指定socket类型。新套接口的类型描述类型,如TCP(SOCK_STREAM)和UDP(SOCK_DGRAM)。常用的socket类型有,SOCK_STREAM(基于流)、SOCK_DGRAM、SOCK_RAW、SOCK_PACKET、SOCK_SEQPACKET等等。

protocol:顾名思义,就是指定协议。套接口所用的协议。如调用者不想指定,可用0。常用的协议有,IPPROTO_TCP、IPPROTO_UDP、IPPROTO_STCP、IPPROTO_TIPC等,它们分别对应TCP传输协议、UDP传输协议、STCP传输协议、TIPC传输协议。

参考:百度百科

若无错误发生,socket()返回引用新套接口的描述字。下图中能够看到,SOCKET是一个uint类型的指针

4-2 绑定端口

// 2 绑定端口 bind
sockaddr_in _sin = {};
_sin.sin_family = AF_INET;//必须与建立socket的af保持一致,表示地址类型
_sin.sin_port = htons(4567);//host to net unsigned short,将主机端口转换为网络端口
_sin.sin_addr.S_un.S_addr = INADDR_ANY;//随意ip地址//inet_addr("127.0.0.1");//本机地址,防止外网访问
if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin)))
{cout << "Error:绑定用于接收客户端连接的网络端口失败" << endl;
}
else
{cout << "Success:绑定网络端口成功..." << endl;
}

bind()函数把一个地址族中的特定地址赋给socket。例如对应AF_INET、AF_INET6就是把一个ipv4或ipv6地址和端口号组合赋给socket。

函数声明:

bind(SOCKET s, const socketaddr *name, int namelen)

参数解释:

SOCKET s:即socket描述字,它是通过socket()函数创建了,唯一标识一个socket。bind()函数就是将给这个描述字绑定一个名字。

const socketaddr *name:一个const struct sockaddr *指针,指向要绑定给SOCKET 的协议地址。

int namelen:对应的是地址的长度。

参考:socket–socket()、bind()、listen()、connect()、accept()、recv()、send()、select()、close()、shutdown()

4-2-1 sockaddr

那么为什么bind()的第二个参数不直接使用sockaddr类型而要使用一个sockaddr_in类型再强制转换呢,这是因为sockaddr_in类型中的变量类型都是常用的变量,方便赋值。

sockaddr在头文件#include <sys/socket.h>中定义,sockaddr的缺陷是:sa_data把目标地址和端口信息混在一起了

struct sockaddr {  sa_family_t sin_family;//地址族char sa_data[14]; //14字节,包含套接字中的目标地址和端口信息               };

4-2-2 sockaddr_in

sockaddr_in在头文件#include<netinet/in.h>或#include <arpa/inet.h>中定义,该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下:

参考:sockaddr和sockaddr_in详解

4-3 监听端口

// 3 监听端口 listen
if (SOCKET_ERROR == listen(_sock, 5))
{cout << "Error:监听网络端口失败" << endl;
}
else
{cout << "Success:监听网络端口成功..." << endl;
}

SOCKET_ERROR : 如调用bind()、listen()、connect()、send()、setsockopt()、fcntl()等函数时出错则会返回该宏:

参考:关于socket的各种错误码

函数声明:int listen (int sockfd, int backlog);

该函数在bind()之后accept()调用之前调用。第一个参数为已经创建的监听socket, 第二个参数是socket 监听队列最大监听连接数。

参考:Linux socket 编程API listen(SOCKET s, int backlog)

4-4 等待客户端连接

// 4 等待客户端连接 accept
sockaddr_in clientAddr = {};//远程客户端地址
int nAddrLen = sizeof(clientAddr);//结构长度
SOCKET _csock = INVALID_SOCKET;//无效的socket地址
_csock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);//核心
if (INVALID_SOCKET == _csock)
{cout << "Error:接收到无效客户端socket..." << endl;
}

使用accept()来接收客户端连接请求

函数声明:SOCKET accept(SOCKET s, struct sockaddr *addr, int *addrlen);

addr用于存放客户端的地址,addrlen在调用函数时被设置为addr指向区域的长度

参考:socket中accept()函数的理解

对于接收到的客户端socket,需要判断是否有效

4-5 发送数据

// 5 向客户端发送消息 send
char msgBuf[] = "Hello, I'm Server.";
send(_csock, msgBuf, strlen(msgBuf)+1, 0);

函数声明:int send(SOCKET s, const char *buf, int len, int flags);

SOCKET s:是本机要发送给谁的socket,本机是服务端,因此s就是要接收数据的客户端socket

const char *buf :应用程序要发送的数据的缓冲区(想要发送的数据)

int len:实际发送的字节数

int flags:一般置0

参考:Socket中send函数的理解

那么为什么int len的位置是strlen(msgBuf)+1呢,因为想把字符数组最后一位结束符也发过去

那么现在经过【4-4】与【4-5】已经能够实现单个客户端的接入,那么怎么实现不断接入客户端呢:需要加入一个循环,来不断接受来自客户端的连接请求。整理【4-4】与【4-5】代码如下,实现不断接入客户端,并为接入的客户端发送一条消息的功能。

// 4 等待客户端连接 accept
sockaddr_in clientAddr = {};//远程客户端地址
int nAddrLen = sizeof(clientAddr);//结构长度
SOCKET _csock = INVALID_SOCKET;//无效的socket地址
char msgBuf[] = "Hello, I'm Server.";
while (true)
{_csock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);if (INVALID_SOCKET == _csock){cout << "Error:接收到无效客户端socket..." << endl;}cout << "新客户端加入:IP = " << inet_ntoa(clientAddr.sin_addr) << endl;// 5 向客户端发送消息 sendsend(_csock, msgBuf, strlen(msgBuf) + 1, 0);
}

这里面,使用了一个比较老的函数inet_ntoa(),需要在程序开头定义一个宏

#define _WINSOCK_DEPRECATED_NO_WARNINGS

4-6 关闭socket

// 6 关闭socket closesocket
closesocket(_sock);

本函数关闭一个套接口。更确切地说,它释放套接口描述字s,以后对s的访问均以WSAENOTSOCK错误返回。若本次为对套接口的最后一次访问,则相应的名字信息及数据队列都将被释放。

参考:百度百科


至此,完成TCP服务器的简易模型,生成项目成功

4-7 服务端全部程序

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include<Windows.h>
#include<WinSock2.h>
#include<iostream>using namespace std;#pragma comment(lib, "ws2_32.lib")//加入静态链接库int main()
{WORD ver = MAKEWORD(2, 2);//WORD版本号WSADATA dat;//一种数据结构//启动windows socket 2.x环境WSAStartup(ver, &dat);//-------------------//--建立简易TCP服务端// 1 建立socketSOCKET _sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);// 2 绑定端口 bindsockaddr_in _sin = {};//网络端口地址_sin.sin_family = AF_INET;_sin.sin_port = htons(4567);//host to net unsigned short_sin.sin_addr.S_un.S_addr = INADDR_ANY;//随意ip地址//inet_addr("127.0.0.1");//本机地址,防止外网访问if (SOCKET_ERROR == bind(_sock, (sockaddr*)&_sin, sizeof(_sin))){cout << "Error:绑定用于接收客户端连接的网络端口失败" << endl;}else{cout << "Success:绑定网络端口成功..." << endl;}// 3 监听端口 listenif (SOCKET_ERROR == listen(_sock, 5)){cout << "Error:监听网络端口失败" << endl;}else{cout << "Success:监听网络端口成功..." << endl;}// 4 等待客户端连接 acceptsockaddr_in clientAddr = {};//远程客户端地址int nAddrLen = sizeof(clientAddr);//结构长度SOCKET _csock = INVALID_SOCKET;//无效的socket地址char msgBuf[] = "Hello, I'm Server.";while (true){_csock = accept(_sock, (sockaddr*)&clientAddr, &nAddrLen);if (INVALID_SOCKET == _csock){cout << "Error:接收到无效客户端socket..." << endl;}cout << "新客户端加入:IP = " << inet_ntoa(clientAddr.sin_addr) << endl;// 5 向客户端发送消息 sendsend(_csock, msgBuf, strlen(msgBuf) + 1, 0);}// 6 关闭socket closesocketclosesocket(_sock);//-------------------//清除Windows socket环境WSACleanup();//关闭windows socket网络环境return 0;
}

5-客户端

首先开始在当前【解决方案】下,新建一个【项目】EasyTcpClient,别忘了修改【输出目录】和【中间目录】,忘了可以看这里【VS2019新建项目、解决方案、多项目生成、防止文件污染】

5-1 建立socket

具体步骤已经在【4-1】中描述

 // 1 建立socketSOCKET _sock = socket(AF_INET, SOCK_STREAM, 0);//0:不规定协议类型if (INVALID_SOCKET == _sock){cout << "Error:建立Socket失败!" << endl;}else{cout << "建立Socket成功..." << endl;}

5-2 连接服务器

这一小节与【4-2】特别像,都要传入一个socketaddr的结构体,不同的是,服务端的bind()所需要的各个参数都是服务器自身的,而客户端的有些参数是来自服务器的

 // 2 连接服务器 connectsockaddr_in _sin = {}; //能够将结构体快速初始化_sin.sin_family = AF_INET;_sin.sin_port = htons(4567);//客户端想要连接服务器的哪个端口_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//连接服务器的ip地址,127.0.0.1是本机地址int res = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));if (SOCKET_ERROR == res){cout << "Error:连接失败!" << endl;}else{cout << "连接成功..." << endl;}

函数原型: int connect(SOCKET s, const struct sockaddr * name, int namelen);

s:标识一个未连接socket

name:指向要连接套接字的sockaddr结构体的指针

namelen:sockaddr结构体的字节长度

https://baike.baidu.com/item/connect%28%29/10081861?fr=aladdin

5-3 接收服务器信息

这一小节使用到数据缓冲,与【4-5】类似

 // 3 接收服务器信息 recvchar recvBuf[256] = {};//接收数据缓冲区int nlen = recv(_sock, recvBuf, 256, 0);//recv()返回接收数据的长度if (nlen > 0){cout << "接收到数据:" << recvBuf << endl;}

函数原型:int recv(SOCKET s, char *buf, int len, int flags);

不论是客户还是服务器应用程序都用recv函数从TCP连接的另一端接收数据。该函数的第一个参数指定接收端套接字描述符;

第二个参数指明一个缓冲区,该缓冲区用来存放recv函数接收到的数据;

第三个参数指明buf的长度;

第四个参数一般置0。

https://baike.baidu.com/item/recv%28%29

5-4 关闭socket

 // 4 关闭socket closesocketclosesocket(_sock);

5-5 客户端全部程序

#define WIN32_LEAN_AND_MEAN
#define _WINSOCK_DEPRECATED_NO_WARNINGS#include<Windows.h>
#include<WinSock2.h>
#include<iostream>using namespace std;
#pragma comment(lib, "ws2_32.lib")//加入静态链接库int main()
{WORD ver = MAKEWORD(2, 2);//WORD版本号WSADATA dat;//一种数据结构//启动windows socket 2.x环境WSAStartup(ver, &dat);//-------------------//--建立简易TCP客户端// 1 建立socketSOCKET _sock = socket(AF_INET, SOCK_STREAM, 0);//0:不规定协议类型if (INVALID_SOCKET == _sock){cout << "Error:建立Socket失败!" << endl;}else{cout << "建立Socket成功..." << endl;}// 2 连接服务器 connectsockaddr_in _sin = {}; //能够将结构体快速初始化_sin.sin_family = AF_INET;_sin.sin_port = htons(4567);//客户端想要连接服务器的哪个端口_sin.sin_addr.S_un.S_addr = inet_addr("127.0.0.1");//连接服务器的ip地址,127.0.0.1是本机地址int res = connect(_sock, (sockaddr*)&_sin, sizeof(sockaddr_in));if (SOCKET_ERROR == res){cout << "Error:连接失败!" << endl;}else{cout << "连接成功..." << endl;}// 3 接收服务器信息 recvchar recvBuf[256] = {};//接收数据缓冲区int nlen = recv(_sock, recvBuf, 256, 0);//recv()返回接收数据的长度if (nlen > 0){cout << "接收到数据:" << recvBuf << endl;}// 4 关闭socket closesocketclosesocket(_sock);//-------------------//清除Windows socket环境WSACleanup();//关闭windows socket网络环境system("pause");return 0;
}

6-测试最终程序

客户端与服务端全部生成之后,在我们设置的【输出目录】中找到对应的exe文件,如下图

注意:先打开【服务端】,再打开【客户端】

【服务端】开启后,自动进入监听模式,监听是否有【客户端】要加入,如下图

然后开启【客户端】,如下图

此时收到【服务端】发来的一条数据,再看【服务端】界面

至此,简易的TCP客户端与服务端搭建成功!

2-3 建立简易TCP服务端、客户端【socket server/client】【socket、bind、listen、accept、send、closesocket】【conect、recv】相关推荐

  1. java mina tcp_Mina TCP服务端客户端 示例

    服务端代码:package com.xd.nms.example; import java.io.IOException; import java.net.InetSocketAddress; imp ...

  2. Go实现简单的TCP服务端客户端通信(有黏包)

    目录 客户端代码 服务端代码 封包协议 客户端代码 // socket_stick/client2/main.gofunc main() {conn, err := net.Dial("tc ...

  3. Qt:Qt实现Winsock网络编程—TCP服务端和客户端通信(多线程)

    Qt实现Winsock网络编程-TCP服务端和客户端通信(多线程) 前言 感觉Winsock网络编程的api其实和Linux下网络编程的api非常像,其实和其他编程语言的网络编程都差不太多.博主用Qt ...

  4. 为什么TCP服务端需要调用bind函数而客户端通常不需要呢

    分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 那一年, ...

  5. TCP服务端程序开发

    TCP服务端程序开发 1. 开发 TCP 服务端程序开发步骤回顾 创建服务端端套接字对象 绑定端口号 设置监听 等待接受客户端的连接请求 接收数据 发送数据 关闭套接字 2. socket 类的介绍 ...

  6. 网络编程之TCP服务端程序开发

    TCP服务端程序开发 学习目标 能够写出TCP服务端应用程序接收和发送消息 1. 开发 TCP 服务端程序开发步骤回顾 创建服务端端套接字对象 绑定端口号 设置监听 等待接受客户端的连接请求 接收数据 ...

  7. Python基于socket实现的多任务版TCP服务端

    ''' 基于socket实现的多任务版TCP服务端 ''' import socket import threadingdef client_task(client_socket,ip_port):p ...

  8. 易语言tcp多线程服务端客户端_从TCP协议到TCP通信的各种异常现象和分析

    很多人总觉得学习TCP/IP协议没什么用,觉得日常编程开发只需要知道socket接口怎么用就可以了.如果大家定位过线上问题就会知道,实际上并非如此.如果应用在局域网内,且设备一切正常的情况下可能确实如 ...

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

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

最新文章

  1. 自动编码器的评级预测
  2. ios 3DTouch初识
  3. 「AI初识境」深度学习模型中的Normalization,你懂了多少?
  4. 渐进式遗传组卷算法(大规模题库,实际可用的算法) 智能组卷系统
  5. 吴恩达机器学习(第六章)——正则化
  6. 在程序里面(服务器端)调用Winrar压缩文件的方法?另寻求一条语句转换的方法。vb.net到C#。...
  7. iPhone 的倒计时竟然会显示假时间?
  8. 软件测试的出路到底在哪?
  9. FRR BGP协议分析 1 --- BGP 初始化
  10. 电子信息工程求职目标_广东海洋大学电子与信息工程学院电子信息工程专业欢迎你...
  11. SQLServer 2012下载及安装教程
  12. c语言生成正弦波,方波等mif
  13. 关于zabbix中vm.memery.size监控项后的参数
  14. .NET pfx文件解析私钥和公钥
  15. 微信开挂怎么防止封号_再也不怕被封号!微信养号秘笈教你防封号防降权
  16. 硅谷初创企业裁员潮已经开始!这些领域正遭受重创
  17. 2念整数(5分) 题目内容: 你的程序要读入一个整数,范围是[-100000,100000]。然后,用汉语拼音将这个整数的每一位输出出来。 如输入1234,则输出: yi er san si
  18. dxwebsetup 解决一些dll缺少的问题
  19. 杂谈——什么是Google Fuchsia ?
  20. PyCharm使用pip安装第三方库

热门文章

  1. 域名该怎么玩?域名玩法介绍
  2. vue报错 Avoided redundant navigation to current location: “/search“
  3. Java里的date类型 加上秒单位后的时间
  4. 数据库与RDBMS的关系
  5. 电脑录音机软件/萧米高音质录音机v8.0_绿色免安装
  6. 为我的女儿小雪写的小学二年级数学练习程序
  7. 实验五:系统检测维护工具Wsycheck使用
  8. 六轴机器人matlab写运动学正解函数(改进DH模型)
  9. 华为ACL原理及配置
  10. Linux Android生成和应用Patch文件