草庐IT

linux远程开发——网络通信(客户端与服务器建立连接)

似末 2025-05-20 原文

目录

一、前言

二、网络编程三要素

1、IP地址

1)IP地址概念

2)通过IP地址访问CSDN官网

3)本地回环IP地址 127.0.0.1

2、端口号

3、通信协议

1)通信协议概念

2)TCP和UDP

三、网络通信基础编程

1、编程流程

2、建立本地服务器

1)socket()初始化网络

2)bind()函数

3)listen()监听函数

4)accept()函数

5)服务器全部代码

3、建立客户端

4、客户端连接服务器测试


一、前言

        本文介绍网络编程的基础知识,使用 Visual Studio 2019linux 本地搭建一个服务器,将客户端与本地服务器连接起来,通过客户端向服务器发送信息,测试服务端能否收到信息。

  • 在编程之前我们先来了解一些基础知识

二、网络编程三要素

网络编程三要素由下面3点组成:

1、IP地址:确定网络中某台计算机的位置

2、端口号:确定是哪个具体的应用程序

3、通信协议:通信双方共同约定和遵循的协议

1、IP地址

1)IP地址概念

  • IP地址是用来区分是哪一台电脑设备,只有知道对方设备的IP地址,才能找到对方的电脑。    

       IP地址就像是我们的家庭住址一样,如果你要写信给一个人,你就要知道他(她)的地址,这样邮递员才能把信送到。计算机发送信息就好比是 “邮递员”,它必须知道唯一的 “家庭地址”才能不至于把信送错人家。

        同样的网络编程也需要用到IP地址,客户端需要知道服务器的IP地址,才能找到需要建立连接的服务器。其实网页也有它对应的IP地址,我们平常搜索东西都是输入域名来访问,也可以输入IP地址进行访问。

2)通过IP地址访问CSDN官网

比如:csdn的官网为 www.csdn.net

  • window 下通过按键 win+r 打开运行框输入 cmd 然后回车
  • 控制台输入 ping  www.csdn.net

  • 浏览器输入 39.106.226.142 也可以访问到CSDN官网

        可以发现域名后面跟着一个IP地址,浏览器输入这个IP地址也可以访问到CSDN官网。实际上域名和IP地址是一个键值对,是唯一的。如果为了访问CSDN官网要输入那么长的IP地址是不是不方便,所以这时候域名就出现了,用域名来访问是不是更加方便。

3)本地回环IP地址 127.0.0.1

  • 本地服务器的IP地址就可以设置成127.0.0.1

        127.0.0.1,通常被称为本地回环地址 (Loopback Address),不属于任何一个有类别地址类。 它代表设备的本地虚拟接口,所以默认被看作是永远不会宕掉的接口。 在Windows操作系统中也有相似的定义,所以通常在安装网卡前就可以ping通这个本地回环地址。

2、端口号

  • 如果仅仅知道IP地址只能找到具体的某一台计算机,要找到某个应用程序需要知道端口号

        简单来说端口号就是正在运行的程序的标识,用来区分具体是那个应用程序。

3、通信协议

1)通信协议概念

  • 建立通信还需要通信协议,就比如中国统一的语言汉语,这样沟通起来就轻而易举了

        通信协议是指双方实体完成通信或服务所必须遵循的规则和约定。通过通信信道和设备互连起来的多个不同地理位置的数据通信系统,要使其能协同工作实现信息交换和资源共享,它们之间必须具有共同的语言。交流什么、怎样交流及何时交流,都必须遵循某种互相都能接受的规则。这个规则就是通信协议。

2)TCP和UDP

  • 目前成熟的通信协议是TCP和UDP,其他协议的前身都是TCP或者是UDP

  • 来看一下两个协议的区别
TCPUDP
是否需要建立连接需要建立连接,才能发送信息不需要建立连接
传输介质流式IO(二进制数据)数据封装成报文包
传输限制可以进行大数据传输每次只能传输64KB
总结牺牲效率,提高传输安全性,可靠协议牺牲部分安全性,提高传输效率,不可靠协议
  • OSI参考模型与TCP/IP参考模型如下图所示 

接下来我们通过代码使用 TCP 协议来建立服务器与客户端之间的联系。

话不多说

 

三、网络通信基础编程

1、编程流程

  • 基于流套接字的编程流程如下图所示

        本文介绍客户端与服务器建立联系之后,客户端write (),服务器read (),先做简单的连接测试,后期再进行客户端与服务器进行互相通信。接下来的编程按照上面的流程进行。

2、建立本地服务器

1)socket()初始化网络

int socketfd = 0;
//初始化网络  参数一:使用ipv4  参数二:流式传输
socketfd = socket(AF_INET, SOCK_STREAM, 0);
  • socket()网络初始化 ,返回文件描述符

