前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >Linux笔记(11)| 网络编程之自己动手写一个服务器和客户端

Linux笔记(11)| 网络编程之自己动手写一个服务器和客户端

作者头像
飞哥
发布2020-07-22 15:46:07
1.2K0
发布2020-07-22 15:46:07
举报

今天分享的是比较有意思的东西——在Linux下通过网络编程实现一个简单的服务器和客户端。通过这个服务器和客户端,用户可以相互收发消息,类似于QQ或者是微信这样的社交软件,当然,从功能上来说只是实现了简单的通信,肯定是不可能像QQ或者微信这样那么强大,但是,对于学习网络编程来说,能实现这样的功能已经足够了,毕竟不是真的要做一个社交软件。

先来说一下这个程序实现的功能,这里就直接把我写程序时的更新日志放在这里好了:

version0.0:

功能:

初步实现了多个客户端与服务器的连接,并且服务器与客户端之间可以正常通信。

存在问题:

只能客户端与服务器通信,客户端之间不能通信。

version1.0

功能:

在上一版本的基础上,实现了客户端之间的通信,比如两个客户端之间可以收发消息

存在问题:

这个版本虽然实现了客户端之间的通信,实际上是客户端发消息到服务器,服务器再转发到客户端,但是没有做到用户可以向指定的客户端发消息,也就是说在服务器里写死了消息转发给谁,用户没有决定权。如果要实现定向发送,需要给每个客户端配一个账号,用来唯一标识,在这个版本里暂时没有。

version 2.0

功能:

在上一版本的基础上,为每一个客户端添加了ID账号,这个ID账号是用户执行客户端程序的时候输入的,通过这个ID号,用户可以向指定的客户端发送消息,经测试,收发正常。

存在问题:

1、当用户执行客户端程序的时候,需要及时输入登录的ID账号,如果用户没有输入,服务器将会被阻塞,以至于不能处理其他客户端的事情。

2、不能防止两个客户端使用相同的账号登录

version3.1

功能:

在上一版本的基础上,解决了同一账号重复登录的问题。

存在问题:

1、暂时没有解决version2.0里提到的第一点问题

2、如果第二个客户端的登录账号和第一个客户端相同,那么会被退出程序。但是即使下次使用别的账号登录,也只能由客户端1向客户端2发消息,客户端1收不到客户端2的消息。除非一开始两个客户端没有使用相同账号登录。

这也是我第一次在写程序的时候顺便写了一下更新日志,这应该是一个好习惯,因为在写程序的时候往往是不可能一次性写的很完美,都是在反复的修改过程的当中去完善它,因此,有必要记录每一次更新的变化,在每次重大更新之前都把程序先复制一份,可以防止后面不小心把前面写好的程序改的不能用。

好了,言归正传,来具体说一下这个程序的实现思路,这个程序分为服务器和客户端两大块,来逐一介绍。

服务器端工作流程:

(1)调用 socket() 函数创建套接字 用 bind() 函数将创建的套接字与服务端IP地址绑定

(2)调用listen()函数监听socket()函数创建的套接字,等待客户端连接 当客户端请求到来之后

(3)调用 accept()函数接受连接请求,返回一个对应于此连接的新的套接字,做好通信准备

(4)调用 write()/read() 函数和 send()/recv()函数进行数据的读写,通过 accept() 返回的套接字和客户端进行通信关闭socket(close)

所以了解了工作流程之后,编程只要围绕这几个函数的用法展开就行了,实际上只要搞清楚这几个函数的输入参数和放回值,基本上就成功一半了。关于这几个函数的用法,如果要展开来说的话,那么需要非常大的篇幅才能讲清楚,因为每个函数的输入参数都是比较复杂,这些输入参数里面有的甚至结构体里嵌套结构体,要想搞清楚结构体里每个成员的作用不是一件那么容易的事情,这里直接推荐几篇优秀的博文:http://c.biancheng.net/view/2123.html,在这个博客里详细介绍了网络通信的各个函数的用法。所以我就不再赘述。

把上面几个函数搞清楚之后,服务器基本上就搭建起来了,只是还没有添加什么功能。那么服务器暂时先放在这里,接下来穿插讲一下客户端。

客户端工作流程就简单多了:

(1)调用 socket() 函数创建套接字

(2)调用connect()函数来连接服务器。

这样写完之后客户端实际上已经可以和服务器通信了,也就是我上面说到version0.0,那么接下来介绍如何过渡到version1.0.

在version0.0里面,我们的客户端可以连接服务器,也可以相互通信,但是如果你再打开一个客户端,会发现根本连不上去,这时,我们可以使用select函数实现多路复用,多路复用,顾名思义,就是说各做各的事,标准输入事件到来,有相关函数处理。服务器处理服务器的事件,客户端到来时有相关函数对其进行处理,通过select遍历各fd的读写情况,就不用担心阻塞了。

