前言
一、网络编程三要素
1.IP地址
2.通信协议
3.端口号
二、SOCKET套接字
SOCKET概述
SOCKET分类
三、代码实现
1.编程思路
2.建立服务器
服务器完整代码
3.建立客户端
客户端完整代码
4.代码测试
本文主要学习Linux内核编程,结合Visual Studio 2019进行跨平台编程,内容包括网络编程基础知识,以及服务器和客户端的案例编程及测试
- IP地址:主机(电脑)的标识,类似家庭住址
- 通信协议:双方实体完成通信或服务所必须遵循的规则和约定
- 端口号:每个应用程序都对应一个端口号,类似于家门口的门牌
举个例子
- 首先,客户在某购物平台下单,商家需要发货给你,对不对?在此过程涉及两个重要的部分
- 其一,需要知道客户的地址,不然没办法送货到客户手上
- 其二,需要通过快递公司来进行运输送货派件(中间人的角色
如下图所示:
同理可得,网络编程也是需要优先知道IP地址
客户端首先需要知道服务器的IP地址,然后与其进行连接,之后再与其他的客户端进行通信
如下图所示:
在我们日常生活中,常用到域名搜索,其实也可以利用IP地址进行搜索,具体怎么操作呢?
那就试试看吧,在网页网址输入框中输入IP号试试看是否能够到达我们的目标页面
首先,我们打开电脑的终端(快捷命令cmd)
输入命令如下:
ping www.baidu.com
打开我们的网址,试试看通过IP进行搜索,同样也能打开我们的百度界面,如下图所示:
因此,我们可以发现 域名 = IP地址 (www.baidu.com = 14.215.177.39)
思考一下,为么会这样呢?
其实,域名和IP地址是一个键值对,是唯一确定的
为什么在我们在生活中,一般不用IP地址进行搜索呢?很简单,需要记住这么多没规则的数字是不是比较难?所以,我们用域名进行搜索会更加的方便!
使用域名的好处:
- 方便用户记忆
- 保护IP地址,防止暴露,遭受攻击(平常隐藏起来)
举个例子
- 人与人之间沟通,需要使用同一种语言进行对话,不然就会听不懂
- 两个人互相打招呼,一个人用日语,一个人用英语,使用的是不同的语言
- 在他们只会本国语言的前提下,是听不懂对方在说什么的
- 所以需要统一为同种语言,英语或者日语,才能方便交便
如下图所示:
这边着重展开介绍下,以下两种协议:
TCP/IP
在TCP/IP网络体系结构中,TCP、UDP 是传输层最重要的两种协议,为上层用户提供级别的通信可靠性
- TCP(传输控制协议):是为了在不可靠的互联网络上提供可靠的端到端字节流而专门设计的一个传输协议
- UDP(用户数据报协议) :是在OSI(开放式系统互联) 参考模型中一种无连接的传输层协议,提供面向事务的简单不可靠信息传送服务
- 应用层 : HTTP 、FTP、SNMP等
- 表示层 : ASCII、PICT、TIFF、JPEG、MIDI、MPEG
- 会话层 : RPC、SQL、NFS.X WINDOWS、ASP
- 传输层 : TCP、UDP
- 网络层 :IP、IPX、APPLETALK、ICMP
- 数据链路层 : 802.2、802.3ATM
- 物理层 :略
TCP和UDP的区别
TCP |
UDP | |
---|---|---|
传输介质 | 流式IO | 数据报文包 |
传输限制 | 理论上无限制 | 每次64KB |
速度 | 慢 | 快 |
传输可靠性 | 可靠 | 不可靠 |
是否连接 | 验证通信双方是否连接 | 不需要验证是否连接 |
应用场景 | 传输少量数据 | 传输大量数据 |
特点总结 | 牺牲效率,提高数据安全性 | 牺牲部分数据安全性,提高效率 |
简而言之,我们得到对方机器的IP地址后需要与对方机器的具体哪一个程序进行通信
是需要一个具体的标识的
举个例子
类似虽然知道你的家庭地址,也要找到你家的门牌子才能确定
" 您的家庭住址这边,门牌是写了一个,保安室嘛?"
" 啊对对对,就是这!"
" 您的快递帮您放这边了!"
如下图所示:
- 所谓套接字(Socket),是对网络中不同主机上的应用进程之间进行双向通信的端点的抽象
- 一个套接字就是网络上进程通信的一端,提供了应用层进程利用网络协议交换数据的机制
- 从所处的地位来讲,套接字上联应用进程,下联网络协议栈,是应用程序通过网络协议进行通信的接口,是应用程序与网络协议栈进行交互的接口
- 流式套接字(SOCK_STREAM)
流式的套接字可以提供可靠的、面向连接的通讯流。它使用了TCP协议
TCP 保证了数据传输的正确性和顺序性
- 数据报套接字(SOCK_DGRAM)
数据报套接字定义了一种无连接的服务,数据通过相互独立的报文进行传输是无序的
并且不保证可靠,无差错。使用数据报协议UDP协议
- 原始套接字
原始套接字允许对低层协议如IP或ICMP直接访问,主要用于新网络协议实现的测试等
- 以上便是网络编程三要素和SOCKET套接字的相关知识的介绍
- 接下来将会利用TCP协议来建立服务器和客户端之间的关系
- 通过实际的案例来加深上述知识点
- 以及能够对其实际使用场景有一个清楚直观的认识
整体编程思路,如下流程图所示:
基于流式套接字的编程流程
相关函数
socket()函数
函数原型:
int socket(int domain, int type, int protocol);
返回值:
成功:返回文件描述符
失败:返回 -1
bind()函数
函数原型:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
返回值:
成功:返回 0
失败:返回 -1
listen()函数
函数原型:
int listen(int sockfd, int backlog);
返回值:
成功:返回 0
失败:返回 -1
accept()函数
函数原型:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
返回值:
成功:返回客户端文件描述符
失败:返回 -1
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器
//服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器
//服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器
//服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器
//服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器-服务器
int main()
{
int socketfd = 0;
int acceptfd = 0;
int len = 0;
int pid = 0;
char buf[255] = { 0 };//存放客户端发过来的信息
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd == -1)
{
perror("socket error");//socket 报错
}
else
{
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;
//默认提供IP地址(服务器自己)因为服务器是被动,需要客户端来连接所以网路通道打开是服务器自己
s_addr.sin_addr.s_addr = inet_addr("192.168.48.128");//linux ubantu iP地址
//端口号(服务器自己)大小端模式 内存存储字节转换
s_addr.sin_port = htons(10086); //端口号为10086
len = sizeof(s_addr);
//绑定IP地址和端口号
int res = bind(socketfd, (struct sockaddr*)&s_addr, len);
if (res == -1)
{
perror("bind error");//bind 报错
}
else
{
//监听:地址和端口有没有客户端连接上
if (listen(socketfd, 2) == -1)
{
//listen 监听失败
perror("listen error");
}
cout << "socket 网络搭建成功" << endl;
//设置死循环:保证服务器一直在线
while (1)
{
cout << "稍等片刻-等待连接-客户端" << endl;
//阻塞式函数 acceptfd
acceptfd = accept(socketfd, NULL, NULL);
cout << "恭喜恭喜-连接成功-客户端 socketfd = " << acceptfd << endl;
pid = fork();//创建子进程 子进程:保持运行提供服务到结束
if (pid == 0)
{
while (1)
{
int res = read(acceptfd, buf, sizeof(buf));
cout << "pid = " << getpid() << "res = "<< res << endl;
cout << " 输入内容: " << buf << endl;
bzero(buf, sizeof(buf));
}
}
}
}
}
}
#include
#include
#include
#include
#include
#include
#include
#include
using namespace std;
//客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端
//客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端
//客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端
//客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端
//客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端-客户端
int main()
{
int socketfd = 0;
int acceptfd = 0;
int len = 0;
char buf[255] = { 0 };//初始化
//网络-初始化
socketfd = socket(AF_INET, SOCK_STREAM, 0);
if (socketfd == -1)
{
perror("socket error");
}
else
{
struct sockaddr_in s_addr;
s_addr.sin_family = AF_INET;
s_addr.sin_addr.s_addr = inet_addr("192.168.48.128");//Linux ubantu ip地址
s_addr.sin_port = htons(10086); //端口号为10086
len = sizeof(s_addr);
//绑定IP地址和端口号
int res = connect(socketfd, (struct sockaddr*)&s_addr, len);
if (res == -1)
{
perror("connect error");//连接失败
}
else
{
cout << "connect success" << endl;//连接成功
while (1)
{
fgets(buf, sizeof(buf), stdin);//控制台输入
write(socketfd, buf, sizeof(buf));
bzero(buf, sizeof(buf));
}
}
}
return 0;
}
博主是利用Linux和VS的跨平台开发,先在VS上重新生成解决方案
然后在Linux上打开对应工程,提供两种方法,如下:
- 通过debug文件下.out文件直接执行
- 或者g++ main.cpp -o xxx 生成可执行文件
方法一:执行.out文件
方法二:g++ main.cpp -o f ( f 这边自定义)
PS:博主这边main函数打错了,按照上述格式来即可
如下图所示:
先打开服务器,再打开客户端
打开服务器成功!如下图所示:
连接上客户端1成功! 如下图所示:
连接上客户端2成功! (重复进行打开客户端1操作,打开客户端2),如下图所示:
开始输入测试,可以看到我们在客户端1和客户端2输入,服务器都会有显示,如下图所示:
参考:linux远程开发——网络通信(客户端与服务器建立连接)_似末的博客-CSDN博客_在linux下如何建立客户端与服务器