函数原型:

int socket(int domain, int type, int protocol);

返回值:

成功:返回文件描述符

失败:返回 -1

  • 第一个参数用AF_INET ,即 ipv4

  • 第二个参数用SOCK_STREAM ,即流式的套接字

  • 第三个参数为0即可

2)bind()函数

  • 绑定ip地址和端口号,以及确定通信协议为ipv4
//原本使用struct sockaddr,通常使用sockaddr_in更为方便,两个数据类型是等效的,可以相互转换
struct sockaddr_in s_addr;
//确定使用哪个协议族  ipv4
s_addr.sin_family = AF_INET;

//系统自动获取本机ip地址 也可以是本地回环地址:127.0.0.1
s_addr.sin_addr.s_addr = INADDR_ANY;

//端口一个计算机有65535个  10000以下是操作系统自己使用的,  自己定义的端口号为10000以后
s_addr.sin_port = htons(12345);  //自定义端口号为12345

len = sizeof(s_addr);

//绑定ip地址和端口号
int res = bind(socketfd, (struct sockaddr *)&s_addr, len);
if (res == -1)
{
	perror("bind error");
}

函数原型:

int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

返回值:

成功:返回 0

失败:返回 -1

  • 第一个参数用上一步返回的文件描述符 
  • 第二个参数改用 struct sockaddr_in 结构体,更方便
  • 第三个参数为 struct sockaddr_in 结构体的大小

3)listen()监听函数

  • 监听有没有客户端来连接
//监听这个地址和端口有没有客户端来连接  第二个参数现在没有用  只要大于0就行
if (listen(socketfd, 2) == -1)
{
	perror("listen error");
}

函数原型:

int listen(int sockfd, int backlog);

返回值:

成功:返回 0

失败:返回 -1

  • 第一个参数为之前获取的服务器文件描述符
  • 第二个参数只要 >0 即可 

4)accept()函数

  • 等待客户端上线,阻塞式函数 ,只有客户端上线才会继续执行

        通过一个while(1)死循环,让服务器一直在线等待客户端上线,一旦某个客户端上线,则开一个子进程,在子进程中循环读取客户端发过来的消息。父进程则继续等待下一个客户端上线。

注:

read()函数也是一个阻塞函数

//死循环保证服务器一直在线
while (1)
{
	cout << "等待客户端上线" << endl;
	//等待客户端上线,阻塞式函数  acceptfd为连上来的客户端fd
	acceptfd = accept(socketfd, NULL, NULL);
	cout << "客户端上线 fd = " << acceptfd << endl;
	pid = fork();
	if (pid == 0)//子进程  持续读取客户端发来的信息
	{
		while (1)
		{
			read(acceptfd, buf, sizeof(buf));
			cout << "pid = " << getpid() << " 说: " << buf << endl;
			bzero(buf, sizeof(buf));
		}
	}
}

函数原型:

int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

返回值:

成功:返回客户端文件描述符

失败:返回 -1

  • 第一个参数为之前获取的服务器文件描述符
  • 第二个第三个参数可以指定为NULL 

5)服务器全部代码

#include <iostream>
#include <sys/types.h>         
#include <sys/socket.h>
#include <netinet/in.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
using namespace std;

int main()
{
	
	int socketfd = 0;
	int acceptfd = 0;
	int len = 0;
	int pid = 0;
	char buf[255] = { 0 };//存放客户端发过来的信息
	//初始化网络  参数一:使用ipv4  参数二:流式传输
	socketfd = socket(AF_INET, SOCK_STREAM, 0);
	if (socketfd == -1)
	{
		perror("socket error");
	}
	else
	{
		//原本使用struct sockaddr,通常使用sockaddr_in更为方便,两个数据类型是等效的,可以相互转换
		struct sockaddr_in s_addr;
		//确定使用哪个协议族  ipv4
		s_addr.sin_family = AF_INET;

		//系统自动获取本机ip地址 也可以是本地回环地址:127.0.0.1
		s_addr.sin_addr.s_addr = INADDR_ANY;

		//端口一个计算机有65535个  10000以下是操作系统自己使用的,  自己定义的端口号为10000以后
		s_addr.sin_port = htons(12345);  //自定义端口号为12345

		len = sizeof(s_addr);

		//绑定ip地址和端口号
		int res = bind(socketfd, (struct sockaddr *)&s_addr, len);
		if (res == -1)
		{
			perror("bind error");
		}
		else
		{
			//监听这个地址和端口有没有客户端来连接  第二个参数现在没有用  只要大于0就行
			if (listen(socketfd, 2) == -1)
			{
				perror("listen error");
			}
			//死循环保证服务器一直在线
			while (1)
			{
				cout << "等待客户端上线" << endl;
				//等待客户端上线,阻塞式函数  acceptfd为连上来的客户端fd
				acceptfd = accept(socketfd, NULL, NULL);
				cout << "客户端上线 fd = " << acceptfd << endl;
				pid = fork();
				if (pid == 0)//子进程  持续读取客户端发来的信息
				{
					while (1)
					{
						read(acceptfd, buf, sizeof(buf));
						cout << "pid = " << getpid() << " 说: " << buf << endl;
						bzero(buf, sizeof(buf));
					}
				}
			}
		}
	}
}

