2-3 建立简易TCP服务端、客户端【socket server/client】【socket、bind、listen、accept、send、closesocket】【conect、recv】
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】相关推荐
- java mina tcp_Mina TCP服务端客户端 示例
服务端代码:package com.xd.nms.example; import java.io.IOException; import java.net.InetSocketAddress; imp ...
- Go实现简单的TCP服务端客户端通信(有黏包)
目录 客户端代码 服务端代码 封包协议 客户端代码 // socket_stick/client2/main.gofunc main() {conn, err := net.Dial("tc ...
- Qt:Qt实现Winsock网络编程—TCP服务端和客户端通信(多线程)
Qt实现Winsock网络编程-TCP服务端和客户端通信(多线程) 前言 感觉Winsock网络编程的api其实和Linux下网络编程的api非常像,其实和其他编程语言的网络编程都差不太多.博主用Qt ...
- 为什么TCP服务端需要调用bind函数而客户端通常不需要呢
分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow 也欢迎大家转载本篇文章.分享知识,造福人民,实现我们中华民族伟大复兴! 那一年, ...
- TCP服务端程序开发
TCP服务端程序开发 1. 开发 TCP 服务端程序开发步骤回顾 创建服务端端套接字对象 绑定端口号 设置监听 等待接受客户端的连接请求 接收数据 发送数据 关闭套接字 2. socket 类的介绍 ...
- 网络编程之TCP服务端程序开发
TCP服务端程序开发 学习目标 能够写出TCP服务端应用程序接收和发送消息 1. 开发 TCP 服务端程序开发步骤回顾 创建服务端端套接字对象 绑定端口号 设置监听 等待接受客户端的连接请求 接收数据 ...
- Python基于socket实现的多任务版TCP服务端
''' 基于socket实现的多任务版TCP服务端 ''' import socket import threadingdef client_task(client_socket,ip_port):p ...
- 易语言tcp多线程服务端客户端_从TCP协议到TCP通信的各种异常现象和分析
很多人总觉得学习TCP/IP协议没什么用,觉得日常编程开发只需要知道socket接口怎么用就可以了.如果大家定位过线上问题就会知道,实际上并非如此.如果应用在局域网内,且设备一切正常的情况下可能确实如 ...
- TCP/IP网络编程之基于TCP的服务端/客户端(二)
回声客户端问题 上一章TCP/IP网络编程之基于TCP的服务端/客户端(一)中,我们解释了回声客户端所存在的问题,那么单单是客户端的问题,服务端没有任何问题?是的,服务端没有问题,现在先让我们回顾下服 ...
最新文章
- 自动编码器的评级预测
- ios 3DTouch初识
- 「AI初识境」深度学习模型中的Normalization,你懂了多少?
- 渐进式遗传组卷算法(大规模题库,实际可用的算法) 智能组卷系统
- 吴恩达机器学习(第六章)——正则化
- 在程序里面(服务器端)调用Winrar压缩文件的方法?另寻求一条语句转换的方法。vb.net到C#。...
- iPhone 的倒计时竟然会显示假时间?
- 软件测试的出路到底在哪?
- FRR BGP协议分析 1 --- BGP 初始化
- 电子信息工程求职目标_广东海洋大学电子与信息工程学院电子信息工程专业欢迎你...
- SQLServer 2012下载及安装教程
- c语言生成正弦波,方波等mif
- 关于zabbix中vm.memery.size监控项后的参数
- .NET pfx文件解析私钥和公钥
- 微信开挂怎么防止封号_再也不怕被封号!微信养号秘笈教你防封号防降权
- 硅谷初创企业裁员潮已经开始!这些领域正遭受重创
- 2念整数(5分) 题目内容: 你的程序要读入一个整数,范围是[-100000,100000]。然后,用汉语拼音将这个整数的每一位输出出来。 如输入1234,则输出: yi er san si
- dxwebsetup 解决一些dll缺少的问题
- 杂谈——什么是Google Fuchsia ?
- PyCharm使用pip安装第三方库
热门文章
- 域名该怎么玩?域名玩法介绍
- vue报错 Avoided redundant navigation to current location: “/search“
- Java里的date类型 加上秒单位后的时间
- 数据库与RDBMS的关系
- 电脑录音机软件/萧米高音质录音机v8.0_绿色免安装
- 为我的女儿小雪写的小学二年级数学练习程序
- 实验五:系统检测维护工具Wsycheck使用
- 六轴机器人matlab写运动学正解函数(改进DH模型)
- 华为ACL原理及配置
- Linux Android生成和应用Patch文件