程序去select的时候,如果没有数据输入,程序会一直等待,直到有数据为止,也就是程序中无需循环和sleep。使用Select就可以完成非阻塞(所谓非阻塞方式non-block,就是进程或线程执行此函数时 不必非要等待事件的发生,一旦执行肯定返回,以返回值的不同来反映函数的执行情况,如果事件发生则与阻塞方式相同,若事件没有发生则返回一个代码来告知事件未发生,而进程或线程继续执行,所以效率较高)方式工作的程序,它能够监视我们需要监视的文件描述符的变化情况——读写或是异常。

关于select函数的用法同样很复杂,所以这里不展开来讲,推荐一篇博文:

https://blog.csdn.net/leo115/article/details/8097143

使用了select函数之后就实现了监听多个客户端,又不会被阻塞。当然,这里还有一个问题,就是客户端要能发能收,这两个该怎么协调?你不能直接使用while(1)里面写一个发送,一个接收,因为接收是一个阻塞的函数,如果收不到数据就一直阻塞在那里,那么发送就会被阻塞,想发送也就发送不了了。该如何解决这个问题呢?可以创建一个子进程,然后父进程和子进程一个用来发送,一个用来接收,这样因为发送和接收在两个不同的进程里面,不会相互干扰,这样就成功解决了这个问题。这样就实现了version1.0的功能。

但是这里还存在一个问题就是不能定向发送。正如日志里所说,要定向发送,需要为每个客户端配一个ID,这个ID相当于是登录账号,用户在每次执行程序的时候,都必须要首先输入登录账号。而且得把你的ID告诉服务器,服务器创建一个数组IDPool[]用来保存好各个客户端的ID,方便数据的转发。另外,要实现定向发送,就要求用户在发送数据的时候要告诉服务器向谁发送,就好比你在使用QQ或者微信聊天的时候,需要选择一个好友一样。

那么这个时候数据就变得复杂了,因为不仅仅要发送你实际的数据,还要发送你的目标联系人给服务器,这时,用结构体来封装数据比较合适,数据填充在结构体里面,然后一次性把整个结构体发送出去。服务器接收这个结构体,并且对结构体内容进行解析,解析完之后就知道了要发给谁,发送内容是什么。

要实现定向转发,还有一个问题要解决,就是对于用户来说,只知道对方的ID,但是对于服务器来说,只知道各个客户端的文件描述符,那么如何通过ID来找到对应的文件描述符就成了问题,找不到文件描述符就无法发送。这里,我就借鉴了uC/OS的思想,采用数组的方法,将文件描述符和ID账号联系起来,比如我们创建一个数组叫做ID_FD_Pool[],当客户端连接服务器的时候,服务器就已经知道了它的文件描述符,这时客户端向服务器发送ID,假设ID是5,那我们就可以给ID_FD_Pool[5]=fd,赋值上它的文件描述符,换句话说,通过这个数组,知道了ID,就能找到fd,这样服务器就可以准确发送了。这就是version2.0实现的功能。

这时自然而然会想到一个问题,如果两个客户端使用相同的账号登录会怎么办,服务器该怎么处理。那么正常情况下,都是不允许这样的情况发生的,所以应该在程序防止这样的情况。

怎么防止呢?方法就是服务器用一个数组来保存用户的ID,当有用户登录的时候,先接收它的登录ID,然后将这个ID和数组里的进行比对,如果没有找到相同的,就给客户端回复一个OK,客户端如果不能收到这个OK,就表明重复登录了,客户端直接退出进程,收到OK才往下执行。还有一个问题就是当客户端退出的时候,服务器应该要将这个ID从数组里面清零,否则下次再使用这个ID登录,还会被误认为是已经登录了,前面讲到服务器将客户端的ID保存在一个叫做IDPool[]里的数组里,但是怎么知道这个退出的客户端的ID在IDPool[]里的位置呢?还是上面uC/OS的思想,客户端退出的时候,知道它的文件描述符,因此可以创建一个数组并且赋值FD_ID_Pool[client_sock_fd]=i,将文件描述符和ID在IDPool[]里的位置绑定起来,这样通过文件描述符就可以找到ID在IDPool[]里的位置,然后将它清零。这都是一些技巧。这样的话就实现了version3.1的功能。

这样的话就基本实现了想要的功能,经过测试,可以在多个客户端之间相互发送消息,消息类型支持字母,数字,汉字等,对于普通的交流已经足够了,当然程序肯定也存在很多不足,一些bug可能没被测试发现,需要持续的改进。

来看一下效果:

实际上程序的优化是没有尽头的,看有没有这个需求了,如果只是通过这个来熟悉网络编程,那么做到这种地步就可以点到为止了,有时间的话可以去继续优化。

当然,如果直接看我上面写的内容,可能会看不懂,必须要先了解网络编程的一些基础知识,可以参考我前面提到的链接先学习,时间有限,我上面讲的都是一些思路,具体的实现还是要对照代码来理解。

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-07-21,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 电子技术研习社 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
相关产品与服务
访问管理
访问管理(Cloud Access Management,CAM)可以帮助您安全、便捷地管理对腾讯云服务和资源的访问。您可以使用CAM创建子用户、用户组和角色,并通过策略控制其访问范围。CAM支持用户和角色SSO能力,您可以根据具体管理场景针对性设置企业内用户和腾讯云的互通能力。
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档