3、建立客户端

  • 客户端这边只要做socket()初始化网络,并绑定ip地址和端口号,确定通信协议ipv4
#include <iostream>
#include <sys/types.h>          
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
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;
		//确定使用哪个协议族  ipv4
		s_addr.sin_family = AF_INET;

		//填入服务器的ip地址  也可以是  127.0.0.1 (回环地址)
		s_addr.sin_addr.s_addr = inet_addr("127.0.0.1");

		//端口一个计算机有65535个  10000以下是操作系统自己使用的,  自己定义的端口号为10000以后
		s_addr.sin_port = htons(12345);  //自定义端口号为12345

		len = sizeof(s_addr);

		//绑定ip地址和端口号
		int res = connect(socketfd, (struct sockaddr*)&s_addr, len);
		if (res == -1)
		{
			perror("connect error");
		}
		else
		{
			while (1)
			{
				//控制台输入
				fgets(buf, sizeof(buf), stdin);
				write(socketfd, buf, sizeof(buf));
				bzero(buf, sizeof(buf));
			}
		}
	}
	return 0;
}

注:

1、因为是本地的客户端,且服务器也是本地的,所以绑定的ip地址为本地的回环地址即可

2、绑定的端口号和使用的通信协议必须和服务器一样

3、write()函数中的文件描述符为服务器的

4、客户端连接服务器测试

  • 在linux下找到对应的main.cpp ,通过g++ 的方式编译, ./ 运行

注:

1、代码有改动的话,需要重新生成解决方案,代码即可同步到linux下

2、先运行服务器,再运行客户端

测试结果

        可以发现每运行一个客户端,服务器都会收到提示,而且客户端发送的信息,服务器都可以收到,说明客户端和服务器连接成功!!

原创不易,转载请标明出处。

对您有帮助的话可以一键三连,会持续更新的(嘻嘻)。

有关linux远程开发——网络通信(客户端与服务器建立连接)的更多相关文章

  1. ruby - 具有身份验证的私有(private) Ruby Gem 服务器 - 2

    我想安装一个带有一些身份验证的私有(private)Rubygem服务器。我希望能够使用公共(public)Ubuntu服务器托管内部gem。我读到了http://docs.rubygems.org/read/chapter/18.但是那个没有身份验证-如我所见。然后我读到了https://github.com/cwninja/geminabox.但是当我使用基本身份验证(他们在他们的Wiki中有)时,它会提示从我的服务器获取源。所以。如何制作带有身份验证的私有(private)Rubygem服务器?这是不可能的吗?谢谢。编辑:Geminabox问题。我尝试“捆绑”以安装新的gem..

  2. ruby - 使用 C 扩展开发 ruby​​gem 时,如何使用 Rspec 在本地进行测试? - 2

    我正在编写一个包含C扩展的gem。通常当我写一个gem时,我会遵循TDD的过程,我会写一个失败的规范,然后处理代码直到它通过,等等......在“ext/mygem/mygem.c”中我的C扩展和在gemspec的“扩展”中配置的有效extconf.rb,如何运行我的规范并仍然加载我的C扩展?当我更改C代码时,我需要采取哪些步骤来重新编译代码?这可能是个愚蠢的问题,但是从我的gem的开发源代码树中输入“bundleinstall”不会构建任何native扩展。当我手动运行rubyext/mygem/extconf.rb时,我确实得到了一个Makefile(在整个项目的根目录中),然后当

  3. ruby-on-rails - 启动 Rails 服务器时 ImageMagick 的警告 - 2

    最近,当我启动我的Rails服务器时,我收到了一长串警告。虽然它不影响我的应用程序,但我想知道如何解决这些警告。我的估计是imagemagick以某种方式被调用了两次?当我在警告前后检查我的git日志时。我想知道如何解决这个问题。-bcrypt-ruby(3.1.2)-better_errors(1.0.1)+bcrypt(3.1.7)+bcrypt-ruby(3.1.5)-bcrypt(>=3.1.3)+better_errors(1.1.0)bcrypt和imagemagick有关系吗?/Users/rbchris/.rbenv/versions/2.0.0-p247/lib/ru

  4. ruby-on-rails - s3_direct_upload 在生产服务器中不工作 - 2

    在Rails4.0.2中,我使用s3_direct_upload和aws-sdkgems直接为s3存储桶上传文件。在开发环境中它工作正常,但在生产环境中它会抛出如下错误,ActionView::Template::Error(noimplicitconversionofnilintoString)在View中,create_cv_url,:id=>"s3_uploader",:key=>"cv_uploads/{unique_id}/${filename}",:key_starts_with=>"cv_uploads/",:callback_param=>"cv[direct_uplo

  5. Ruby Sinatra 配置用于生产和开发 - 2

    我已经在Sinatra上创建了应用程序,它代表了一个简单的API。我想在生产和开发上进行部署。我想在部署时选择,是开发还是生产,一些方法的逻辑应该改变,这取决于部署类型。是否有任何想法,如何完成以及解决此问题的一些示例。例子:我有代码get'/api/test'doreturn"Itisdev"end但是在部署到生产环境之后我想在运行/api/test之后看到ItisPROD如何实现? 最佳答案 根据SinatraDocumentation:EnvironmentscanbesetthroughtheRACK_ENVenvironm

  6. ruby - 是否可以覆盖 gemfile 进行本地开发? - 2

    我们的git存储库中目前有一个Gemfile。但是,有一个gem我只在我的环境中本地使用(我的团队不使用它)。为了使用它,我必须将它添加到我们的Gemfile中,但每次我checkout到我们的master/dev主分支时,由于与跟踪的gemfile冲突,我必须删除它。我想要的是类似Gemfile.local的东西,它将继承从Gemfile导入的gems,但也允许在那里导入新的gems以供使用只有我的机器。此文件将在.gitignore中被忽略。这可能吗? 最佳答案 设置BUNDLE_GEMFILE环境变量:BUNDLE_GEMFI

  7. ruby - 在 Windows 机器上使用 Ruby 进行开发是否会适得其反? - 2

    这似乎非常适得其反,因为太多的gem会在window上破裂。我一直在处理很多mysql和ruby​​-mysqlgem问题(gem本身发生段错误,一个名为UnixSocket的类显然在Windows机器上不能正常工作,等等)。我只是在浪费时间吗?我应该转向不同的脚本语言吗? 最佳答案 我在Windows上使用Ruby的经验很少,但是当我开始使用Ruby时,我是在Windows上,我的总体印象是它不是Windows原生系统。因此,在主要使用Windows多年之后,开始使用Ruby促使我切换回原来的系统Unix,这次是Linux。Rub

  8. ruby - 用 Ruby 编写一个简单的网络服务器 - 2

    我想在Ruby中创建一个用于开发目的的极其简单的Web服务器(不,不想使用现成的解决方案)。代码如下:#!/usr/bin/rubyrequire'socket'server=TCPServer.new('127.0.0.1',8080)whileconnection=server.acceptheaders=[]length=0whileline=connection.getsheaders想法是从命令行运行这个脚本,提供另一个脚本,它将在其标准输入上获取请求,并在其标准输出上返回完整的响应。到目前为止一切顺利,但事实证明这真的很脆弱,因为它在第二个请求上中断并出现错误:/usr/b

  9. ruby-on-rails - 在 Rails 开发环境中为 .ogv 文件设置 Mime 类型 - 2

    我正在玩HTML5视频并且在ERB中有以下片段:mp4视频从在我的开发环境中运行的服务器很好地流式传输到chrome。然而firefox显示带有海报图像的视频播放器,但带有一个大X。问题似乎是mongrel不确定ogv扩展的mime类型,并且只返回text/plain,如curl所示:$curl-Ihttp://0.0.0.0:3000/pr6.ogvHTTP/1.1200OKConnection:closeDate:Mon,19Apr201012:33:50GMTLast-Modified:Sun,18Apr201012:46:07GMTContent-Type:text/plain

  10. ruby-on-rails - 在 Rails 中调试生产服务器 - 2

    您如何在Rails中的实时服务器上进行有效调试,无论是在测试版/生产服务器上?我试过直接在服务器上修改文件,然后重启应用,但是修改好像没有生效,或者需要很长时间(缓存?)我也试过在本地做“脚本/服务器生产”,但是那很慢另一种选择是编码和部署,但效率很低。有人对他们如何有效地做到这一点有任何见解吗? 最佳答案 我会回答你的问题,即使我不同意这种热修补服务器代码的方式:)首先,你真的确定你已经重启了服务器吗?您可以通过跟踪日志文件来检查它。您更改的代码显示的View可能会被缓存。缓存页面位于tmp/cache文件夹下。您可以尝试手动删除

随机推荐